Move agent-run orchestration from the legacy supervisor path into the new agentrun worker/service package and wire it through coredata, server, policies, and GraphQL resolvers. This consolidates run lifecycle handling around lease-aware workers and aligns API surface with the new agent-run domain model so reviewers can follow one coherent execution path. Signed-off-by: Bryan Frimin <bryan@probo.com>
3.6 KiB
Agent (pkg/agent)
LLM agent orchestration framework.
Agent construction
agent := agent.NewAgent(
"agent-name",
"System instructions here",
agent.WithTools(tool1, tool2),
agent.WithHandoffs(otherAgent),
agent.WithModel(model),
)
Functional options: WithTools, WithHandoffs, WithInstructions, WithModel, WithModelSettings, WithMCPServers, WithInputGuardrails, WithOutputGuardrails, WithApproval, WithSession.
Execution
result, err := agent.Run(ctx, messages)
result.FinalMessage().Text() // final output
result.LastAgent // agent that produced the result
Prefer RunTyped[T] over Run + manual unmarshalling when the agent declares a
structured output type via WithOutputType. RunTyped validates the response
against the JSON Schema and returns the typed value directly:
result, err := agent.RunTyped[TrackerIdentification](ctx, ag, messages)
identification := result.Output // already typed, no json.Unmarshal needed
Only fall back to agent.Run when the agent produces free-form text with no
output schema.
Tool interface
type Tool interface {
Name() string
Description() string
Parameters() jsonschema.Schema
Execute(ctx context.Context, input json.RawMessage) (string, error)
}
Agent-as-tool
agent.AsTool(name, description) wraps an agent as a tool for composition.
Cancellation semantics
ctx.Done() is a graceful-suspend signal, not a hard abort. When
the caller cancels ctx, Run/RunStreamed/Resume/Restore let
the in-flight LLM call and tool finish, persist a checkpoint via the
configured Checkpointer, and return *SuspendedError. The framework
shields downstream calls (LLM, tools, hooks, guardrails, save) from
the cancellation so they complete naturally; the cancel is only
observed at the next safe boundary.
Use agent.ErrSuspendForCheckpoint as the cancel cause when the
intent is graceful suspend — workers that distinguish a
graceful-stop request from infrastructure-level causes (lease loss,
heartbeat failure) inspect context.Cause(ctx) to dispatch.
Implications:
- A
context.WithTimeoutbecomes a "max wall-clock budget then suspend" — strictly better than the alternative where the deadline kills work outright. - There is no in-process hard-abort path. Callers that genuinely need to kill a run terminate the process; stale recovery handles the row.
- Tools receive a non-cancellable ctx; if a tool needs a hard deadline
it must derive its own with
context.WithTimeout(ctx, ...)inside the tool body.
The agent run worker (pkg/agentrun/handler.go) maps a SIGTERM-driven
shutdown broadcast onto a per-run cancelRun(agent.ErrSuspendForCheckpoint),
so the same contract drives both the public Go API and the worker
infrastructure path.
Prompt templates
Prompt files with placeholders use .txt.tmpl (see general template naming
convention in .cursor/rules/template-files.mdc).
Use agent.WithInstructionsFunc to build prompts dynamically at runtime:
//go:embed prompts/tracker_identification.txt.tmpl
var trackerIdentificationPrompt string
func trackerMappingInstructions(_ context.Context, _ *agent.Agent) string {
categories := coredata.ThirdPartyCategories()
parts := make([]string, len(categories))
for i, c := range categories {
parts[i] = string(c)
}
return strings.Replace(trackerIdentificationPrompt, "{{.Categories}}", strings.Join(parts, ", "), 1)
}
Limits
- Max turns: 10 (default)
- Max tool depth: 16 (default)
- Depth tracking prevents infinite recursion in handoffs