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>
111 lines
3.6 KiB
Markdown
111 lines
3.6 KiB
Markdown
# Agent (`pkg/agent`)
|
|
|
|
LLM agent orchestration framework.
|
|
|
|
## Agent construction
|
|
|
|
```go
|
|
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
|
|
|
|
```go
|
|
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:
|
|
|
|
```go
|
|
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
|
|
|
|
```go
|
|
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.WithTimeout` becomes 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
|
|
//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
|