* Add initial skill for testing, which is simply Steve's skill (#1) * Add initial skill for testing, which is simply Steve's skill * Rename skill to 'temporal-dev' and update version Updated skill name and version for Temporal Python. * Use claude to merge Steve's, Max's, and Mason's skills. (#2) * Use claude to merge Steve's, Max's, and Mason's skills. Did a review pass using claude's skill devlopment skills * Add missing things from Steve * trigger tweaks * Add in common gotchas from Johann * add simple feedback mechanism (#3) * Change skill name to kebab-case, for compatibility with Amp and Cline (#7) * Clean up references/core/ai-integration.md * Clean up references/core/common-gotchas.md * Clean up references/core/common-gotchas.md * Clean up references/core/determinism.md * Clean up references/core/determinism.md * Update error-reference.md * Update interactive-workflows.md * Clean up patterns.md * Cut shell scripts * Edit troubleshooting.md * remove interceptors for now * remove dynamic workflows * clarify on heartbeating of async activity completions, and prompt it a bit in relation to signals * Improve references/python/advanced-features.md * Use explicit namespace in connect * remove duplicated content from determinism.md, clean up * Improve references/python/data-handling.md * Prefer start_to_close_timeout * don't explicitely provide defaults for retry policies * error-handling.md cleanup * move idempotency patterns to patterns.md * remove multi-param activities * small edits * Unify sandbox stuff into one file * local activities aren't experimental * Clean up references/python/sync-vs-async.md * Cleanup observability.md, remove duplicated search attributes * Cut otel for now * cut a lot of duplicate stuff from python gotchas, address comments * de-duplicate content * Lots of improvements to testing * cleanup to top level of skill (like CLI install instructions), and to top-level of python * Improve patterns.md * clean up ai-patterns.md * Update readme with installation instructions * remove ts directory * De-couple core from python and TypeScript as much as possible * Remove TypeScript hints * add prompting for feedback at startup - wait for ethan on slack channel * shorten url * Update slack channel * Automated pass over on python cleanup & deduplication * Remove multi-patching from Python, since its obvious, dont waste tokens on it. (#34) * Add TypeScript (#31) Adds initial support for TypeScript to the skill --------- Co-authored-by: James Watkins-Harvey <mjameswh@users.noreply.github.com> Co-authored-by: Chris Olszewski <chrisdolszewski@gmail.com> * Fix typos and reference links (#36) * Fix typos and reference links * 2 more typo fixes * quick edit to readme (#37) * Fix saga compensations to run under cancellation protection (#43) When a workflow is cancelled mid-saga, compensations must run in a cancellation-protected scope, otherwise they are immediately cancelled before they can execute. - Python: wrap compensation loop in asyncio.shield() so it runs even when the workflow receives a CancelledError - TypeScript: wrap compensation loop in CancellationScope.nonCancellable() so it runs even when the root scope is cancelled (per official docs: "Cleanup logic must be in a nonCancellable scope") - TypeScript: also fix compensation registration order — register BEFORE calling the activity (was already correct in Python) Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * Update readme for public preview (#45) * a few more readme tweaks (#46) * Add MIT License to the project (#47) * Add Go (supersedes other PR) (#38) * progress on go * Go translation workflow completed. * missed a few spots * Manual edits * Address feedback * Add gotcha about anonymous local activities * Sample code for payload converter * clarify sdk protection mechanisms * Setup CODEOWNERS to AI SDK team (#48) * Align version number in SKILL.md and plugin.json. (#49) --------- Co-authored-by: James Watkins-Harvey <mjameswh@users.noreply.github.com> Co-authored-by: Chris Olszewski <chrisdolszewski@gmail.com> Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
6.1 KiB
Go SDK Advanced Features
Schedules
Create recurring workflow executions using the Schedule API.
scheduleHandle, err := c.ScheduleClient().Create(ctx, client.ScheduleOptions{
ID: "daily-report",
Spec: client.ScheduleSpec{
CronExpressions: []string{"0 9 * * *"},
},
Action: &client.ScheduleWorkflowAction{
ID: "daily-report-workflow",
Workflow: DailyReportWorkflow,
TaskQueue: "reports",
},
})
Using intervals instead of cron:
scheduleHandle, err := c.ScheduleClient().Create(ctx, client.ScheduleOptions{
ID: "hourly-sync",
Spec: client.ScheduleSpec{
Intervals: []client.ScheduleIntervalSpec{
{Every: time.Hour},
},
},
Action: &client.ScheduleWorkflowAction{
ID: "hourly-sync-workflow",
Workflow: SyncWorkflow,
TaskQueue: "sync",
},
})
Manage schedules:
handle := c.ScheduleClient().GetHandle(ctx, "daily-report")
// Pause / unpause
handle.Pause(ctx, client.SchedulePauseOptions{Note: "Maintenance window"})
handle.Unpause(ctx, client.ScheduleUnpauseOptions{Note: "Maintenance complete"})
// Trigger immediately
handle.Trigger(ctx, client.ScheduleTriggerOptions{})
// Describe
desc, err := handle.Describe(ctx)
// Delete
handle.Delete(ctx)
Async Activity Completion
For activities that complete asynchronously (e.g., human tasks, external callbacks). If you configure a heartbeat_timeout on this activity, the external completer is responsible for sending heartbeats via the async handle. If you do NOT set a heartbeat_timeout, no heartbeats are required.
Note: If the external system that completes the asynchronous action can reliably be trusted to do the task and Signal back with the result, and it doesn't need to Heartbeat or receive Cancellation, then consider using signals instead.
Step 1: Return activity.ErrResultPending from the activity.
func RequestApproval(ctx context.Context, requestID string) (string, error) {
activityInfo := activity.GetInfo(ctx)
taskToken := activityInfo.TaskToken
// Store taskToken externally (e.g., database) for later completion
err := storeTaskToken(requestID, taskToken)
if err != nil {
return "", err
}
// Signal that this activity will be completed externally
return "", activity.ErrResultPending
}
Step 2: Complete from another process using the task token.
temporalClient, err := client.Dial(client.Options{})
// Complete the activity
err = temporalClient.CompleteActivity(ctx, taskToken, "approved", nil)
// Or fail it
err = temporalClient.CompleteActivity(ctx, taskToken, nil, errors.New("rejected"))
Or complete by ID (no task token needed):
err = temporalClient.CompleteActivityByID(ctx, namespace, workflowID, runID, activityID, "approved", nil)
Worker Tuning
Configure worker.Options for production workloads:
w := worker.New(c, "my-task-queue", worker.Options{
// Max concurrent activity executions (default: 1000)
MaxConcurrentActivityExecutionSize: 500,
// Max concurrent workflow task executions (default: 1000)
MaxConcurrentWorkflowTaskExecutionSize: 500,
// Max concurrent activity task pollers (default: 2)
MaxConcurrentActivityTaskPollers: 4,
// Max concurrent workflow task pollers (default: 2)
MaxConcurrentWorkflowTaskPollers: 4,
// Graceful shutdown timeout (default: 0)
WorkerStopTimeout: 30 * time.Second,
})
Scale pollers based on task queue throughput. If you observe high schedule-to-start latency, increase the number of pollers or add more workers.
Sessions
Go-specific feature for routing multiple activities to the same worker. All activities using the session context execute on the same worker host.
Enable on the worker:
w := worker.New(c, "fileprocessing", worker.Options{
EnableSessionWorker: true,
MaxConcurrentSessionExecutionSize: 100, // default: 1000
})
Use in a workflow:
func FileProcessingWorkflow(ctx workflow.Context, file FileParam) error {
ao := workflow.ActivityOptions{
StartToCloseTimeout: time.Minute,
}
ctx = workflow.WithActivityOptions(ctx, ao)
sessionCtx, err := workflow.CreateSession(ctx, &workflow.SessionOptions{
CreationTimeout: time.Minute,
ExecutionTimeout: 10 * time.Minute,
})
if err != nil {
return err
}
defer workflow.CompleteSession(sessionCtx)
// All three activities run on the same worker
var downloadResult string
err = workflow.ExecuteActivity(sessionCtx, DownloadFile, file.URL).Get(sessionCtx, &downloadResult)
if err != nil {
return err
}
var processResult string
err = workflow.ExecuteActivity(sessionCtx, ProcessFile, downloadResult).Get(sessionCtx, &processResult)
if err != nil {
return err
}
err = workflow.ExecuteActivity(sessionCtx, UploadFile, processResult).Get(sessionCtx, nil)
return err
}
Key points:
workflow.ErrSessionFailedis returned if the worker hosting the session diesCompleteSessionreleases resources -- always call it (usedefer)- Use case: file processing (download, process, upload on same host), GPU workloads, or any pipeline needing local state
MaxConcurrentSessionExecutionSizeonworker.Optionslimits how many sessions a single worker can handle
Limitations:
- Sessions do not survive worker process restarts — if the worker dies, the session fails and activities must be retried from the workflow level
- There is no server-side support for sessions — the Go SDK implements them entirely client-side using internal task queue routing
- Session concurrency limiting is per-process, not per-host — only one worker process per host if you rely on this
Relationship to worker-specific task queues: Sessions are essentially a convenience API over the "worker-specific task queue" pattern, where each worker creates a unique task queue and routes activities to it. For simple cases where you don't need separate activities (e.g., download + process + upload can be one unit), consider using a single long-running activity with heartbeating instead.