Documentation
¶
Overview ¶
Package agent provides the runtime core for autonomous LLM agents: a loop that calls a language model, executes the tools it requests, feeds results back, and repeats until the model finishes or a stop condition fires.
It builds on the sibling github.com/rsbin1178/pips/ai package — any ai.LanguageModel (with whatever middleware stack) drives an agent, and conversations are ordinary ai.Message histories.
The core abstraction is Agent, an immutable bundle of model, system prompt, tools, and loop policy. Runs mutate a Session:
calc := agent.NewTool("add", "Add two integers.",
func(ctx context.Context, args struct {
A int `json:"a"`
B int `json:"b"`
}) (string, error) {
return strconv.Itoa(args.A + args.B), nil
})
a, err := agent.New(model, agent.WithTools(calc))
sess := agent.NewSession()
result, err := a.Run(ctx, sess, ai.UserText("What is 2+3?"))
Agent.Stream exposes the same loop as an event sequence (model deltas, tool lifecycle, turn boundaries); breaking out of the range loop cancels the run.
Tool failures — errors, panics, timeouts, undecodable arguments, calls to unknown tools — never abort a run: each becomes an error tool result the model can react to. Every tool call the model issues is answered before the next model call, even on cancellation, so sessions stay resumable.
A WithBeforeTool gate intercepts calls before execution: deny with a reason the model sees, or pause the run for out-of-band approval and resume it later via Session.ResolveToolCalls or Session.ResolvePending. WithInputGuardrail and WithOutputGuardrail validate conversation boundaries, while WithAfterTool inspects and overrides executed results. WithTransformContext reshapes what each model call sees (the context-compaction injection point), and WithPrepareTurn swaps the model, commits a history rewrite, or replaces the run-scoped tool snapshot between turns.
A running loop can be redirected without restarting: Session.Steer injects messages before the next model call, and Session.FollowUp queues work for after the model would otherwise finish. AsTool turns any agent into another agent's tool for sub-agent delegation, and a tool returning ErrTerminate ends the run from inside a tool batch.
WithName labels an agent. Every event and result carries RunMetadata; nested agents inherit a parent run ID through context, and tools can read the current identity with RunMetadataFromContext.
The package has no third-party runtime dependencies.
Example (Run) ¶
Run drives the agent loop to completion and returns the final result.
package main
import (
"context"
"fmt"
"log"
"os"
"strconv"
"github.com/rsbin1178/pips/agent"
"github.com/rsbin1178/pips/ai"
"github.com/rsbin1178/pips/ai/openai"
)
func main() {
model := openai.New("gpt-6-astra", openai.WithAPIKey(os.Getenv("OPENAI_API_KEY")))
add := agent.NewTool("add", "Add two integers.",
func(_ context.Context, args struct {
A int `json:"a"`
B int `json:"b"`
},
) (string, error) {
return strconv.Itoa(args.A + args.B), nil
})
a, err := agent.New(model, agent.WithTools(add))
if err != nil {
log.Fatal(err)
}
sess := agent.NewSession()
result, err := a.Run(context.Background(), sess, ai.UserText("What is 21+21?"))
if err != nil {
log.Fatal(err)
}
fmt.Println(result.Stop, result.Text())
}
Output:
Example (Stream) ¶
Stream exposes the loop as events; breaking out cancels the run.
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/rsbin1178/pips/agent"
"github.com/rsbin1178/pips/ai"
"github.com/rsbin1178/pips/ai/openai"
)
func main() {
model := openai.New("gpt-6-astra", openai.WithAPIKey(os.Getenv("OPENAI_API_KEY")))
a, err := agent.New(model)
if err != nil {
log.Fatal(err)
}
for ev, err := range a.Stream(context.Background(), agent.NewSession(), ai.UserText("Hello!")) {
if err != nil {
log.Fatal(err)
}
if event, ok := ev.Payload().(agent.ModelStreamEvent); ok &&
event.Event.Type == ai.StreamTextDelta {
fmt.Print(event.Event.Text)
}
}
}
Output:
Index ¶
- Constants
- Variables
- func ReportProgress(ctx context.Context, parts ...ai.Part)
- func TextResult(s string) []ai.Part
- type Agent
- type CandidateAnswerDecision
- type CandidateAnswerInfo
- type CandidateDiscarded
- type ConcurrencySafe
- type Event
- type EventPayload
- type EventType
- type GuardrailError
- type GuardrailPhase
- type InputGuardrailInfo
- type MessageCommitted
- type ModelRequestUpdate
- type ModelStreamEvent
- type Option
- func WithAfterTool(fn func(ctx context.Context, info ToolResultInfo) *ToolResultOverride) Option
- func WithBeforeTool(fn func(ctx context.Context, info ToolCallInfo) ToolDecision) Option
- func WithCandidateAnswer(fn func(context.Context, CandidateAnswerInfo) CandidateAnswerDecision) Option
- func WithFollowUpMode(m QueueMode) Option
- func WithInputGuardrail(name string, fn func(context.Context, InputGuardrailInfo) error) Option
- func WithMaxTokens(n int) Option
- func WithMaxTurns(n int) Option
- func WithName(name string) Option
- func WithOnEvent(fn func(ctx context.Context, ev Event)) Option
- func WithOutputGuardrail(name string, fn func(context.Context, OutputGuardrailInfo) error) Option
- func WithParallelTools(limit int) Option
- func WithPrepareTurn(fn func(ctx context.Context, info RunInfo) TurnUpdate) Option
- func WithRequest(fn func(*ai.Request)) Option
- func WithSteeringMode(m QueueMode) Option
- func WithStopWhen(cond func(RunInfo) bool) Option
- func WithSystem(s string) Option
- func WithToolTimeout(d time.Duration) Option
- func WithTools(tools ...Tool) Option
- func WithTransformContext(fn func(ctx context.Context, msgs []ai.Message) ([]ai.Message, error)) Option
- type OutputGuardrailInfo
- type QueueMode
- type RunCompleted
- type RunInfo
- type RunMetadata
- type RunResult
- type RunStarted
- type Session
- func (s *Session) Append(msgs ...ai.Message)
- func (s *Session) ClearFollowUps()
- func (s *Session) ClearSteering()
- func (s *Session) FollowUp(msgs ...ai.Message)
- func (s *Session) HasQueued() bool
- func (s *Session) MarshalJSON() ([]byte, error)
- func (s *Session) Messages() ai.Messages
- func (s *Session) Pending() []ai.ToolCallPart
- func (s *Session) Replace(msgs ...ai.Message)
- func (s *Session) ResolvePending(ctx context.Context, ...) error
- func (s *Session) ResolveToolCalls(resolutions ...ToolResolution) error
- func (s *Session) Steer(msgs ...ai.Message)
- func (s *Session) UnmarshalJSON(data []byte) error
- func (s *Session) Usage() ai.Usage
- type StopReason
- type Tool
- type ToolCall
- type ToolCallInfo
- type ToolCompleted
- type ToolDecision
- type ToolDecisionAction
- type ToolResolution
- type ToolResultInfo
- type ToolResultOverride
- type ToolStarted
- type ToolUpdated
- type TurnCompleted
- type TurnStarted
- type TurnUpdate
Examples ¶
Constants ¶
const ( // DefaultMaxTurns bounds a run to 25 model calls unless overridden with // [WithMaxTurns]. An unbounded loop is the classic agent failure mode, so // the limit is opt-out rather than opt-in. DefaultMaxTurns = 25 // DefaultParallelTools is the per-turn concurrency limit for tools marked // with [Parallel]. DefaultParallelTools = 4 )
Default limits.
Variables ¶
var ( // ErrRunActive means the session already has a run in progress. A // [Session] serializes runs; wait for the active one to finish. ErrRunActive = errors.New("agent: session already has an active run") // ErrPendingToolCalls means the session tail contains tool calls that have // no results yet (a previous run stopped with [StopPaused], or a stream // was abandoned mid-turn). Resolve them with [Session.ResolveToolCalls] or // [Session.ResolvePending] before starting another run. ErrPendingToolCalls = errors.New("agent: session has unresolved tool calls") // ErrToolCallNotPending means a supplied tool resolution references a call // that is not currently awaiting a result. ErrToolCallNotPending = errors.New("agent: tool call is not pending") // ErrInvalidToolResolution means a supplied resolution has an empty or // duplicate tool-call ID. ErrInvalidToolResolution = errors.New("agent: invalid tool resolution") // ErrInvalidEvent classifies invalid process-local event metadata or // payloads. Construct events with [NewEvent] to validate and snapshot them. ErrInvalidEvent = errors.New("agent: invalid event") // ErrEventWireFormat reports an attempt to marshal or unmarshal [Event]. // Agent events are process-local; use an application-owned, versioned // projection for durable or remote transport. ErrEventWireFormat = errors.New("agent: event has no wire format") // ErrGuardrail classifies input and output validation failures. Extract a // [GuardrailError] with [errors.As] for the phase, name, and cause. ErrGuardrail = errors.New("agent: guardrail rejected the run") // ErrSubagentPaused means an [AsTool] child requested durable approval, // which the tool result contract cannot preserve across invocations. ErrSubagentPaused = errors.New("agent: subagent paused with pending tool calls") // ErrTerminate is a control sentinel (in the spirit of [io.EOF]) a tool // returns alongside its parts to ask the run to stop after the current // tool batch: // // return agent.TextResult("final answer"), agent.ErrTerminate // // The parts are recorded as a successful result. The run stops with // [StopTerminated] only when every result in the batch carries the hint // (failed or denied calls never do); queued follow-ups still run first. ErrTerminate = errors.New("agent: tool requested run termination") )
Sentinel errors returned by Agent.Run, Agent.Stream, and Session methods. Match them with errors.Is.
Functions ¶
func ReportProgress ¶
ReportProgress publishes a partial-result update from inside a running tool's Exec, surfacing as an EventToolUpdated event to the run's event consumers. Updates are advisory: delivery is best-effort (slow consumers drop updates rather than block the tool), and calls outside a run are no-ops.
Types ¶
type Agent ¶
type Agent struct {
// contains filtered or unexported fields
}
Agent binds a language model to a system prompt, a tool set, and loop policy. It is immutable after construction and safe for concurrent use; run-scoped state lives in the Session passed to each run.
Resilience is layered at the model, not the agent: wrap the model with ai.Chain and the ai middleware packages before passing it in.
func New ¶
func New(model ai.LanguageModel, opts ...Option) (*Agent, error)
New returns an agent bound to model. It fails when model is nil or the configured tools have duplicate or empty names.
func (*Agent) Run ¶
Run appends msgs to the session and drives the agent loop to completion: call the model, execute requested tools, feed results back, repeat. It blocks until the run terminates cleanly (see StopReason) or fails; on failure the returned result carries what completed before the error.
Mid-run, Session.Steer injects messages before the next model call and Session.FollowUp queues work for after the model would otherwise finish.
Run uses ai.LanguageModel.Generate, so retry middleware on the model is fully effective. Use Agent.Stream for incremental output.
func (*Agent) Stream ¶
func (a *Agent) Stream(ctx context.Context, sess *Session, msgs ...ai.Message) iter.Seq2[Event, error]
Stream is Agent.Run as an event sequence: it yields run, turn, and tool lifecycle events plus model deltas as they happen. Breaking out of the loop cancels the run; the session keeps everything appended up to that point, and any tool calls left unanswered surface through Session.Pending.
A clean termination ends with an EventRunCompleted carrying the RunCompleted payload and StopReason (after StopPaused, resolve Session.Pending and run again); failures yield a non-nil error as the final element.
type CandidateAnswerDecision ¶
type CandidateAnswerDecision struct {
Retry *ModelRequestUpdate
Err error
}
CandidateAnswerDecision accepts the candidate when both fields are zero. Err rejects it and aborts the run. Retry rejects it without committing the assistant message and constrains exactly the next model request.
type CandidateAnswerInfo ¶
CandidateAnswerInfo is the read-only view passed to a WithCandidateAnswer hook before a no-Tool assistant answer is committed.
type CandidateDiscarded ¶
type CandidateDiscarded struct {
Turn int
}
CandidateDiscarded reports a provisional no-tool answer rejected before session commit. Turn is one-based; rejected content is never retained.
type ConcurrencySafe ¶
type ConcurrencySafe interface {
// Concurrent reports whether the tool may execute in parallel.
Concurrent() bool
}
ConcurrencySafe is an optional interface a Tool may implement to declare that it can run in parallel with other concurrency-safe tools of the same turn. Tools that do not implement it (or report false) run serially, each acting as a barrier within the turn's batch. Wrap an existing tool with Parallel instead of implementing this by hand.
type Event ¶
type Event struct {
// RunID correlates every event in one invocation. ParentRunID links a
// nested invocation to the run whose tool or callback started it. Agent is
// the configured [Agent.Name], and Time is the UTC emission time.
RunID string
ParentRunID string
Agent string
Time time.Time
// contains filtered or unexported fields
}
Event is one immutable, process-local increment of an agent run. The envelope carries correlation metadata shared by every variant; Payload carries exactly one semantic variant. Events are safe to retain. Consumers must treat the returned payload as read-only.
Event intentionally has no JSON wire format. Durable or remote consumers must define a versioned projection appropriate to their disclosure and compatibility requirements.
func NewEvent ¶
func NewEvent(meta RunMetadata, occurredAt time.Time, payload EventPayload) (Event, error)
NewEvent constructs a validated event and snapshots all mutable payload data. occurredAt is normalized to UTC. It returns an error matching ErrInvalidEvent when metadata or payload invariants are invalid.
func (Event) MarshalJSON ¶
MarshalJSON prevents the process-local event union from being mistaken for a stable wire protocol.
func (Event) Payload ¶
func (e Event) Payload() EventPayload
Payload returns the event's sealed semantic payload. The returned value is owned by this event snapshot and must be treated as read-only.
func (Event) Type ¶
Type returns the discriminator for the event's concrete payload. It returns the zero EventType for an invalid zero Event.
func (*Event) UnmarshalJSON ¶
UnmarshalJSON prevents decoding an unspecified wire representation into an Event. Use an application-owned, versioned projection instead.
func (Event) Validate ¶
Validate verifies the event envelope and payload invariants. All failures match ErrInvalidEvent. It does not replace independent validation of a complete model message or model stream state machine.
type EventPayload ¶
type EventPayload interface {
// contains filtered or unexported methods
}
EventPayload is the sealed union of semantic Event variants. Only the concrete value types declared in this package are valid payloads.
type EventType ¶
type EventType string
EventType identifies the semantic payload carried by an Event.
const ( // EventRunStarted opens a run. EventRunStarted EventType = "run_started" // EventTurnStarted opens one model turn. EventTurnStarted EventType = "turn_started" // EventModelStream carries one normalized model stream event. It is only // produced by [Agent.Stream], never [Agent.Run]. EventModelStream EventType = "model_stream" // EventMessageCommitted reports a message after it has been appended to // the session. EventMessageCommitted EventType = "message_committed" // EventCandidateDiscarded reports a provisional answer rejected before // session commit. Candidate content is deliberately absent. EventCandidateDiscarded EventType = "candidate_discarded" // EventToolStarted opens one tool-call lifecycle. EventToolStarted EventType = "tool_started" // EventToolUpdated carries a best-effort progress update from a running // tool. EventToolUpdated EventType = "tool_updated" // EventToolCompleted closes one tool-call lifecycle, including denials and // synthesized failures. EventToolCompleted EventType = "tool_completed" // EventTurnCompleted closes one model turn. EventTurnCompleted EventType = "turn_completed" // EventRunCompleted closes a cleanly terminated run. EventRunCompleted EventType = "run_completed" )
Event types, in the order a run can produce them. Failed runs return an iterator error instead of producing EventRunCompleted.
type GuardrailError ¶
type GuardrailError struct {
Phase GuardrailPhase
Name string
Cause error
}
GuardrailError reports a named input or output guardrail rejection. Its error chain matches both ErrGuardrail and Cause when Cause is non-nil.
func (*GuardrailError) Unwrap ¶
func (e *GuardrailError) Unwrap() []error
Unwrap exposes the guardrail class and underlying cause.
type GuardrailPhase ¶
type GuardrailPhase string
GuardrailPhase identifies where a conversation guardrail rejected a run.
const ( // GuardrailInput validates newly supplied messages before transcript // mutation or model I/O. GuardrailInput GuardrailPhase = "input" // GuardrailOutput validates a candidate assistant answer (a response with // no tool calls) before it is committed to the transcript. GuardrailOutput GuardrailPhase = "output" )
Guardrail phases.
type InputGuardrailInfo ¶
type InputGuardrailInfo struct {
RunMetadata
Session []ai.Message
Input []ai.Message
}
InputGuardrailInfo is the read-only input validation snapshot. Session is the existing transcript and Input contains the messages supplied to this run; treat both slices and their parts as read-only.
type MessageCommitted ¶
MessageCommitted reports a message after it has been appended to the session. Turn is one-based.
type ModelRequestUpdate ¶
type ModelRequestUpdate struct {
Tools []Tool
ToolChoice ai.ToolChoice
SystemSuffix string
}
ModelRequestUpdate constrains exactly one subsequent model request. It is run-local: applying it never mutates the Agent or the persistent Session. A non-nil Tools slice replaces the declaration and execution snapshot for that request only.
type ModelStreamEvent ¶
type ModelStreamEvent struct {
Turn int
Event ai.StreamEvent
}
ModelStreamEvent carries one normalized model stream increment. Turn is one-based. The embedded stream event remains provisional until a MessageCommitted payload is emitted.
type Option ¶
type Option func(*config)
Option configures an Agent.
func WithAfterTool ¶
func WithAfterTool(fn func(ctx context.Context, info ToolResultInfo) *ToolResultOverride) Option
WithAfterTool installs a hook that runs after each executed tool call, before its result is recorded and emitted. The returned ToolResultOverride replaces result fields (nil keeps everything). The hook only sees calls that actually executed — denials, unknown tools, undecodable arguments, and cancellations skip it. It runs serially on the run's goroutine; a panic converts the result into an error result.
func WithBeforeTool ¶
func WithBeforeTool(fn func(ctx context.Context, info ToolCallInfo) ToolDecision) Option
WithBeforeTool installs a gate consulted before each tool call executes. Gates run serially in call order on the run's goroutine. See ToolDecisionAction for the available verdicts.
func WithCandidateAnswer ¶
func WithCandidateAnswer( fn func(context.Context, CandidateAnswerInfo) CandidateAnswerDecision, ) Option
WithCandidateAnswer installs a hook for no-Tool assistant answers before they are committed. The hook may accept, abort, or discard and retry with a one-request constraint. Streaming events are provisional; a retry emits an EventCandidateDiscarded event so consumers can clear the rejected draft.
func WithFollowUpMode ¶
WithFollowUpMode sets how queued follow-up messages are drained (default QueueDrainOne).
func WithInputGuardrail ¶
WithInputGuardrail appends a named validator that runs once per invocation, before new messages are committed or a model is called. Validators run in option order; the first non-nil error rejects the run with a GuardrailError. Use WithBeforeTool for tool side-effect policy.
func WithMaxTokens ¶
WithMaxTokens stops the run with StopBudget once its cumulative input plus output tokens reach n. Zero (the default) means unlimited.
func WithMaxTurns ¶
WithMaxTurns caps the number of model calls per run (default DefaultMaxTurns). Zero or negative means unlimited — combine with another stop condition to avoid unbounded loops.
func WithName ¶
WithName assigns a stable human-readable name used in run metadata and events. It does not affect model prompts or tool names.
func WithOnEvent ¶
WithOnEvent installs a callback receiving every run event, for both Agent.Run and Agent.Stream (Run produces no EventModelStream events). The callback runs on the run's goroutine; keep it fast and non-blocking.
func WithOutputGuardrail ¶
func WithOutputGuardrail( name string, fn func(context.Context, OutputGuardrailInfo) error, ) Option
WithOutputGuardrail appends a named validator for every candidate assistant answer (a response with no tool calls). It runs before that message is committed. Queued steering or follow-up can extend the run after a validated answer. During Agent.Stream, model deltas are provisional and may already have been observed before validation rejects them.
func WithParallelTools ¶
WithParallelTools caps how many Parallel-marked tools of one turn execute concurrently (default DefaultParallelTools). One serializes everything.
func WithPrepareTurn ¶
func WithPrepareTurn(fn func(ctx context.Context, info RunInfo) TurnUpdate) Option
WithPrepareTurn installs a hook that runs after each completed turn, before the loop decides whether to continue. The returned TurnUpdate can swap the model for subsequent turns or rewrite the session history (the commit point for context compaction).
func WithRequest ¶
WithRequest installs an escape hatch applied to each ai.Request just before it is sent, after the agent has set Messages and Tools. The configured system prompt is the leading ai.SystemMessage in Messages. Use it for generation parameters, reasoning configuration, or provider options:
agent.WithRequest(func(req *ai.Request) {
req.Temperature = ai.Ptr(0.2)
})
func WithSteeringMode ¶
WithSteeringMode sets how queued steering messages are drained (default QueueDrainOne).
func WithStopWhen ¶
WithStopWhen stops the run with StopWhen once cond reports true. It is checked after each completed turn.
func WithSystem ¶
WithSystem sets the system prompt sent on every model call.
func WithToolTimeout ¶
WithToolTimeout bounds each tool execution; on expiry the call becomes an error tool result and the run continues. Zero (the default) means no per-tool deadline.
func WithTools ¶
WithTools adds tools to the agent. Tool names must be unique and non-empty; New fails otherwise.
func WithTransformContext ¶
func WithTransformContext(fn func(ctx context.Context, msgs []ai.Message) ([]ai.Message, error)) Option
WithTransformContext installs a transform applied to the session snapshot before each model call — a non-destructive injection point for context pruning or augmentation. The session itself is untouched; use WithPrepareTurn or Session.Replace to rewrite history permanently. A returned error terminates the run with that error.
type OutputGuardrailInfo ¶
OutputGuardrailInfo is the read-only answer validation snapshot. Message is the candidate assistant answer; treat its parts as read-only.
type QueueMode ¶
type QueueMode int
QueueMode controls how many queued messages a drain point injects (see Session.Steer and Session.FollowUp).
type RunCompleted ¶
type RunCompleted struct {
Turns int
Stop StopReason
Usage ai.Usage
}
RunCompleted closes a cleanly terminated run. Turns is the number of model calls made, Stop is the clean termination reason, and Usage is cumulative.
type RunInfo ¶
type RunInfo struct {
RunMetadata
// Turns is the number of model calls made so far in this run.
Turns int
// Usage is the token usage accumulated by this run.
Usage ai.Usage
// Response is the most recent model response.
Response *ai.Response
}
RunInfo is a read-only snapshot of run progress, passed to the WithStopWhen condition after each turn.
type RunMetadata ¶
RunMetadata identifies one agent invocation and its place in a nested run tree. ParentRunID is empty for a root run.
func RunMetadataFromContext ¶
func RunMetadataFromContext(ctx context.Context) (RunMetadata, bool)
RunMetadataFromContext returns the current run metadata. Tools and event observers can use it to correlate work without depending on a tracing SDK.
type RunResult ¶
type RunResult struct {
RunMetadata
// Stop is why the run terminated. It is empty when the run failed with an
// error.
Stop StopReason
// Turns is the number of model calls made.
Turns int
// Usage is the token usage accumulated across the run's model calls. The
// session separately accumulates usage across runs (see [Session.Usage]).
Usage ai.Usage
// Response is the final model response, nil when the run failed before
// the first model call completed.
Response *ai.Response
// Pending holds the tool calls awaiting resolution when Stop is
// [StopPaused]; it is nil otherwise.
Pending []ai.ToolCallPart
}
RunResult is the outcome of a completed run.
type RunStarted ¶
type RunStarted struct{}
RunStarted opens a run and carries no variant-specific data.
type Session ¶
type Session struct {
// contains filtered or unexported fields
}
Session holds the conversation state an agent runs against: the message history and the token usage accumulated across runs. It is safe for concurrent use, but only one run may be active at a time (ErrRunActive).
Sessions serialize to a stable JSON envelope (reusing the ai.Message format), so they can be persisted and resumed across processes. The zero value is not usable; construct with NewSession.
func NewSession ¶
NewSession returns a session seeded with the given messages (for example a restored history), oldest first.
func (*Session) Append ¶
Append adds messages to the history. Runs snapshot the history at each turn, so appending during an active run affects the next model call, not the in-flight one.
func (*Session) ClearFollowUps ¶
func (s *Session) ClearFollowUps()
ClearFollowUps discards all queued follow-up messages.
func (*Session) ClearSteering ¶
func (s *Session) ClearSteering()
ClearSteering discards all queued steering messages.
func (*Session) FollowUp ¶
FollowUp queues messages that run only after the agent would otherwise stop cleanly: when a turn produces no tool calls and no steering is queued, follow-ups are injected and the loop continues instead of finishing. Safe to call from any goroutine.
func (*Session) HasQueued ¶
HasQueued reports whether any steering or follow-up messages are queued.
func (*Session) MarshalJSON ¶
MarshalJSON implements json.Marshaler. The running flag is transient and not serialized.
func (*Session) Messages ¶
Messages returns a copy of the conversation so far, oldest first. The message structs are copies; treat their Parts as read-only.
func (*Session) Pending ¶
func (s *Session) Pending() []ai.ToolCallPart
Pending returns the tool calls at the session tail that have no matching results, in call order. A non-empty result means the conversation cannot continue until they are resolved (see Session.ResolvePending).
func (*Session) Replace ¶
Replace swaps the entire message history for msgs. It is the commit point for context compaction: rewrite the history out-of-band (or from a WithPrepareTurn hook) and the next model call sees the new form. Usage accounting and queued messages are unaffected.
func (*Session) ResolvePending ¶
func (s *Session) ResolvePending(ctx context.Context, fn func(ctx context.Context, call ai.ToolCallPart) ([]ai.Part, error)) error
ResolvePending answers all of the session's pending tool calls: fn is invoked for each call in order, and the outcomes are appended as a single tool-result message. An fn error becomes an error tool result carrying the error text (return an error to reject a call), so the model learns the outcome either way. It is a no-op when nothing is pending and fails with ErrRunActive during an active run.
Example ¶
A gate pauses risky calls for out-of-band approval; ResolvePending answers them and a second Run continues the conversation.
package main
import (
"context"
"log"
"os"
"github.com/rsbin1178/pips/agent"
"github.com/rsbin1178/pips/ai"
"github.com/rsbin1178/pips/ai/openai"
)
func main() {
model := openai.New("gpt-6-astra", openai.WithAPIKey(os.Getenv("OPENAI_API_KEY")))
deploy := agent.NewTool("deploy", "Deploy to production.",
func(_ context.Context, _ struct{}) (string, error) {
return "deployed", nil
})
a, err := agent.New(model,
agent.WithTools(deploy),
agent.WithBeforeTool(func(_ context.Context, info agent.ToolCallInfo) agent.ToolDecision {
if info.Name == "deploy" {
return agent.ToolDecision{Action: agent.ToolDecisionPause}
}
return agent.ToolDecision{}
}),
)
if err != nil {
log.Fatal(err)
}
sess := agent.NewSession()
result, err := a.Run(context.Background(), sess, ai.UserText("Ship it."))
if err != nil {
log.Fatal(err)
}
if result.Stop == agent.StopPaused {
// Approval happens outside the runtime, then the calls are resolved.
err := sess.ResolvePending(context.Background(), func(_ context.Context, _ ai.ToolCallPart) ([]ai.Part, error) {
return agent.TextResult("approved and deployed"), nil
})
if err != nil {
log.Fatal(err)
}
if _, err := a.Run(context.Background(), sess); err != nil {
log.Fatal(err)
}
}
}
Output:
func (*Session) ResolveToolCalls ¶
func (s *Session) ResolveToolCalls(resolutions ...ToolResolution) error
ResolveToolCalls answers any subset of the session's pending tool calls. Resolutions are validated atomically and appended in the original call order, regardless of argument order. Calls omitted from resolutions remain pending and survive session serialization. It fails with ErrRunActive during a run, ErrInvalidToolResolution for empty/duplicate IDs, or ErrToolCallNotPending for a stale/unknown ID. An empty resolution list is a no-op.
func (*Session) Steer ¶
Steer queues messages for injection into the running loop: they are appended at the start of the next turn, before the next model call, letting a user redirect the agent mid-run. Queued messages survive until a run drains them (see WithSteeringMode); pending steering also keeps the loop going when the model would otherwise finish. Safe to call from any goroutine, with or without an active run.
func (*Session) UnmarshalJSON ¶
UnmarshalJSON implements json.Unmarshaler, replacing the session's state.
type StopReason ¶
type StopReason string
StopReason is why a run terminated cleanly. Runs that fail (model error, context cancellation) return a Go error instead; a RunResult carried alongside a non-nil error has an empty StopReason.
const ( // StopEndTurn means the model completed its turn without requesting any // tool calls — the natural end of an agent loop. StopEndTurn StopReason = "end_turn" // StopMaxTurns means the configured turn limit was reached (see // [WithMaxTurns]). StopMaxTurns StopReason = "max_turns" // StopBudget means the cumulative token budget was exhausted (see // [WithMaxTokens]). StopBudget StopReason = "budget" // StopPaused means a [WithBeforeTool] gate requested a pause. The // unexecuted calls are in [RunResult.Pending]; resolve them with // [Session.ResolveToolCalls] or [Session.ResolvePending] and run again to // continue. StopPaused StopReason = "paused" // StopWhen means the [WithStopWhen] condition reported true. StopWhen StopReason = "stop_when" // StopTerminated means every tool result in the final batch carried the // [ErrTerminate] hint, ending the run at the tools' request. StopTerminated StopReason = "terminated" )
Stop reasons.
type Tool ¶
type Tool interface {
// Decl returns the declaration advertised to the model.
Decl() ai.Tool
// Exec runs the call and returns the result content. A nil part slice
// with a nil error is a valid empty result.
Exec(ctx context.Context, call ToolCall) ([]ai.Part, error)
}
Tool is a capability the model may invoke during a run. Decl describes the tool to the model; Exec performs the call. Implementations must be safe for concurrent use when they also implement ConcurrencySafe.
Exec errors do not abort the run: the runtime converts them (along with panics, timeouts, and undecodable arguments) into error tool results that are fed back to the model, which typically corrects course. Most tools are easier to write with NewTool than by implementing the interface directly.
func AsTool ¶
AsTool exposes an agent as a tool of another agent, the primitive for sub-agent delegation: the outer model calls the tool with a prompt, the inner agent runs it to completion in a fresh Session, and its final text becomes the tool result.
Each invocation is isolated (new session), so the tool is safe for Parallel marking and for concurrent calls. Cancellation propagates through ctx. An inner-agent failure surfaces as an error tool result the outer model can react to.
func NewTool ¶
func NewTool[Args any](name, description string, fn func(ctx context.Context, args Args) (string, error)) Tool
NewTool defines a tool from a typed Go function. The argument schema is derived from Args with ai.SchemaFor (json tags name fields, description tags document them), and the model's JSON arguments are decoded into Args before fn runs. Use struct{} for a tool that takes no arguments.
NewTool panics when a schema cannot be derived from Args — like regexp.MustCompile, it is meant for declarations whose type is fixed at compile time. Implement Tool directly for dynamic schemas.
Example ¶
NewTool derives the argument schema from an ordinary Go struct.
package main
import (
"context"
"fmt"
"strconv"
"github.com/rsbin1178/pips/agent"
)
func main() {
add := agent.NewTool("add", "Add two integers.",
func(_ context.Context, args struct {
A int `json:"a"`
B int `json:"b"`
},
) (string, error) {
return strconv.Itoa(args.A + args.B), nil
})
decl := add.Decl()
fmt.Println(decl.Name, decl.InputSchema.Type, decl.InputSchema.Properties["a"].Type)
}
Output: add object integer
func NewToolParts ¶
func NewToolParts[Args any](name, description string, fn func(ctx context.Context, args Args) ([]ai.Part, error)) Tool
NewToolParts is NewTool for tools whose results are multi-modal (for example an image-producing tool) rather than plain text.
func Parallel ¶
Parallel marks a tool as safe for parallel execution within a turn (see ConcurrencySafe). Only mark tools whose Exec is free of shared mutable state, such as read-only lookups.
func ProviderTool ¶
ProviderTool wraps a provider-executed ai.Tool (such as Gemini's GoogleSearch or OpenAI's WebSearch) as an agent.Tool. Provider-executed tools are managed server-side by the model provider, so their Exec method is a no-op.
type ToolCall ¶
type ToolCall struct {
// ID is the provider-assigned call identifier.
ID string
// Name is the tool being called.
Name string
// Args is the raw JSON arguments object produced by the model.
Args ai.JSON
}
ToolCall is the invocation passed to Tool.Exec.
type ToolCallInfo ¶
type ToolCallInfo struct {
ToolCall
// Turn is the turn (1-based) that produced the call.
Turn int
// BatchIndex is this call's zero-based position in the model response.
BatchIndex int
// BatchSize is the total number of calls in the model response. Gates may
// use it to reject interaction-owning calls before a leading Pause hides
// the remaining suffix.
BatchSize int
}
ToolCallInfo is the read-only view of a tool call passed to gates.
type ToolCompleted ¶
type ToolCompleted struct {
Turn int
Call ai.ToolCallPart
Result ai.ToolResultPart
}
ToolCompleted closes the lifecycle of Call in the one-based Turn. Result includes denials and synthesized failures through Result.IsError.
type ToolDecision ¶
type ToolDecision struct {
Action ToolDecisionAction
// Reason is sent to the model when Action is [ToolDecisionDeny]. Empty
// falls back to a generic denial message.
Reason string
// UpdatedInput replaces the JSON arguments for an allowed call before the
// next gate and before execution. Callers must validate it for their tool
// protocol; nil leaves the original arguments unchanged.
UpdatedInput ai.JSON
}
ToolDecision is a gate's verdict on one tool call.
func DenyTool ¶
func DenyTool(reason string) ToolDecision
DenyTool returns a ToolDecisionDeny decision carrying the given reason.
type ToolDecisionAction ¶
type ToolDecisionAction int
ToolDecisionAction is what a WithBeforeTool gate tells the runtime to do with a tool call.
const ( // ToolDecisionAllow lets the call execute. ToolDecisionAllow ToolDecisionAction = iota // ToolDecisionDeny blocks the call; [ToolDecision.Reason] is fed back to // the model as an error tool result and the run continues. ToolDecisionDeny // ToolDecisionPause stops the run before executing this call. The call and every // later call in the same turn become [RunResult.Pending]; resolve them // with [Session.ResolvePending] and run again to continue. ToolDecisionPause )
Gate actions. The zero value allows the call, so a gate only needs to return non-zero decisions for the calls it wants to intercept.
type ToolResolution ¶
ToolResolution supplies an out-of-band result for one pending tool call. Content is returned to the model; IsError marks a rejection or failed approval as a tool error rather than a run error.
type ToolResultInfo ¶
type ToolResultInfo struct {
ToolCall
// Turn is the turn (1-based) that produced the call.
Turn int
// Result is the executed outcome (IsError reflects execution failures;
// terminate hints are visible via [ToolResultOverride], not here).
Result ai.ToolResultPart
}
ToolResultInfo is the read-only view passed to a WithAfterTool hook: the call that ran and the result it produced, before any override.
type ToolResultOverride ¶
type ToolResultOverride struct {
// Content replaces the result content.
Content []ai.Part
// IsError replaces the error flag.
IsError *bool
// Terminate replaces the tool's termination hint (see [ErrTerminate]).
Terminate *bool
}
ToolResultOverride replaces fields of an executed tool result from a WithAfterTool hook. Each field is a full replacement; nil (or a nil pointer) keeps the executed value. There is no deep merge.
type ToolStarted ¶
type ToolStarted struct {
Turn int
Call ai.ToolCallPart
}
ToolStarted opens the lifecycle of Call in the one-based Turn.
type ToolUpdated ¶
type ToolUpdated struct {
Turn int
Call ai.ToolCallPart
Update []ai.Part
}
ToolUpdated carries a best-effort progress Update for Call in the one-based Turn.
type TurnCompleted ¶
TurnCompleted closes the one-based Turn. Usage is cumulative for the run.
type TurnStarted ¶
type TurnStarted struct {
Turn int
}
TurnStarted opens one model turn. Turn is one-based.
type TurnUpdate ¶
type TurnUpdate struct {
// Err aborts the run before another model call. Hook composition stops at
// the first error so an application can make a failed context rewrite an
// explicit run failure instead of continuing with stale messages.
Err error
// Model, when non-nil, serves all subsequent model calls of this run.
Model ai.LanguageModel
// ReplaceMessages, when non-nil, replaces the session history via
// [Session.Replace] — the commit point for context compaction.
ReplaceMessages []ai.Message
// Tools, when non-nil, replaces the complete tool snapshot used for all
// subsequent model calls and executions in this run. The replacement is
// validated before it takes effect, so a declaration can never be shown to
// the model without the matching executable implementation. This enables
// deferred tool discovery without mutating the Agent shared by other runs.
Tools []Tool
// NextRequest, when non-nil, constrains exactly the next model request.
// Unlike Tools, it does not replace the run's persistent tool snapshot.
NextRequest *ModelRequestUpdate
}
TurnUpdate adjusts the run between turns, returned by a WithPrepareTurn hook. The zero value changes nothing.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package bundle loads bounded local Bundles and activates their declarative resources through an extension.Runtime.
|
Package bundle loads bounded local Bundles and activates their declarative resources through an extension.Runtime. |
|
Package catalog composes explicitly registered agent tools into policy-gated snapshots.
|
Package catalog composes explicitly registered agent tools into policy-gated snapshots. |
|
Package continuation provides durable, application-driven execution across bounded worker runs.
|
Package continuation provides durable, application-driven execution across bounded worker runs. |
|
Package extension composes trusted, application-compiled Agent extensions into immutable runtime generations.
|
Package extension composes trusted, application-compiled Agent extensions into immutable runtime generations. |
|
Package goal provides evidence-based completion policies for continuation executions.
|
Package goal provides evidence-based completion policies for continuation executions. |
|
Package harness provides the stateful orchestration layer over the agent runtime: persistent session trees with branching, automatic context compaction, branch summaries, and skill/template resources — the pieces an agent application needs beyond a single run.
|
Package harness provides the stateful orchestration layer over the agent runtime: persistent session trees with branching, automatic context compaction, branch summaries, and skill/template resources — the pieces an agent application needs beyond a single run. |
|
Package loop provides fixed and dynamic activation policies for continuation executions.
|
Package loop provides fixed and dynamic activation policies for continuation executions. |
|
Package agentmcp bridges tools from Model Context Protocol servers into the agent runtime using the official Go MCP SDK.
|
Package agentmcp bridges tools from Model Context Protocol servers into the agent runtime using the official Go MCP SDK. |
|
Package observability derives vendor-neutral traces and aggregate metrics from agent events.
|
Package observability derives vendor-neutral traces and aggregate metrics from agent events. |
|
otel
Package otel adapts pips agent events to OpenTelemetry traces and metrics.
|
Package otel adapts pips agent events to OpenTelemetry traces and metrics. |
|
Package team provides durable coordination for a flat team of independent Agent sessions.
|
Package team provides durable coordination for a flat team of independent Agent sessions. |