“Context” means two distinct things, and the Go SDK keeps them separate by design:
RunContextTwo concerns that are often conflated stay separate here:
context.Context passed to Run (and through to every tool, guardrail and hook).RunContext.Context, an any value you supply via RunOptions.Context. The SDK never inspects it.type AppContext struct {
UserID string
DB *sql.DB
}
res, err := agents.RunSync(ctx, agent, input, agents.RunOptions{
Context: &AppContext{UserID: "u_123", DB: db},
Model: agents.ModelOptions{Provider: provider},
})
Every tool, guardrail, hook and dynamic-instructions function receives the same *agents.RunContext; type-assert your value back:
tool := agents.NewTool("whoami", "Return the current user.",
func(ctx context.Context, tc *agents.ToolContext, _ struct{}) (string, error) {
app := tc.RunContext.Context.(*AppContext)
return app.UserID, nil
})
RunContext also carries run-scoped state the SDK maintains for you:
| Field | Meaning |
|---|---|
Context |
Your value, verbatim |
Usage |
Token usage accumulated so far (Usage) |
Approvals |
Recorded human-in-the-loop decisions (Human-in-the-loop) |
ToolContext embeds *RunContext and adds the call’s ToolName, ToolCallID and raw ToolArguments.
Tools run concurrently within a turn. If tools mutate your context value, make it goroutine-safe.
The LLM only sees the conversation history. To expose data to it:
Run.