Skip to main content

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 fieldUse
Contextpass to the next agent.Input
NewMessagesrecord prompts, assistant attempts, and tool results from the run
Finaldisplay or inspect the last assistant attempt
Compactionsinspect 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.

API reference