{"id":48903398,"url":"https://github.com/agenticenv/agent-sdk-go","last_synced_at":"2026-04-25T09:10:32.754Z","repository":{"id":349471210,"uuid":"1180653673","full_name":"agenticenv/agent-sdk-go","owner":"agenticenv","description":"Build durable, distributed AI agents in Go — tool calling, MCP, human approvals, and sub-agent delegation. Temporal-first.","archived":false,"fork":false,"pushed_at":"2026-04-19T05:00:41.000Z","size":21877,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-19T07:03:23.510Z","etag":null,"topics":["agentic-ai","ai","ai-agents","distributed","distributed-ag","durable","durable-execution","go","golang","llm","orchestration","sdk","sdk-go","temporal","workflow"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/agenticenv.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-13T09:11:14.000Z","updated_at":"2026-04-19T04:57:18.000Z","dependencies_parsed_at":null,"dependency_job_id":"592f07ab-a113-4602-b091-9ddd428b738d","html_url":"https://github.com/agenticenv/agent-sdk-go","commit_stats":null,"previous_names":["agenticenv/agent-sdk-go"],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/agenticenv/agent-sdk-go","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agenticenv%2Fagent-sdk-go","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agenticenv%2Fagent-sdk-go/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agenticenv%2Fagent-sdk-go/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agenticenv%2Fagent-sdk-go/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/agenticenv","download_url":"https://codeload.github.com/agenticenv/agent-sdk-go/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agenticenv%2Fagent-sdk-go/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32256307,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-25T04:23:17.126Z","status":"ssl_error","status_checked_at":"2026-04-25T04:21:53.360Z","response_time":59,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agentic-ai","ai","ai-agents","distributed","distributed-ag","durable","durable-execution","go","golang","llm","orchestration","sdk","sdk-go","temporal","workflow"],"created_at":"2026-04-16T18:00:22.393Z","updated_at":"2026-04-25T09:10:32.723Z","avatar_url":"https://github.com/agenticenv.png","language":"Go","funding_links":[],"categories":["Artificial Intelligence","⚙️ Implementations \u0026 Libraries","Go"],"sub_categories":["Libraries"],"readme":"# Agent SDK for Go - Temporal-first\n\n**Build durable, production-grade AI agents in Go** — tools, MCP, human approvals, and sub-agent delegation.\n\n[![CI](https://github.com/agenticenv/agent-sdk-go/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/agenticenv/agent-sdk-go/actions)\n[![Release](https://img.shields.io/github/v/release/agenticenv/agent-sdk-go?label=Release)](https://github.com/agenticenv/agent-sdk-go/releases)\n[![Go Reference](https://pkg.go.dev/badge/github.com/agenticenv/agent-sdk-go.svg)](https://pkg.go.dev/github.com/agenticenv/agent-sdk-go)\n[![Go Report Card](https://goreportcard.com/badge/github.com/agenticenv/agent-sdk-go?v=0.1.2)](https://goreportcard.com/report/github.com/agenticenv/agent-sdk-go)\n[![License](https://img.shields.io/github/license/agenticenv/agent-sdk-go?label=License\u0026cacheSeconds=0)](LICENSE)\n[![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go)\n\n\u003e **Note:** Independent community library — **not** affiliated with Temporal Technologies.\n\u003e\n\u003e **Versioning:** [Semantic versioning](https://semver.org/); published lines are **git tags** (e.g. `v0.1.2`). See the **[latest release](https://github.com/agenticenv/agent-sdk-go/releases/latest)** — the README does not pin a patch number so it stays accurate after each tag.\n\n## Overview\n\n**agent-sdk-go** is a Go SDK for production AI agents — tools, MCP, human-in-the-loop approvals, and multi-agent delegation — built on [Temporal](https://temporal.io) durable execution.\n\n\u003e **Most agent frameworks live and die inside a single process: if your server restarts, the run is lost.** Here, every agent run is a Temporal workflow end to end. Runs survive crashes and deploys, respect timeouts and retries, and are observable as real service operations. There is no execution path outside Temporal.\n\n`pkg/agent` exposes three entry points — `Run`, `Stream`, and `RunAsync` — each mapped directly to a Temporal workflow. Connect via `WithTemporalConfig` or `WithTemporalClient` to your cluster. See [Getting Started](#getting-started) to set up, or [Temporal Runtime](#temporal-runtime) for deeper detail on workers, queues, and streaming.\n\n## Capabilities\n\n- **LLM providers** — OpenAI, Anthropic, and Gemini out of the box; bring your own via `interfaces.LLMClient`.\n- **Streaming** — Partial tokens and events via `Stream` and `WithStream`.\n- **Reasoning** — Extended thinking / chain-of-thought where supported (Anthropic, Gemini).\n- **Token usage** — Track input, output, and reasoning token counts per run.\n- **Tools** — Register built-in or custom tools via `interfaces.Tool`.\n- **MCP** — Extend agent capabilities by connecting any MCP server as a tool source via `WithMCPConfig` or `WithMCPClients`.\n- **Human-in-the-loop** — Approval gates on tool calls and delegation across `Run`, `RunAsync`, and `Stream`.\n- **Sub-agents** — Delegate to specialist agents via `WithSubAgents`.\n- **Scale** — Add Temporal workers to scale agent execution horizontally.\n\n## Temporal Runtime\n\n**Temporal** powers agents through three moving parts: a **Temporal client** that launches agent workflows, **workers** (typically `NewAgentWorker`) that poll task queues and execute workflow and activity code, and **workflow history** that makes each run durable. Workers are stateless — they replay and advance history, not hold state themselves.\n\n- **Workflows** — Durable, **replay-safe** orchestration: the agent “loop” (model rounds, tool routing, when to delegate). Workflow code must stay deterministic; long work happens in activities.\n- **Activities** — **LLM calls**, **tool** execution (including **MCP tool** calls), **conversation** updates, approval steps—side effects and I/O. Retries, timeouts, and failure handling apply here.\n- **Child workflows** — **Sub-agent delegation** is modeled as **child workflows** so specialists can run on their own task queues with their own workers.\n- **Workers \u0026 task queues** — Processes **poll** a queue and run scheduled workflow and activity tasks; **scale horizontally** by adding workers. Each agent / sub-agent typically has its own **task queue** name.\n\n```mermaid\ngraph TD\n    A[Agent] --\u003e WF[Workflow: agent loop]\n    W[Agent Worker] --\u003e WF\n    WF --\u003e LLM[Activity: LLM call]\n    WF --\u003e Appr[Activity: approval]\n    WF --\u003e Child[Child Workflow: sub-agent loop]\n    WF --\u003e Tool[Activity: tool execution]\n    WF --\u003e Mem[Activity: save memory]\n    Child --\u003e LLM2[Activity: LLM call]\n    Child --\u003e Tool2[Activity: tool execution]\n    Child --\u003e Mem2[Activity: save memory]\n```\n\nDetails: [Temporal connection](#temporal-connection), [Sub-agents](#sub-agents), [Agent and worker in separate processes](#agent-and-worker-in-separate-processes).\n\n### Durable agents: survive crashes, restarts, and deploys\n\nBecause every agent run is a Temporal workflow, **the process can crash and restart without losing a single step** — tool calls already made are not replayed, approvals already given are not re-requested, and the agent resumes exactly where it left off. `DisableLocalWorker` and `NewAgentWorker` let you split the client and execution across OS processes or machines while the cluster holds all state.\n\n**[examples/durable_agent](examples/durable_agent)** walks through this split across two terminals, including crash and retry scenarios. Step-by-step exercises: **[examples/durable_agent/README.md](examples/durable_agent/README.md)**.\n\n### Streaming and approvals\n\nStream events and approval events cross two boundaries: **Temporal** (durable workflow) and **your** hosts and subscribers. The guarantees differ on each side. These constraints matter most in interactive, user-facing scenarios — autonomous backend agents are largely unaffected since they do not depend on live event delivery.\n\n- **Agent runs are durable.** After a worker restart or deploy, the run resumes from recorded progress in Temporal. You do not need a single process alive for the entire run.\n- **Live stream is not backfilled.** Incremental stream traffic — tokens, tool updates — is delivered as produced. If your client was disconnected, you may miss chunks even though the agent completed successfully in Temporal.\n- **Approvals degrade gracefully.** If an approval event cannot be delivered, the run continues rather than hanging — the tool is skipped with a clear message. This is intentional for autonomous backend execution; for interactive scenarios, design your UX so users are not silently blocked.\n- **Your responsibility.** Keep worker processes supervised and restarting on crash, maintain a stable connection to your Temporal cluster, and ensure stream subscribers can reconnect.\n- **Client reconnection and UX.** For interactive apps, if the process serving `Stream` crashes, the workflow continues in Temporal but your client loses the connection. Once a stream is lost, reconnecting to that specific run is not supported — the recommended approach is to block the user from sending a new prompt until the current one completes, then fetch the final response and display it. This keeps conversation turns sequential and avoids out-of-order state. For autonomous agents, this is a non-issue since the caller waits for completion and the workflow finishes regardless.\n\n## Reference apps\n\nDemo applications that use **agent-sdk-go** end-to-end. More may be added over time (e.g. web apps, autonomous agents, other integration patterns).\n\n- **[Agent Chat](https://github.com/agenticenv/agent-chat)** — Web chat demo with durable conversations; a good reference for wiring the SDK into an HTTP-backed app.\n\n## Getting Started\n\nHow to **use** the SDK—agents, LLMs, Temporal connection, examples.\n\n### Prerequisites\n\n**agent-sdk-go** runs agents on the **[Temporal](https://temporal.io)** runtime (durable workflows and activities), so a **running Temporal server** is required. See **[Temporal setup](temporal-setup.md)**. Also **Go 1.24+** (see `go.mod`) and credentials for your LLM provider.\n\n**Module:** `github.com/agenticenv/agent-sdk-go`\n\n```bash\ngo get github.com/agenticenv/agent-sdk-go@latest\n```\n\n### Create an agent and run\n\n```go\nimport (\n    \"github.com/agenticenv/agent-sdk-go/pkg/agent\"\n    \"github.com/agenticenv/agent-sdk-go/pkg/llm\"\n    \"github.com/agenticenv/agent-sdk-go/pkg/llm/openai\"\n)\n\nllmClient, _ := openai.NewClient(\n    llm.WithAPIKey(\"sk-...\"),\n    llm.WithModel(\"gpt-4o\"),\n)\n\na, _ := agent.NewAgent(\n    agent.WithTemporalConfig(\u0026agent.TemporalConfig{\n        Host: \"localhost\", Port: 7233,\n        Namespace: \"default\", TaskQueue: \"my-app\",\n    }),\n    agent.WithSystemPrompt(\"You are a helpful assistant.\"),\n    agent.WithLLMClient(llmClient),\n)\ndefer a.Close()\n\nresult, err := a.Run(ctx, \"Hello\", \"\")\n// result.Content, result.AgentName, result.Model\n```\n\n[examples/simple_agent](examples/simple_agent)\n\n### Temporal connection\n\nProvide **either** `WithTemporalConfig` or `WithTemporalClient`, not both.\n\n**Option 1 — WithTemporalConfig** (simple, local dev):\n\n```go\nagent.WithTemporalConfig(\u0026agent.TemporalConfig{\n    Host: \"localhost\", Port: 7233,\n    Namespace: \"default\", TaskQueue: \"my-app\",\n})\n```\n\n**Option 2 — WithTemporalClient** (TLS, API key auth, Temporal Cloud):\n\nUse when you need mTLS, Temporal Cloud API keys, or other connection options. Create the client yourself and pass it. You must also call `WithTaskQueue`. The agent does not close the client; you own its lifecycle.\n\n```go\nimport \"go.temporal.io/sdk/client\"\n\ntc, _ := client.Dial(client.Options{\n    HostPort:  \"namespace-id.tmprl.cloud:7233\",\n    Namespace: \"my-namespace\",\n    Credentials: client.NewAPIKeyStaticCredentials(apiKey),\n    // Or: ConnectionOptions for mTLS, etc.\n})\ndefer tc.Close()\n\na, _ := agent.NewAgent(\n    agent.WithTemporalClient(tc),\n    agent.WithTaskQueue(\"my-app\"),\n    agent.WithLLMClient(llmClient),\n)\ndefer a.Close()\n```\n\n[examples/agent_with_temporal_client](examples/agent_with_temporal_client) demonstrates the full pattern.\n\n### Create an LLM client (OpenAI, Anthropic, or Gemini)\n\n```go\n// OpenAI\nllmClient, err := openai.NewClient(\n    llm.WithAPIKey(\"sk-...\"),\n    llm.WithModel(\"gpt-4o\"),\n    llm.WithBaseURL(\"https://api.openai.com/v1\"),  // optional\n)\n\n// Anthropic\nllmClient, err := anthropic.NewClient(\n    llm.WithAPIKey(\"...\"),\n    llm.WithModel(\"claude-3-5-sonnet-20241022\"),\n)\n\n// Gemini\nllmClient, err := gemini.NewClient(\n    llm.WithAPIKey(\"...\"),  // or GOOGLE_API_KEY\n    llm.WithModel(\"gemini-2.5-flash\"),\n)\n```\n\n### Supported LLMs\n\n| Provider      | Package             | Notes                     |\n| ------------- | ------------------- | ------------------------- |\n| **OpenAI**    | `pkg/llm/openai`    | GPT-4o, GPT-4o-mini, etc. |\n| **Anthropic** | `pkg/llm/anthropic` | Claude models             |\n| **Gemini**    | `pkg/llm/gemini`    | gemini-2.5-flash, etc.    |\n\nOther providers: implement [`interfaces.LLMClient`](pkg/interfaces/llm.go) (`Generate`, `GenerateStream`, metadata). Copy patterns from `pkg/llm/`.\n\n### Stream events (Stream)\n\n`Stream` returns a channel of `AgentEvent`. Use `agent.WithStream(true)` for partial tokens as they arrive. For how **Temporal** durability relates to **live** events (restarts, approvals), see **[Streaming and approvals](#streaming-and-approvals)** under **[Temporal Runtime](#temporal-runtime)**.\n\n```go\na, _ := agent.NewAgent(\n    agent.WithTemporalConfig(...),\n    agent.WithLLMClient(...),\n    agent.WithStream(true),\n)\ndefer a.Close()\n\neventCh, err := a.Stream(ctx, \"What's 17 * 23?\", \"\")\nfor ev := range eventCh {\n    switch ev.Type {\n    case agent.AgentEventContentDelta:\n        fmt.Print(ev.Content)\n    case agent.AgentEventToolCall:\n        fmt.Printf(\"tool: %s\\n\", ev.ToolCall.ToolName)\n    case agent.AgentEventComplete:\n        fmt.Println(\"done:\", ev.Content)\n    }\n}\n```\n\n[examples/agent_with_stream](examples/agent_with_stream)\n\n#### Displaying stream events\n\n`ContentDelta` then `Complete` often duplicate the same text—don’t print both. Use `ev.AgentName` to distinguish agents; several `complete` events can appear before the main one finishes. See [examples/agent_with_stream_conversation](examples/agent_with_stream_conversation).\n\n### Token usage (`LLMUsage`)\n\nEach LLM completion can report token counts via [`interfaces.LLMUsage`](pkg/interfaces/llm.go) on [`interfaces.LLMResponse.Usage`](pkg/interfaces/llm.go). OpenAI, Anthropic, and Gemini clients populate **`PromptTokens`**, **`CompletionTokens`**, **`TotalTokens`**, and optional **`CachedPromptTokens`** / **`ReasoningTokens`** when the provider returns them.\n\n- **`Agent.Run` / `RunAsync`:** [`AgentResponse.Usage`](pkg/agent/agent.go) is the **sum** of usage across all LLM calls in that run (including tool rounds). Use it for cost estimates, quotas, and logging.\n- **`Stream`:** the root agent’s final [`AgentEventComplete`](pkg/agent/event.go) includes **`Usage`** with the same aggregate. OpenAI streaming requests **`include_usage`** automatically so totals appear on the final stream result.\n\nExamples: [examples/simple_agent](examples/simple_agent) (prints usage after `Run`), [examples/agent_with_stream](examples/agent_with_stream) (prints usage on `complete`).\n\n### Tools\n\nRegister tools and pass to the agent. Use `agent.WithToolApprovalPolicy(agent.AutoToolApprovalPolicy())` to skip approval (or omit for default approval flow).\n\nCustom tools may also implement:\n\n- `interfaces.ToolApproval` — tool-level hint for **interactive human approval**. Use this when a person should decide whether the tool runs, and no agent-level approval policy is set.\n- `interfaces.ToolAuthorizer` — tool-level **programmatic authorization**. Use this when code should decide whether the tool runs before approval/execute (for example: scopes, tenancy, environment flags, or feature access). Return `Allow=false` to deny the tool call without executing it.\n\n```go\nreg := tools.NewRegistry()\nreg.Register(calculator.New())\nreg.Register(weather.New())\n\na, _ := agent.NewAgent(\n    agent.WithTemporalConfig(...),\n    agent.WithLLMClient(...),\n    agent.WithToolRegistry(reg),\n    agent.WithToolApprovalPolicy(agent.AutoToolApprovalPolicy()),\n)\ndefer a.Close()\n\nresult, _ := a.Run(ctx, \"What's the weather in Tokyo?\", \"\")\n```\n\n[examples/agent_with_tools](examples/agent_with_tools)\n\n### MCP (Model Context Protocol)\n\nMCP servers extend your agent with external tools that work identically to built-in tools across `Run`, `Stream`, `RunAsync`, and approval gates. Each server needs a **unique** name in config (the `WithMCPConfig` map key or the first argument to `mcpclient.NewClient`); tools are registered under stable names so they do not collide when several servers expose the same logical tool id.\n\nAt `NewAgent`, the SDK connects to each server, discovers its tools, applies any **`ToolFilter`** (`AllowTools`/`BlockTools`), and registers the results — failing fast if a server is unreachable.\n\nUse `mcp.MCPStdio` (local process) or `mcp.MCPStreamableHTTP` (remote) from `pkg/mcp` for transport. Streamable HTTP supports `Token`, `OAuthClientCreds`, custom `Headers`, and `SkipTLSVerify` for local HTTPS. You can register multiple servers per agent with different transports, timeouts, retries, and filters per server.\n\nPass `WithMCPConfig` or `WithMCPClients` into `agent.NewAgent` alongside your other options.\n\n**Option 1 — `WithMCPConfig`**\n\nDeclare each server as one entry in `agent.MCPServers`: set `Transport`, and optionally `ToolFilter`, `Timeout`, and `RetryAttempts`. The map key is the server name (must be unique).\n\n```go\nimport (\n    \"time\"\n\n    \"github.com/agenticenv/agent-sdk-go/pkg/agent\"\n    \"github.com/agenticenv/agent-sdk-go/pkg/mcp\"\n)\n\nagent.WithMCPConfig(agent.MCPServers{\n    // Subprocess MCP (stdio): different command/args per local server\n    \"local\": {\n        Transport: mcp.MCPStdio{\n            Command: \"node\",\n            Args:    []string{\"path/to/your-mcp-server.js\", \"--verbose\"},\n        },\n        Timeout: 60 * time.Second,\n    },\n    // Remote streamable HTTP: different URL, bearer token, tool filter, and retries\n    \"remote\": {\n        Transport: mcp.MCPStreamableHTTP{\n            URL:   \"https://mcp.example.com/mcp\",\n            Token: \"replace-with-bearer-or-use-OAuthClientCreds-or-Headers\",\n        },\n        ToolFilter:    mcp.MCPToolFilter{AllowTools: []string{\"search\", \"wiki\"}},\n        Timeout:       30 * time.Second,\n        RetryAttempts: 3,\n    },\n})\n```\n\n**Option 2 — `WithMCPClients`**\n\nBuild one client per server with `mcpclient.NewClient` (server name, transport, then options such as `WithTimeout`, `WithRetryAttempts`, `WithToolFilter`, `WithLogger`). Pass every client to `agent.WithMCPClients` in one call; each `NewClient` name must be unique.\n\n\u003e Note: The packaged client is built on the official [Go MCP SDK](https://github.com/modelcontextprotocol/go-sdk) (`modelcontextprotocol/go-sdk`).\n\n```go\nimport (\n    \"time\"\n\n    \"github.com/agenticenv/agent-sdk-go/pkg/agent\"\n    \"github.com/agenticenv/agent-sdk-go/pkg/mcp\"\n    mcpclient \"github.com/agenticenv/agent-sdk-go/pkg/mcp/client\"\n)\n\nlocalCl, err := mcpclient.NewClient(\"local\",\n    mcp.MCPStdio{Command: \"node\", Args: []string{\"path/to/your-mcp-server.js\"}},\n    mcpclient.WithTimeout(60*time.Second),\n)\nif err != nil {\n    // handle\n}\n\nremoteCl, err := mcpclient.NewClient(\"remote\",\n    mcp.MCPStreamableHTTP{\n        URL:   \"https://mcp.example.com/mcp\",\n        Token: \"replace-with-bearer-or-use-OAuthClientCreds-or-Headers\",\n    },\n    mcpclient.WithTimeout(30*time.Second),\n    mcpclient.WithRetryAttempts(3),\n    mcpclient.WithToolFilter(mcp.MCPToolFilter{AllowTools: []string{\"search\", \"wiki\"}}),\n)\nif err != nil {\n    // handle\n}\n\na, err := agent.NewAgent(\n    agent.WithTemporalConfig(...),\n    agent.WithLLMClient(...),\n    agent.WithMCPClients(localCl, remoteCl),\n    agent.WithToolApprovalPolicy(agent.AutoToolApprovalPolicy()),\n)\nif err != nil {\n    // handle\n}\ndefer a.Close()\n```\n\nYou may use **Option 1** for some servers and **Option 2** for others on the same agent; keep server names unique across both.\n\n[examples/agent_with_mcp_config](examples/agent_with_mcp_config) and [examples/agent_with_mcp_client](examples/agent_with_mcp_client) show MCP from env (`stdio` or streamable HTTP, URL-only OK, optional bearer/OAuth); see [examples/env.sample](examples/env.sample) and [examples/README.md](examples/README.md).\n\n### Sub-agents\n\nBuild each specialist with `NewAgent` (its own `TaskQueue`, LLM, tools, and prompts). Register specialists on the main agent with `WithSubAgents`. Use `WithName` and `WithDescription` when you want clearer labels for routing. Use `WithMaxSubAgentDepth` only if the default nesting limit is not enough. Run `Run`, `Stream`, or `RunAsync` on the main agent. Sub-agents always run without a conversation ID—they do not inherit the main agent session history. If you use `DisableLocalWorker`, pair each `NewAgentWorker` with the same options as the `NewAgent` that runs that agent.\n\nFor streaming scenarios, the main agent is the single subscription point. When using `Stream`, events from all delegated sub-agents fan in to the same main-agent stream, including sub-agent tool approvals and tool call/result events.\n\n```go\nmathAgent, _ := agent.NewAgent(\n    agent.WithName(\"MathSpecialist\"),\n    agent.WithDescription(\"Arithmetic; uses calculator tools.\"),\n    agent.WithTemporalConfig(\u0026agent.TemporalConfig{\n        Host: \"localhost\", Port: 7233, Namespace: \"default\",\n        TaskQueue: \"my-app-math\",\n    }),\n    agent.WithLLMClient(llmClient),\n    agent.WithToolRegistry(mathTools),\n    agent.WithToolApprovalPolicy(agent.AutoToolApprovalPolicy()),\n)\ndefer mathAgent.Close()\n\nmainAgent, _ := agent.NewAgent(\n    agent.WithName(\"Main agent\"),\n    agent.WithSystemPrompt(\"You are a helpful assistant.\"),\n    agent.WithTemporalConfig(\u0026agent.TemporalConfig{\n        Host: \"localhost\", Port: 7233, Namespace: \"default\",\n        TaskQueue: \"my-app-main-agent\",\n    }),\n    agent.WithLLMClient(llmClient),\n    agent.WithSubAgents(mathAgent),\n    agent.WithMaxSubAgentDepth(2),\n    agent.WithToolApprovalPolicy(agent.AutoToolApprovalPolicy()),\n)\ndefer mainAgent.Close()\n\nresult, _ := mainAgent.Run(ctx, \"What is 144 divided by 12?\", \"\")\n```\n\n[examples/agent_with_subagents](examples/agent_with_subagents)\n\n**Stream event fan-in:** Subscribe once on the main agent and you receive events from the whole delegation tree, including sub-agent tool calls and approvals. Use `ev.AgentName` on each `AgentEvent` to see which agent produced the event (content, tools, approvals, complete). The approval payload is `ev.Approval` (`ApprovalEvent`); the requesting agent is not duplicated there—use `ev.AgentName`.\n\n### Approvals\n\nThe model can trigger registry tools (`WithTools` / registry), MCP tools, and delegation to specialists (`WithSubAgents`). **User approval** can be required before any of those run. `WithToolApprovalPolicy` is the one setting that governs all of them. If you omit it, the default is **require-all**—each path goes through your approval handler. For `Run`, set `WithApprovalHandler` whenever approvals can occur. See [examples/agent_with_subagents](examples/agent_with_subagents).\n\n#### Built-in approval policies\n\nThese three types are provided by the `agent` package. For anything else, implement `interfaces.AgentToolApprovalPolicy` (`RequiresApproval`) and pass that value to `WithToolApprovalPolicy`.\n\n- **`RequireAllToolApprovalPolicy`** (default when you omit `WithToolApprovalPolicy`) — every registry tool call, MCP call, and delegation to a sub-agent goes through your approval handler before it runs.\n- **`AutoToolApprovalPolicy()`** — nothing requires approval; use only when you fully trust the agent and its tools.\n- **`AllowlistToolApprovalPolicy`** — only the tools, specialists, and MCP tool ids you list skip approval; everything else still requires approval. Build the policy from `agent.AllowlistToolApprovalConfig` (`ToolNames`, `SubAgentNames`, optional `MCPTools`), check the error, then pass the result to `WithToolApprovalPolicy`.\n\n  ```go\n  approvalPol, err := agent.AllowlistToolApprovalPolicy(agent.AllowlistToolApprovalConfig{\n      ToolNames:     []string{\"calculator\"},\n      SubAgentNames: []string{\"MathSpecialist\"},\n      MCPTools:      map[string][]string{\"remote\": {\"search\"}}, // optional\n  })\n  if err != nil {\n      log.Fatal(err)\n  }\n\n  a, err := agent.NewAgent(\n      agent.WithToolApprovalPolicy(approvalPol),\n      // ... WithApprovalHandler, WithTemporalConfig, etc.\n  )\n  if err != nil {\n      log.Fatal(err)\n  }\n  ```\n\n- Custom tools may implement `interfaces.ToolApproval`; in a standard `NewAgent` configuration, the configured `WithToolApprovalPolicy` is the approval gate used by that agent.\n\n#### Sub-agents (approval behavior)\n\n- `ApprovalRequest` (Run / RunAsync) and stream `ev.Approval` (`ApprovalEvent`) include `Kind` (`tool` or `delegation`) and `DelegateToName` (target specialist when `Kind` is `delegation`). The agent that asked for approval is on `ev.AgentName` for Stream (and `req.AgentName` on `ApprovalRequest`).\n- **Parent (main agent):** one policy for its whole list—e.g. `RequireAll` → approving delegation to MathSpecialist is the same flow as approving `calculator` on that agent. `AutoToolApprovalPolicy()` → no approval for delegation or other tools on that agent.\n- **Specialist:** separate agent, **its own** `WithToolApprovalPolicy`. Calculator calls inside the specialist use **that** policy, not the parent’s.\n\n```text\nMain agent: WithToolApprovalPolicy(RequireAll)     → delegate to math → user approval\nMath agent:  WithToolApprovalPolicy(Auto)         → calculator inside specialist → no approval\nMath agent:  WithToolApprovalPolicy(RequireAll)   → calculator inside specialist → approval (fan-in on main stream)\n```\n\nEach `ApprovalRequest` includes `Respond`; call `req.Respond(Approved|Rejected)` when ready (same as RunAsync):\n\n```go\na, _ := agent.NewAgent(\n    agent.WithApprovalHandler(func(ctx context.Context, req *agent.ApprovalRequest) {\n        // Prompt user, then:\n        _ = req.Respond(agent.ApprovalStatusApproved) // or Rejected\n    }),\n    // ...\n)\na.Run(ctx, prompt, \"\")\n```\n\n**Stream** — receive `AgentEventApproval` and call `agent.OnApproval`:\n\n```go\nfor ev := range eventCh {\n    if ev.Type == agent.AgentEventApproval \u0026\u0026 ev.Approval != nil {\n        // Show UI, then:\n        a.OnApproval(ctx, ev.Approval.ApprovalToken, agent.ApprovalStatusApproved)\n    }\n}\n```\n\n**RunAsync** — channel-based completion without streaming. Do not set `WithApprovalHandler` for this path (it is replaced for the duration of the run). Receive each pending approval on `approvalCh` and call `req.Respond` (same idea as `WithApprovalHandler`):\n\n```go\nresultCh, approvalCh, err := a.RunAsync(ctx, prompt, \"\")\nif err != nil { /* validation error before goroutine started */ }\n\ngo func() {\n    for req := range approvalCh {\n        _ = req.Respond(agent.ApprovalStatusApproved) // or Rejected\n    }\n}()\n\nres := \u003c-resultCh\nif res.Err != nil { /* handle */ }\n// res.Response.Content\n```\n\nFor **Run** / **RunAsync**, use `req.Respond` only. For **Stream**, use `OnApproval` as in the snippet above (first argument comes from `ev.Approval`).\n\n[examples/agent_with_tools_approval](examples/agent_with_tools_approval)\n\n[examples/agent_with_run_async](examples/agent_with_run_async)\n\n**Approval timeout:** `WithApprovalTimeout` (default: `timeout − 30s`) limits how long the user has to approve or reject a tool. If they do not respond in time:\n\n- **Run:** `Run()` returns `nil, err` with the failure.\n- **Stream:** An `AgentEventError` is emitted on the event channel with the error message.\n- **RunAsync:** `resultCh` receives `RunAsyncResult` with `Err` set.\n\n### Timeouts and deadlines\n\nYou can limit run duration in two ways:\n\n**Option 1 — Context with deadline** (per-call):\n\n```go\nctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)\ndefer cancel()\nresult, err := a.Run(ctx, \"Hello\", \"\")\n```\n\n**Option 2 — Agent `WithTimeout`** (when ctx has no deadline):\n\n```go\na, _ := agent.NewAgent(\n    agent.WithTimeout(5 * time.Minute),\n    // ...\n)\nresult, err := a.Run(context.Background(), \"Hello\", \"\")\n```\n\n**Notes:**\n\n- ctx deadline always wins. If ctx has 2 min but agent has `WithTimeout(10 min)`, the run ends at 2 min.\n- approvalTimeout (per-approval limit) comes from agent config. If ctx has 1 hour and you use neither option, approval still expires at ~4.5 min (default). Set `WithTimeout` or `WithApprovalTimeout` for longer approvals.\n- **Agent mode default timeout:** If neither `ctx` nor `WithTimeout` is set, the default timeout follows `WithAgentMode`: **`AgentModeInteractive`** → **5 minutes**; **`AgentModeAutonomous`** → **60 minutes**.\n\n### Custom tools\n\nImplement `interfaces.Tool`: `Name()`, `Description()`, `Parameters()`, `Execute()`. Register with `agent.WithTools(tool1, tool2)`.\n\n[examples/agent_with_custom_tools](examples/agent_with_custom_tools)\n\n### Response format\n\nBy default the agent uses **text-only** output. Use `agent.WithResponseFormat` to request structured output (e.g. JSON with a schema).\n\n**Default (text):** No `WithResponseFormat` — the LLM responds as plain text.\n\n```go\na, _ := agent.NewAgent(\n    agent.WithTemporalConfig(...),\n    agent.WithLLMClient(...),\n    // No WithResponseFormat — text output\n)\n```\n\n**JSON with schema:** Use `interfaces.ResponseFormatJSON` and a valid JSON Schema. The schema must have `type: \"object\"` at the root with `properties`:\n\n```go\nimport \"github.com/agenticenv/agent-sdk-go/pkg/interfaces\"\n\na, _ := agent.NewAgent(\n    agent.WithTemporalConfig(...),\n    agent.WithLLMClient(...),\n    agent.WithResponseFormat(\u0026interfaces.ResponseFormat{\n        Type:   interfaces.ResponseFormatJSON,\n        Name:   \"AgentResponse\",\n        Schema: interfaces.JSONSchema{\n            \"type\":       \"object\",\n            \"properties\": interfaces.JSONSchema{\n                \"response\": interfaces.JSONSchema{\"type\": \"string\"},\n            },\n            \"required\": []any{\"response\"},\n        },\n    }),\n)\n```\n\n[examples/agent_with_json_response](examples/agent_with_json_response) — runnable example: `WithResponseFormat` with `interfaces.ResponseFormat` and `interfaces.JSONSchema` (no tools; validates and pretty-prints JSON on stdout).\n\n**Text explicitly:** Force plain text even if you later add other config:\n\n```go\nagent.WithResponseFormat(\u0026interfaces.ResponseFormat{Type: interfaces.ResponseFormatText})\n```\n\n**Note:** Structured Outputs (JSON schema) require supported models (e.g. `gpt-4o`, `gpt-4o-mini`). Older models may use JSON mode instead. See your provider docs.\n\n### Reasoning / extended thinking\n\nUse **`Reasoning: \u0026interfaces.LLMReasoning{...}`** on **`WithLLMSampling`** (same struct on **`interfaces.LLMRequest`**). Fields are **generic**; each provider maps them:\n\n- **`Enabled`** — **OpenAI**: does not infer `reasoning_effort` from **`Enabled`** alone (standard chat models reject that parameter). **Anthropic**: if **`BudgetTokens`** is 0, uses **1024** tokens minimum for extended thinking. **Gemini**: helps turn on thought output (`IncludeThoughts`).\n- **`Effort`** — **OpenAI** → `reasoning_effort` only when non-empty (use with reasoning-capable models). **Gemini** → `ThinkingLevel` for `low` / `medium` / `high` / `minimal` (only when **`BudgetTokens`** is 0; Gemini forbids budget and level together). **Anthropic** does not use **`Effort`** for its thinking API.\n- **`BudgetTokens`** — **Anthropic** extended-thinking budget (≥1024 when non-zero; smaller values are clamped). **Gemini** → `ThinkingBudget` (wins over **`Effort`** → level). **OpenAI** does not use this field.\n\nStreaming still emits **`AgentEventThinkingDelta`** from Anthropic when the API returns thinking deltas.\n\nRunnable example: [examples/agent_with_reasoning](examples/agent_with_reasoning) (`go run ./examples/agent_with_reasoning/` from the repo root; Temporal + `.env` as in [examples/README.md](examples/README.md)).\n\n### Multiple agents\n\nUse `agent.WithInstanceId` when multiple agents share a base TaskQueue:\n\n```go\na1, _ := agent.NewAgent(\n    agent.WithTemporalConfig(cfg),\n    agent.WithInstanceId(\"agent-1\"),\n    ...\n)\na2, _ := agent.NewAgent(\n    agent.WithTemporalConfig(cfg),\n    agent.WithInstanceId(\"agent-2\"),\n    ...\n)\n```\n\n[examples/multiple_agents](examples/multiple_agents)\n\n### Agent and worker in separate processes\n\nAgent process: use `agent.DisableLocalWorker()`. Worker process: use `agent.NewAgentWorker()` with the same config.\n\n```go\n// Worker process\nw, _ := agent.NewAgentWorker(agent.WithTemporalConfig(...), agent.WithLLMClient(...))\ndefer w.Close()\ngo w.Start()\n\n// Agent process\na, _ := agent.NewAgent(\n    agent.WithTemporalConfig(...),\n    agent.WithLLMClient(...),\n    agent.DisableLocalWorker(),\n)\nresult, _ := a.Run(ctx, \"Hello\", \"\")\n```\n\n[examples/agent_with_worker](examples/agent_with_worker) · [examples/durable_agent](examples/durable_agent)\n\n\u003e **Interactive vs autonomous:** By default the agent uses `AgentModeInteractive`\n\u003e (5-minute timeout, worker check enabled). For long-running background agents, set\n\u003e `agent.WithAgentMode(agent.AgentModeAutonomous)` to skip the worker check and use a\n\u003e 60-minute default timeout. See [`WithAgentMode`](#configuration) for full detail.\n\n### Conversation (message history)\n\nPass `agent.WithConversation(conv)` to persist message history for multi-turn context. Use `agent.WithConversationSize(n)` to limit how many messages are fetched for LLM context (default 20).\n\n**Conversation ID:** When the agent is configured with a conversation, pass the same `conversationID` to both `Run(ctx, prompt, conversationID)` and `Stream(ctx, prompt, conversationID)` for the same session—so history is shared across turns.\n\nChoose implementation by deployment:\n\n| Deployment                                                           | Use                                                       |\n| -------------------------------------------------------------------- | --------------------------------------------------------- |\n| **Single process** (agent and worker in same process)                | `inmem.NewInMemoryConversation`                           |\n| **Remote workers** (`DisableLocalWorker` or `EnableRemoteWorkers()`) | `redis.NewRedisConversation` or another distributed store |\n\nTo add a new conversation store (e.g., Postgres, MongoDB), implement the `interfaces.Conversation` interface in `[pkg/interfaces/conversation.go](pkg/interfaces/conversation.go)`. The interface requires `AddMessage`, `ListMessages`, `Clear`, and `IsDistributed`. See `pkg/conversation/inmem` and `pkg/conversation/redis` for reference.\n\nIn-memory cannot be used with remote workers—the agent will return an error at build time.\n\n**Remote workers:** Agent and worker must use the same conversation store (same Redis config) so both processes access the same data. Only the process that calls `Run` or `Stream` passes the conversation ID; the worker does not.\n\n```go\n// Single process (default)\nconv := inmem.NewInMemoryConversation(inmem.WithMaxSize(100))\na, _ := agent.NewAgent(\n    agent.WithTemporalConfig(...),\n    agent.WithLLMClient(...),\n    agent.WithConversation(conv),\n    agent.WithConversationSize(20), // optional; default 20\n)\nresult, _ := a.Run(ctx, \"Hello\", \"session-1\")\n\n// Worker process\nconvW, _ := redis.NewRedisConversation(redis.WithAddr(\"localhost:6379\"))\ndefer convW.Close()\nw, _ := agent.NewAgentWorker(\n    agent.WithTemporalConfig(...),\n    agent.WithLLMClient(...),\n    agent.WithConversation(convW),\n)\ngo w.Start()\n\n// Agent process\nconvA, _ := redis.NewRedisConversation(redis.WithAddr(\"localhost:6379\"))\ndefer convA.Close()\na, _ := agent.NewAgent(\n    agent.WithTemporalConfig(...),\n    agent.WithLLMClient(...),\n    agent.DisableLocalWorker(),\n    agent.WithConversation(convA),\n)\nresult, _ := a.Run(ctx, \"Hello\", \"session-1\")\n```\n\n**Lifecycle:** You own the conversation. Call `Clear` when ending a session or when you no longer need the history. The agent never calls `Clear`.\n\n**Example (in-memory, single process):**\n\n```go\nimport (\n    \"github.com/agenticenv/agent-sdk-go/pkg/agent\"\n    \"github.com/agenticenv/agent-sdk-go/pkg/conversation/inmem\"\n)\n\nconv := inmem.NewInMemoryConversation(inmem.WithMaxSize(100))\na, _ := agent.NewAgent(\n    agent.WithTemporalConfig(...),\n    agent.WithLLMClient(...),\n    agent.WithConversation(conv),\n    agent.WithConversationSize(20),\n)\ndefer a.Close()\n\nconvID := \"session-1\"\na.Run(ctx, \"I'm Alice. Remember that.\", convID)\na.Run(ctx, \"What's my name?\", convID) // agent uses history: \"Alice\"\n```\n\n[examples/agent_with_conversation](examples/agent_with_conversation)\n\n---\n\n## Configuration\n\nA Temporal connection is **required** — one of `WithTemporalConfig` or `WithTemporalClient` must be set; the agent does not run with LLM-only config.\n\n- **WithTemporalConfig**: Temporal connection (Host, Port, Namespace, TaskQueue). Use for simple setups. See [Temporal connection](#temporal-connection).\n- **WithTemporalClient**: Pre-configured Temporal client. Use for TLS, API key auth, Temporal Cloud. Requires `WithTaskQueue`. Agent does not close the client.\n- **WithTaskQueue**: Task queue name. Required when using `WithTemporalClient`. Ignored when using `WithTemporalConfig`.\n- **WithResponseFormat**: LLM response format. Omit for text-only. Use `\u0026interfaces.ResponseFormat{Type, Name, Schema}` for JSON with schema. See [Response format](#response-format).\n- **WithConversation**: Message history store. Use `inmem` for single process; `redis` for remote workers. Pass same `conversationID` to `Run` and `Stream` for a session. See [Conversation](#conversation-message-history).\n- **WithConversationSize**: Max messages to fetch for LLM context (default 20). Only applies when `WithConversation` is set.\n- **EnableRemoteWorkers**: Pass `EnableRemoteWorkers()` when using `DisableLocalWorker` with approval or streaming (starts the event worker/workflow path).\n- **WithSubAgents**: Attach specialist agents the main agent can delegate to. Each needs its own task queue and worker. See [Sub-agents](#sub-agents).\n- **WithMaxSubAgentDepth**: Maximum delegation hops from this agent (default 2). See [Sub-agents](#sub-agents).\n- **WithMaxIterations**: Max LLM rounds (default 5).\n- **WithStream**: Enable `Stream` partial content streaming.\n- **Token usage:** Not a separate option. On **`Run`**, read **`result.Usage`** (`*interfaces.LLMUsage`) when set. On **`Stream`**, read **`ev.Usage`** on the root agent’s **`complete`** event (aggregated across tool rounds). See [Token usage](#token-usage-llmusage).\n- **WithLLMSampling**: Pass `\u0026agent.LLMSampling{...}`; nil or zero fields leave that knob to the provider default. Which fields apply where:\n  - **`Temperature`** — OpenAI, Anthropic, Gemini.\n  - **`MaxTokens`** — OpenAI, Anthropic, Gemini (max output / completion tokens).\n  - **`TopP`** — OpenAI, Gemini only (not sent to Anthropic).\n  - **`TopK`** — Anthropic only (not sent to OpenAI or Gemini).\n  - **`Reasoning`** (`*interfaces.LLMReasoning`) — optional generic controls; each LLM client maps them:\n    - **`Enabled`** — requests reasoning/thinking on providers that support it (see below).\n    - **`Effort`** — `\"none\"` … `\"xhigh\"`; **OpenAI** → `reasoning_effort` when non-empty; **Gemini** → `ThinkingLevel` when `low` / `medium` / `high` / `minimal`; **Anthropic** ignores (use **`BudgetTokens`** for extended thinking).\n    - **`BudgetTokens`** — **Anthropic** extended-thinking budget (non-zero; SDK clamps below 1024 to 1024); **Gemini** `thinkingBudget` (if set, **`Effort`** is not sent as `ThinkingLevel`—Gemini allows only one of budget or level); **OpenAI** ignores.\n- **WithApprovalTimeout**: Max wait per tool approval; must be less than agent timeout. Defaults to timeout−30s when tools require approval. Capped at 31 days.\n- **WithAgentMode**: Sets the agent mode. Default timeout when neither `ctx` nor `WithTimeout` is set follows the mode (**5 minutes** / **60 minutes**). Worker-check and queueing behaviour below are **mostly relevant to the Temporal runtime** (this SDK’s execution backend).\n  - **`AgentModeInteractive` (default)** — Optimised for chat, REPL, and web apps where a human is waiting. When `DisableLocalWorker` is set, the agent checks for available external workers before submitting work — if no worker is ready within the check window, it returns a clear error immediately rather than leaving the user with a hanging prompt. Default timeout is **5 minutes**.\n  - **`AgentModeAutonomous`** — Optimised for background jobs, pipelines, and long-running tasks where no human is watching. The worker check is skipped entirely — if no worker is available, Temporal queues the workflow and waits for one naturally. Default timeout is **60 minutes**.\n\n**Env config:** [examples/README.md](examples/README.md) for examples; [cmd/README.md](cmd/README.md) for CLI.\n\n---\n\n## Development\n\nContributors: see **[CONTRIBUTING.md](CONTRIBUTING.md)** for prerequisites (Go, Temporal setup, workflow, and guidelines).\nProject policies: **[SECURITY.md](SECURITY.md)** for vulnerability reporting and **[CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)** for community standards.\n\nQuick commands: `make test` | `make lint` | `make fmt` | `make spell` | `make tidy` | `make test-coverage` (`make lint` runs `gofmt -s`, `misspell`, then `go vet` + `golangci-lint`)\n\n## Code Coverage\n\nCoverage reports (PR and default branch) are on **[Codecov](https://app.codecov.io/gh/agenticenv/agent-sdk-go)**. Locally, run `make test-coverage` to produce `coverage.out` and `coverage.html`.\n\n---\n\n## Setup and run examples\n\n\u003e Note: Meet **[Getting Started — Prerequisites](#prerequisites)** (Go, LLM, Temporal). For installing or running a Temporal server (Docker, CLI, Cloud, self-hosted), use **[Temporal setup](temporal-setup.md)**. Then:\n\n```bash\ngit clone \u003crepo-url\u003e\ncd agent-sdk-go\ncp examples/env.sample examples/.env\n# Edit examples/.env: set LLM_APIKEY, LLM_MODEL\n```\n\nSee **[examples/README.md](examples/README.md)** for how to run examples, the CLI, and optional flows such as MCP streamable HTTP (including example-specific environment variables).\n\n### CLI configuration\n\nThe CLI uses a YAML config file. Copy the sample and add your values:\n\n```bash\ncp cmd/config.sample.yaml cmd/config.yaml\n# Edit cmd/config.yaml: set llm.apiKey (or use AGENT_LLM_APIKEY env var)\ngo run ./cmd\n```\n\nOr run with a custom config path: `go run ./cmd -config /path/to/config.yaml`.\n\n- **config.sample.yaml** — template in the repo (safe to commit)\n- **config.yaml** — your config (gitignored; copy from sample)\n- **Env overrides** — `AGENT_LLM_APIKEY`, `AGENT_TEMPORAL_HOST`, etc. override file values\n\nSee **[cmd/README.md](cmd/README.md)** for CLI details and env vars.\n\n## Production Readiness Checklist\n\n- **Run and approval limits** — Use `WithTimeout` and/or a context deadline on `Run` / `Stream`; use `WithApprovalTimeout` when tools require approval (activity retry counts inside workflows are fixed in the SDK, not user-tunable).\n- **Bound agent loops** — Set `WithMaxIterations` and, if you use sub-agents, `WithMaxSubAgentDepth`.\n- **Tool and delegation risk** — Choose `WithToolApprovalPolicy` per agent (main and specialists); use human review for dangerous tools, MCP-exposed capabilities, and delegation where policy requires it.\n- **MCP** — Remote servers widen your tool surface; prefer TLS in production; avoid `SkipTLSVerify` outside local dev; protect bearer tokens, OAuth secrets, and header-based credentials.\n- **Split processes** — If you use `DisableLocalWorker` or `EnableRemoteWorkers()`, use a distributed conversation store (e.g. Redis) and exercise approval/streaming paths in integration tests.\n- **Secrets and data** — Keep LLM and Temporal credentials out of source control; treat tool arguments and model output as untrusted in your app.\n- **LLM safety** — Validate and sanitize prompts, tool args, and model output at your integration boundary.\n- **Operations** — Use your logger (`WithLogger` / `WithLogLevel`) and the Temporal UI/history for a given run; after upgrading this module, confirm workflows still replay in your environment.\n\n---\n\n## Disclaimer\n\nThis project is provided \"as is\" under the Apache License 2.0. When building AI agents that execute real-world actions, ensure appropriate safeguards, validation, and human-in-the-loop approval workflows are in place. You are responsible for compliance, access control, and operational safety in your deployment. For security issues, follow **[SECURITY.md](SECURITY.md)**.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fagenticenv%2Fagent-sdk-go","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fagenticenv%2Fagent-sdk-go","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fagenticenv%2Fagent-sdk-go/lists"}