Skip to content

temporal-sa/go-agent

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Temporal Agent Platform Template for Go

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.

Architecture

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.

Quick Start

Start Temporal:

temporal server start-dev

Start the worker:

go run ./cmd/worker

Send 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 -query

Try 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"

Configuration

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

Tools And Guards

Tool extension has two sides:

  • ToolDefinition: deterministic metadata passed to the workflow and model.
  • ToolHandler: worker-side code that executes in ExecuteToolActivity.
  • GuardDefinition: deterministic policy metadata used by the workflow.
  • GuardHandler: worker-side code that executes in ExecuteGuardActivity.

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:

  • ToolTypeRead
  • ToolTypeMutating
  • ToolTypeMCP
  • ToolTypeAdmin

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.

Built-In Capabilities

  • 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-gated save_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_approval pre-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.

Workflow API

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 in PendingApprovals.

Query:

  • state: returns AgentState with status, bounded recent messages, ContextSummary, pending message count, and pending approvals.

Enterprise Production Checklist

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.

Tests

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.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors