resolute

package module
v0.12.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 18, 2026 License: MIT Imports: 15 Imported by: 0

README

pi-core-agent-go

Stateful agent loop for Go, built on pi-llm-go.

Features

  • Single-runner mutable Agent: one Agent per conversation; Agent.Prompt returns an EventStream. Runtime config is mutable via setters and picked up on the next turn (ADR-0006).
  • Typed tools: Tool[P any] with compile-time parameter checking.
  • Streaming events: Sealed AgentEvent interface with 16+ variants.
  • Session persistence: MemorySession (default) or JSONLSession (disk).
  • Compaction: Manual transcript summarization.
  • Steering + follow-up: Inject messages mid-prompt or after completion.
  • Cancellation: Bounded shutdown with ToolLeakEvent observability.

Install

go get github.com/dev-resolute/resolute-agent-core-go

Usage

agent, _ := resolute.NewAgent(resolute.AgentConfig{
    Providers:    []llm.LLMProvider{provider},
    DefaultModel: "openai-compat/gpt-4o",
    Tools:        []resolute.RegisteredTool{myTool},
})

stream, _ := agent.Prompt(ctx, resolute.NewText("user", "Hello"), resolute.PromptOpts{})

for ev := range stream.Events {
    // type-switch on resolute.AgentEvent
}
result := <-stream.Done

// Reconfigure mid-conversation; takes effect on the next prompt's turn.
agent.SetModel("gemini/gemini-2.5-flash")

Testing

Provider-backed tests run against a live Gemini provider and skip when GEMINI_API_KEY is unset; pure-logic tests (turn snapshot, compaction, schema) run without it.

GEMINI_API_KEY=... go test -race ./...

License

MIT

Documentation

Overview

Package pi provides a stateful agent loop built on pi-llm-go.

Index

Constants

View Source
const (
	DefaultSummarizationRetryBaseDelay = time.Second
	DefaultSummarizationRetryMaxDelay  = 60 * time.Second
)

Defaults for SummarizationRetryPolicy zero fields.

View Source
const SummarizationPrompt = `` /* 879-byte string literal not displayed */

SummarizationPrompt is the user prompt for the first summarization of a conversation prefix. Aligned with upstream 0.79.1 structured template.

View Source
const SummarizationSystemPrompt = `` /* 310-byte string literal not displayed */

SummarizationSystemPrompt is the system prompt used for summarization calls. Matches upstream 0.79.1 — neutral "AI assistant" wording so compaction works correctly for non-coding harnesses.

View Source
const TurnPrefixSummarizationPrompt = `` /* 440-byte string literal not displayed */

TurnPrefixSummarizationPrompt is used for the turn-prefix summarization in split-turn compaction. Aligned with upstream 0.79.1 structured template.

View Source
const UpdateSummarizationPrompt = `` /* 1289-byte string literal not displayed */

UpdateSummarizationPrompt is used when updating an existing summary with new messages. Aligned with upstream 0.79.1 structured template. Adaptation: upstream references "<previous-summary> tags" (injected inline in TS); Go passes the previous summary as a BranchSummary message above, so that phrase is reworded.

Variables

View Source
var (
	ErrPromptCancelled = errors.New("prompt cancelled by caller context")
	ErrAgentStopped    = errors.New("prompt stopped by caller")
	// ErrToolLeaked is reserved (ADR-0004); not returned — the leak terminal error is the cancellation cause, ToolLeakEvent is the per-call signal.
	ErrToolLeaked      = errors.New("tool execution leaked goroutine")
	ErrToolNotFound    = errors.New("tool not found")
	ErrCompactFailed   = errors.New("compaction failed")
	ErrInvalidModel    = errors.New("invalid model")
	ErrInvalidModelRef = errors.New("invalid model reference")
	ErrAgentBusy       = errors.New("agent is busy")

	// ErrNoPromptInFlight is returned by Steer and FollowUp when no prompt
	// is currently in flight. It is also the cancel cause of the idle context
	// returned by Agent.Context(), ensuring any stale nested work tied to
	// that context exits immediately rather than leaking.
	ErrNoPromptInFlight = errors.New("no prompt in flight")
	ErrSessionNotFound  = errors.New("session not found")

	// ErrNothingToResume is returned by Agent.Resume when the transcript tail
	// is not tool results (or no session was given): there is no suspended
	// prompt to continue.
	ErrNothingToResume    = errors.New("nothing to resume")
	ErrUnsupportedFeature = errors.New("unsupported feature")
	ErrDuplicateToolName  = errors.New("duplicate tool name")
	ErrUnknownActiveTool  = errors.New("active tool not registered")
)

Sentinel errors for pi-core-agent-go.

View Source
var DefaultCompactionSettings = CompactionSettings{
	Enabled:          true,
	ReserveTokens:    16384,
	KeepRecentTokens: 20000,
}

DefaultCompactionSettings matches upstream's DEFAULT_COMPACTION_SETTINGS.

Functions

func DefaultConvertToLLM

func DefaultConvertToLLM(messages []Message) []llm.Message

DefaultConvertToLLM converts the built-in agent message types to llm.Message.

func EstimateTokens

func EstimateTokens(messages []Message) int

EstimateTokens returns a rough token estimate using the chars/4 heuristic. Per ADR-0003, this is a coarse approximation until local tokenizers land. Images count 4800 chars each (upstream 0.76.0 attachment heuristic, AGENT-14); conservative per ADR-0003.

func NewSessionID

func NewSessionID() string

NewSessionID generates a new random session ID.

func ShouldCompact

func ShouldCompact(contextTokens, contextWindow int, settings CompactionSettings) bool

ShouldCompact returns true when the context has grown large enough to warrant compaction. It is exported so callers can build their own auto-trigger logic.

Types

type AfterCompactCtx

type AfterCompactCtx struct {
	SessionID     SessionID
	BranchSummary BranchSummary
}

AfterCompactCtx is passed to the AfterCompact hook.

type AfterProviderResponseCtx

type AfterProviderResponseCtx struct {
	Provider   string
	Model      string
	StatusCode int
	Headers    map[string]string
}

AfterProviderResponseCtx is passed to the AfterProviderResponse hook.

type AfterToolCallCtx

type AfterToolCallCtx struct {
	CallID   string
	ToolName string
	Result   ToolResult
}

AfterToolCallCtx is passed to the AfterToolCall hook.

type AfterTurnCtx

type AfterTurnCtx struct {
	// Turn is the 1-based index of the turn that just completed.
	Turn int
	// HadToolCalls reports whether the LLM returned tool calls this turn.
	HadToolCalls bool
}

AfterTurnCtx is passed to the ShouldStopAfterTurn hook.

type Agent

type Agent struct {
	// contains filtered or unexported fields
}

Agent is the persistent, configurable object that owns tools, hooks, the session backend, default options, and the system prompt.

func NewAgent

func NewAgent(cfg AgentConfig) (*Agent, error)

NewAgent creates an Agent from the given config.

func (*Agent) Close

func (a *Agent) Close() error

Close stops any in-flight prompt and releases the Agent. Idempotent.

func (*Agent) Compact

func (a *Agent) Compact(ctx context.Context, opts CompactOpts) (*CompactResult, error)

Compact collapses older transcript messages into a BranchSummary. It must be called when the agent is idle (no in-flight run).

func (*Agent) Context

func (a *Agent) Context() context.Context

Context returns the in-flight prompt's context. Tools and hooks can use it to forward cancellation into nested goroutines they spawn: when Stop is called (or the caller's context is cancelled), the returned context is cancelled with the same cause, and any goroutine blocking on its Done channel unblocks.

Idle contract: when no prompt is in flight, Context returns a non-nil, already-cancelled context with cause ErrNoPromptInFlight. Callers that hold a reference across a prompt boundary should not start new work from it — its Done channel is already closed.

Concurrent-safe: safe to call from any goroutine while a prompt is starting, running, or stopping.

func (*Agent) FollowUp

func (a *Agent) FollowUp(ctx context.Context, m Message) error

FollowUp enqueues a message for after the in-flight prompt completes.

func (*Agent) Phase

func (a *Agent) Phase() AgentPhase

Phase returns the current (or most recent) prompt's phase.

func (*Agent) Prompt

func (a *Agent) Prompt(ctx context.Context, msg Message, opts PromptOpts) (*EventStream, error)

Prompt starts a new prompt and returns its EventStream. The user message is the second argument; per-prompt overrides are carried on opts.

func (*Agent) Resume added in v0.11.0

func (a *Agent) Resume(ctx context.Context, opts PromptOpts) (*EventStream, error)

Resume continues the agent loop from the current transcript without appending input — the entry point for prompts suspended on ToolResult.Suspend once their external results have landed (AGENT-25). Precondition: opts.SessionID names a session whose transcript tail is a tool_result message; otherwise ErrNothingToResume. Leave opts.SystemPrompt empty: the override appends a system message before the resume tail check, so the tail is never a tool_result and Resume always returns ErrNothingToResume (after the override has already mutated the session). A SessionID no backend knows is ambiguous: the outcome depends on the backend's Load behavior — memory sessions load empty (ErrNothingToResume), an erroring backend surfaces its own error.

func (*Agent) SetActiveTools

func (a *Agent) SetActiveTools(ctx context.Context, names []string) error

SetActiveTools sets the subset of registered tools offered to the model on subsequent turns; nil means all registered tools are active. The change takes effect on the next turn snapshot, not the in-flight turn. It returns an error without mutating the Agent when a name is not registered or is duplicated.

Persistence follows the bound session: when a session is bound and idle the change is written immediately; when a prompt is in flight it is queued and flushed at the next turn-end safe point. Before the first prompt no session is bound and nothing is persisted — instead the change is recorded at bind time (see Prompt) if the active set differs from the full registered set, so resume still restores it.

func (*Agent) SetModel

func (a *Agent) SetModel(model string)

SetModel sets the model reference used for subsequent turns. The change takes effect on the next turn snapshot, not the in-flight turn.

func (*Agent) SetSkills

func (a *Agent) SetSkills(skills []Skill)

SetSkills replaces the Agent's skill set. The change takes effect on the next turn snapshot.

func (*Agent) SetSystemPrompt

func (a *Agent) SetSystemPrompt(prompt string)

SetSystemPrompt replaces the Agent's system prompt. The change takes effect on the next turn snapshot.

func (*Agent) SetThinkingLevel

func (a *Agent) SetThinkingLevel(level llm.ThinkingLevel)

SetThinkingLevel sets the thinking level used for subsequent turns. The change takes effect on the next turn snapshot, not the in-flight turn.

func (*Agent) SetTools

func (a *Agent) SetTools(tools []RegisteredTool) error

SetTools replaces the Agent's tool set. The change takes effect on the next turn snapshot, not the in-flight turn. It returns an error without mutating the Agent when tool names are not unique, or when the current active set references a tool the new set no longer registers.

func (*Agent) State

func (a *Agent) State() AgentState

State returns a snapshot of the current (or most recent) prompt's state.

func (*Agent) Steer

func (a *Agent) Steer(ctx context.Context, m Message) error

Steer enqueues a message for injection into the in-flight prompt at the next safe point.

func (*Agent) Stop

func (a *Agent) Stop()

Stop fire-and-forget cancels the in-flight prompt. Idempotent; a no-op when no prompt is in flight.

func (*Agent) Transcript

func (a *Agent) Transcript() []Message

Transcript returns a copy of the current (or most recent) prompt's transcript.

type AgentConfig

type AgentConfig struct {
	Providers        []llm.LLMProvider
	DefaultModel     string
	SystemPrompt     string
	Tools            []RegisteredTool
	ActiveToolNames  []string
	Hooks            Hooks
	Session          SessionRepo
	ConvertToLLM     ConvertToLLMFn
	ToolExecution    ToolExecutionMode
	MaxParallelTools int
	ShutdownTimeout  time.Duration
	EventBufferSize  int
	SteerBufferSize  int
	DefaultThinking  llm.ThinkingLevel
	// ThinkingBudgets optionally sets per-level token caps forwarded to the
	// provider on every turn. Nil or empty means "use provider defaults".
	ThinkingBudgets  map[llm.ThinkingLevel]int
	ReserveTokens    int
	KeepRecentTokens int
	// SummarizationRetry configures bounded retries with exponential backoff
	// for the summarization calls made by Compact. The zero value disables
	// retries, matching pre-0.7.0 behavior. Retry lifecycle is reported
	// through Hooks.OnSummarizationRetry. Ported from upstream 0.81.1.
	SummarizationRetry SummarizationRetryPolicy
	// Transport is the preferred stream transport forwarded to every LLMRequest.
	// Zero value behaves as TransportAuto.
	Transport llm.TransportPreference
	// Skills is the initial skill set offered to the model; hot-reload via SetSkills.
	Skills []Skill
}

AgentConfig carries all settings for constructing an Agent.

type AgentEndEvent

type AgentEndEvent struct{ Messages []Message }

AgentEndEvent signals the end of a run.

type AgentEvent

type AgentEvent interface {
	// contains filtered or unexported methods
}

AgentEvent is a sealed interface for every event that flows on Run.Events.

type AgentPhase

type AgentPhase int

AgentPhase describes the current phase of a run.

const (
	// PhaseIdle is the agent's phase at construction and between prompts, with no prompt in flight.
	PhaseIdle AgentPhase = iota
	// PhaseWaitingLLM means the run is blocked on a model response.
	PhaseWaitingLLM
	// PhaseExecutingTools means the run is executing tool calls requested by the model.
	PhaseExecutingTools
	// PhaseCompacting means the run is compacting the transcript to reclaim context window.
	PhaseCompacting
	// PhaseShuttingDown means the run is draining in-flight work after a stop.
	PhaseShuttingDown
	// PhaseDone means the prompt has finished.
	PhaseDone
)

type AgentStartEvent

type AgentStartEvent struct{}

AgentStartEvent signals the beginning of a run.

type AgentState

type AgentState struct {
	Phase            AgentPhase
	ActiveModel      string
	Thinking         llm.ThinkingLevel
	SessionID        SessionID
	TurnNumber       int
	TranscriptLen    int
	PendingToolCalls []PendingToolCall
	LastEvent        AgentEvent
	LastError        error
	StartedAt        time.Time
	LastActivityAt   time.Time
}

AgentState is a value-type snapshot of a run's current state.

type BeforeAgentStartCtx

type BeforeAgentStartCtx struct {
	PromptOpts PromptOpts
}

BeforeAgentStartCtx is passed to the BeforeAgentStart hook.

type BeforeCompactCtx

type BeforeCompactCtx struct {
	SessionID SessionID
	CutPoint  int
}

BeforeCompactCtx is passed to the BeforeCompact hook.

type BeforeProviderRequestCtx

type BeforeProviderRequestCtx struct {
	Provider string
	Model    string
	Headers  map[string]string
}

BeforeProviderRequestCtx is passed to the BeforeProviderRequest hook.

type BeforeToolCallCtx

type BeforeToolCallCtx struct {
	CallID   string
	ToolName string
	Args     []byte
}

BeforeToolCallCtx is passed to the BeforeToolCall hook. Args may be rewritten by the hook.

type BranchSummary

type BranchSummary struct {
	StartIdx  int
	EndIdx    int
	Summary   string
	CreatedAt time.Time
	// Usage is the summarization call usage; nil when the provider reported
	// none. Split-turn compaction sums both calls (upstream #6671).
	Usage *Usage `json:"Usage,omitempty"`
}

BranchSummary is a persisted compaction artifact.

type CompactOpts

type CompactOpts struct {
	KeepRecentTokens int
	// SessionID selects the session to compact. Empty means the session
	// bound by the most recent prompt — which is also empty on an Agent
	// that has never prompted, making Compact a silent no-op there; set
	// SessionID explicitly to compact such a session (e.g. a harness
	// compacting a durable conversation with a fresh Agent).
	SessionID SessionID
}

CompactOpts carries options for a compaction operation.

type CompactResult

type CompactResult struct {
	Summary      BranchSummary
	RemovedCount int
}

CompactResult carries the outcome of a compaction.

type CompactionEndEvent

type CompactionEndEvent struct{}

CompactionEndEvent signals the end of compaction.

type CompactionSettings

type CompactionSettings struct {
	Enabled          bool
	ReserveTokens    int
	KeepRecentTokens int
}

CompactionSettings controls when and how compaction runs.

type CompactionStartEvent

type CompactionStartEvent struct{}

CompactionStartEvent signals the start of compaction.

type ConfigField

type ConfigField string

ConfigField identifies which Agent configuration field changed.

const (
	// ConfigFieldModel reports a SetModel change.
	ConfigFieldModel ConfigField = "model"
	// ConfigFieldThinkingLevel reports a SetThinkingLevel change.
	ConfigFieldThinkingLevel ConfigField = "thinking_level"
	// ConfigFieldTools reports a SetTools change.
	ConfigFieldTools ConfigField = "tools"
	// ConfigFieldSystemPrompt reports a SetSystemPrompt change.
	ConfigFieldSystemPrompt ConfigField = "system_prompt"
	// ConfigFieldSkills reports a SetSkills change.
	ConfigFieldSkills ConfigField = "skills"
	// ConfigFieldActiveTools reports a SetActiveTools change.
	ConfigFieldActiveTools ConfigField = "active_tools"
)

type ConfigUpdateCtx

type ConfigUpdateCtx struct {
	Field ConfigField

	OldModel string
	NewModel string

	OldThinkingLevel llm.ThinkingLevel
	NewThinkingLevel llm.ThinkingLevel

	OldTools []RegisteredTool
	NewTools []RegisteredTool

	OldSystemPrompt string
	NewSystemPrompt string

	OldSkills []Skill
	NewSkills []Skill

	OldActiveTools []string
	NewActiveTools []string
}

ConfigUpdateCtx is passed to the OnConfigUpdate hook. Only the typed pair matching Field is populated; all other pairs are zero.

type ConvertToLLMFn

type ConvertToLLMFn func(messages []Message) []llm.Message

ConvertToLLMFn transforms agent-side Messages into LLM-shaped messages.

type DynamicToolOption

type DynamicToolOption func(*dynamicTool)

DynamicToolOption configures an optional capability on a dynamic tool.

func WithPrepareArguments

func WithPrepareArguments(fn PrepareArgumentsFunc) DynamicToolOption

WithPrepareArguments attaches a PrepareArgumentsFunc hook to a dynamic tool. The hook transforms raw LLM-supplied arguments before they reach the handler. Returning an error surfaces as a tool error result; the prompt continues.

type EventStream

type EventStream struct {
	Events <-chan AgentEvent
	Done   <-chan PromptResult
}

EventStream is the shared return shape for an Agent prompt. It carries a stream of typed events on Events (closed by the sender when the prompt completes) and exactly one terminal PromptResult on Done.

type FollowUpInjectedEvent

type FollowUpInjectedEvent struct{ Message Message }

FollowUpInjectedEvent signals that a follow-up message has been injected.

type Hooks

type Hooks struct {
	BeforeAgentStart      func(ctx context.Context, c BeforeAgentStartCtx) error
	BeforeToolCall        func(ctx context.Context, c BeforeToolCallCtx) error
	AfterToolCall         func(ctx context.Context, c AfterToolCallCtx) error
	BeforeCompact         func(ctx context.Context, c BeforeCompactCtx) error
	AfterCompact          func(ctx context.Context, c AfterCompactCtx) error
	TransformContext      func(ctx context.Context, c TransformContextCtx) ([]Message, error)
	BeforeProviderRequest func(ctx context.Context, c BeforeProviderRequestCtx) error
	AfterProviderResponse func(ctx context.Context, c AfterProviderResponseCtx)

	// OnSummarizationRetry is called at each retry-lifecycle point when a
	// summarization call made by Compact fails transiently and
	// AgentConfig.SummarizationRetry allows a retry. It is never called when
	// the policy disables retries or the first call succeeds. Calls are
	// serial: split-turn summarization runs its two summarization calls in
	// sequence, so lifecycle events never interleave. It must not call back
	// into the Agent. Nil is a no-op.
	OnSummarizationRetry func(ctx context.Context, c SummarizationRetryCtx)

	// ShouldStopAfterTurn is called at each turn boundary — after turn_end is
	// emitted and tool results are flushed to the session, before the
	// steer/follow-up queues are polled or the next LLM call starts. When it
	// returns true the loop exits with a clean, nil-error PromptResult. Nil is
	// a no-op. Matches upstream pi 0.72.0 shouldStopAfterTurn decision-point
	// semantics. It is also invoked on turns that end via ToolResult.Terminate;
	// on those turns the return value is ignored — the prompt ends regardless.
	// The auto-continue loop imposes no turn cap by design (parity with
	// upstream pi); this hook is the mechanism for imposing one.
	ShouldStopAfterTurn func(ctx context.Context, c AfterTurnCtx) bool

	// OnConfigUpdate is called synchronously by each setter (SetModel,
	// SetThinkingLevel, SetTools, SetSystemPrompt, SetSkills, SetActiveTools)
	// after the new
	// value is committed, on the setter's calling goroutine, without holding
	// the Agent's internal mutex. This means the hook may safely call Agent
	// getters (e.g. State()) without deadlocking. Because the mutex is released
	// before the hook runs, a concurrent setter may write a newer value between
	// the commit and the hook invocation — the hook may therefore observe a
	// newer Agent state than ConfigUpdateCtx.Old* reflects. Nil is a no-op.
	OnConfigUpdate func(ConfigUpdateCtx)
}

Hooks is a flat struct of optional function fields covering every lifecycle point. Nil fields are no-ops.

type LLMErrorEvent

type LLMErrorEvent struct {
	Error     error
	Transient bool
}

LLMErrorEvent signals an error from the LLM provider.

type LLMRetryEvent

type LLMRetryEvent struct {
	Provider   string
	Model      string
	Attempt    int
	NextDelay  int64 // milliseconds
	Reason     string
	ServerHint bool
}

LLMRetryEvent signals a retry attempt.

type Message

type Message struct {
	Role string
	Type string
	Body json.RawMessage
	// Images carries attachments outside Body so Body stays text-only and token estimation can count images at a flat rate.
	Images []llm.ImageContent `json:",omitempty"`
}

Message is the agent-side unit of transcript content. The framework treats Body as opaque bytes for user-defined custom message types.

func BuildLLMContext

func BuildLLMContext(transcript []Message, summaries []BranchSummary) []Message

BuildLLMContext returns a message slice with BranchSummary messages substituted for the ranges they cover. Summaries are sorted by StartIdx and applied in order. Bookkeeping entries (active_tools_change) are stripped — they are state, not conversation, and must never reach the model.

func NewActiveToolsChange

func NewActiveToolsChange(names []string) Message

NewActiveToolsChange creates an active_tools_change bookkeeping entry recording the set of active tool names (nil means all registered tools are active). It is never sent to the model — DefaultConvertToLLM and BuildLLMContext both exclude it — and is never chosen as a compaction cut point. On resume, the active set is restored by scanning the transcript for the last such entry.

func NewBranchSummaryMessage

func NewBranchSummaryMessage(summary string) Message

NewBranchSummaryMessage creates a branch_summary message.

func NewSystem

func NewSystem(text string) Message

NewSystem creates a system prompt message.

func NewText

func NewText(role, text string) Message

NewText creates a text message.

func NewTextWithSignature added in v0.10.0

func NewTextWithSignature(role, text string, thoughtSignature []byte) Message

NewTextWithSignature creates a text message carrying the provider's opaque thought signature (Gemini). With an empty signature it is exactly NewText, so transcripts without signatures keep the plain-string body shape.

func NewThinking

func NewThinking(role, text string) Message

NewThinking creates a thinking message.

func NewThinkingWithSignature added in v0.10.0

func NewThinkingWithSignature(role, text string, thoughtSignature []byte) Message

NewThinkingWithSignature creates a thinking message carrying the provider's opaque thought signature (Gemini). With an empty signature it is exactly NewThinking, so transcripts without signatures keep the plain-string body shape.

func NewToolCall

func NewToolCall(role string, callID, toolName string, args json.RawMessage) Message

NewToolCall creates a tool call message.

func NewToolCallWithSignature added in v0.6.0

func NewToolCallWithSignature(role string, callID, toolName string, args json.RawMessage, thoughtSignature []byte) Message

NewToolCallWithSignature creates a tool call message that also persists the provider's opaque thought signature (Gemini 3), so replaying the transcript carries it back verbatim. A nil signature is equivalent to NewToolCall.

func NewToolResult

func NewToolResult(role string, callID, toolName, content string, data json.RawMessage, isError bool) Message

NewToolResult creates a tool result message.

func NewToolResultMsg added in v0.8.0

func NewToolResultMsg(callID, toolName string, result ToolResult) Message

NewToolResultMsg is the ToolResult-aware variant of NewToolResult: it takes the tool's ToolResult directly and copies result.Images to Message.Images (images ride the Message, never the JSON Body). Tool results are always authored with role "tool".

func (Message) ActiveToolNames

func (m Message) ActiveToolNames() (names []string, ok bool)

ActiveToolNames extracts the recorded active tool names from an active_tools_change message. The second return is false for other types.

func (Message) Text

func (m Message) Text() string

Text extracts the text from a text-typed or branch_summary message. Signature-carrying bodies (NewTextWithSignature) store an object; both shapes read back.

func (Message) TextThoughtSignature added in v0.10.0

func (m Message) TextThoughtSignature() []byte

TextThoughtSignature extracts the provider's opaque thought signature from a text message. Nil when absent (pre-existing transcripts, providers without signatures) or when the message is not a text.

func (Message) ThinkingText added in v0.10.0

func (m Message) ThinkingText() string

ThinkingText extracts the text from a thinking message. Signature-carrying bodies (NewThinkingWithSignature) store an object; both shapes read back.

func (Message) ThinkingThoughtSignature added in v0.10.0

func (m Message) ThinkingThoughtSignature() []byte

ThinkingThoughtSignature extracts the provider's opaque thought signature from a thinking message. Nil when absent or when the message is not a thinking.

func (Message) ToolCall

func (m Message) ToolCall() (callID, toolName string, args json.RawMessage, ok bool)

ToolCall extracts fields from a tool_call message.

func (Message) ToolCallThoughtSignature added in v0.6.0

func (m Message) ToolCallThoughtSignature() []byte

ToolCallThoughtSignature extracts the provider's opaque thought signature from a tool_call message. Nil when absent (pre-existing transcripts, providers without signatures) or when the message is not a tool_call.

func (Message) ToolResult

func (m Message) ToolResult() (callID, toolName, content string, data json.RawMessage, isError bool, ok bool)

ToolResult extracts fields from a tool_result message.

func (Message) Usage added in v0.10.0

func (m Message) Usage() *Usage

Usage extracts the provider token usage recorded on the message (WithUsage). Nil when absent — older transcripts, providers that report none — so callers fall back to EstimateTokens.

func (Message) WithUsage added in v0.10.0

func (m Message) WithUsage(u Usage) Message

WithUsage returns a copy of the message with provider token usage recorded under the body's usage key, preserving existing fields. Plain-string bodies (unsigned text/thinking) convert to the object form; the Usage is metadata and is never sent to a provider.

type MessageEndEvent

type MessageEndEvent struct{ Message Message }

MessageEndEvent signals the end of a message.

type MessageStartEvent

type MessageStartEvent struct {
	Role        string
	MessageType string
}

MessageStartEvent signals the beginning of a message.

type PendingToolCall

type PendingToolCall struct {
	CallID   string
	ToolName string
}

PendingToolCall describes an in-flight tool execution.

type PrepareArgumentsFunc

type PrepareArgumentsFunc func(ctx context.Context, raw json.RawMessage) (json.RawMessage, error)

PrepareArgumentsFunc transforms raw LLM-supplied arguments before unmarshalling into P. A typical use case is shimming a deprecated argument shape — e.g. migrating a legacy_value key to the current value key — without requiring callers to update their prompts. Returning an error surfaces as a tool error result; the prompt continues.

type PromptOpts

type PromptOpts struct {
	SessionID     SessionID
	Model         string
	SystemPrompt  string
	Thinking      llm.ThinkingLevel
	ProviderHints llm.ProviderHints
}

PromptOpts carries per-prompt overrides. The user message is passed as the second argument to Agent.Prompt, not on this struct.

type PromptResult

type PromptResult struct {
	Messages []Message
	Err      error
	// Suspended reports the prompt ended with at least one Suspend-marked
	// tool call pending external resolution (AGENT-25).
	Suspended bool
}

PromptResult is the terminal value delivered on EventStream.Done.

type RegisteredTool

type RegisteredTool interface {
	Name() string
	Description() string
	Schema() json.RawMessage
	Execute(ctx context.Context, callID string, args json.RawMessage) (ToolResult, error)
	IsSequential() bool
}

RegisteredTool is the internal interface that the agent loop uses to invoke tools. Tools built from Tool.ExecuteStream support both call styles: Execute runs them with partial updates discarded, and the unexported streamingTool capability (checked via type assertion) runs them with updates observed.

func NewDynamicTool

func NewDynamicTool(name, description string, schema json.RawMessage, execute func(ctx context.Context, callID string, args json.RawMessage) (ToolResult, error), opts ...DynamicToolOption) RegisteredTool

NewDynamicTool creates a tool from a runtime schema and raw handler. Optional DynamicToolOption values (e.g. WithPrepareArguments) may be appended; existing callers that pass none are unaffected.

func NewTool

func NewTool[P any](t Tool[P]) RegisteredTool

NewTool creates a RegisteredTool from a typed Tool. Exactly one of Execute or ExecuteStream must be set; NewTool panics otherwise, since this is API misuse rather than a runtime condition.

type SessionID

type SessionID string

SessionID is an opaque string that identifies a single session.

type SessionMeta

type SessionMeta struct {
	ID        SessionID
	CreatedAt time.Time
	UpdatedAt time.Time
}

SessionMeta carries metadata about a session.

type SessionRepo

type SessionRepo interface {
	Create(ctx context.Context) (SessionID, error)
	Append(ctx context.Context, id SessionID, msgs ...Message) error
	Load(ctx context.Context, id SessionID) ([]Message, error)
	List(ctx context.Context) ([]SessionMeta, error)
	AppendBranchSummary(ctx context.Context, id SessionID, summary BranchSummary) error
	LoadBranchSummaries(ctx context.Context, id SessionID) ([]BranchSummary, error)
	Delete(ctx context.Context, id SessionID) error
}

SessionRepo is the interface every storage backend implements.

type Skill

type Skill struct {
	// Name is the model-visible identifier rendered into the skill index.
	// When omitted from the SKILL.md frontmatter it defaults to the parent
	// directory name.
	Name string
	// Description is the one-line summary shown to the model in the skill
	// index. It is required; a skill without one is rejected with a Diagnostic.
	Description string
	// Content holds the full text of the SKILL.md body. The framework does
	// not deliver this to the model automatically; the host application must
	// supply a tool that resolves FilePath so the model can fetch it on demand.
	Content string
	// FilePath is the absolute path to the SKILL.md file. It is included in
	// the model-visible index so the model can request the file via a
	// host-supplied file-reader tool.
	FilePath string
	// DisableModelInvocation excludes the skill from the model-visible index
	// when true. The skill remains accessible to the host application.
	DisableModelInvocation bool
}

Skill is a unit of model-invokable expertise carried on the Agent. It is part of the mutable runtime config and the turn snapshot; the model-visible index (name, description, location) is auto-rendered into the system prompt at turn-snapshot time by formatSkillsForSystemPrompt.

Content-reader contract: the framework does NOT ship a tool that reads FilePath. The rendered index exposes only the skill's name, description, and FilePath — never Content — so the model fetches a skill's full instructions on demand through a user-supplied tool (e.g. a file-reader tool registered on the Agent) that resolves FilePath. Carrying Content here is purely informational for the host application; populating it does not make the framework deliver it to the model.

type SteerInjectedEvent

type SteerInjectedEvent struct{ Message Message }

SteerInjectedEvent signals that a steered message has been injected.

type SummarizationRetryCtx added in v0.7.0

type SummarizationRetryCtx struct {
	Phase SummarizationRetryPhase
	// Attempt is the 1-indexed retry attempt this point belongs to.
	Attempt int
	// MaxAttempts is the configured MaxRetries. Set on Scheduled.
	MaxAttempts int
	// Delay is the backoff about to be slept. Set on Scheduled.
	Delay time.Duration
	// Success reports the loop outcome. Set on Finished.
	Success bool
	// Err is the error that triggered this retry on Scheduled, and the final
	// error on an unsuccessful Finished. Nil on AttemptStart and on a
	// successful Finished.
	Err error
}

SummarizationRetryCtx describes one retry-lifecycle point of a failed summarization call (Compact's first summary, summary update, or split-turn pair).

type SummarizationRetryPhase added in v0.7.0

type SummarizationRetryPhase int

SummarizationRetryPhase identifies which point of the retry lifecycle an OnSummarizationRetry call reports. Mirrors upstream 0.81.1's summarization_retry_scheduled / _attempt_start / _finished events.

const (

	// SummarizationRetryScheduled fires before the backoff sleep of each retry.
	SummarizationRetryScheduled SummarizationRetryPhase
	// SummarizationRetryAttemptStart fires after the backoff sleep, immediately
	// before the retried call starts.
	SummarizationRetryAttemptStart
	// SummarizationRetryFinished fires once when the retry loop ends, whether
	// it succeeded, exhausted its budget, or was aborted during backoff.
	SummarizationRetryFinished
)

type SummarizationRetryPolicy added in v0.7.0

type SummarizationRetryPolicy struct {
	// MaxRetries bounds retry attempts; the initial call never counts as a
	// retry. 0 disables retries.
	MaxRetries int
	// BaseDelay is the first retry delay; attempt n waits BaseDelay * 2^(n-1),
	// capped at MaxDelay. <= 0 uses DefaultSummarizationRetryBaseDelay.
	BaseDelay time.Duration
	// MaxDelay caps the per-attempt delay. <= 0 uses DefaultSummarizationRetryMaxDelay.
	MaxDelay time.Duration
}

SummarizationRetryPolicy configures bounded retries with exponential backoff for the summarization calls made by Compact (first summary, summary update, and the split-turn pair). The zero value disables retries, matching upstream when no retry policy is configured. Ported from upstream 0.81.1 (resilient compaction and branch summaries, pi#6901).

type TextDeltaEvent

type TextDeltaEvent struct {
	Delta string
	// ThoughtSignature is the provider's opaque signature for the text part
	// this delta belongs to (Gemini). Present on at most some deltas — possibly
	// one with an empty Delta — so durable-log consumers retain the last
	// non-empty value; empty for providers without signatures.
	ThoughtSignature []byte
}

TextDeltaEvent carries a fragment of assistant text.

type ThinkingDeltaEvent

type ThinkingDeltaEvent struct {
	Delta string
	// ThoughtSignature behaves as on TextDeltaEvent, for thinking parts.
	ThoughtSignature []byte
}

ThinkingDeltaEvent carries a fragment of assistant thinking.

type ThinkingUnsupportedEvent

type ThinkingUnsupportedEvent struct {
	Requested string
	Provider  string
	Model     string
	Reason    string
}

ThinkingUnsupportedEvent signals that thinking was requested but unsupported.

type Tool

type Tool[P any] struct {
	Name        string
	Description string
	Sequential  bool
	Execute     func(ctx context.Context, params P) (ToolResult, error)
	// ExecuteStream is the streaming alternative to Execute: it may call emit
	// zero or more times with partial results before returning the final
	// ToolResult. Exactly one of Execute or ExecuteStream must be set;
	// NewTool panics otherwise. Partial results are ephemeral — the loop
	// forwards each emit as a ToolUpdateEvent and never persists them to the
	// transcript.
	ExecuteStream func(ctx context.Context, params P, emit func(ToolResult)) (ToolResult, error)
	// PrepareArguments is an optional hook that runs on raw args before
	// unmarshalling into P. See PrepareArgumentsFunc for details.
	PrepareArguments PrepareArgumentsFunc
}

Tool is the generic, compile-time-typed tool struct.

type ToolCallEndEvent

type ToolCallEndEvent struct {
	CallID   string
	ToolName string
	Result   ToolResult
}

ToolCallEndEvent signals that a tool call has completed.

type ToolCallStartEvent

type ToolCallStartEvent struct {
	CallID           string
	ToolName         string
	Args             []byte
	ThoughtSignature []byte
}

ToolCallStartEvent signals that a tool call has started. v0.9.0 guarantee: fires once per tool call when the call is finalized at the provider's ToolCallEndEvent, always carrying complete arguments. ThoughtSignature is the provider's opaque per-call signature (Gemini 3); nil for providers without one. Event consumers that persist tool calls outside the transcript (e.g. a durable harness) must carry it so replayed calls keep their signature.

type ToolErrorEvent

type ToolErrorEvent struct {
	CallID   string
	ToolName string
	Error    error
}

ToolErrorEvent signals that a tool call errored.

type ToolExecutionMode

type ToolExecutionMode int

ToolExecutionMode controls whether tools execute in parallel or serially.

const (
	// ToolExecParallel runs a turn's tool calls concurrently. It is the zero value and the default.
	ToolExecParallel ToolExecutionMode = iota
	// ToolExecSequential runs a turn's tool calls one at a time, in the order the model requested them.
	ToolExecSequential
)

type ToolLeakEvent

type ToolLeakEvent struct {
	ToolName string
	CallID   string
	// Duration is milliseconds measured from batch start to leak declaration
	// (≈ time-to-cancellation + ShutdownTimeout) — not the leaked tool's
	// eventual runtime, which is unbounded since the goroutine is still running.
	Duration int64
}

ToolLeakEvent signals that a tool ignored context cancellation.

type ToolResult

type ToolResult struct {
	Content   string
	Data      json.RawMessage
	IsError   bool
	Terminate bool
	// Suspend marks a call that resolves externally (HARNESS-15's task tool):
	// the loop persists sibling results, persists NO tool_result for this
	// call — the pending call is the suspension point — and ends the prompt
	// gracefully after the batch, reporting PromptResult.Suspended.
	// Terminate dominates when both are set in an all-terminating batch.
	Suspend bool
	// Images carries optional image parts of the result (flows to llm.ToolResultContent.Images).
	Images []llm.ImageContent
}

ToolResult is the concrete struct returned by a tool's Execute function.

type ToolUpdateEvent added in v0.8.0

type ToolUpdateEvent struct {
	CallID string
	Name   string
	Result ToolResult
}

ToolUpdateEvent carries a partial-result snapshot from a running tool. Zero or more are emitted between ToolCallStartEvent and ToolCallEndEvent. Updates are ephemeral: events only, never persisted to the transcript.

type TransformContextCtx

type TransformContextCtx struct {
	Messages []Message
}

TransformContextCtx is passed to the TransformContext hook. The returned messages replace the transcript sent to the LLM.

type TurnEndEvent

type TurnEndEvent struct{ Turn int }

TurnEndEvent signals the end of an agent turn.

type TurnStartEvent

type TurnStartEvent struct{ Turn int }

TurnStartEvent signals the beginning of a new agent turn.

type Usage added in v0.9.0

type Usage struct {
	InputTokens  int `json:"inputTokens"`
	OutputTokens int `json:"outputTokens"`
}

Usage records provider token usage for the LLM call(s) that produced an artifact. Values are sums when multiple calls contributed (split-turn compaction runs two).

type UserMessageEvent

type UserMessageEvent struct{ Message Message }

UserMessageEvent signals a user message being processed.

Directories

Path Synopsis
Package agenttest provides test helpers for pi-core-agent-go.
Package agenttest provides test helpers for pi-core-agent-go.
examples
providers command
Command providers constructs an agent with Gemini plus the six OpenAI-compatible targets (OpenAI, OpenCode Zen, xAI, Mistral, Qwen, z.ai) and routes one prompt by "<provider>/<model>" ref.
Command providers constructs an agent with Gemini plus the six OpenAI-compatible targets (OpenAI, OpenCode Zen, xAI, Mistral, Qwen, z.ai) and routes one prompt by "<provider>/<model>" ref.
internal
harness
Package harness contains the internal agent loop implementation.
Package harness contains the internal agent loop implementation.
Package piskills loads pi.Skill values from a directory tree of SKILL.md files.
Package piskills loads pi.Skill values from a directory tree of SKILL.md files.
Package session provides pi.SessionRepo implementations for persisting agent transcripts: a JSONL file-backed store and an in-memory store.
Package session provides pi.SessionRepo implementations for persisting agent transcripts: a JSONL file-backed store and an in-memory store.
Package tools provides utilities consumed by agent-facing tool implementations.
Package tools provides utilities consumed by agent-facing tool implementations.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL