Running an Agent
In Getting started we wrote the tool loop by hand: run the call, keep
the assistant message with its result, send the request again. agent.Agent is that loop, packaged as a reusable
definition. The application configures it once, and every Start gives an independent stateful Run that validates
tool calls, executes them, feeds the results back, and keeps going until the model stops, a batch requests termination,
or the run fails.
Defining the Agent once
An agent.Agent definition needs a Generator and a model. Tools, compaction, and token estimation attach here too.
assistant, err := agent.New(agent.Config{
Generator: generator,
Model: model,
Options: generation.Options{
MaxOutputTokens: new(1000),
},
})
if err != nil {
return err
}
agent.New validates the configuration, compiles every registered tool schema, and copies the definition. The
Generator, estimator, summarizer, and tool functions must support concurrent calls, because one Agent can run many
conversations at the same time.
Starting one run
Each run receives the stored conversation state and the new prompts.
input := agent.Input{
Context: agent.Context{
SystemPrompt: "You are a concise technical assistant.",
},
Prompts: []message.UserMessage{
message.NewUserMessage("Explain how an Agent works in one sentence."),
},
}
Start copies the input before any work begins and rejects a nil context.Context or invalid messages before returning
a Run. Per-run Input.Options overrides only the non-nil fields of Config.Options.
Taking the result without a display
When no interface needs live events, Run drains the run internally and returns the settled result.
result, err := assistant.Run(ctx, input)
if err != nil {
fmt.Fprintf(os.Stderr, "last attempt: %s\n", result.Final.Text())
return err
}
fmt.Println(result.Final.Text())
Both values matter on failure. Final holds the last assistant attempt and can keep accepted partial content even when
err is non-nil.
Watching the run live
A run is watched with Start, an Events iteration, and a final Wait after the iteration ends.
run, err := assistant.Start(ctx, input)
if err != nil {
return err
}
for event := range run.Events() {
switch event := event.(type) {
case agent.MessageUpdateEvent:
update, ok := event.AssistantMessageEvent.(generation.TextDeltaEvent)
if ok {
fmt.Fprint(os.Stderr, update.Delta)
}
case agent.ToolExecutionStartEvent:
fmt.Fprintf(os.Stderr, "\ncalling %s\n", event.Call.Name)
case agent.ToolExecutionEndEvent:
fmt.Fprintf(os.Stderr, "%s finished, error=%t\n", event.Call.Name, event.Result.IsError)
}
}
result, err := run.Wait()
if err != nil {
return err
}
fmt.Println(result.Final.Text())
Run.Abort() cancels only that run. Observing Agent events explains the event stream in detail.
Keeping the settled result
Wait returns the same cloned result and error on every call, and the host application stores one field of it for the
next run.
| Result field | Use |
|---|---|
Context | pass to the next agent.Input |
NewMessages | record prompts, assistant attempts, and tool results from the run |
Final | display or inspect the last assistant attempt |
Compactions | inspect successful context rewrites |
The returned Context already accounts for what can be replayed safely, so appending Final or NewMessages would
double-count it.
Continuing the conversation
The application replaces its stored state with the returned Context before supplying the next prompt. This is the
whole persistence model, and Storing and continuing conversation state explains it in full, including a
two-round continuation example.
result, err := assistant.Run(ctx, agent.Input{
Context: state,
Prompts: []message.UserMessage{
message.NewUserMessage("Give one example."),
},
})
if err != nil {
return err
}
state = result.Context
Understanding a run
An assistant response with calls produces one complete batch. The Agent validates every call, executes the batch,
records one result message per call, and starts another turn unless the batch terminates. A response with
message.StopReasonLength that still contains calls is not executed. The Agent records error results instead, because
the JSON arguments may be incomplete.
There is no built-in turn count. Every run should carry a deadline or a cancellable context.
Handling failures and cancellation
Failures during a run fall into three groups, and each group is handled differently. The deciding question is which layer the failure came from.
Handling generation and provider failures
Generation, provider, and required-compaction errors settle the run, and Wait returns them for classification with
errors.Is and errors.As. The one exception is a context-window rejection, which the Agent recovers from by
compacting and retrying when a Compactor is attached, at most twice. The
Compacting conversation context chapter describes that
path.
Handling tool failures
Tool lookup failures, invalid arguments, function errors, invalid returned content, and tool panics become IsError
result messages for the model. They do not fail the run by themselves, and the model can respond to them, correct its
arguments, and try again.
Keeping state after a failed run
The Storing and continuing conversation state chapter owns the full
inventory of what a failed run keeps: which attempts stay in NewMessages, which stay out of Context.Messages, what
survives a tool-batch failure, and what the application decides about tool side effects.