This repository is a Go starter template for teams building an enterprise agent platform on top of Temporal. It is intentionally small enough to read end to end, but it encodes the operating model a large organization should preserve:
- Workflows own durable orchestration, policy gates, approvals, signals, queries, and Continue-As-New.
- Activities own every non-deterministic operation: LLM calls, HTTP, filesystem, SaaS APIs, credentials, wall-clock time, and external writes.
- Tool metadata is deterministic and replay-safe; tool execution handlers are worker-local and registered through an extensible registry.
- Guard metadata is deterministic and replay-safe; guard handlers are worker-local unless the guard is workflow-native, such as human approval.
- Model calls receive bounded context, not an unbounded transcript.
- Human cancellation, chat signals, and approval Updates are processed while activities are in flight.
- Tool outputs are kept history-safe: large payloads become artifact references with bounded previews.
cmd/worker/ Worker process; connects to Temporal and registers code
cmd/chat/ CLI client for signal-with-start, query, approval Update, cancel
internal/agent/ Workflow, activity contracts, provider adapter, tool/guard registry
internal/config/ Environment-driven runtime configuration
internal/temporal/ Worker registration helper
Dockerfile Minimal container image for the worker
The core workflow is AgentWorkflow. It receives AgentInput, queues chat
signals, calls the model activity, validates tool guard coverage, runs pre-guards,
schedules tool activities, runs post-guards, exposes state query data, and
continues as new after a configurable message count.
Start Temporal:
temporal server start-devStart the worker:
go run ./cmd/workerSend a message. Without ANTHROPIC_API_KEY, the model activity uses a local
mock provider that still exercises tool calls.
go run ./cmd/chat -workflow-id demo-chat -message "what time is it?"Query durable state:
go run ./cmd/chat -workflow-id demo-chat -queryTry an approval-gated mutating tool:
go run ./cmd/chat -workflow-id demo-chat -message "remember a note titled deploy checklist with content run tests before deploy"
go run ./cmd/chat -workflow-id demo-chat -query
go run ./cmd/chat -workflow-id demo-chat -approve approval-1 -actor alice@example.com -reason "approved for demo"Cancel the workflow:
go run ./cmd/chat -workflow-id demo-chat -cancel "finished demo"The commands read environment variables directly.
| Variable | Default | Purpose |
|---|---|---|
TEMPORAL_ADDRESS |
127.0.0.1:7233 |
Temporal frontend address |
TEMPORAL_NAMESPACE |
default |
Temporal namespace |
TEMPORAL_TASK_QUEUE |
go-agent-task-queue |
Worker task queue |
AGENT_MODEL |
claude-sonnet-4-5 |
Model sent to the provider activity |
ANTHROPIC_API_KEY |
empty | Enables real Anthropic Messages API calls |
AGENT_DATA_DIR |
.agent_data |
Activity-owned directory for note and fetched-content artifacts |
Important workflow input fields:
| Field | Purpose |
|---|---|
SystemPrompt |
Platform or tenant instructions for the model |
ToolDefinitions |
Replay-safe tool metadata for this workflow run |
GuardDefinitions |
Replay-safe guard metadata for this workflow run |
GuardPolicy |
Tool categories that require pre/post guards |
EnabledTools |
Optional allowlist over available definitions |
MaxTurns |
Max model/tool loop iterations for one user message |
MaxMessagesRun |
Continue-As-New threshold |
MaxContextMessages |
Recent messages sent to model, query, and next run |
ContextSummary |
Bounded deterministic summary of compacted older context |
ApprovalPolicyVersion |
Version string that approval Updates must echo |
Tool extension has two sides:
ToolDefinition: deterministic metadata passed to the workflow and model.ToolHandler: worker-side code that executes inExecuteToolActivity.GuardDefinition: deterministic policy metadata used by the workflow.GuardHandler: worker-side code that executes inExecuteGuardActivity.
Add a corporate tool by defining a ToolSpec:
crmLookup := agent.ToolSpec{
Definition: agent.ToolDefinition{
Name: "crm_lookup_account",
Description: "Look up an account in the corporate CRM.",
Type: agent.ToolTypeRead,
Execution: agent.ToolExecutionPolicy{
TaskQueue: "crm-read-tools",
TimeoutProfile: agent.ToolTimeoutStandard,
RetryProfile: agent.ToolRetryStandard,
SensitivityClass: agent.ToolSensitivityConfidential,
},
InputSchema: map[string]any{
"type": "object",
"properties": map[string]any{
"account_id": map[string]any{"type": "string"},
},
"required": []string{"account_id"},
"additionalProperties": false,
},
},
Handler: func(ctx context.Context, req agent.ToolRequest) (agent.ToolResult, error) {
// Resolve credentials, call CRM, summarize output, and return
// ArtifactReference values for data too large for workflow history.
return agent.ToolResult{
ToolCallID: req.Call.ID,
Name: req.Call.Name,
Content: "account found",
}, nil
},
}Guardrails mirror the original Python harness. Tools are categorized as:
ToolTypeReadToolTypeMutatingToolTypeMCPToolTypeAdmin
By default, mutating, mcp, and admin tools must declare at least one
pre-guard. Guard definitions state which tool categories they fulfill. If a tool
declares a guard that is missing or does not fulfill the tool category, the
workflow returns a guarded tool failure before scheduling the tool activity.
Pre-guards run sequentially before the tool. A failed pre-guard prevents the tool from running. Post-guards run sequentially after the tool. A failed post-guard masks the raw tool result and returns the guard's model-safe content instead.
opsApproval := agent.GuardSpec{
Definition: agent.GuardDefinition{
Name: "ops_approval",
Description: "Require operations approval for admin tools.",
Fulfills: []agent.ToolType{agent.ToolTypeAdmin},
Execution: agent.ToolExecutionPolicy{
TaskQueue: "ops-guard-tools",
TimeoutProfile: agent.ToolTimeoutStandard,
RetryProfile: agent.ToolRetryNone,
SensitivityClass: agent.ToolSensitivityRestricted,
},
},
Handler: func(ctx context.Context, req agent.GuardRequest) (agent.GuardResult, error) {
// Call policy service, inspect req.Tool, req.Call.Arguments, and req.Timing.
return agent.GuardResult{Passed: true}, nil
},
}Attach guards to protected tools:
restartService := agent.ToolSpec{
Definition: agent.ToolDefinition{
Name: "restart_service",
Description: "Restart a production service.",
Type: agent.ToolTypeAdmin,
PreGuards: []string{"ops_approval"},
PostGuards: []string{"redact_restart_result"},
InputSchema: map[string]any{
"type": "object",
"properties": map[string]any{
"service": map[string]any{"type": "string"},
},
"required": []string{"service"},
"additionalProperties": false,
},
},
Handler: func(ctx context.Context, req agent.ToolRequest) (agent.ToolResult, error) {
return agent.ToolResult{
ToolCallID: req.Call.ID,
Name: req.Call.Name,
Content: "restart requested",
}, nil
},
}Register tools and guards during worker bootstrap, before worker.Run:
toolRegistry := agent.MustNewToolRegistry(
append(agent.BuiltInToolSpecs(), crmLookup)...,
)
guardRegistry := agent.MustNewGuardRegistry(
append(agent.BuiltInGuardSpecs(), opsApproval)...,
)
agent.ConfigureToolRegistry(toolRegistry)
agent.ConfigureGuardRegistry(guardRegistry)Then include the same tool and guard definitions in AgentInput.ToolDefinitions
and AgentInput.GuardDefinitions when starting a workflow. In a production API,
do not accept arbitrary client-provided definitions. Build definitions
server-side from trusted registries or a signed catalog release. The worker
validates that workflow-provided tool and guard definitions match the trusted
registries before handlers are allowed to run.
The built-in human_approval guard is workflow-native. It fulfills mutating,
mcp, and admin, waits on the validated approve_tool Update, and is used by
the built-in save_note tool.
If a tool policy sets TaskQueue, run workers that poll that task queue and
register agent.ExecuteToolActivity. This lets one workflow route CRM reads,
privileged writes, long report exports, and low-latency cache tools to different
worker pools with different concurrency and rate-limit settings.
- Durable agent loop with bounded
MaxTurns. - Provider-neutral model activity with Anthropic adapter and mock provider.
- Extensible tool registry with built-in
current_time,fetch_url, and approval-gatedsave_note. - Extensible guard registry with Python-harness-style
GuardPolicy,GuardDefinition, sequential pre-guards, sequential post-guards, and guard fulfillment validation. - Human-in-the-loop approval state for mutating tools using validated Temporal
Updates with actor identity and policy version through the built-in
human_approvalpre-guard. - Per-tool execution policy metadata: task queue, timeout profile, retry profile, heartbeat requirement, and sensitivity class.
- Signal/update-aware model/tool activity waiting, so chat, cancel, and approval decisions are processed while activities are running.
- Continue-As-New with final signal drain and compact carry-forward state.
- Bounded model/query/next-run context using recent messages plus
ContextSummary. - Hard 8 KiB tool-result content cap for workflow history; larger built-in fetch results are written as artifacts with short previews.
- Non-retryable validation errors for bad tool arguments and model 4xx errors.
- Idempotent mutating tool pattern using the model tool call ID as the write key.
Signals:
chat: enqueue user text.cancel: request workflow shutdown.
Updates:
approve_tool: approve or deny a pending mutating tool. The validator rejects empty actor IDs, stale policy versions, duplicate approvals, and approval IDs that are not currently inPendingApprovals.
Query:
state: returnsAgentStatewith status, bounded recent messages,ContextSummary, pending message count, and pending approvals.
Before using this as a corporate platform baseline, decide and document:
- Worker Versioning or patching strategy for long-lived workflow code changes.
- Authentication and authorization for chat, query, approval, and cancellation.
- Approval authorization policy, including actor identity mapping and policy version rollout.
- Corporate guard catalog ownership: which guard fulfills each tool category, which tool categories require pre/post guards, and which guards may call external policy engines.
- Data converter policy for encryption, compression, and PII handling.
- External artifact storage for large tool outputs; replace local file:// artifacts before production.
- Search attributes for visibility, not business state.
- Separate task queues or worker pools for high-latency tools, sensitive tools, and model-provider rate limits.
- Provider observability: request IDs, model usage metrics, cost attribution, tenant routing, and policy failures.
- Replay tests from captured histories before deploying incompatible workflow changes.
go test ./...
go vet ./...The tests mock activities by registered activity names, validate custom tool and guard registry policy enforcement, verify approval Updates, verify missing-guard blocking, verify post-guard masking, verify cancellation while a model activity is in flight, and assert Continue-As-New signal drain plus context compaction.