agents-go tracks openai-agents-python v0.17.4: the run loop, item model, defaults (max turns 10, strict schemas on, tool errors fed back to the model, tool_choice reset after tool use) and most names map one-to-one. This page lists everything that intentionally differs — first how the same concepts look in Go, then what each side has that the other lacks.
| Python | Go |
|---|---|
Agent(name=..., instructions=...) |
&agents.Agent{Name: ..., Instructions: agents.StaticInstructions(...)} |
instructions= callable |
agents.InstructionsFunc(func(ctx, rc, agent) (string, error)) |
Runner.run / Runner.run_sync |
agents.Run(ctx, agent, input, opts) (Go has no sync/async split) |
Runner.run_streamed |
agents.RunStreamed(ctx, agent, input, opts) |
run_config / Runner.run(...) kwargs |
agents.RunOptions{...} |
@function_tool decorator |
agents.NewFunctionTool[Args, Result](name, desc, fn) |
| pydantic argument model + docstring | argument struct + json:"..."/jsonschema:"..." tags |
output_type=MyModel |
OutputType: agents.OutputType[MyModel]() |
ToolOutputText / ToolOutputImage / ToolOutputFileContent |
agents.ToolOutputText / ToolOutputImage / ToolOutputFile (return one, or []agents.ToolOutputContent, from a function tool) |
result.final_output_as(T) |
agents.FinalOutputAs[T](res) |
handoff(agent) / agent.handoffs |
agents.HandoffTo(agent) / Agent.Handoffs |
agent.as_tool(...) |
agent.AsTool(agents.AgentToolConfig{...}) |
@input_guardrail / @output_guardrail |
agents.InputGuardrail{Name, Run} / agents.OutputGuardrail{Name, Run} struct values |
RunContextWrapper[T] |
*agents.RunContext with Context any (type-assert back) |
SQLiteSession |
memory.FileSession (JSONL file; same Session interface) |
reset_tool_choice=True (default) |
DisableToolChoiceReset (zero value = Python's default behavior) |
max_turns=10 |
RunOptions.MaxTurns (0 means the same default of 10) |
exceptions (MaxTurnsExceeded, …) |
error values (*MaxTurnsError, …) matched with errors.As |
RunErrorDetails on exceptions |
AgentsError.Details, reachable via agents.AsAgentsError(err) |
set_default_openai_key / globals |
none — pass openai.NewProvider(...) explicitly in RunOptions |
Generics and reflection instead of pydantic. Tool schemas come from struct reflection at construction time (NewFunctionTool[A, R]), structured outputs from OutputType[T](). Validation on the way back in uses encoding/json plus a root-level required-key check — looser than pydantic's full validation (nested required fields are not enforced).
Two contexts instead of one wrapper. Python's RunContextWrapper[T] carries both your data and run state. Go splits them: context.Context handles cancellation/deadlines (and is honored mid-run, mid-stream and inside tools), while RunContext.Context any carries your data without generics on every type.
Errors instead of exceptions. Every failure is a returned error. SDK error types embed AgentsError; errors.As matches concrete types even through %w wrapping, and agents.AsAgentsError extracts the embedded base (with RunErrorDetails) generically.
Concurrency is explicit. Tools requested in one turn run concurrently via goroutines (Python interleaves on the event loop). Hooks and shared context values must be goroutine-safe. Streaming uses iter.Seq2 (for event, err := range sr.Events()) instead of async for, and there is no run_sync because Run is already synchronous.
Sealed interfaces instead of unions. Tool, StreamEvent, RunItem and ToolUseBehavior are closed interfaces you type-switch on, mirroring Python's Union types.
| Area | Python v0.17.4 | Go |
|---|---|---|
| Tool errors | failure_error_function default feeds the error to the model |
Same default (DefaultToolErrorFunction); set the field to nil for fatal |
| Tool timeout | timeout_seconds + timeout_behavior (error_as_result / raise_exception) |
FunctionTool.Timeout → *ToolTimeoutError, fed back via FailureErrorFunction when set (≈ error_as_result), else fatal (≈ raise_exception) |
| Model refusal | refusal text surfaces as plain content | run fails with *ModelRefusalError carrying the refusal |
| Handoff input filter | receives input_history / pre_handoff_items / new_items separately |
receives one flattened InputHistory; the session always keeps the unfiltered conversation. NestHandoffHistory ports nest_handoff_history (fold + flatten) on top of this |
| HITL state | RunState JSON (Python format) |
RunState JSON round-trips Go↔Go only, and rebuilding needs an agent-name registry (Go functions don't serialize) |
| Input guardrail timing | parallel with the first model call | same for Run; RunStreamed runs them synchronously before the first call |
| Streamed text items | message_output_created fires once per completed message |
same (use raw delta events for token-level UI) |
| Session backends | SQLite / SQLAlchemy / Redis / encrypted / OpenAI Conversations / compaction | InMemorySession + FileSession (JSONL) in core; sessions module adds SQLite/PostgreSQL via bun; openai.ConversationsSession (server-side via the Conversations API); openai.CompactionSession (responses.compact decorator, attempted once per run vs Python's per turn); implement Session for anything else |
| Tracing backend | OpenAI traces dashboard by default | generic tracer → processor → exporter pipeline (console/HTTP/custom); not the OpenAI dashboard wire format. Traces export at start, spans at finish |
| Server-side conversation state | previous_response_id / conversation_id parameters |
RunOptions.UsePreviousResponseID and RunOptions.ConversationID (both send only deltas; neither combines with a local Session). openai.ConversationsSession also persists history server-side via the Conversations API |
| Stored prompts | Agent(prompt=Prompt(id, version, variables)) / DynamicPromptFunction |
Agent.Prompt = StaticPrompt(agents.Prompt{...}) or PromptFunc(...) (OpenAI Responses backend only) |
Usage of nested as_tool runs |
separate from parent | same (separate), but nested spans join the parent trace |
- Hosted OpenAI tools: web search, file search, code interpreter, computer use, image generation,
local_shell,apply_patch— deliberately not modeled; tools are provider-agnostic function tools, and a non-standardtool_choiceis sent as a function name. (For file editing without the hostedapply_patch, seetools/editor's provider-agnostic str_replace tools; tools) - Chat Completions model layer — only the Responses API (use a Responses-compatible gateway, or implement
Model) - LiteLLM adapter — but native multi-provider routing, retry and fallback are supported via
Modeldecorators (models) - Redis / encrypted / SQLAlchemy session backends — only SQLite & PostgreSQL are provided (
sessionsmodule); implementSessionfor others. (OpenAIConversationsSessionandOpenAIResponsesCompactionSessionare ported, asopenai.ConversationsSessionandopenai.CompactionSession.) - Realtime and voice agents
- REPL utility (
run_demo_loop) and visualization (Graphviz)
- Self-hosted sandboxes: run model-written code in locked-down Docker containers or Kubernetes Jobs in your own infrastructure (
sandbox,sandbox/docker,sandbox/k8smodules), exposed viasandbox.CodeTool. Python's sandboxes target hosted providers (e2b / modal / blaxel) rather than self-hosted Docker/K8s - Hooks can veto: any hook returning an error aborts the run (Python hooks are observe-only)
FileSession: zero-dependency JSONL persistence with per-path locking and atomic rewrites- Skills (
skillsmodule): the open Agent SkillsSKILL.mdformat implemented onInstructions+ a function tool — provider-agnostic and sandbox-free, unlike Python's sandbox-capability skills