Documentation
¶
Overview ¶
Package agentcore is a reusable, product-agnostic agent runtime: the turn loop, tool calling, hooks, permissions, memory, and the Agent Definition. It knows nothing about analytics or any other product. A consumer injects a ToolSet, a Policy, a MemoryStore, and an AgentDefinition; agentcore drives them.
Boundaries ¶
Two rules define this package, and both are tests rather than promises (see boundary_test.go):
- Composition depends only on shared runtime modules. Provider-neutral contracts live in ai/protocol and are re-exported here during migration; ai no longer imports agentcore. The native loop lives in engine and reusable native compaction/checkpoint policy lives in host.
- The kernel names no plugin. Delete every package under agentcore/plugins and this package still compiles, still runs, and still passes its tests — it just does less.
Composition ¶
An agent is not constructed, it is composed. A Plugin contributes to a Registry; Build turns a plugin set into an Agent. There is no privileged core to patch — agentcore's own behavior is just the default plugin set, and any of it can be replaced by registering something else in its place.
agent, err := agentcore.Build(
agentcore.ModelPlugin{...}, // seam: provider, ladder, retry
agentcore.DefinitionPlugin{...}, // seam: persona, skills, limits
agentcore.PolicyPlugin{...}, // seam: the permission gate
todo.Plugin{...}, // extension: adds a tool and a step interceptor
)
New is the same composition reached through a flat Config instead of a plugin list; both write through the same exported Registry setters, and agentcore/plugins/preset carries a parity test proving they agree.
A plugin contributes in exactly one of three ways, and which one it is can be read off its Register body:
- Seam — r.SetX(...). Keyed, exactly one provider, a second claim is a build error naming both plugins. Bought property: replaceability.
- Extension — r.AddExtension(...). Ordered by explicit Priority, never by registration order. This is how a capability reaches a running agent.
- Additive — r.AddTools / r.AddHooks. Accumulates.
Reading this package ¶
The engine subpackage is the native Go port of Pi's state machine, using the lossless ai transcript. It is replacing this legacy flat kernel in stages; it does not call the TypeScript worker. Its dependencies are checked separately.
The runtime root is deliberately flat: these files are not independent concerns that happen to sit together, they are one machine reached through unexported fields of one Agent. Black-box composition tests live in agentcore/integration; the real production boundary is agentcore/plugins, so the organizing question here is not "which folder?" but "why is this file in the kernel and not a plugin?". Every file answers with one of four words — loop, contract, log, or seam default — and README.md holds the map, which TestEveryKernelFileJustifiesItself gates.
The layers that map runs through, outermost first:
- Composition — doc.go, plugin.go, compose.go, agent.go, seams.go.
- Contracts — provider.go, provider_session.go, tool.go, permission.go, definition.go, hooks.go, extension.go, memory.go, env.go. The types a plugin or provider outside this repo implements.
- The loop — loop.go, turn.go, tooldispatch.go, result.go, prompt.go, skill_tool.go.
- Durable state — session.go, session_tree.go, memsession.go, compaction.go, fork.go, lab.go. The loop is the only writer; everything else reduces the log.
One file sits outside those layers on purpose: faux.go, the providers that make the loop, the hooks and the gate exercisable with no network and no key — FauxProvider scripted, ReplayProvider over a recorded transcript. They ship in the kernel rather than a test package because the plugins are tested against them too, and a plugin proving its behavior against the real loop is the point of the whole split.
Provider contracts are owned by ai/protocol. These aliases keep existing plugin and SDK implementations source-compatible during the engine migration.
Index ¶
- Constants
- Variables
- func AcquireSessionLease(ctx context.Context, store SessionStore, sessionID string) (context.Context, func() error, error)
- func ActiveLeaf(log []SessionEntry) string
- func Cosine(a, b []float32) float64
- func DelegationDepth(ctx context.Context) int
- func IdempotencyKey(ctx context.Context) (key string, ok bool)
- func IsRetryable(err error) bool
- func LaunchBackground(ctx context.Context, tool, label string, ...) (json.RawMessage, error)
- func PendingQuestion(log []SessionEntry) (callID string, question json.RawMessage, found bool)
- func PiQuestionFromEntry(entry SessionEntry) (string, json.RawMessage, bool)
- func RecordGoalRevision(ctx context.Context, revision GoalRevision) error
- func RecordSessionAnswer(ctx context.Context, store SessionStore, sessionID, callID, answer string) (bool, error)
- func RecoverNativeTranscript(entries []SessionEntry) (json.RawMessage, error)
- func ReportProgress(ctx context.Context, note string)
- func Rewind(ctx context.Context, store SessionStore, sessionID, targetID string, ...) (string, error)
- func RunSessionFrom(ctx context.Context) string
- func SandboxSessionFrom(ctx context.Context) string
- func StringProperties(names ...string) map[string]any
- func ToolCallID(ctx context.Context) (id string, ok bool)
- func ToolInvocationScope(ctx context.Context) (string, bool)
- func TruncateBytes(s string, maxBytes int) string
- func TruncateMiddle(s string, maxBytes int) string
- func WithBackgroundLauncher(ctx context.Context, launch BackgroundLauncher) context.Context
- func WithDelegationDepth(ctx context.Context, depth int) context.Context
- func WithGoalRevisionRecorder(ctx context.Context, record func(context.Context, GoalRevision) error) context.Context
- func WithRunProgress(ctx context.Context, progress func(string)) context.Context
- func WithRunSession(ctx context.Context, id string) context.Context
- func WithSandboxSession(ctx context.Context, id string) context.Context
- func WithToolInvocationScope(ctx context.Context, invocationID string) context.Context
- type AccountScopedProviderState
- type AfterToolCall
- type Agent
- func (a *Agent) AddChildUsage(u Usage)
- func (a *Agent) Continue(ctx context.Context, history []Message, task string) (RunResult, error)
- func (a *Agent) ContinueStream(ctx context.Context, history []Message, task string, sink StreamSink) (RunResult, error)
- func (a *Agent) Describe() string
- func (a *Agent) FollowUp(ctx context.Context, m Message) error
- func (a *Agent) Fork(childSessionID string) *Agent
- func (a *Agent) IsDurable() bool
- func (a *Agent) OpenPiTools(ctx context.Context) (*PiToolHost, error)
- func (a *Agent) Prompt(ctx context.Context, userInput string) (RunResult, error)
- func (a *Agent) PromptStream(ctx context.Context, userInput string, sink StreamSink) (RunResult, error)
- func (a *Agent) RunNative(ctx context.Context, input NativeRun) (RunResult, error)
- func (a *Agent) SessionID() string
- func (a *Agent) Steer(ctx context.Context, m Message) error
- type AgentDefinition
- type AgentEndHook
- type AllowList
- type ArgPreparer
- type BackgroundLauncher
- type BatchDecision
- type BatchInterceptor
- type BeforeAgentStartHook
- type BeforeCompactHook
- type BeforeToolCall
- type BookkeepingTool
- type BranchOptions
- type BudgetPlugin
- type CallRetrySafeTool
- type CapabilitySupport
- type CardPoint
- type CardStat
- type ChatDelta
- type ChatRequest
- type ChatResponse
- type CheckpointState
- type ChildQuestionError
- type CompactDecision
- type CompactRequest
- type CompactionPlugin
- type CompactionRequest
- type CompactionResult
- type CompactionSettings
- type Compactor
- type Config
- type ContentPart
- type ContextContributor
- type ContextHook
- type ContextPruneRequest
- type ContextPruner
- type CredentialResolver
- type Decision
- type DefinitionPlugin
- type DenyAll
- type Diagnostic
- type Embedder
- type Env
- type Extension
- type ExtensionFactory
- type FauxProvider
- type GoalReviser
- type GoalRevision
- type HookErrorPolicy
- type Hooks
- type HooksPlugin
- type InboxItem
- type KeyUpdater
- type LLMProvider
- type LabSkillRef
- type LabStep
- type LabStepKind
- type LabToolCall
- type Limits
- type MemoryCurator
- type MemoryEntry
- type MemoryKind
- type MemorySessionStore
- func (m *MemorySessionStore) AcquireSessionLease(ctx context.Context, id string) (context.Context, func() error, error)
- func (m *MemorySessionStore) Append(ctx context.Context, id string, e SessionEntry) error
- func (m *MemorySessionStore) AppendBatch(_ context.Context, id string, entries []SessionEntry) error
- func (m *MemorySessionStore) CheckpointSeq(_ context.Context, id string) (int, bool, error)
- func (m *MemorySessionStore) Log(_ context.Context, id string) ([]SessionEntry, error)
- func (m *MemorySessionStore) LogFrom(_ context.Context, id string, sinceSeq int) ([]SessionEntry, error)
- func (m *MemorySessionStore) Sessions() []string
- type MemoryStore
- type Message
- type MessageEndHook
- type ModelCapabilities
- type ModelCapabilityError
- type ModelCapabilityProvider
- type ModelPlugin
- type ModelRung
- type NativeContextContributor
- type NativeRun
- type NativeStateContributor
- type ObservePhase
- type OutputSchema
- type ParallelTool
- type PiArgumentPreparer
- type PiCompletedTurn
- type PiContextHook
- type PiToolHost
- func (h *PiToolHost) Close() error
- func (h *PiToolHost) CompletePiRun(ctx context.Context, result RunResult) RunResult
- func (h *PiToolHost) ControlPiRun(ctx context.Context, info StepInfo) (string, error)
- func (h *PiToolHost) Definitions(ctx context.Context) (json.RawMessage, error)
- func (h *PiToolHost) Execute(ctx context.Context, params json.RawMessage, emit func(json.RawMessage) error) (json.RawMessage, PiToolOutcome, error)
- func (h *PiToolHost) FinishPiDelegationBatch(ctx context.Context, calls []ToolCall, outcomes []PiToolOutcome) (PiTurnDecision, error)
- func (h *PiToolHost) FinishPiTurn(ctx context.Context, turn PiCompletedTurn) (PiTurnDecision, error)
- func (h *PiToolHost) ObservePiMessages(ctx context.Context, phase ObservePhase, turn int, raw json.RawMessage) error
- func (h *PiToolHost) PiCompactionPolicy() (budget, keepRecent int)
- func (h *PiToolHost) PiGoal() string
- func (h *PiToolHost) PreparePiTurn(ctx context.Context, info StepInfo) (PiTurnPreparation, error)
- func (h *PiToolHost) RefreshPiGoal() (system string, changed bool, err error)
- func (h *PiToolHost) ResumeDelegation(ctx context.Context, effectID string, original PiToolOutcome, ...) (json.RawMessage, PiToolOutcome, error)
- func (h *PiToolHost) StartPiRun(ctx context.Context, task string) (string, error)
- func (h *PiToolHost) StartPiRunWithLifecycle(ctx context.Context, task string, lifecycle func(string)) (string, error)
- func (h *PiToolHost) TransformPiContext(ctx context.Context, raw json.RawMessage) json.RawMessage
- func (h *PiToolHost) ValidatePiSession(id string, durable bool) error
- type PiToolOutcome
- type PiTurnDecision
- type PiTurnPreparation
- type Plugin
- type Policy
- type PolicyPlugin
- type Priority
- type ProcessSandbox
- type PromptContributor
- type ProviderError
- type ProviderRequestHook
- type ProviderSession
- type ProviderSessionRegistry
- type ProviderSessionState
- type ReasoningBlock
- type RecoveryPolicy
- type ReducedState
- type Registry
- func (r *Registry) AddExtension(f ExtensionFactory)
- func (r *Registry) AddHooks(p Priority, h Hooks)
- func (r *Registry) AddTools(tools ...Tool)
- func (r *Registry) Agent() (*Agent, error)
- func (r *Registry) ApplyConfig(cfg Config) error
- func (r *Registry) Describe() string
- func (r *Registry) Plugins() []string
- func (r *Registry) Provider(seam string) string
- func (r *Registry) SetBudgetGate(fn func(ctx context.Context, u Usage) bool) error
- func (r *Registry) SetCompaction(s CompactionSettings) error
- func (r *Registry) SetCompactionModel(p LLMProvider, model string) error
- func (r *Registry) SetCompactor(c Compactor) error
- func (r *Registry) SetContextWindow(tokens int) error
- func (r *Registry) SetDefinition(d AgentDefinition) error
- func (r *Registry) SetEnv(e Env) error
- func (r *Registry) SetEscalation(rungs []ModelRung) error
- func (r *Registry) SetFollowUpSource(fn func(ctx context.Context) []Message) error
- func (r *Registry) SetGoal(goal string) error
- func (r *Registry) SetLimits(l Limits) error
- func (r *Registry) SetMaxTokens(n int) error
- func (r *Registry) SetMemory(m MemoryStore) error
- func (r *Registry) SetModel(p LLMProvider, model string) error
- func (r *Registry) SetModelCapabilities(c ModelCapabilities) error
- func (r *Registry) SetNativeProvider(p *ai.FallbackProvider) error
- func (r *Registry) SetOutputSchema(s *OutputSchema) error
- func (r *Registry) SetParallelToolCalls(enabled bool) error
- func (r *Registry) SetPolicy(p Policy) error
- func (r *Registry) SetPrepareNextTurn(fn func(ctx context.Context, state TurnState) TurnState) error
- func (r *Registry) SetPromptCache(key, retention string) error
- func (r *Registry) SetProviderSession(s *ProviderSession, id string) error
- func (r *Registry) SetReasoningEffort(e string) error
- func (r *Registry) SetRefreshKey(fn func(ctx context.Context, provider string) (string, error)) error
- func (r *Registry) SetRetry(p RetryPolicy) error
- func (r *Registry) SetSeedDisabledTools(names []string) error
- func (r *Registry) SetSession(s SessionStore, id string, resume bool) error
- func (r *Registry) SetSteeringSource(fn func(ctx context.Context) []Message) error
- func (r *Registry) SetStepGate(fn func(ctx context.Context, turn int) error) error
- func (r *Registry) SetToolChoice(choice ToolChoice) error
- func (r *Registry) Unload(name string) error
- func (r *Registry) UsePolicy(p Policy) error
- type ReplayProvider
- type ResultCard
- type ResumePlan
- type RetryPolicy
- type RetrySafeTool
- type RichTool
- type Role
- type RunChapter
- type RunCloser
- type RunCommandHandler
- type RunController
- type RunFinalizer
- type RunInfo
- type RunObserver
- type RunResult
- type RunStart
- type Sandbox
- type SandboxExec
- type SandboxLimits
- type SandboxMount
- type SandboxProcess
- type SandboxResult
- type SelfGated
- type Session
- type SessionBatchStore
- type SessionEntry
- type SessionEntryKind
- type SessionLeaseStore
- type SessionNode
- type SessionPlugin
- type SessionSandbox
- type SessionStore
- type SessionWindowStore
- type Severity
- type Skill
- type SkillLoader
- type SteeringPlugin
- type StepDecision
- type StepInfo
- type StepInterceptor
- type StopDecision
- type StopInfo
- type StopInterceptor
- type StreamDecision
- type StreamEvent
- type StreamEventType
- type StreamInterceptor
- type StreamSink
- type StreamingTool
- type StringTool
- type Tool
- type ToolCall
- type ToolChoice
- type ToolChoiceMode
- type ToolContributor
- type ToolDenialReason
- type ToolGate
- type ToolInterceptor
- type ToolInvocation
- type ToolInvoker
- type ToolOutcomeRecord
- type ToolOutput
- type ToolResultArchiver
- type ToolResultDecision
- type ToolSchema
- type ToolSet
- type ToolStrictness
- type ToolTrace
- type ToolsPlugin
- type TurnHook
- type TurnInfo
- type TurnRecord
- type TurnState
- type Usage
Constants ¶
const ( CapabilityUnknown = protocol.CapabilityUnknown CapabilitySupported = protocol.CapabilitySupported CapabilityUnsupported = protocol.CapabilityUnsupported RoleSystem = protocol.RoleSystem RoleUser = protocol.RoleUser RoleAssistant = protocol.RoleAssistant RoleTool = protocol.RoleTool ReasoningBlockThinking = protocol.ReasoningBlockThinking ReasoningBlockRedacted = protocol.ReasoningBlockRedacted ContentPartText = protocol.ContentPartText ContentPartImage = protocol.ContentPartImage ToolStrictDefault = protocol.ToolStrictDefault ToolStrictEnabled = protocol.ToolStrictEnabled ToolStrictDisabled = protocol.ToolStrictDisabled ToolChoiceDefault = protocol.ToolChoiceDefault ToolChoiceAuto = protocol.ToolChoiceAuto ToolChoiceNone = protocol.ToolChoiceNone ToolChoiceRequired = protocol.ToolChoiceRequired ToolChoiceNamed = protocol.ToolChoiceNamed )
const ( InboxSteer = "steer" InboxFollow = "follow" )
Inbox lanes: which drain point an EntryInbox feeds.
Variables ¶
var ErrAnswerConflict = errors.New("agentcore: answer conflicts with recorded answer")
ErrAnswerConflict reports a second, different answer for an already-answered call. Retrying the same answer is idempotent; changing it is not.
var ErrBusy = errors.New("agentcore: agent is busy with another run")
ErrBusy is returned by a run entry point (Prompt/Continue/…) when the Agent is already running. One Agent instance drives one run at a time; spin up a second instance (or wait for the first to finish) for concurrent work.
var ErrNoPendingQuestion = errors.New("agentcore: no pending question")
ErrNoPendingQuestion reports that an answer does not match an open ask call.
var ErrParked = errors.New("tool parked the run awaiting a human answer")
ErrParked is the sentinel a tool returns to park the run on a human answer (the ask tool): the loop records an EntryQuestion for the call, emits a StreamQuestion, and ends the run WITHOUT a tool result — the call stays dangling in the durable log until an EntryAnswer resolves it or a resume re-issues it. A tool that parks must also be retry-safe (RetrySafeTool / CallRetrySafeTool) or a crash-resume would close the call as interrupted instead of re-parking.
var ErrSessionLeaseLost = errors.New("agentcore: durable session lease lost")
ErrSessionLeaseLost reports that a durable-session owner no longer holds the fencing token that authorized its resume. Callers must stop provider/tool work and may retry by acquiring a fresh lease.
Functions ¶
func AcquireSessionLease ¶
func AcquireSessionLease(ctx context.Context, store SessionStore, sessionID string) (context.Context, func() error, error)
AcquireSessionLease uses the strongest ownership contract a store exposes. Legacy stores remain source-compatible and receive a no-op lease; they retain their previous single-owner semantics.
func ActiveLeaf ¶
func ActiveLeaf(log []SessionEntry) string
ActiveLeaf returns the effective id of the entry the next append will chain from ("" for an empty log).
func Cosine ¶
Cosine is the cosine similarity of two equal-length vectors, in [-1, 1]. Mismatched lengths or a zero-magnitude vector yield 0 (no signal) rather than NaN, so ranking degrades gracefully instead of panicking.
func DelegationDepth ¶
DelegationDepth returns the current delegation depth (0 for a top-level run).
func IdempotencyKey ¶
IdempotencyKey returns the framework-generated idempotency key for the current tool invocation, for use inside Tool.Run. ok is false on a run without a durable session — a tool performing a non-idempotent external effect should treat that as "no crash-retry protection", not an error.
func IsRetryable ¶
func LaunchBackground ¶
func PendingQuestion ¶
func PendingQuestion(log []SessionEntry) (callID string, question json.RawMessage, found bool)
PendingQuestion returns the newest parked call still awaiting an answer: an EntryQuestion with no matching EntryAnswer, or a settled native parked effect with no EntryPiAnswer. Native IDs identify physical effects, not provider calls. For a delegated ask, the ID belongs to this parent and the question comes from the child's routed receipt; the original spawn arguments stay in audit. The second return is the question's validated arguments; the third reports whether one was found. Consumers (the answer route, the reattach read) use it to render or resolve the open question without knowing which tool asked it.
func PiQuestionFromEntry ¶
func PiQuestionFromEntry(entry SessionEntry) (string, json.RawMessage, bool)
PiQuestionFromEntry recognizes only a successfully settled governed parked effect. The physical effect ID is the workflow ID: provider call IDs may be reused by a later assistant turn and must not reuse a previous human answer.
func RecordGoalRevision ¶
func RecordGoalRevision(ctx context.Context, revision GoalRevision) error
RecordGoalRevision commits a change through the current run owner. Legacy runs without a recorder retain their existing extension-drain persistence.
func RecordSessionAnswer ¶
func RecordSessionAnswer(ctx context.Context, store SessionStore, sessionID, callID, answer string) (bool, error)
RecordSessionAnswer appends one answer to the currently pending question. It is idempotent for an exact retry and rejects a different answer for the same call. Hosts should call it while holding the session lease so the read and append form one logical ownership interval. Native Pi questions use a physical effect ID and an append-only user message; legacy questions retain their original provider-call/tool-result workflow. Delegated questions forward to the settled child question under its lease before recording the parent's acknowledgement. Exact retries finish a partial child/parent commit without changing or duplicating the child's answer.
func RecoverNativeTranscript ¶
func RecoverNativeTranscript(entries []SessionEntry) (json.RawMessage, error)
RecoverNativeTranscript refuses ambiguous effects even when cancellation caused Pi to emit a synthetic error result. A native error event is not proof an external write didn't happen. Entries in this format never go through RecoverSession. This restores provider state only. The consumer still validates workflow metadata (questions, delegation, invocation and goals) before running it.
func ReportProgress ¶
ReportProgress publishes a display note, never transcript content or a tool result. It is inert without an observer or after cancellation.
func Rewind ¶
func Rewind(ctx context.Context, store SessionStore, sessionID, targetID string, opts BranchOptions) (string, error)
Rewind moves a session's active leaf to targetID (an effective id from SessionTree — an entry's own ID, or "#<index>" for legacy id-less entries). Subsequent appends chain from there, and ReduceSession/RecoverSession reconstruct history along the new branch. It returns the new leaf id — the target itself, or the branch-summary node when one was written in front of it. The abandoned branch stays in the log untouched.
func RunSessionFrom ¶
RunSessionFrom returns the session id of the run making the current call, or "" outside any run (a connectivity probe, a classifier call) and on a run with no durable session. Consumers must treat "" as "attribute it to the run", not as an error.
func SandboxSessionFrom ¶
SandboxSessionFrom reads the session id set by WithSandboxSession ("" when absent — the safe default that keeps execution ephemeral and unshared).
func StringProperties ¶
StringProperties builds bounded, non-empty text arguments for a declaration.
func ToolCallID ¶
ToolCallID returns the provider-assigned ID of the current tool call, for use inside Tool.Run. ok is false when the tool was invoked outside the loop.
func ToolInvocationScope ¶
ToolInvocationScope returns the host identity for this invocation. Native checkpoint consumers journal effects with this scope and ToolCallID rather than treating identical arguments as the same intended action.
func TruncateBytes ¶
TruncateBytes trims s to at most maxBytes without splitting a UTF-8 rune, appending a marker when it cuts. maxBytes <= 0 disables truncation.
func TruncateMiddle ¶
TruncateMiddle trims s to at most maxBytes by cutting its MIDDLE out, keeping head and tail verbatim with an omission marker between them. This is the shape tool results get: the end of a long result usually carries the signal. maxBytes <= 0 disables truncation.
func WithBackgroundLauncher ¶
func WithBackgroundLauncher(ctx context.Context, launch BackgroundLauncher) context.Context
func WithDelegationDepth ¶
WithDelegationDepth returns ctx marked as depth hops deep. Consumers normally never call this — the spawn tool wraps the child ctx itself — but a consumer embedding a run inside another delegation system may seed a floor.
func WithGoalRevisionRecorder ¶
func WithGoalRevisionRecorder(ctx context.Context, record func(context.Context, GoalRevision) error) context.Context
WithGoalRevisionRecorder installs the run owner's commit boundary. The consumer must call RecordGoalRevision before publishing the new condition, while holding the same lock that serializes its updates.
func WithRunProgress ¶
WithRunProgress binds a host's run-lived observer. Background tools must use this instead of retaining a tool-lived streaming emitter. The host serializes callbacks and fences delivery when the run ends.
func WithRunSession ¶
WithRunSession tags ctx with the session id of the run about to execute. The loop sets it once per run, before the first turn; an empty id is a no-op, so an in-memory run (which has no session) leaves the tag absent rather than stamping a meaningless empty string over an outer one.
func WithSandboxSession ¶
WithSandboxSession tags ctx with a stable session id a SessionSandbox uses to reuse one persistent container across tool calls. The consumer (the Runner) sets it to the conversation session id just before driving the loop; an empty id is a no-op and leaves tools on the ephemeral, throwaway-container path.
func WithToolInvocationScope ¶
WithToolInvocationScope binds a tool call to an opaque durable invocation ID. A native session host must persist this identity before executing a tool; provider call IDs alone may be reused. Nested calls inherit the scope.
Types ¶
type AccountScopedProviderState ¶
type AccountScopedProviderState = protocol.AccountScopedProviderState
type AfterToolCall ¶
type AfterToolCall func(ctx context.Context, call ToolCall, result string, runErr error) (rewritten string, terminate bool)
AfterToolCall fires after a tool executes. It may annotate or rewrite the result string, and may set terminate to end the run cleanly (a terminal tool such as submit_recommendation/finish).
type Agent ¶
type Agent struct {
// contains filtered or unexported fields
}
Agent is a configured runtime instance: a provider + model, an injected ToolSet, a Policy, lifecycle Hooks, an optional MemoryStore, and an AgentDefinition. It is product-agnostic — the same Agent type powers the Growth Analyst and any future consumer.
func Build ¶
Build composes an Agent from plugins.
Registration order does not affect behavior: seams are keyed and hooks are prioritized, so a caller may reorder the list freely. A plugin that fails aborts the whole composition — a half-built agent is never returned.
func New ¶
New constructs an Agent from a Config.
It is a plugin composition like any other: the Config is applied to a Registry through the same exported setters the plugin packages use, and the Agent is built from that Registry. Config stays the ergonomic front door for the common case; reach for Build with plugins from agentcore/plugins/... when you need to REPLACE a seam (your own governance plugin) rather than configure one.
The permission gate is installed by Registry.UsePolicy at PriorityGate, so it is still consulted before any consumer hook — now as a property of the hook ordering rather than a side effect of prepending to a slice.
func (*Agent) AddChildUsage ¶
AddChildUsage folds a delegated run's usage into this agent's accumulator, so the parent's RunResult and its budget gate both account for what its children spent. A delegation plugin calls this for every child it drives — including one that FAILED, whose tokens were still burned.
func (*Agent) Continue ¶
Continue submits host-authored input to the native engine. To resume provider history, pass the prior NativeState to RunNative instead of replaying Messages.
func (*Agent) ContinueStream ¶
func (a *Agent) ContinueStream(ctx context.Context, history []Message, task string, sink StreamSink) (RunResult, error)
ContinueStream submits host-authored input and streams the native run. Provider history resumes through RunNative with its opaque checkpoint.
func (*Agent) Describe ¶
Describe renders what an Agent is actually configured with.
The question it answers is the one that is hard to answer from a config file or a plugin list: after every default, every override, and every plugin, what does this agent actually do? Reach for it when a run behaves in a way the configuration does not explain — an ungated tool, compaction that never fires, a resume that starts fresh.
It reports presence, never values, for anything that could carry a secret or a closure: a credential resolver, a budget gate, and a steering source each print as "set". Tool NAMES are printed because the model already sees them.
The output is stable and ordered, so two agents can be compared by string — which is how agentcore/plugins/preset proves that composing from plugin packages produces the same agent as New(Config).
func (*Agent) FollowUp ¶
FollowUp queues work for after the agent would stop (pi's followUp()): the loop drains it when the model produces a final answer and restarts instead of returning. Same durability contract as Steer.
func (*Agent) Fork ¶
Fork builds an ephemeral child Agent for one delegated task.
The child inherits every capability-bearing field verbatim — provider, ladder, tools, policy (including the permission gate already installed in hooks), memory, definition, limits, env, compaction, retry, caching, output cap, and the parent's extensions — so scope can only NARROW, never widen.
It deliberately drops the run-control seams: no durable session, no steering/follow-up queues, no step gate, no PrepareNextTurn. A child is one bounded task, not a conversation.
childSessionID, when non-empty AND the parent is durable, gives the child a durable log of its own and marks it resumable. Callers derive that id deterministically from the spawn (parent session + tool call id) so a replayed spawn REATTACHES — a completed child returns its recorded answer without re-running, and an interrupted one resumes from its own log — instead of duplicating the spend and the side effects. Empty leaves the child's history purely in memory.
Depth is not a field: it rides the context (see WithDelegationDepth), so a cap holds across an A -> B -> A cycle the same as a straight chain.
func (*Agent) IsDurable ¶
IsDurable reports whether this agent writes an append-only session log, which is what makes a deterministic child-session id worth deriving.
func (*Agent) OpenPiTools ¶
func (a *Agent) OpenPiTools(ctx context.Context) (*PiToolHost, error)
OpenPiTools creates the run's effective tool registry without entering the Go model loop. The Pi host must close the worker before closing this host.
func (*Agent) Prompt ¶
Prompt runs a single interactive turn-loop from a user message and returns the result. task seeds skill selection and memory recall (defaults to the user input).
func (*Agent) PromptStream ¶
func (a *Agent) PromptStream(ctx context.Context, userInput string, sink StreamSink) (RunResult, error)
PromptStream runs the same turn-loop but streams the assistant's tokens and tool-call traces to sink as they are produced, for a live (SSE) viewer. The returned RunResult is identical to Prompt's — streaming is additive.
func (*Agent) RunNative ¶
RunNative connects composed plugins/tools to the native engine. The engine owns all turn/tool scheduling; AI owns retry and fallback. This entry point uses consumer-owned checkpoints. The server's durable session host additionally journals in-flight effects, leases and parked delegations.
func (*Agent) SessionID ¶
SessionID returns the durable log this agent writes to, or "" when the run is purely in-memory.
func (*Agent) Steer ¶
Steer queues a mid-run correction (pi's steer()): the loop drains it at the top of the next turn and threads it into the conversation before the model reasons. On a durable run the message is also appended to the session log as an EntryInbox side record the moment it is queued, so a crash between queue and drain cannot lose it — a resume re-reads the log and delivers it then. A nil error means queued; a non-nil error means the durable write failed and the message is in-memory only.
type AgentDefinition ¶
type AgentDefinition struct {
ScopeID string
Soul string // SOUL.md — who the agent is (always loaded)
Agents string // AGENTS.md — what it works on (always loaded)
Skills []Skill // headers selected by description match; body loads on demand
SkillLoader SkillLoader // optional deferred content loader for selected skills
}
AgentDefinition is the complete user-authored description of one agent: the stable identity (Soul), the changeable mission/context (Agents), and the on-demand skills. Tools, hooks, permission, and memory are bound at the Agent level by the consumer, not authored here.
func (AgentDefinition) IsZero ¶
func (d AgentDefinition) IsZero() bool
IsZero reports whether nothing was authored into this definition. The struct holds slices, so it is not comparable with ==; composition uses this to decide whether a definition seam is actually being claimed.
func (AgentDefinition) Validate ¶
func (d AgentDefinition) Validate() []Diagnostic
Validate returns diagnostics for an over-budget or malformed definition. It never fails the run; the caller surfaces warnings (e.g. in Settings) and proceeds with what loaded.
type AgentEndHook ¶
AgentEndHook observes a finished run (pi's agent_end), after sub-agent usage is folded in and any synthesized failure turn is appended — so res is exactly what the caller receives. Read-only, fires on every exit path including error and abort.
type AllowList ¶
type AllowList struct {
// contains filtered or unexported fields
}
AllowList is a simple Policy permitting an explicit set of tool names. It is the building block consumers compose from scope definitions.
func NewAllowList ¶
NewAllowList builds an AllowList from the given permitted tool names.
type ArgPreparer ¶
ArgPreparer is an optional Tool capability (pi's prepareArguments): it normalizes the raw JSON argument string before validation and execution — defaulting fields, coercing shapes the model commonly gets wrong. A tool that does not implement it runs with the model's arguments verbatim.
type BackgroundLauncher ¶
type BackgroundLauncher func(tool, label string, run func(context.Context) (string, error)) (json.RawMessage, error)
BackgroundLauncher is the run-owned scheduling capability. Its receipt is opaque to tools; the installed scheduler supplies status/wait/cancel tools. Keeping it here lets contribution plugins cooperate without importing peers.
type BatchDecision ¶
type BatchDecision struct {
// AdditionalContexts are appended after the batch's tool results and
// persisted, exactly like ToolResultDecision.AdditionalContexts.
AdditionalContexts []Message
}
BatchDecision is a BatchInterceptor's answer. The zero value adds nothing.
type BatchInterceptor ¶
type BatchInterceptor interface {
InterceptBatch(ctx context.Context, calls []ToolCall) BatchDecision
}
BatchInterceptor observes a whole batch of tool calls at once. Implement it when a decision depends on the SET of calls rather than any single one — detecting that the model repeated itself, or that a group of calls made no progress.
It runs after every call in the batch has been intercepted individually.
type BeforeAgentStartHook ¶
BeforeAgentStartHook shapes a run before its first provider request (pi's before_agent_start). Return the input with your edits: an empty System keeps the assembled prompt, and nil Messages keep the seeds, so a hook that only wants to append a reminder can return RunStart{Messages: append(...)} without restating the prompt. Runs before the seed messages are persisted to the durable log, so an injected message is part of the recorded conversation and survives resume.
type BeforeCompactHook ¶
type BeforeCompactHook func(ctx context.Context, req CompactRequest) CompactDecision
BeforeCompactHook is consulted immediately before the loop rewrites a transcript for pruning or summary compaction (pi's session_before_compact). The first hook that returns Skip, or a non-nil Messages, decides; later hooks are not consulted. Use it to pin content the default policy would drop, or to swap in a domain-specific reducer.
type BeforeToolCall ¶
BeforeToolCall fires after a tool's arguments are schema-validated and before execution. Returning a blocked Decision stops the call; the reason is fed back to the model. The permission gate is itself a BeforeToolCall hook; consumers may add more (PII redaction, cost ceilings, human-approval gates).
type BookkeepingTool ¶
type BookkeepingTool interface {
// Bookkeeping reports that calls to this tool are not task progress.
Bookkeeping() bool
}
BookkeepingTool is an optional Tool capability: a tool whose calls are administrative rather than progress toward the task.
A turn spent ONLY on bookkeeping calls is refunded against MaxTurns, so keeping a plan current — or any similar self-management — cannot starve the turn budget on a long task. The MaxToolCalls budget still backstops a runaway bookkeeping loop, so this cannot be used to buy unbounded turns.
The loop asks the TOOL rather than consulting a list of names, which is what lets a planning capability live in its own package: a bookkeeping tool from a plugin earns the refund without the loop knowing the tool exists.
type BranchOptions ¶
type BranchOptions struct {
// Summarize, when non-nil, distills the messages of the abandoned span (old
// leaf back to the common ancestor with the target, exclusive) into an
// EntryBranchSummary appended to the new branch, so context from the
// abandoned work survives the switch (pi's branch summarization on /tree).
// A summarizer error or empty summary degrades to a bare leaf move — a
// rewind never fails on a flaky summarizer. The returned Usage is the
// summarization call's own spend; Rewind stamps it onto the branch-summary
// entry so this real provider call is never invisible spend.
Summarize func(ctx context.Context, abandoned []Message) (string, Usage, error)
}
BranchOptions configure Rewind.
type BudgetPlugin ¶
type BudgetPlugin struct {
Gate func(ctx context.Context, u Usage) bool
Step func(ctx context.Context, turn int) error
}
BudgetPlugin installs the per-turn spend ceiling and step gate into a Registry.
func (BudgetPlugin) Register ¶
func (p BudgetPlugin) Register(r *Registry) error
Register claims the budget and step gates.
type CallRetrySafeTool ¶
CallRetrySafeTool refines RetrySafeTool per invocation: a tool whose retry-safety depends on the specific call's arguments (spawn_subagent is crash-safe only when it self-forks, not when it routes to a delegate agent) implements this and receives the original dangling call. When both interfaces are implemented, this one wins.
type CapabilitySupport ¶
type CapabilitySupport = protocol.CapabilitySupport
type ChatRequest ¶
type ChatRequest = protocol.ChatRequest
type ChatResponse ¶
type ChatResponse = protocol.ChatResponse
func AssistantText ¶
func AssistantText(content string) ChatResponse
AssistantText is a helper to script a plain text response.
func AssistantToolCall ¶
func AssistantToolCall(id, name, args string) ChatResponse
AssistantToolCall is a helper to script a tool-calling response.
type CheckpointState ¶
type CheckpointState struct {
Model string `json:"model,omitempty"`
ActiveTools []string `json:"active_tools,omitempty"`
DisabledTools []string `json:"disabled_tools,omitempty"`
Goal string `json:"goal,omitempty"`
// Completed records whether an EntryLeaf was already seen before this
// checkpoint — true only on a chained log whose earlier run finished. Without
// it a window would report a chained session as unfinished where the whole
// log reports it finished, and the two reads would disagree about whether
// there is an answer to reattach to.
Completed bool `json:"completed,omitempty"`
}
CheckpointState is the non-transcript half of a checkpoint: the run state a fold would have accumulated by the time the compaction completed. The loop stamps it from state it already holds rather than re-deriving it, so it is exact by construction. It is AUTHORITATIVE, not additive: the fold adopts it wholesale, because a suffix has no earlier entries to merge with. A writer that fills it partially does not degrade gracefully — it erases the fields it left out — which is why only the loop writes one.
type ChildQuestionError ¶
type ChildQuestionError struct {
SessionID string `json:"sessionId"`
QuestionID string `json:"questionId"`
Question json.RawMessage `json:"question"`
}
ChildQuestionError identifies a delegated run waiting for human input. It deliberately does not unwrap to ErrParked: the child's question is not the parent's tool arguments, and its answer belongs to the child's session. Native tool receipts retain this route even after the error is stringified.
func PiChildQuestionFromEntry ¶
func PiChildQuestionFromEntry(entry SessionEntry) (string, *ChildQuestionError, bool)
PiChildQuestionFromEntry returns the route only for a settled, governed question receipt. Its first ID belongs to the parent; the route's ID belongs to the child and must never be substituted into the parent's answer ledger.
func (*ChildQuestionError) Error ¶
func (e *ChildQuestionError) Error() string
type CompactDecision ¶
type CompactDecision struct {
// Skip abandons compaction for this turn. The transcript is left as-is, so a
// hook that skips forever will run the context past its budget; use it to
// defer (e.g. mid-tool-sequence), not to disable compaction.
Skip bool
// Messages, when non-nil, replaces the transcript with a consumer-supplied
// compaction and suppresses the built-in one. The loop persists the same
// compaction bracket either way, but no summarization call is billed.
Messages []Message
}
CompactDecision is a BeforeCompact hook's answer. The zero value means "no opinion" — the built-in summarize-and-elide compaction runs as usual.
type CompactRequest ¶
type CompactRequest struct {
Turn int
// Messages is the candidate history the active strategy would compact. For
// the default strategy it already contains deterministic pruning; Skip still
// leaves the original live transcript untouched.
Messages []Message
// Budget is the run's effective MaxContextTokens ceiling.
Budget int
// Settings are the effective compaction settings for this run (already
// clamped to the budget).
Settings CompactionSettings
}
CompactRequest describes an imminent context reduction. The default strategy may be at its half-budget deterministic-pruning threshold or at its full summarization threshold.
type CompactionPlugin ¶
type CompactionPlugin struct {
Settings *CompactionSettings
Provider LLMProvider
Model string
Strategy Compactor
}
CompactionPlugin installs context compaction settings and strategy into a Registry.
func CompactionUsing ¶
func CompactionUsing(c Compactor) CompactionPlugin
CompactionUsing swaps the compaction strategy, keeping the default retention policy.
func (CompactionPlugin) Register ¶
func (p CompactionPlugin) Register(r *Registry) error
Register claims the compaction seams.
type CompactionRequest ¶
type CompactionRequest struct {
// Messages is the live transcript, leading system prompt included.
Messages []Message
// Budget is the run's context ceiling in estimated tokens
// (Limits.MaxContextTokens), already defaulted.
Budget int
// Turn is the turn about to be taken, for correlating with the durable log.
Turn int
// Settings is the retention policy, already clamped against Budget.
Settings CompactionSettings
// Provider and Model are the tier the summarization call should use: the
// dedicated compaction rung when the composition pinned one, otherwise the
// rung the run has escalated to. A compactor that makes no model call
// ignores both.
Provider LLMProvider
Model string
}
CompactionRequest is everything a compactor needs to decide and act. It is a struct rather than a parameter list so a new input is an additive change that no existing implementation has to acknowledge.
type CompactionResult ¶
type CompactionResult struct {
// Messages is the replacement transcript.
Messages []Message
// Usage is what the compaction itself spent — real billable spend on a
// summarizing backend, zero on a deterministic one. The loop folds it into
// the run's accounting and stamps it on the durable completion entry, so
// the audit trail shows what staying inside the window cost.
Usage Usage
}
CompactionResult is what a compactor produced.
type CompactionSettings ¶
type CompactionSettings struct {
KeepRecentTokens int
MaxSummaryTokens int
// PruneProtectedTools exempts tool results from generic age-based pruning.
// Superseded identical results and stale file reads may still be pruned: a
// newer result already carries the truth. Nil selects the safe built-ins;
// an explicitly empty slice disables built-in protection. Add tools whose
// outputs are non-repeatable artifacts or durable instructions.
PruneProtectedTools []string
// PruneCacheWarmSuffixTokens protects deep results while prompt caching is
// active. A candidate is rewritten only when the estimated message suffix
// after it is no larger than this value. Zero selects the default; a negative
// value disables the guard.
PruneCacheWarmSuffixTokens int
// PruneMinimumSavingsTokens is the minimum aggregate saving required for
// generic age-only pruning. Superseded and stale results bypass this floor.
// <=0 selects the default; the effective value is capped to 10% of Budget.
PruneMinimumSavingsTokens int
}
CompactionSettings tunes how the loop compacts a long transcript. It sizes both halves of what a compaction leaves behind: KeepRecentTokens is the approximate token budget of recent messages kept verbatim, MaxSummaryTokens the ceiling on the checkpoint that stands for everything older. Both are clamped against the run's real budget by effectiveCompaction.
func DefaultCompactionSettings ¶
func DefaultCompactionSettings() CompactionSettings
DefaultCompactionSettings returns conservative defaults.
type Compactor ¶
type Compactor interface {
// Name identifies the compactor in diagnostics.
Name() string
// ShouldCompact reports whether the transcript has grown past the point
// where this strategy wants to act. The loop asks once per turn; a
// compactor that measures pressure differently (message count, a real
// tokenizer, a provider-reported context length) answers here.
ShouldCompact(messages []Message, budget int) bool
// Compact returns the shrunk transcript. It must preserve provider
// validity — every tool result stays adjacent to the assistant turn that
// called it — because the loop hands the result straight to the next
// request. Returning the input unchanged is a legal no-op.
//
// An error degrades to "no compaction this turn" rather than failing the
// run: a transcript that could not be shrunk is still a transcript the
// model can work with, while a killed run loses everything.
Compact(ctx context.Context, req CompactionRequest) (CompactionResult, error)
}
Compactor is transcript shrinking, as a seam.
The loop owns WHEN to consider compacting (once per turn, past the context budget) and owns the durable bracket around it. A Compactor owns WHAT compaction does: which span is old enough to lose, what replaces it, and what that costs. Those are strategy decisions with more than one right answer — summarize the older span with a model call, prune stale tool results with no call at all, keep a domain-specific span pinned — so they belong behind an interface rather than inside loop.go.
DefaultCompactor is the built-in provider, and a composition that wants different behaviour registers its own through Registry.SetCompactor without touching the package.
func DefaultCompactor ¶
func DefaultCompactor() Compactor
DefaultCompactor returns the built-in summarizing compactor. Wrap or replace it in a custom compaction plugin to change the strategy without touching the package.
type Config ¶
type Config struct {
// NativeProvider executes through engine with AI-owned fallback.
NativeProvider *ai.FallbackProvider
Provider LLMProvider
Model string
ModelCapabilities ModelCapabilities
// ContextWindow is the primary model's input window in tokens — the same
// fact ModelRung.ContextWindow carries for the escalation rungs. 0 means
// unknown.
ContextWindow int
Tools *ToolSet
Policy Policy
Hooks Hooks
Memory MemoryStore
Definition AgentDefinition
Limits *Limits
Env *Env
// Compaction overrides the default compaction settings (recent-token budget
// kept verbatim). nil uses DefaultCompactionSettings().
Compaction *CompactionSettings
// CompactionProvider + CompactionModel, when both set, pin the in-loop
// compaction summary call to a dedicated tier instead of borrowing the run's
// active escalation rung. Leaving either unset keeps today's behavior (the
// active rung summarizes). The consumer's tier system maps a "compaction"
// task kind onto these.
CompactionProvider LLMProvider
CompactionModel string
// Compactor replaces the transcript-shrinking strategy. nil keeps
// DefaultCompactor (summarize the older span with a model call).
Compactor Compactor
// RefreshKey is an optional per-turn API-key resolver. It is invoked before
// each turn with the provider name; a non-empty result is pushed into the
// provider via KeyUpdater. Use it for short-lived / rotating BYO credentials.
RefreshKey func(ctx context.Context, provider string) (string, error)
// Escalation is an optional ordered fallback ladder. When the primary
// Provider/Model errors on a turn, the loop retries that turn down the ladder
// before giving up, then sticks with the working rung for later turns.
Escalation []ModelRung
// GetSteeringMessages is an optional callback drained at the top of each turn;
// returned messages are injected before the model reasons (mid-run steering).
// The consumer owns the source (channel, DB, SSE input) — and its durability:
// messages it has not yet returned are lost on a crash. Agent.Steer is the
// durable alternative: it writes the queue into the session log itself.
GetSteeringMessages func(ctx context.Context) []Message
// GetFollowUpMessages is an optional callback drained when the agent would
// stop; returned messages restart the loop instead of ending the run.
// Agent.FollowUp is the durable equivalent.
GetFollowUpMessages func(ctx context.Context) []Message
// Goal, when non-empty, declares the condition under which this run may
// stop (Claude Code /goal analog; see session.go). The completion contract is
// appended to the system prompt, and a normal finish whose answer lacks a
// STATUS: DONE or STATUS: BLOCKED sentinel is re-opened with a keep-going
// nudge. Uncapped, but still bounded by MaxTurns / MaxToolCalls / the
// budget gate, and a repeated identical answer breaks the loop with
// StopReason "goal_stalled". The goal is recorded in the durable log
// (EntryGoal), so a resumed run stays gated even when the resuming caller
// cannot re-supply it. Empty — the default — disables the gate.
Goal string
// PrepareNextTurn is an optional save-point hook called after each turn; the
// returned TurnState (model / tools / system) drives the next turn. nil keeps
// the model, tools, and prompt fixed for the whole run. Model and tool-set
// changes are durable (EntryModelChange / EntryActiveToolsChange), so a
// crash-resumed run rebuilds them; a system change is not — the prompt is
// re-derived on every run.
PrepareNextTurn func(ctx context.Context, state TurnState) TurnState
// BudgetGate is an optional per-turn ceiling check (#4). Consulted with the
// run's accumulated usage at the top of each turn; returning true triggers a
// one-turn graceful stop that summarizes and halts. nil leaves the run
// uncapped (bounded only by MaxTurns / MaxToolCalls).
BudgetGate func(ctx context.Context, u Usage) bool
// Session + SessionID enable durable, resumable runs (P9): when both are set
// the loop appends typed entries to the append-only SessionStore. Leaving
// either unset keeps the run in-memory only.
Session SessionStore
SessionID string
// ProviderSession and ProviderSessionID are independent of durable run
// logging. They retain provider transport/compatibility state for a logical
// conversation even when each user turn gets a fresh Agent and run id.
ProviderSession *ProviderSession
ProviderSessionID string
// ResumeSession, when true (with Session + SessionID set), continues the
// existing durable log at SessionID instead of starting a fresh run: the
// loop rebuilds history from the log (the seed messages are used only if
// the log turns out empty), re-issues dangling retry-safe tool calls with
// their ORIGINAL call IDs — reproducing their idempotency keys and child
// session IDs, so a replayed spawn_subagent reattaches instead of
// re-running — and closes the remaining dangling calls with interrupted
// notes. A log that already reached its leaf returns its recorded final
// answer without any provider call.
ResumeSession bool
// StepGate is an optional pause-before-each-turn hook. When set, the loop calls
// it at the top of every turn and blocks until it returns; a non-nil error
// halts the run. The Lab's explain mode uses it to step a live run; leaving it
// nil keeps runs continuous (the production default).
StepGate func(ctx context.Context, turn int) error
// Retry overrides the same-model backoff policy applied before escalation. nil
// uses DefaultRetryPolicy(); a partial override fills its zero fields from it.
Retry *RetryPolicy
// PromptCacheKey, when set, opts every provider call into prompt caching under
// this key (typically the session id). PromptCacheRetention hints the window
// ("" | "short" | "long" | "24h"). Empty key leaves caching off — the default.
PromptCacheKey string
PromptCacheRetention string
// SeedDisabledTools pre-disables the named tools for this run's circuit
// breaker. A resume passes the tools that were disabled in the crashed run
// (recovered from its durable log) so a persistently broken tool is not
// retried from scratch after resume. Empty starts every tool enabled.
SeedDisabledTools []string
// MaxTokens caps the model's output tokens per turn. 0 lets the provider use
// its own default. Set this for agents that emit large artifacts so the
// gateway's default cap doesn't truncate output with stop_reason:"length".
MaxTokens int
// ReasoningEffort, when set ("low" | "medium" | "high"), asks reasoning
// models to spend that much thinking effort per turn (OpenAI-wire
// reasoning_effort). Providers without the knob ignore it; empty sends
// nothing.
ReasoningEffort string
// OutputSchema, when non-nil, constrains every text answer this agent
// produces to the given JSON Schema (grammar-constrained decoding at the
// provider: OpenAI response_format json_schema strict, Anthropic
// structured-outputs output_format). Meant for verdict-shaped agents —
// moderation / classification presets that must return machine-parseable
// verdict×confidence JSON — not general chat: any plain-text turn must fit
// the schema. Providers without the capability ignore it, so callers still
// validate the answer. nil — the default — leaves output free-form.
OutputSchema *OutputSchema
// ToolChoice optionally constrains model tool use on every ordinary turn.
// The zero value keeps provider defaults. Required/named choices are removed
// automatically from the loop's borrowed tool-free finalization turn.
ToolChoice ToolChoice
// ParallelToolCalls asks capable providers whether they may emit several
// tool calls in one assistant turn. nil omits the hint for compatibility.
// AgentCore still executes only tools that independently opt into ParallelTool.
ParallelToolCalls *bool
// Extensions are the run capabilities this agent is built with — spill,
// background jobs, session retrieval, delegation, the repeated-call
// reminder, verify-on-stop, log-invariant observation, and whatever a
// consumer writes next. Each is a value from a package under
// agentcore/plugins/, and the loop reaches them ONLY through the interfaces
// in extension.go: it never names one, so the set here is the complete
// answer to "what can this agent do beyond the core loop?".
//
// Empty — the default — is a working agent. It reasons, calls tools, obeys
// its policy, compacts, and logs; it just has no capability that a plugin
// would have added.
Extensions []ExtensionFactory
}
Config wires an Agent. Provider, Model, Tools, and Policy are required; the rest have safe defaults (DenyAll policy, no memory, DefaultLimits, DefaultEnv).
type ContentPart ¶
type ContentPart = protocol.ContentPart
type ContextContributor ¶
ContextContributor lets an extension attach a value to the RUN context, so a capability is reachable from inside a tool call without the tool importing the extension. Background jobs use it: a long-running tool finds its launcher on the context it was handed.
Contributions are folded in registration order before the first turn. Prefer a contributed Tool where one works — a context value is invisible in the composition, so it is the weaker form of dependency.
type ContextHook ¶
ContextHook transforms the message list immediately before a provider request (pi's `context` event). It sees a copy of the live history and returns the view the model should reason over — e.g. redaction, trimming, injecting a reminder. Returning nil keeps the input unchanged. It does not mutate the persisted run history; only the outgoing request is affected.
type ContextPruneRequest ¶
type ContextPruneRequest struct {
Messages []Message
Budget int
Settings CompactionSettings
PromptCacheActive bool
}
ContextPruneRequest carries the live pruning policy. PromptCacheActive is a run property rather than static compaction configuration: the same Agent can be composed with or without a provider cache key on laptops and servers.
type ContextPruner ¶
type ContextPruner interface {
// PruneContext returns a provider-valid replacement and whether it differs
// from messages. Implementations must not mutate messages. Returning the
// original slice with false is the no-op path.
PruneContext(req ContextPruneRequest) ([]Message, bool)
}
ContextPruner is an optional, deterministic first stage of a Compactor.
The loop asks for pruning before ShouldCompact. This lets a strategy remove reproducible bulk at a soft threshold and avoid a summarization call when that alone restores headroom. It is deliberately optional: a custom Compactor that does not implement ContextPruner retains its exact behavior. The loop, not the pruner, owns hooks, durable checkpoints, and observer rebases around any returned rewrite.
type CredentialResolver ¶
CredentialResolver substitutes opaque {{cred:NAME}} placeholders in a tool's argument JSON with real secret values. It runs at the trust boundary inside the tool loop: the call is traced and permission-gated in placeholder form first, so neither the model nor the persisted trace ever observes the literal secret — it exists only in the string handed to the executing tool.
agentcore defines the contract only; the concrete vault (which holds the secrets and decides which the agent may resolve) is injected by the host via Env.Credentials, keeping this package a leaf with no infrastructure imports. Resolve MUST fail closed: an unknown or disallowed placeholder returns an error, which blocks the call and feeds the reason back to the model rather than silently leaving the placeholder in place.
type Decision ¶
Decision is the outcome of a permission check. A blocked decision carries a human-readable reason that is returned to the model (not a silent failure) so it can adapt.
type DefinitionPlugin ¶
type DefinitionPlugin struct {
Definition AgentDefinition
Limits *Limits
Env *Env
}
DefinitionPlugin installs identity, limits, and host environment into a Registry.
func (DefinitionPlugin) Register ¶
func (p DefinitionPlugin) Register(r *Registry) error
Register claims the definition, limits, and env seams.
type DenyAll ¶
type DenyAll struct{}
DenyAll is the safe default Policy: it permits nothing. Useful as a base and in tests.
type Diagnostic ¶
type Diagnostic struct {
Severity Severity `json:"severity"`
Part string `json:"part"`
Message string `json:"message"`
}
Diagnostic is a non-fatal problem found while loading a definition. A bad SKILL.md yields a warning, not a crashed run (pi load-diagnostics pattern).
type Embedder ¶
Embedder turns text into dense vectors for semantic memory recall (§14.7). It is the embedding analogue of LLMProvider: a narrow, product-agnostic seam the consumer backs with a real vendor (OpenAIEmbedder) or a test fake. A nil Embedder means the consumer falls back to keyword recall — embeddings are an additive relevance upgrade, never a hard dependency.
type Env ¶
type Env struct {
// Sandbox is the optional isolation substrate for tools that execute
// untrusted code (shell, file, browser). nil when no such tools are wired —
// the analytics tools never touch it. The concrete backend lives outside
// this leaf package and is injected by the host. This field records the
// backend for diagnostics; the host must also bind it to each tool that uses
// it. Setting this field alone does not sandbox a tool.
Sandbox Sandbox
// Credentials is the optional secret resolver. When set, tool arguments are
// passed through it at the trust boundary — after the call has been traced
// and gated, immediately before the tool executes — so opaque {{cred:NAME}}
// placeholders the model emits become real secret values the tool can use
// (an API key, a scoped DB DSN) without the model, the persisted trace, or
// the before-hooks ever seeing the literal. nil = no resolution; arguments
// reach the tool unchanged.
Credentials CredentialResolver
}
Env describes host-owned tool execution infrastructure. It is executor configuration, not an agent capability installed through plugins/extensions.
func DefaultEnv ¶
func DefaultEnv() Env
DefaultEnv returns an Env backed by real implementations. Both capabilities are host-injected and optional, so the default env is the empty one: a run with no sandbox and no vault is a complete, working agent that simply cannot execute untrusted code or resolve a secret.
type Extension ¶
type Extension interface {
// Name identifies this extension instance.
Name() string
}
Extension is one plugin's per-run instance.
The interface is deliberately near-empty: capabilities are OPTIONAL interfaces discovered by type assertion, the same way net/http treats Flusher or Hijacker. An extension implements only the points it cares about, and adding a new point never breaks an existing plugin.
type ExtensionFactory ¶
type ExtensionFactory interface {
// Name identifies the extension in diagnostics and in Agent.Describe.
Name() string
// BeginRun builds the per-run instance. An error aborts the run before any
// provider call, so a misconfigured extension fails loudly rather than
// silently doing nothing.
BeginRun(ctx context.Context, info RunInfo) (Extension, error)
}
ExtensionFactory is what a plugin registers. The loop calls BeginRun exactly once per run, so a fresh run gets a fresh instance without resetting shared state. State accessed by parallel tools still needs synchronization.
Returning a nil Extension is how a plugin declines to participate in a particular run (delegation already at max depth, no session to query, a disabled feature). It is not an error.
type FauxProvider ¶
type FauxProvider struct {
Responses []ChatResponse
Recorded []ChatRequest
// contains filtered or unexported fields
}
FauxProvider is a scripted LLM seam for tests: it replays a fixed list of responses in order, with no network, no keys, and no tokens (pi faux-provider pattern). It lets the loop, hooks, and permission gate be tested deterministically.
func NewFauxProvider ¶
func NewFauxProvider(responses ...ChatResponse) *FauxProvider
NewFauxProvider builds a provider that returns the given responses in order.
func (*FauxProvider) Chat ¶
func (f *FauxProvider) Chat(_ context.Context, req ChatRequest) (ChatResponse, error)
Chat returns the next scripted response. After the script is exhausted it returns a plain assistant message with stop reason "stop" so loops terminate.
func (*FauxProvider) Name ¶
func (f *FauxProvider) Name() string
func (*FauxProvider) Stream ¶
func (f *FauxProvider) Stream(ctx context.Context, req ChatRequest) (<-chan ChatDelta, error)
Stream adapts Chat into a delta channel: it emits the content word-by-word (so tests exercise token concatenation), then one delta per tool call, then a terminal Done — mirroring how the real provider streams a turn.
func (*FauxProvider) SupportsTools ¶
func (f *FauxProvider) SupportsTools() bool
type GoalReviser ¶
type GoalReviser interface {
// Native Pi integrations must call RecordGoalRevision in a governed tool
// context before publishing the condition. The drain then refreshes the
// prompt; durability already belongs to that tool's physical effect.
// ReviseGoal returns the run's new completion condition and true when one
// has been set since the last call. It is drained, not polled: returning
// (x, true) twice for the same revision would write the same EntryGoal every
// turn for the rest of the run.
ReviseGoal() (goal string, ok bool)
}
GoalReviser is an extension that can change what the run is trying to achieve, partway through achieving it.
A long run discovers things. The requirement it was given can turn out to be impossible, to have been based on a wrong assumption, or to be the wrong shape for what the work actually found — and a run that can only ever pursue the condition it started with either grinds against that discovery until MaxTurns or lies about being done. So an extension may revise the condition, and the loop drains the revision once per turn.
The loop owns what follows, because both consequences are the loop's to guarantee. It records the new condition as an EntryGoal, so a crashed run resumes gated on the CURRENT objective rather than the original one; and it rebuilds the system prompt, so the contract the model reads is the contract being enforced. An extension that changed the condition privately would leave the model working against a stale prompt and recovery re-arming a stale gate.
This is a real transfer of authority: an agent that can redefine success can declare success. The gate stops being a contract the model cannot relax and becomes one it can renegotiate — which is the point, and the cost. What keeps it accountable is that every revision is durable and attributable: the log holds each condition the run has held, in order, so a narrowing is visible afterwards rather than silent.
type GoalRevision ¶
type GoalRevision struct {
Previous string `json:"previous"`
Goal string `json:"goal"`
Reason string `json:"reason"`
}
GoalRevision is a consumer-authored change to a run's completion contract. The host persists it separately from provider messages.
type HookErrorPolicy ¶
type HookErrorPolicy string
HookErrorPolicy governs what happens when a hook handler panics or returns an error. The default (continue) keeps a single bad hook from taking down a run.
const ( // HookContinue logs/attributes the failure and proceeds (default). A // panicking observer never aborts the run. HookContinue HookErrorPolicy = "continue" // HookThrow aborts the run, surfacing the attributed hook error from the // loop. Opt-in, for consumers that treat a hook failure as fatal. HookThrow HookErrorPolicy = "throw" )
type Hooks ¶
type Hooks struct {
Before []BeforeToolCall
After []AfterToolCall
// BeforeAgentStart runs in order once per run, on the assembled system
// prompt + seed messages, each seeing the previous one's output.
BeforeAgentStart []BeforeAgentStartHook
// TurnStart observers run in order at the top of every turn, before any of
// the turn's work (compaction, steering, the provider call).
TurnStart []TurnHook
// TurnEnd observers run in order once a turn is complete, on every path out
// of that turn (final answer, tool round, guard stop, abort).
TurnEnd []TurnHook
// BeforeCompact runs in order when the active strategy proposes pruning or
// compaction; the first decisive answer (Skip, or replacement Messages) wins.
BeforeCompact []BeforeCompactHook
// AgentEnd observers run in order as the run returns, on every exit path.
AgentEnd []AgentEndHook
// Context runs in order before every provider request, each transforming the
// message view the next one sees (a reducer over the message list).
Context []ContextHook
// PiContext is the native counterpart of Context, used by the Pi host.
PiContext []PiContextHook
// BeforeProviderRequest runs in order on the assembled request, each seeing
// the previous one's output.
BeforeProviderRequest []ProviderRequestHook
// MessageEnd observers run in order after each assistant message completes.
MessageEnd []MessageEndHook
// ErrorPolicy governs handler panics/errors. Zero value == HookContinue.
ErrorPolicy HookErrorPolicy
// OnError, when set, receives every attributed hook failure (source + error)
// regardless of policy, so a consumer can log/meter it. Source is a stable
// identifier like "context[1]" or "before_tool_call[0]".
OnError func(source string, err error)
}
Hooks is the ordered set of lifecycle interceptors for a run. The tool hooks (Before/After) gate and rewrite tool execution; the typed event hooks (Context/BeforeProviderRequest/MessageEnd) participate in or observe the reasoning turn. All handlers are panic-hardened per ErrorPolicy.
type HooksPlugin ¶
HooksPlugin contributes consumer lifecycle hooks to a Registry.
func (HooksPlugin) Register ¶
func (p HooksPlugin) Register(r *Registry) error
Register adds the hooks.
type InboxItem ¶
InboxItem is one queued-but-undelivered steering or follow-up message: the EntryInbox intent (ID) plus the message it carries.
type KeyUpdater ¶
type KeyUpdater = protocol.KeyUpdater
type LLMProvider ¶
type LLMProvider = protocol.LLMProvider
type LabSkillRef ¶
type LabSkillRef struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
}
LabSkillRef is one skill advertised to the model in the system prompt (header only — id + name + description). Whether its body was actually loaded is tracked separately in LabStep.SkillsLoaded (the read_skill calls observed).
type LabStep ¶
type LabStep struct {
NativeTrace json.RawMessage `json:"native_trace,omitempty"`
SessionKey string `json:"session_key,omitempty"`
Depth int `json:"depth,omitempty"`
Index int `json:"index"` // 0-based position in the step list
Turn int `json:"turn"` // 1-based agent turn this step belongs to
Kind LabStepKind `json:"kind"`
// Assembled context + prompt as it entered this step.
System string `json:"system"` // the full assembled system prompt
Persona string `json:"persona"` // the Identity (SOUL) section of the prompt
Memory []string `json:"memory"` // recalled-memory bullet lines
Context []Message `json:"context"` // the messages sent to the model this turn
// Skills: advertised (headers in the prompt) vs loaded (read_skill calls seen
// up to and including this step).
SkillsAdvertised []LabSkillRef `json:"skills_advertised"`
SkillsLoaded []string `json:"skills_loaded"`
// Tools available this turn (advertised schemas, by name) and the tool calls
// the model made this turn paired with their results.
Tools []string `json:"tools"`
ToolCalls []LabToolCall `json:"tool_calls"`
Response string `json:"response"`
StopReason string `json:"stop_reason,omitempty"`
Error string `json:"error,omitempty"`
// Compaction (Kind == LabStepCompaction): the summary kept in place of the
// dropped span. Context holds the retained tail.
Summary string `json:"summary,omitempty"`
// Per-step and cumulative accounting (real provider numbers).
TokensIn int `json:"tokens_in"`
TokensOut int `json:"tokens_out"`
CostUSD float64 `json:"cost_usd"`
CumTokensIn int `json:"cum_tokens_in"`
CumTokensOut int `json:"cum_tokens_out"`
CumCostUSD float64 `json:"cum_cost_usd"`
}
LabStep is the full per-step inspector state the Lab renders: the context that entered the step, the assembled system prompt broken into persona / memory / advertised-skills, the tools available, the tool calls and their results, the assistant response, and both per-step and cumulative token/cost accounting (the real numbers from the run, never a re-estimate).
func FoldSteps ¶
func FoldSteps(records []TurnRecord) []LabStep
FoldSteps turns a run's ordered turn records into the Lab's step list. It detects a compaction by the summary message the loop folds into the history (a system message prefixed with summaryMarker that first appears on a given turn) and emits it as its own step before that turn. Everything else is one turn = one step. Pure: same input -> same steps, for live and replay alike.
type LabStepKind ¶
type LabStepKind string
LabStepKind classifies one step of a run as the Lab presents it.
const ( // LabStepTurn is one agent turn: reason -> tool calls + results (the default // "one step" per the requirement's open question). LabStepTurn LabStepKind = "turn" // LabStepCompaction is a context-compaction step: the older span was summarized // and the recent tail kept. It is shown as its own step so a builder sees what // the summary kept and what was dropped. LabStepCompaction LabStepKind = "compaction" )
type LabToolCall ¶
type LabToolCall struct {
ID string `json:"id"`
Name string `json:"name"`
Args string `json:"args"` // {{cred:NAME}} placeholder form — never a literal
Result string `json:"result"` // the tool-result message content, if recorded
Allowed bool `json:"allowed"`
Error string `json:"error,omitempty"`
}
LabToolCall is one tool invocation within a step: the model's request (name + placeholder-form args) paired with the result it received and the gate outcome.
type Limits ¶
type Limits struct {
MaxTurns int // hard cap on LLM calls
MaxToolCalls int // hard cap on tool executions across the run
MaxToolResultLen int // byte cap per tool result before it reaches the LLM
MaxContextTokens int // soft budget; old turns are compacted above it (§5.2)
}
Limits bound a run so long autonomous loops stay safe and cheap (§7).
func DefaultLimits ¶
func DefaultLimits() Limits
DefaultLimits are the caps a run gets when nobody says otherwise.
The turn/tool numbers are measured, not guessed: at MaxTurns 12 an analytics agent answering a real question ("which feature drives retention?") ran out of budget on 2 of 3 first-run attempts — schema discovery, a couple of exploratory queries and one correction already spend a dozen turns before the answer is written. 24 turns / 40 tool calls clears that with room for a wrong turn.
The graceful wrap-up turn a ceiling now triggers is a floor, not a substitute for enough budget: it buys an honest partial answer, never the answer. Size the budget so the wrap-up stays the exception.
type MemoryCurator ¶
type MemoryCurator interface {
// Supersede retracts the entry id within scopeID, optionally naming the
// entry that replaces it ("" retracts without a successor). It must
// report not-found when no live entry with that id exists in the scope —
// a silent no-op would tell the model a memory is gone when it is not.
Supersede(ctx context.Context, scopeID, id, replacementID string) error
// Update replaces the content of the entry id within scopeID: the new
// entry is written and the old one retracted to it, atomically, so a
// failure never leaves a retracted memory with no successor.
Update(ctx context.Context, scopeID, id string, entry MemoryEntry) error
}
MemoryCurator is the OPTIONAL write-back half of the memory seam: a store that implements it lets the model revise what it previously remembered — update a stale fact, or retract one that turned out wrong. It is separate from MemoryStore (rather than new methods on it) so a consumer's existing store keeps satisfying the seam and simply does not offer the curation tool; the memory plugin discovers the capability by type assertion.
Both methods are soft: a retracted row stays in the store and is filtered out of recall, so the history of having held the belief survives the retraction. There is no hard delete on this seam.
type MemoryEntry ¶
type MemoryEntry struct {
ID string `json:"id"`
ScopeID string `json:"scope_id"`
Kind MemoryKind `json:"kind"`
Content string `json:"content"`
Tags []string `json:"tags"`
Confidence float64 `json:"confidence"`
SourceRun string `json:"source_run_id"`
CreatedAt time.Time `json:"created_at"`
}
MemoryEntry is one durable, distilled fact the agent carries across runs. Recalled into the Perceive step by tag/keyword match (v1) and injected after AGENTS.md. PII is redacted before persistence (§7).
type MemoryKind ¶
type MemoryKind string
MemoryKind classifies a long-term memory entry.
const ( MemoryFact MemoryKind = "fact" MemoryLearning MemoryKind = "learning" MemoryOutcome MemoryKind = "outcome" )
type MemorySessionStore ¶
type MemorySessionStore struct {
// contains filtered or unexported fields
}
MemorySessionStore is an in-process, append-only SessionStore.
It makes a run resumable within one process — a compacted transcript can still be reduced back, and the log invariant has something to check against — but it does NOT survive a restart. Use it for tests, for local development, and for single-process runs where a crash means the work is gone anyway. Anything that must outlive the process needs a durable store.
Safe for concurrent use: a run appends from the loop while a job or a session_query reads.
func NewMemorySessionStore ¶
func NewMemorySessionStore() *MemorySessionStore
NewMemorySessionStore returns an empty in-process session store.
func (*MemorySessionStore) AcquireSessionLease ¶
func (m *MemorySessionStore) AcquireSessionLease(ctx context.Context, id string) (context.Context, func() error, error)
AcquireSessionLease serializes resumes of one session inside this process. It intentionally has no cross-process claim; server deployments use the PostgreSQL implementation, while laptops keep a dependency-free fast path.
func (*MemorySessionStore) Append ¶
func (m *MemorySessionStore) Append(ctx context.Context, id string, e SessionEntry) error
Append records one entry, assigning its sequence number.
func (*MemorySessionStore) AppendBatch ¶
func (m *MemorySessionStore) AppendBatch(_ context.Context, id string, entries []SessionEntry) error
AppendBatch records one save point atomically. Holding the store lock across the whole slice guarantees that a concurrent side record cannot split the batch and that readers observe either the state before it or the complete state after it.
func (*MemorySessionStore) CheckpointSeq ¶
CheckpointSeq reports the newest self-contained checkpoint and whether the log has ever branched. Both are scans here; a real store answers them with an index.
func (*MemorySessionStore) Log ¶
func (m *MemorySessionStore) Log(_ context.Context, id string) ([]SessionEntry, error)
Log returns a copy of the ordered entry log for a session, so a caller iterating it cannot be raced by a concurrent append.
func (*MemorySessionStore) LogFrom ¶
func (m *MemorySessionStore) LogFrom(_ context.Context, id string, sinceSeq int) ([]SessionEntry, error)
LogFrom returns the session's entries with Seq >= sinceSeq, in order. The in-memory store gains nothing from a windowed read (the slice is already in hand), but implementing the capability is what lets the resume path be exercised end to end without a database — the alternative is a windowing rule that only ever runs in production.
func (*MemorySessionStore) Sessions ¶
func (m *MemorySessionStore) Sessions() []string
Sessions returns the ids that have at least one entry, in no particular order. Useful for a local resume picker.
type MemoryStore ¶
type MemoryStore interface {
// Recall returns long-term entries relevant to the query for a scope.
Recall(ctx context.Context, scopeID, query string, limit int) ([]MemoryEntry, error)
// Remember persists a long-term entry (PII already redacted by the caller).
Remember(ctx context.Context, entry MemoryEntry) error
}
MemoryStore is the long-term recall seam backed by the consumer. Native checkpoints and SessionStore own working history. A nil MemoryStore disables recall and learning.
type Message ¶
func CloseDanglingCalls ¶
CloseDanglingCalls returns a transcript in which every assistant tool call that never received a result is satisfied by a synthesized interrupted-note tool message. Providers reject a history with an unanswered tool call, so this is what makes a recovered transcript replayable. Already-satisfied calls and non-assistant messages pass through untouched, in order.
type MessageEndHook ¶
MessageEndHook observes a completed assistant message (pi's `message_end`). It is read-only: a return value is not threaded back. Use it for metrics, audit, or surfacing the turn to an external sink.
type ModelCapabilities ¶
type ModelCapabilities = protocol.ModelCapabilities
func CapabilitiesOf ¶
func CapabilitiesOf(provider LLMProvider, model string) ModelCapabilities
type ModelCapabilityError ¶
ModelCapabilityError reports a request that a provider/model pair is known not to accept. It is intentionally non-retryable: repeating the same rung cannot add a missing capability, while reason may still advance to a capable fallback rung.
func (*ModelCapabilityError) Error ¶
func (e *ModelCapabilityError) Error() string
type ModelCapabilityProvider ¶
type ModelCapabilityProvider = protocol.ModelCapabilityProvider
type ModelPlugin ¶
type ModelPlugin struct {
NativeProvider *ai.FallbackProvider
Provider LLMProvider
Model string
Capabilities ModelCapabilities
ContextWindow int
Escalation []ModelRung
Retry *RetryPolicy
RefreshKey func(ctx context.Context, provider string) (string, error)
MaxTokens int
ReasoningEffort string
OutputSchema *OutputSchema
ToolChoice ToolChoice
ParallelToolCalls *bool
PromptCacheKey string
CacheRetention string
}
ModelPlugin installs the model spine into a Registry.
func (ModelPlugin) Register ¶
func (p ModelPlugin) Register(r *Registry) error
Register claims the model seam and decoding knobs.
type ModelRung ¶
type ModelRung struct {
Provider LLMProvider
Model string
// Capabilities is an optional discovery/config snapshot for this exact
// provider/model rung. It overlays the provider adapter's defaults.
Capabilities ModelCapabilities
// ContextWindow is this model's input window in tokens, which caps the
// compaction budget while this rung is answering. 0 means unknown and the
// configured MaxContextTokens stands alone.
//
// It lives on the rung rather than in Limits because a ladder is routinely
// built from models with different windows, and the loop switches between
// them mid-run. A single run-wide number is therefore wrong for every rung
// but one — too high and the loop never compacts before the provider
// rejects the request, which no retry or escalation can rescue.
//
// agentcore does not know any model's window and must not learn: the value
// is supplied by whoever built the rung.
ContextWindow int
}
ModelRung is one provider+model the loop may fall back to when the rung above it errors. Consumers build the ladder (e.g. lite→flash→pro); agentcore just walks it.
type NativeContextContributor ¶
type NativeContextContributor interface {
Extension
TransformNativeContext(context.Context, []json.RawMessage) ([]json.RawMessage, error)
}
NativeContextContributor transforms the outgoing native request view using this run's extension state. It must not reconstruct provider messages from display projections. Like Pi's transformContext, failures retain the prior valid view; returned additions are not written into authoritative history.
type NativeRun ¶
type NativeRun struct {
// Commands are explicit host controls, keyed by installed extension name.
Commands map[string]json.RawMessage
// ControlOnly applies commands and checkpoints without making a model request.
ControlOnly bool
State json.RawMessage
Input []Message
Task string
Sink StreamSink
// Lifecycle receives bounded setup steps that happen before the provider
// loop starts, such as recalling relevant memory.
Lifecycle func(string)
Compaction *nativehost.CompactionPolicy
Telemetry telemetry.Context
}
NativeRun supplies an opaque checkpoint and newly authored host input. State must come from RunResult.NativeState, never the display Messages projection. Consumers persist the checkpoint with their own successful-turn transaction.
type NativeStateContributor ¶
type NativeStateContributor interface {
NativeState() (json.RawMessage, error)
RestoreNativeState(json.RawMessage) error
}
NativeStateContributor persists a plugin's bounded state in the opaque native checkpoint. Restore runs before lifecycle hooks; the consumer commits the resulting checkpoint together with its successful run transaction.
type ObservePhase ¶
type ObservePhase string
ObservePhase distinguishes why an observer is being called.
const ( // PhaseRestore supplies the original checkpoint history before new input. PhaseRestore ObservePhase = "restore" // PhaseAppend reports messages newly added to the history. PhaseAppend ObservePhase = "append" // PhaseRebase reports that the history was deliberately REPLACED // (compaction, a context edit), so any prior baseline is void. PhaseRebase ObservePhase = "rebase" // PhaseRequest reports the history about to be sent to the provider. PhaseRequest ObservePhase = "request" // PhaseExternalInput reports that new input arrived from OUTSIDE the model // (a steer, a follow-up). An extension tracking the model's own behavior // should treat this as a break: the model has been given information it did // not have, so what it does next is a fresh decision rather than a // continuation of what it was doing. PhaseExternalInput ObservePhase = "external_input" )
type OutputSchema ¶
type OutputSchema = protocol.OutputSchema
type ParallelTool ¶
type ParallelTool interface {
Parallel() bool
}
ParallelTool is an optional Tool capability: a tool whose Parallel() returns true may be executed concurrently with the other parallel-eligible tool calls in the same assistant turn (pi's executionMode). The safe default is sequential — tools that mutate state or depend on call ordering must NOT implement this, so concurrency is opt-in per read-only tool.
type PiArgumentPreparer ¶
type PiArgumentPreparer interface {
PiArgumentPreparation() string
}
PiArgumentPreparer identifies a bundled native implementation of a tool's synchronous preparation hook. The worker rejects unknown identifiers; Go functions cannot be transported as synchronous JavaScript callbacks.
type PiCompletedTurn ¶
type PiCompletedTurn struct {
Info StopInfo
Usage Usage
Model, StopReason string
Calls []ToolCall
Outcomes []PiToolOutcome
}
PiCompletedTurn is a display/control projection of one completed native turn. Outcomes must be in assistant source order, not parallel completion order.
type PiContextHook ¶
type PiContextHook func(context.Context, []json.RawMessage) ([]json.RawMessage, error)
PiContextHook transforms only the outgoing native message view. Provider messages retain their original JSON, including signed and custom content. Returning nil keeps the input. Failures are reported and retain the last valid view, as Pi requires transformContext to resolve without throwing.
type PiToolHost ¶
type PiToolHost struct {
// contains filtered or unexported fields
}
PiToolHost exposes a composed Agent's governed tools to the original Pi loop. It owns the Agent's busy slot and extension resources until Close. The native run adapter connects its turn policies through the methods in pi_lifecycle.go.
func (*PiToolHost) Close ¶
func (h *PiToolHost) Close() error
Close cancels host work and waits for admitted tool callbacks before releasing extension resources and the Agent's busy slot. It is safe to call repeatedly.
func (*PiToolHost) CompletePiRun ¶
func (h *PiToolHost) CompletePiRun(ctx context.Context, result RunResult) RunResult
CompletePiRun closes run resources, folds child spend once, and emits terminal observers. Provider history stays native; result.Messages is a read-only display projection for observers, never input to another provider request.
func (*PiToolHost) ControlPiRun ¶
ControlPiRun checks the composed controllers without executing a provider.
func (*PiToolHost) Definitions ¶
func (h *PiToolHost) Definitions(ctx context.Context) (json.RawMessage, error)
Definitions returns policy-filtered native AgentTool definitions. Native Pi applies its own batch scheduling; unmarked Go tools request sequential mode.
func (*PiToolHost) Execute ¶
func (h *PiToolHost) Execute(ctx context.Context, params json.RawMessage, emit func(json.RawMessage) error) (json.RawMessage, PiToolOutcome, error)
Execute is the tool branch of a PiCallback. The second result carries host workflow data that the enclosing server must handle at its event boundary. No provider message is decoded or reconstructed here.
func (*PiToolHost) FinishPiDelegationBatch ¶
func (h *PiToolHost) FinishPiDelegationBatch(ctx context.Context, calls []ToolCall, outcomes []PiToolOutcome) (PiTurnDecision, error)
FinishPiDelegationBatch applies batch policy after every parked call in the original assistant batch has settled. Calls and outcomes must retain source order and include nondelegated siblings. This is not another model turn: it does not rerun TurnEnd, output validation, steering, or stop guards. The durable host must record the decision before delivering its contexts.
func (*PiToolHost) FinishPiTurn ¶
func (h *PiToolHost) FinishPiTurn(ctx context.Context, turn PiCompletedTurn) (PiTurnDecision, error)
FinishPiTurn runs batch and stop policies over a completed native turn. Tool effects and native transcript placement have already settled at this point.
func (*PiToolHost) ObservePiMessages ¶
func (h *PiToolHost) ObservePiMessages(ctx context.Context, phase ObservePhase, turn int, raw json.RawMessage) error
ObservePiMessages delivers a detached display projection to existing plugin observers. Native request/history JSON remains authoritative and cannot be rewritten by a legacy observer.
func (*PiToolHost) PiCompactionPolicy ¶
func (h *PiToolHost) PiCompactionPolicy() (budget, keepRecent int)
PiCompactionPolicy exposes the composed host's existing context limits to native consumer transforms, including limits inherited by a fork.
func (*PiToolHost) PiGoal ¶
func (h *PiToolHost) PiGoal() string
PiGoal is the composed host's completion contract. Native persistence keeps it separately from provider messages so a resumed host can re-arm the gate.
func (*PiToolHost) PreparePiTurn ¶
func (h *PiToolHost) PreparePiTurn(ctx context.Context, info StepInfo) (PiTurnPreparation, error)
PreparePiTurn applies the composed step gate and step extensions. A ceiling allows one tool-free wrap-up; FinishPiTurn ends it regardless of stop guards. The native host calls this serially, once before each provider turn.
func (*PiToolHost) RefreshPiGoal ¶
func (h *PiToolHost) RefreshPiGoal() (system string, changed bool, err error)
RefreshPiGoal drains committed extension updates between native turns. The returned prompt is a new named system section, never a history replacement.
func (*PiToolHost) ResumeDelegation ¶
func (h *PiToolHost) ResumeDelegation(ctx context.Context, effectID string, original PiToolOutcome, emit func(json.RawMessage) error) (json.RawMessage, PiToolOutcome, error)
ResumeDelegation re-enters a retry-safe governed call with its original physical identity. Self-delegation then reattaches the original child, including any output-schema retry, instead of creating a new session.
func (*PiToolHost) StartPiRun ¶
StartPiRun claims the host for one native run and assembles the composed definition, recalled memory, skills, and extension instructions. It does not execute legacy transcript-editing hooks; callers integrating those hooks must preserve the native transcript.
func (*PiToolHost) StartPiRunWithLifecycle ¶
func (h *PiToolHost) StartPiRunWithLifecycle(ctx context.Context, task string, lifecycle func(string)) (string, error)
StartPiRunWithLifecycle is StartPiRun with an optional observer for bounded setup stages. It exposes the stage, never recalled memory content.
func (*PiToolHost) TransformPiContext ¶
func (h *PiToolHost) TransformPiContext(ctx context.Context, raw json.RawMessage) json.RawMessage
TransformPiContext applies native request-view hooks without rewriting the durable transcript. A failing or mutating hook cannot damage the last valid view: each receives an isolated copy, and invalid output is discarded.
func (*PiToolHost) ValidatePiSession ¶
func (h *PiToolHost) ValidatePiSession(id string, durable bool) error
ValidatePiSession checks that native persistence and the governed tool host agree on the session identity used for tool idempotency and extension scope.
type PiToolOutcome ¶
type PiToolOutcome struct {
Trace ToolTrace `json:"trace"`
Invocations []ToolInvocation `json:"invocations,omitempty"`
AdditionalContexts []Message `json:"additionalContexts,omitempty"`
Parked bool `json:"parked,omitempty"`
QuestionID string `json:"questionId,omitempty"`
ChildQuestion *ChildQuestionError `json:"childQuestion,omitempty"`
Executed bool `json:"executed"`
Terminate bool `json:"terminate,omitempty"`
}
PiToolOutcome is host audit/control data from one governed tool invocation. Pi owns the native transcript; these projections are for the server's traces, auxiliary context, and human-input workflow, not provider message replay.
type PiTurnDecision ¶
type PiTurnDecision struct {
StopDecision
End bool
Parked bool
}
PiTurnDecision tells the native host whether to end or schedule another turn.
type PiTurnPreparation ¶
type PiTurnPreparation struct {
Messages []Message
DisableTools bool
StopReason string
Note string
}
PiTurnPreparation contains host-authored additions for the next native turn. Append Messages through Pi's prompt/prepareNextTurnWithContext lifecycle; never rebuild provider history from these legacy host message values.
type Plugin ¶
type Plugin interface {
// Name identifies the plugin. Two plugins with the same name in one
// composition is a configuration error, not a silent overwrite.
Name() string
// Register contributes services and extension points. Returning an error
// aborts the whole composition — a plugin that cannot install its capability
// must not leave a half-built agent behind.
Register(r *Registry) error
}
Plugin contributes capabilities to a Registry.
A plugin must be idempotent with respect to its own configuration and must not depend on registration ORDER for correctness: services are keyed, and extension points are ordered by explicit Priority, not by when a plugin happened to run. Order-dependence is what makes plugin systems fragile, so the Registry refuses to provide it.
func ConfigPlugin ¶
ConfigPlugin adapts a flat Config into a Plugin that writes every configured seam to a Registry through ApplyConfig.
type Policy ¶
type Policy interface {
// Allow is consulted in the beforeToolCall preflight, after arguments are
// schema-validated and before execution.
Allow(ctx context.Context, call ToolCall) Decision
// PermittedTools filters the advertised tool names down to those the policy
// currently allows, so the model never sees a tool it cannot use.
PermittedTools(ctx context.Context, all []string) []string
}
Policy decides whether a tool call may execute. It is default-deny: a project starts with no tools permitted until the user opts in. The consumer supplies the implementation (e.g. scope -> tool mapping for the Growth Analyst).
type PolicyPlugin ¶
type PolicyPlugin struct {
Policy Policy
}
PolicyPlugin installs the permission gate into a Registry.
func PolicyAllowList ¶
func PolicyAllowList(names ...string) PolicyPlugin
PolicyAllowList creates a PolicyPlugin permitting exactly the named tools.
func PolicyDenyAll ¶
func PolicyDenyAll() PolicyPlugin
PolicyDenyAll creates a PolicyPlugin denying everything.
func (PolicyPlugin) Register ¶
func (p PolicyPlugin) Register(r *Registry) error
Register claims the policy seam and registers the gate hook.
type Priority ¶
type Priority int
Priority orders listeners on an extension point. Lower runs first. The permission gate uses PriorityGate so it is always consulted before any consumer hook, whatever order plugins were listed in.
const ( // PriorityGate is for authorization: it must run before anything that could // observe or rewrite a call it would have blocked. PriorityGate Priority = -100 // PriorityDefault is ordinary consumer behavior. PriorityDefault Priority = 0 // PriorityLate is for observers that want to see the final decision. PriorityLate Priority = 100 )
type ProcessSandbox ¶
type ProcessSandbox interface {
Sandbox
// Start must enforce SandboxExec.Constraints.TimeoutSeconds for the whole
// process lifetime and hard-kill the process group/container when it expires.
// Retained protocol tools rely on that backend-independent lifetime bound.
Start(ctx context.Context, req SandboxExec) (SandboxProcess, error)
}
ProcessSandbox is the optional interactive-process capability implemented by backends that can keep stdin/stdout open while a command runs. Protocol tools such as LSP and DAP need this; ordinary shell/file tools intentionally stay on the smaller run-to-completion Sandbox contract.
type PromptContributor ¶
type PromptContributor interface {
// SystemPrompt returns text to append, or "" to contribute nothing.
SystemPrompt() string
}
PromptContributor appends to the system prompt. Implement it to give the model a standing instruction (a completion contract, a capability explanation) that must survive compaction.
The contribution is appended after the definition and recalled memory, in extension registration order, so the prompt prefix stays stable across turns and the provider's KV cache is not invalidated mid-run.
type ProviderError ¶
type ProviderError = protocol.ProviderError
func NewProviderError ¶
func NewProviderError(provider string, response *http.Response, message string) *ProviderError
type ProviderRequestHook ¶
type ProviderRequestHook func(ctx context.Context, req ChatRequest) ChatRequest
ProviderRequestHook inspects or rewrites the assembled ChatRequest right before it is sent (pi's `before_provider_request`). Use it to pin a stop sequence, cap tools, or tee the request to a logger. Returning a zero-value request (no messages) is treated as "no change".
type ProviderSession ¶
type ProviderSession = protocol.ProviderSession
func NewProviderSession ¶
func NewProviderSession() *ProviderSession
type ProviderSessionRegistry ¶
type ProviderSessionRegistry struct {
// contains filtered or unexported fields
}
ProviderSessionRegistry retains conversation state across short-lived Agent instances. It is bounded and lease-aware: only idle sessions are evicted, so pressure may temporarily exceed capacity rather than closing state a live request is using. A cache miss merely makes a provider relearn an optimization; request correctness never depends on process affinity.
func NewProviderSessionRegistry ¶
func NewProviderSessionRegistry(capacity int, idleTTL time.Duration) *ProviderSessionRegistry
NewProviderSessionRegistry builds a bounded registry. Non-positive values use conservative defaults suitable for both a desktop daemon and a server worker.
func (*ProviderSessionRegistry) Acquire ¶
func (r *ProviderSessionRegistry) Acquire(key string) (*ProviderSession, func())
Acquire leases the state for key. The release function is idempotent. An empty key gets an ephemeral session that is closed on release, which keeps one-off runs isolated without growing the registry.
func (*ProviderSessionRegistry) Close ¶
func (r *ProviderSessionRegistry) Close()
Close releases every retained idle or active session. Hosts should call it only after their requests have stopped; it is idempotent.
type ProviderSessionState ¶
type ProviderSessionState = protocol.ProviderSessionState
type ReasoningBlock ¶
type ReasoningBlock = protocol.ReasoningBlock
type RecoveryPolicy ¶
type RecoveryPolicy string
RecoveryPolicy governs how an interrupted run is resumed.
const ( // RecoveryMarkInterrupted is the conservative default: rebuild state, mark an // unfinished turn interrupted, re-run an unfinished compaction, and re-issue // only the tool calls whose tools declare themselves retry-safe. RecoveryMarkInterrupted RecoveryPolicy = "mark_interrupted" )
type ReducedState ¶
type ReducedState struct {
Messages []Message
Model string
ActiveTools []string
DisabledTools []string // tools the circuit breaker disabled (EntryToolDisabled)
Completed bool // an EntryLeaf was seen
PendingCompaction bool // a compaction start with no completion
Goal string // goal-gate condition of the unfinished tail run (EntryGoal)
LastTurn int
// Inbox holds queued steering/follow-up messages never delivered before the
// crash (EntryInbox with no matching EntryInboxDone). Side records are read
// from the raw log — they are not tree nodes — so a pending inbox survives
// even when it was appended mid-turn between two buffered entries.
Inbox []InboxItem
// Draft is the partial assistant text the in-flight turn had produced when
// the run died (the newest trailing EntryAssistantFrame), "" when the last
// turn settled or produced no text.
Draft string
// ToolProgress maps a tool-call ID to the newest partial output it reported
// before the crash (trailing EntryToolProgress records), for calls whose
// result never landed.
ToolProgress map[string]string
// ToolOutcomes holds completed physical executions whose canonical result
// message may not have reached the transcript before a crash. Recovery
// chooses them by the assistant's original call order.
ToolOutcomes map[string]ToolOutcomeRecord
}
ReducedState is the run state rebuilt by folding a session log: the message history to resume from, the active model and tools, and flags for whether the run completed and whether a compaction was left unfinished.
func ReduceSession ¶
func ReduceSession(log []SessionEntry) ReducedState
ReduceSession folds an append-only log into the current run state. It is a pure function of the log — the heart of durable resume. The fold walks only the ACTIVE branch (leaf → root, reversed): a log that was rewound or branched reduces to the history the next turn should actually see, while abandoned branches stay in the log for inspection. A flat log's active branch is the whole log, so pre-tree logs reduce exactly as before.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry is the composition surface. Plugins write to it; Build reads from it.
It is not a general service locator: the seams are named fields, so a missing or duplicated provider is a compile-time-shaped error rather than a runtime lookup failure, and reading the registry needs no type assertions.
func BuildRegistry ¶
BuildRegistry composes the registry WITHOUT building an Agent, so a caller can inspect the composition (Describe, Provider, Plugins) or Unload a plugin first. Call Agent() on the result to finish.
func (*Registry) AddExtension ¶
func (r *Registry) AddExtension(f ExtensionFactory)
AddExtension contributes a run capability. Unlike a seam this is additive and unkeyed — several extensions may intercept the same point and they compose in registration order — because a capability is not a slot: two interceptors bounding a tool result is a waterfall, not a conflict.
The registry never inspects what the factory does. It cannot: the loop discovers each extension's abilities by type assertion at run start, so the set of things a plugin may do is open, and adding a new kind of plugin does not touch this file.
func (*Registry) AddHooks ¶
AddHooks contributes lifecycle listeners at the given priority. Listeners run in priority order, ties broken by registration order, so behavior never depends on how the plugin list happened to be sorted.
func (*Registry) AddTools ¶
AddTools contributes tools to the run's registry. Unlike a seam this is additive: many plugins may each contribute capabilities.
func (*Registry) ApplyConfig ¶
ApplyConfig writes an entire Config into the registry.
It is the Config-shaped front door to the same setters the granular plugins use. Every rule (validation, defaults, seam claiming, undo) lives in the setter, so this function is pure field routing: if it and a plugin package ever disagree, the disagreement is about WHICH field, never about what the field means.
Only non-zero fields are applied, so a Config leaves unclaimed the seams it says nothing about — which is what lets ApplyConfig be combined with extra plugins in one composition.
func (*Registry) Describe ¶
Describe renders the composition for diagnostics: which plugin owns which seam, and how many listeners sit on the extension points. This is the Go equivalent of `dsh --dump-config` — the answer to "what is actually running?"
func (*Registry) SetBudgetGate ¶
SetBudgetGate installs the per-turn spend ceiling.
func (*Registry) SetCompaction ¶
func (r *Registry) SetCompaction(s CompactionSettings) error
SetCompaction installs the transcript-shrinking retention policy (how much recent context survives). WHAT replaces the older span is SetCompactor.
func (*Registry) SetCompactionModel ¶
func (r *Registry) SetCompactionModel(p LLMProvider, model string) error
SetCompactionModel pins the compaction summary call to a dedicated tier instead of borrowing whichever rung the run has escalated to.
func (*Registry) SetCompactor ¶
SetCompactor installs the compaction strategy. Unclaimed leaves DefaultCompactor (summarize the older span with a model call), so a composition only names this seam when it wants a different strategy.
func (*Registry) SetContextWindow ¶
SetContextWindow declares the primary model's input window in tokens, which caps the compaction budget. It is a separate seam from SetModel because the window is knowledge about the model rather than a way to reach it: the same provider serves models with different windows, and a composition that cannot find out leaves it at 0 rather than guessing.
func (*Registry) SetDefinition ¶
func (r *Registry) SetDefinition(d AgentDefinition) error
SetDefinition installs the authored persona, skills, and instructions.
func (*Registry) SetEnv ¶
SetEnv installs the host's tool execution environment as one value. Sandbox backends are bound to tools by the host; credential resolution runs inside the executor after permission checks, not as an agent extension.
func (*Registry) SetEscalation ¶
SetEscalation installs the ordered fallback ladder tried when the primary rung errors.
func (*Registry) SetFollowUpSource ¶
SetFollowUpSource installs the follow-up queue, drained when the run would otherwise end.
func (*Registry) SetLimits ¶
SetLimits overrides the run's bounds (turns, tool calls, context, result size).
func (*Registry) SetMaxTokens ¶
SetMaxTokens caps the model's output tokens per turn (0 = provider default).
func (*Registry) SetMemory ¶
func (r *Registry) SetMemory(m MemoryStore) error
SetMemory installs the working-memory store.
func (*Registry) SetModel ¶
func (r *Registry) SetModel(p LLMProvider, model string) error
SetModel installs the primary provider and model.
func (*Registry) SetModelCapabilities ¶
func (r *Registry) SetModelCapabilities(c ModelCapabilities) error
SetModelCapabilities installs live/configured facts for the primary model.
func (*Registry) SetNativeProvider ¶
func (r *Registry) SetNativeProvider(p *ai.FallbackProvider) error
SetNativeProvider installs the native provider composition.
func (*Registry) SetOutputSchema ¶
func (r *Registry) SetOutputSchema(s *OutputSchema) error
SetOutputSchema constrains every text answer to a JSON Schema at the provider.
func (*Registry) SetParallelToolCalls ¶
SetParallelToolCalls configures the optional provider generation hint.
func (*Registry) SetPolicy ¶
SetPolicy installs the permission gate. The gate hook itself is contributed separately (at PriorityGate) by whichever plugin owns governance.
func (*Registry) SetPrepareNextTurn ¶
func (r *Registry) SetPrepareNextTurn(fn func(ctx context.Context, state TurnState) TurnState) error
SetPrepareNextTurn installs the per-turn save-point hook.
func (*Registry) SetPromptCache ¶
SetPromptCache opts every provider call into prompt caching under key.
func (*Registry) SetProviderSession ¶
func (r *Registry) SetProviderSession(s *ProviderSession, id string) error
SetProviderSession installs provider-private state for a logical conversation. It is separate from SetSession because provider affinity spans run logs and remains useful when durable logging is disabled.
func (*Registry) SetReasoningEffort ¶
SetReasoningEffort asks reasoning models for that much thinking per turn.
func (*Registry) SetRefreshKey ¶
func (r *Registry) SetRefreshKey(fn func(ctx context.Context, provider string) (string, error)) error
SetRefreshKey installs the per-turn API-key resolver for rotating BYO credentials.
func (*Registry) SetRetry ¶
func (r *Registry) SetRetry(p RetryPolicy) error
SetRetry overrides the same-model backoff policy applied before escalation. Zero fields are filled from DefaultRetryPolicy().
func (*Registry) SetSeedDisabledTools ¶
SetSeedDisabledTools pre-disables tools for this run's circuit breaker, so a tool that was broken in a crashed run stays disabled across a resume.
func (*Registry) SetSession ¶
func (r *Registry) SetSession(s SessionStore, id string, resume bool) error
SetSession installs durability.
func (*Registry) SetSteeringSource ¶
SetSteeringSource installs the mid-run steering queue, drained at the top of every turn.
func (*Registry) SetStepGate ¶
SetStepGate installs the pause-before-each-turn hook.
func (*Registry) SetToolChoice ¶
func (r *Registry) SetToolChoice(choice ToolChoice) error
SetToolChoice configures provider-neutral tool routing for ordinary turns.
func (*Registry) Unload ¶
Unload reverses everything a plugin registered. It is the property that makes a plugin composable: a plugin you can add but not remove is a patch.
func (*Registry) UsePolicy ¶
UsePolicy installs the permission gate AND the BeforeToolCall hook that enforces it, at PriorityGate so it is consulted before any consumer hook whatever order plugins were listed in.
This pairing is the whole trust boundary, so it is one call: a composition cannot install the policy and forget the hook that reads it.
type ReplayProvider ¶
type ReplayProvider struct {
Records []TurnRecord
// contains filtered or unexported fields
}
ReplayProvider is a deterministic LLMProvider over a recorded []TurnRecord.
omp has no record/replay of this shape — the recon looked — so this is agentray's. TurnRecord already persists everything a provider needs to be replayed (the request Messages, the Response, the ToolCalls the model asked for, advertised Tools, StopReason, Error, tokens/cost). Serving those as ChatResponses, in order, lets a test drive the real loop against a transcript instead of a scripted FauxProvider that ignores the request.
The assertion is the whole point. FauxProvider will happily answer a loop that rebuilt history differently; ReplayProvider fails the call, which is what turns a replay into a regression test. Comparison is the recorded transcript, not the whole ChatRequest:
- Messages: Role, Content, ContentParts, Name, ToolCallID, Directive, Error, and each ToolCall's ID/Name/Arguments. That is the history the loop rebuilt.
- Advertised tool names, in order (TurnRecord.Tools), against req.Tools.
Ignored, because they are not the history and are legitimately nondeterministic or unrecorded:
- Message.CacheAnchor — request-scoped, json:"-", dropped on any persistence round-trip of the trace.
- Message.Usage — provider-reported accounting, not part of rebuild.
- Model, Temperature, MaxTokens, CacheKey, CacheRetention, ReasoningEffort, OutputSchema — not on TurnRecord.
Empty recording: Chat returns an error ("replay: empty recording"). Extra call past the transcript: Chat returns an error naming the index. Neither invents a response — FauxProvider's "(end)" would hide a loop that failed to stop. A recorded Error is returned as Chat's error (with the recorded response still filled in); the loop's retry policy still applies, so a test of a failed turn should set Retry.MaxAttempts=1 or record the retries.
func NewReplayProvider ¶
func NewReplayProvider(records ...TurnRecord) *ReplayProvider
NewReplayProvider plays the given records in order.
func (*ReplayProvider) Chat ¶
func (r *ReplayProvider) Chat(_ context.Context, req ChatRequest) (ChatResponse, error)
Chat serves the next recorded response after asserting the request matches that turn's recording. See ReplayProvider's doc for the comparison rule.
func (*ReplayProvider) Name ¶
func (r *ReplayProvider) Name() string
func (*ReplayProvider) Stream ¶
func (r *ReplayProvider) Stream(ctx context.Context, req ChatRequest) (<-chan ChatDelta, error)
Stream adapts Chat into a delta channel, matching FauxProvider's shape so the loop's streamTurn path is exercised. Unlike FauxProvider it must not swallow Chat's error: a mismatch or recorded failure has to reach streamTurn as ChatDelta.Err, or a drifting loop would look like a silent empty turn.
func (*ReplayProvider) SupportsTools ¶
func (r *ReplayProvider) SupportsTools() bool
type ResultCard ¶
type ResultCard struct {
Title string `json:"title"`
Kind string `json:"kind"` // "stat" | "series"
Unit string `json:"unit,omitempty"` // optional label for the values
Stats []CardStat `json:"stats,omitempty"`
Points []CardPoint `json:"points,omitempty"`
}
ResultCard is a compact, structured answer artifact a consumer may attach to a streamed turn so the UI can render a stat block or a small chart instead of prose alone. It is deliberately product-agnostic: a title, a kind, and either a few stat rows or a short series of points. agentcore never builds one — a consumer (e.g. the orchestrator) emits it via the sink; the type lives here so the stream vocabulary is shared.
type ResumePlan ¶
type ResumePlan struct {
Messages []Message
Model string
ActiveTools []string
DisabledTools []string // tools the circuit breaker disabled; re-applied on resume
Completed bool // run already reached a leaf; nothing to resume
Goal string // goal-gate condition of the interrupted run, re-armed on resume
Interrupted bool // the last turn did not complete
RerunCompaction bool // an unfinished compaction must be re-run first
RetryCalls []ToolCall // dangling calls whose tools are retry-safe
DroppedCalls []ToolCall // dangling calls left for the model (not retry-safe)
// Answers is the out-of-band tool results recorded while the run was parked:
// call id -> the human's answer, from EntryAnswer entries. A dangling call
// with an entry here is closed with the answer as its result — it is neither
// retried nor interrupted.
Answers map[string]string
// Inbox holds steering/follow-up messages queued but never delivered before
// the crash (pi's durable inbox). The resumed run drains them at their
// lane's point and settles each with EntryInboxDone.
Inbox []InboxItem
// Draft is the partial assistant text the in-flight turn had produced;
// surfaced to the resumed run as context, not replayed as a message.
Draft string
// ToolProgress maps a dangling call's ID to the last partial output it
// reported, so its interrupted note can say how far it got.
ToolProgress map[string]string
// ToolOutcomes are completed calls recovered from side records. They are
// neither retried nor marked interrupted; the resume path materializes their
// messages beside unresolved siblings in original source order.
ToolOutcomes map[string]ToolOutcomeRecord
}
ResumePlan is the recovery output: the history to resume from, the active model/tools, the conservative decisions about an interrupted turn, and the side-record state (queued inbox, partial draft, tool progress) the crashed run had in flight.
func RecoverSession ¶
func RecoverSession(log []SessionEntry, tools *ToolSet, policy RecoveryPolicy) ResumePlan
RecoverSession turns a durable log into a conservative resume plan. It reduces the log, then — under the (default) mark_interrupted policy — detects a turn that crashed mid-flight: an assistant message whose tool calls have no matching tool result. Retry-safe calls are queued for re-run; the rest are dropped so non-idempotent side effects are never silently repeated.
type RetryPolicy ¶
type RetryPolicy = protocol.RetryPolicy
func DefaultRetryPolicy ¶
func DefaultRetryPolicy() RetryPolicy
type RetrySafeTool ¶
type RetrySafeTool interface {
RetrySafe() bool
}
RetrySafeTool is an optional Tool capability: a tool that is safe to re-run after a crash (idempotent / read-only) declares RetrySafe() true. Recovery auto-retries only these; a tool that does not implement it is treated as non-idempotent and is never auto-retried (its dangling call is left for the model to decide).
type RichTool ¶
type RichTool interface {
RunRich(ctx context.Context, args string) (ToolOutput, error)
}
RichTool is an additive tool capability for outputs such as eval-generated images. Implementations still provide Run for direct/legacy callers; the agent loop prefers RunRich when it is available (unless a StreamingTool is actively streaming). It does not bypass the permission gate or any other execution policy.
type RunChapter ¶
type RunChapter struct {
// Index is the chapter's 0-based position.
Index int `json:"index"`
// FirstStep and LastStep are inclusive indices into the step list, so a
// client turns a chapter into a page request without a second lookup.
FirstStep int `json:"first_step"`
LastStep int `json:"last_step"`
// FirstTurn and LastTurn are the run turns the chapter spans.
FirstTurn int `json:"first_turn"`
LastTurn int `json:"last_turn"`
// Title is a one-line label drawn from the closing summary — the model's own
// words, not a generated paraphrase.
Title string `json:"title"`
// Summary is the compaction summary that CLOSED this chapter: what the agent
// had accomplished by the end of the span. The final chapter has none (the
// run ended before the next compaction), which is the honest answer — its
// outcome is the run's final answer, not a summary.
Summary string `json:"summary,omitempty"`
// Steps is how many steps the chapter contains, and ToolCalls how many tool
// calls were made in it — the two numbers that say whether a chapter is
// worth opening.
Steps int `json:"steps"`
ToolCalls int `json:"tool_calls"`
// TokensIn/TokensOut/CostUSD are the chapter's own spend, differenced from
// the cumulative totals the fold already computed.
TokensIn int `json:"tokens_in"`
TokensOut int `json:"tokens_out"`
CostUSD float64 `json:"cost_usd"`
}
RunChapter is one span of a run bounded by compaction: the work between one context rewrite and the next, and the model's own account of it.
func RunChapters ¶
func RunChapters(steps []LabStep) []RunChapter
RunChapters divides a folded step list into chapters at its compaction steps.
A compaction step CLOSES the chapter it appears in rather than opening the next one, because its summary describes the span behind it. The result is always at least one chapter for a non-empty run.
type RunCloser ¶
type RunCloser interface {
CloseRun()
}
RunCloser releases whatever the extension acquired for this run. The loop calls it on EVERY exit path, including error and abort, so an extension that starts goroutines cannot leak them past the run that owns them.
type RunCommandHandler ¶
type RunCommandHandler interface {
HandleRunCommand(context.Context, json.RawMessage) (json.RawMessage, error)
}
RunCommandHandler accepts host-authorized commands after checkpoint restore. Commands are never inferred from transcript text by the kernel.
type RunController ¶
RunController may stop at a settled turn boundary, before another provider request. A nonempty reason suspends execution without a synthetic wrap-up. Unlike StopInterceptor this also runs after tool turns and before turn one. Usage includes child/reviewer work. Controllers must not perform workload effects.
type RunFinalizer ¶
RunFinalizer settles plugin state in reverse registration order before it is checkpointed. Result is a read-only snapshot; failure describes the primary execution error. Returning an error prevents the host from treating the checkpoint as successful.
type RunInfo ¶
type RunInfo struct {
// SessionID is the durable session, or "" on an in-memory run.
SessionID string
// Owner is a stable token identifying THIS run, used to fence run-scoped
// resources (a background job started here must not be visible to another
// run). It equals SessionID on a durable run and is otherwise unique.
Owner string
// ScopeID is the running agent's own memory/persona scope — the same value
// recall reads under (def.ScopeID). Extensions that write agent-private
// state pin to it so a model-supplied value can never widen the scope.
ScopeID string
// Limits are the run's effective bounds.
Limits Limits
// Depth is the delegation depth: 0 for a top-level run, higher inside a
// spawned sub-agent.
Depth int
// Durable reports whether a session store is recording this run.
Durable bool
// Session is the store recording this run, or nil. It is handed over so a
// retrieval extension can read the run's OWN log without the consumer
// wiring the same store twice and risking the two disagreeing about which
// session is being searched. Read-only by convention: the loop owns every
// write, which is what keeps "model-visible means logged" enforceable.
Session SessionStore
// Goal is the run's completion condition when one is set — the configured
// goal, or the one recovered from the durable log on a resume. Empty means
// the run is ungated. It is carried here so a gate extension never has to
// read the log itself; only the loop writes and recovers the record.
Goal string
// Bookkeeping reports whether a tool's calls are administrative rather than
// progress toward the task (see BookkeepingTool). Never nil.
//
// It exists so an extension can treat such calls differently — a loop
// detector must not count a plan updater's repeats as a stuck chain —
// WITHOUT depending on the package that provides the tool. Asking the
// question generically is what keeps capabilities from importing each
// other.
Bookkeeping func(tool string) bool
// Agent is the running agent, non-nil. It is here for the one capability
// that cannot be built without it — delegation, which needs Fork to produce
// a child whose scope provably only narrows. Most extensions ignore it.
Agent *Agent
}
RunInfo describes the run an extension is being created for. It is what a plugin needs to build per-run state without reaching into the Agent.
type RunObserver ¶
type RunObserver interface {
// ObserveMessages is called with the live history immediately before each
// provider request, and again after any rewrite (compaction, context edit)
// so an observer tracking history can rebase.
ObserveMessages(ctx context.Context, phase ObservePhase, turn int, msgs []Message)
}
RunObserver watches a run without changing it. Implement it for metering, audit, or invariant checking.
Every method is called for effect only; return values are not threaded back, so an observer cannot alter the run even by mistake.
type RunResult ¶
type RunResult struct {
Final string `json:"final"`
Messages []Message `json:"messages"`
Tools []ToolTrace `json:"tool_calls"`
Usage Usage `json:"usage"`
Turns int `json:"turns"`
StopReason string `json:"stop_reason"`
// CommandResults contains structured receipts from explicit host controls.
CommandResults map[string]json.RawMessage `json:"command_results,omitempty"`
// NativeState and NativeTelemetry retain the original Pi artifacts. When
// present, Messages is only a display projection and must not seed a run.
NativeState json.RawMessage `json:"native_state,omitempty"`
NativeTelemetry json.RawMessage `json:"native_telemetry,omitempty"`
NativeRevision string `json:"native_revision,omitempty"`
// UnpersistedEntries counts durable session entries the run buffered but
// could not commit because the session store kept failing through the final
// flush. Zero on a healthy (or storeless) run. Non-zero means the durable
// log holds a valid prefix of the run — recovery stays safe (the
// conservative resume never re-runs non-idempotent work) but the tail of
// this run cannot be reconstructed (pi's Faulted state, degraded to a
// flagged result instead of a halt).
UnpersistedEntries int `json:"unpersisted_entries,omitempty"`
// Parked is true when the run ended inside a tool call that is waiting on a
// human (the ask tool): the call stays dangling in the durable log behind an
// EntryQuestion, and the run resumes when an EntryAnswer lands. Distinct
// from every other stop — the run is neither done nor failed, it is paused.
Parked bool `json:"parked,omitempty"`
// Question carries the parked call's validated arguments when Parked is
// true, so a consumer can render the prompt/options directly from RunResult.
Question json.RawMessage `json:"question,omitempty"`
}
RunResult is the outcome of a run: the final assistant text, the full message history (working memory), the tool trace, and summed usage.
type RunStart ¶
type RunStart struct {
// System is the assembled system prompt. It has not yet been prepended to
// Messages — the loop does that after the hooks run.
System string
// Messages are the seed messages (the user prompt, or a continued thread).
// They never include the system message.
Messages []Message
// Task is the recall/skill-selection task string for this run. Read-only:
// changing it here has no effect (memory recall already happened).
Task string
}
RunStart is the assembled starting state of a run, handed to the BeforeAgentStart hooks after the system prompt is built (definition + recalled memory + skill headers + any goal contract) and before the first turn. It is the only seam that can shape the *first* request; PrepareNextTurn shapes every turn after one has completed.
type Sandbox ¶
type Sandbox interface {
// Exec runs one command to completion inside an ephemeral sandbox and
// returns its captured output. It MUST NOT inherit the host process
// environment; only req.Env is exposed inside. A non-zero exit code is a
// SandboxResult, not an error — error is reserved for the sandbox itself
// failing to run (backend unavailable, image missing).
Exec(ctx context.Context, req SandboxExec) (SandboxResult, error)
}
Sandbox executes untrusted commands in an isolated environment that cannot reach the host's environment, filesystem, or network unless explicitly granted. It is the substrate the agent's shell / file / browser tools run in, so a prompt-injected command cannot read server secrets, touch the DB, or exfiltrate over the network — the in-process Policy gate decides *whether* a tool may run; the Sandbox decides *what the host it runs against can see*.
agentcore defines the contract only. A concrete backend (Docker container, gVisor/Kata, micro-VM, …) is injected by the host via Env.Sandbox, keeping this package a leaf with no infrastructure imports — the same boundary that keeps the core reusable across agents (see agentcore/README.md).
type SandboxExec ¶
type SandboxExec struct {
// Argv is the command and its arguments; Argv[0] is resolved against the
// sandbox image's PATH, not the host's.
Argv []string
// Stdin is fed to the command's standard input.
Stdin string
// Env is the ONLY environment visible inside the sandbox. The host process
// environment is never inherited — this is the property that stops a
// prompt-injected command from reading DB creds or API keys.
Env map[string]string
// Mounts are explicit host paths exposed to the sandbox. Backends must reject
// mounts they cannot enforce; callers provide only paths already narrowed by a
// workspace guard.
Mounts []SandboxMount
// Workdir overrides the working directory the command starts in. Empty keeps
// the backend default (the ephemeral scratch workdir). Set it to a mount
// target so the command runs against shared workspace files.
Workdir string
// Session, when non-empty, requests persistent execution: a SessionSandbox
// reuses one long-lived container keyed by this id across calls, so installed
// packages and written files survive between tool invocations. Empty (the
// default) runs the command in a fresh, throwaway container — the fail-safe
// path every backend supports. The container's network/mount/limit envelope
// is fixed by the first call that opens the session.
Session string
// Image, when non-empty, requests a specific sandbox image for this exec,
// overriding the backend's default image selection. It lets distinct tools run
// in purpose-built images within one host (e.g. computer_use in a doc-toolchain
// image, browser_use in a Chrome image) without sharing a container. A backend
// that cannot honor it MUST ignore it and fall back to its default — the
// fail-safe path; agentcore stays generic and never interprets the value.
Image string
// Constraints are the resource + isolation caps for this execution.
Constraints SandboxLimits
}
SandboxExec is one sandboxed command request.
type SandboxLimits ¶
type SandboxLimits struct {
Network bool // false (default) = no network egress at all
NetworkAllow []string // when Network is true and non-empty, egress is confined to these hosts (+subdomains) via the sandbox filtering proxy; empty = open network
WritableFS bool // false (default) = read-only root + small writable workdir
// RunAsHostUser requests the host process UID/GID inside a container while
// preserving a read-only root. It is for writable bind-mounted workspaces:
// nobody often cannot modify a 0755 host directory, while container-root
// would leave root-owned files behind. Backends that cannot map numeric host
// identities may ignore it and retain their unprivileged default.
RunAsHostUser bool
MemoryMB int // 0 = backend default
CPUs float64 // 0 = backend default
PidsLimit int // 0 = backend default
TimeoutSeconds float64 // 0 = backend default; negative disables the HostSandbox deadline; positive hard-kills after this elapses
}
SandboxLimits are the isolation and resource caps applied to one execution. The zero value is fail-closed: no network, read-only root filesystem, and the default resource caps applied by the backend.
type SandboxMount ¶
SandboxMount exposes one host directory or file inside a sandboxed execution.
type SandboxProcess ¶
type SandboxProcess interface {
Stdin() io.WriteCloser
Stdout() io.ReadCloser
Stderr() io.ReadCloser
Wait() (SandboxResult, error)
Kill() error
}
SandboxProcess is one started interactive command. Callers own all three pipes and must call Wait. Kill is idempotent and terminates the command's process group or container, not merely its direct child.
type SandboxResult ¶
type SandboxResult struct {
ExitCode int
Stdout string
Stderr string
Killed bool // true if the timeout fired and the command was killed
KillReason string // human-readable kill cause, set when Killed
}
SandboxResult is the captured outcome of a sandboxed execution.
type SelfGated ¶
type SelfGated interface {
// SelfGated reports that this tool bypasses the permission gate.
SelfGated() bool
}
SelfGated is an optional Tool capability: a tool that CANNOT reach anything the agent does not already have may run without consulting the permission policy.
This is a real security surface, so it is narrow and explicit. The rule: a self-gated tool may only return something this run already produced, or something authored into this agent's own definition. read_skill (definition bodies), read_spill (output this run generated and had truncated), and session_query (this session's own log) qualify. A tool that reaches the network, the filesystem, or another run does NOT, whatever its author believes.
Installing a plugin is already a trusted act — it is Go code compiled into the binary — so a plugin declaring its own exemption is no weaker than the hardcoded list it replaces. It is strictly more auditable: Agent.Describe prints every gate-exempt tool.
type Session ¶
type Session struct {
ID string `json:"id"`
ScopeID string `json:"scope_id"`
ParentID string `json:"parent_id,omitempty"` // set when forked
Messages []Message `json:"messages"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
Session is a working-memory thread: the message history of one run, persisted so a chat or long autonomous run can resume or be inspected.
type SessionBatchStore ¶
type SessionBatchStore interface {
AppendBatch(ctx context.Context, sessionID string, entries []SessionEntry) error
}
SessionBatchStore is an optional SessionStore capability for committing one save point atomically. The loop buffers chain entries for a turn and hands the whole ordered slice to AppendBatch; either every entry must become visible, with consecutive sequence numbers in the supplied order, or none of them may become visible.
Side records (stream frames, tool progress/outcomes, queued input) still use Append: their purpose is to become durable while a turn is in flight. AppendBatch is for the settled transcript boundary only. Backends that do not implement the capability remain supported; the loop falls back to ordered Append calls and retains the successfully written prefix on failure.
type SessionEntry ¶
type SessionEntry struct {
Seq int `json:"seq"` // append order, assigned by the store
Kind SessionEntryKind `json:"kind"`
ID string `json:"id,omitempty"` // stable entry id (writer-assigned)
ParentID string `json:"parent_id,omitempty"` // tree parent; for outcome side records, the intent anchor
Target string `json:"target,omitempty"` // EntryLeafMove: the new active leaf
Turn int `json:"turn,omitempty"`
Message *Message `json:"message,omitempty"` // EntryMessage
Model string `json:"model,omitempty"` // EntryModelChange
Tools []string `json:"tools,omitempty"` // EntryActiveToolsChange
Tool string `json:"tool,omitempty"` // EntryToolDisabled
// Lane selects which queue an EntryInbox feeds: "steer" drains at the top
// of the next turn, "follow" drains when the run would stop.
Lane string `json:"lane,omitempty"`
// CallID links a side record to the tool call it concerns: an
// EntryToolProgress reports on it, an EntryQuestion parks it, an
// EntryAnswer resolves it (the provider's call id, stable across a resume).
CallID string `json:"call_id,omitempty"`
// Content carries a side record's payload: the accumulated text for an
// EntryAssistantFrame, the accumulated partial output for an
// EntryToolProgress. Snapshots, not deltas — the newest wins.
Content string `json:"content,omitempty"`
Goal string `json:"goal,omitempty"` // EntryGoal: the run's completion condition
Summary string `json:"summary,omitempty"` // EntryCompaction (completion) / EntryBranchSummary
Final bool `json:"final,omitempty"` // EntryCompaction completion marker
// Retained is the transcript a completed EntryCompaction left behind: the
// summary (or elided fallback) plus the recent tail kept verbatim, MINUS the
// run's own leading system prompt, which every run re-derives and prepends
// itself. It makes the compaction a self-contained checkpoint (pi's
// CompactionEntry.retainedTail): reduce restarts history here instead of
// replaying the span the summary already represents.
//
// Without it a resumed run silently rebuilds the FULL pre-compaction
// history — measured at 3.75x the messages and 2.8x over the context budget
// on a 60-turn run — then pays to summarize it all again, and the fresh
// summary is not the one the original run reasoned over. Nil on a legacy
// entry (and on the start half of the bracket), which reduces exactly as
// before.
Retained []Message `json:"retained,omitempty"`
// State is the rest of the reduced run state as of a completed
// EntryCompaction — everything the fold accumulated that Retained does not
// carry. Retained makes the checkpoint self-contained for the TRANSCRIPT;
// this makes it self-contained for the run, which is what lets a resume read
// a suffix of the log instead of all of it (see SessionWindowStore).
//
// Nil on a legacy entry, and on the start half of the bracket. A nil State is
// why a windowed read is an optimization a store opts into rather than the
// default: a log whose newest checkpoint predates this field has no suffix
// that reduces to the same answer, so it is read whole.
State *CheckpointState `json:"state,omitempty"`
// Usage records what the summarization call itself cost (EntryCompaction
// completion / EntryBranchSummary). Compaction and branch summaries are real
// billable provider calls; without this they are invisible spend (pi #6671).
Usage *Usage `json:"usage,omitempty"`
// Question is the parked call's validated arguments (EntryQuestion), kept
// verbatim so a consumer can re-render the prompt and options without
// knowing the tool's schema.
Question json.RawMessage `json:"question,omitempty"`
// Answer is the human's reply to a parked question (EntryAnswer): the text
// recovery feeds back as the call's tool result.
Answer string `json:"answer,omitempty"`
// Outcome is populated only for EntryToolOutcome. CallID is kept on the
// envelope so stores and recovery can identify it without decoding a nested
// message first.
Outcome *ToolOutcomeRecord `json:"outcome,omitempty"`
CreatedAt time.Time `json:"created_at"`
}
SessionEntry is one immutable record in the append-only session log. The log is a tree, not just a line: ID/ParentID give each entry a stable address and a tree parent, so a consumer can branch from any earlier entry in place (pi's id/parentId session format). Both are optional — an entry without them chains implicitly to the entry appended before it, so a flat log written by an older writer is a single-branch tree and reduces exactly as before.
func ActivePath ¶
func ActivePath(log []SessionEntry) []SessionEntry
ActivePath returns the entries on the active branch, oldest first: the chain from the root to the active leaf. A flat (id-less, never-rewound) log returns every entry in order — the pre-tree behavior.
func LoadResumeLog ¶
func LoadResumeLog(ctx context.Context, store SessionStore, sessionID string) ([]SessionEntry, error)
LoadResumeLog reads the entries a resume needs: a suffix when the store can serve one and the log's shape allows it, the whole log otherwise.
The suffix begins AT the checkpoint entry, not after it — the checkpoint is what carries the retained transcript and the run state, so dropping it would drop everything the window exists to preserve. Entries after it chain onto it implicitly, so the suffix is a well-formed log in its own right and reduces through the same fold, unchanged.
Every failure degrades to the full read rather than to a partial one. A resume that reads too much is slow; a resume that reads too little silently forgets work the run already did.
type SessionEntryKind ¶
type SessionEntryKind string
SessionEntryKind classifies one append-only entry in a durable session log (pi's semi-durable harness). The log is the source of truth: run state is rebuilt by reducing it, never by mutating a row in place.
const ( EntryPiState SessionEntryKind = "pi_state" // EntryPiModelSelection commits a host-owned native ladder identity without credentials. EntryPiModelSelection SessionEntryKind = "pi_model_selection" EntryPiEvent SessionEntryKind = "pi_event" EntryPiEffectStart SessionEntryKind = "pi_effect_start" EntryPiEffectDone SessionEntryKind = "pi_effect_done" // EntryPiAnswer is host workflow metadata. Content holds the exact native // user message to append; it never replaces an already emitted tool result. EntryPiAnswer SessionEntryKind = "pi_answer" EntryPiGoal SessionEntryKind = "pi_goal" // EntryPiGoalRevision records a committed host contract change within a // physical tool effect. It never substitutes for a native tool result. EntryPiGoalRevision SessionEntryKind = "pi_goal_revision" // Invocation binds an isolated native run to its immutable request; Result // records a completed child's reattachable answer without rewriting messages. EntryPiInvocation SessionEntryKind = "pi_invocation" EntryPiChildResult SessionEntryKind = "pi_child_result" // EntryPiDelegation records continuation of a parked delegation. It links // an answered parent question to a new child question or a final delivery, // without replacing the original provider tool result. EntryPiDelegation SessionEntryKind = "pi_delegation" // EntryPiDelegationBatch commits the resumed batch policy and its deliveries // before any completion context reaches the next native model request. EntryPiDelegationBatch SessionEntryKind = "pi_delegation_batch" // Request-view summary metadata; never replaces native transcript messages. EntryPiContextSummary SessionEntryKind = "pi_context_summary" )
Native Pi records keep opaque JSON in SessionEntry.Content. The host owns their persistence; legacy RecoverSession must not project this transcript.
const ( // EntryMessage records one conversation message reaching its final form // (message_end): a user prompt, an assistant turn, or a tool result. EntryMessage SessionEntryKind = "message" // EntryLeaf marks the run reaching a final answer — a leaf of the session // tree. Its presence means the run completed normally. EntryLeaf SessionEntryKind = "leaf" // EntryCompaction brackets a compaction. Final=false is the start record // (compaction in flight); Final=true is the completion record carrying the // summary. A start with no matching completion means compaction was // interrupted and must be re-run on resume. EntryCompaction SessionEntryKind = "compaction" // EntryBranchSummary records a fork/branch checkpoint summary. EntryBranchSummary SessionEntryKind = "branch_summary" // EntryModelChange records the active model switching (escalation or a // save-point bump), so a resumed run reconstructs the right model. EntryModelChange SessionEntryKind = "model_change" // EntryActiveToolsChange records the active tool set changing mid-run (a // PrepareNextTurn swap), so a resumed run rebuilds the tools the crashed // run ended on rather than the registry it started with. EntryActiveToolsChange SessionEntryKind = "active_tools_change" // EntryTurnInterrupted is written by recovery to mark a turn that never // completed (a crash between a tool result and the next assistant turn). EntryTurnInterrupted SessionEntryKind = "turn_interrupted" // EntryToolDisabled records the circuit breaker disabling a tool for the rest // of the run after repeated failures, so the disable is reconstructed on // resume (and the broken tool isn't retried from scratch). EntryToolDisabled SessionEntryKind = "tool_disabled" // EntryLeafMove is a control entry (not a tree node): it moves the session's // active leaf to Target, so the next appended entry chains from there. This // is how a consumer rewinds a session to an earlier entry, or switches to // another branch, without rewriting history (pi's branch()/leaf pointer). EntryLeafMove SessionEntryKind = "leaf_move" // EntryGoal records the run's goal-gate condition (Config.Goal), written at // the start of a goal-gated run so a resume re-arms the gate — without it a // crashed /goal run would resume ungated while its replayed transcript still // carries the gate's nudge messages. A leaf closes the goal along with the // run, so later runs chained onto the same log are not gated by it. EntryGoal SessionEntryKind = "goal" // EntryQuestion records a tool call that parked the run waiting on a human // (the ask tool): CallID links it to the dangling call, Question carries the // validated arguments. The run ends without a tool result, so the call stays // dangling in the log — a resume either closes it with a matching // EntryAnswer or re-issues it when the tool is retry-safe (re-park). EntryQuestion SessionEntryKind = "question" // EntryAnswer records the human's answer to a parked EntryQuestion, keyed by // the same CallID. Recovery closes the dangling call with Answer as its tool // result — the answer IS the tool result, not a new user turn. EntryAnswer SessionEntryKind = "answer" // --- Side records (pi's intent/progress granularity) --- // // The kinds below are written OUTSIDE the per-turn save-point buffer — // directly to the store, mid-turn — because the crash window they cover is // inside a turn. To keep the log a valid prefix of the conversation they // are NOT tree nodes: buildChain skips them, so they never become the leaf // and never break a pending entry's chain. The fold reads them by scanning // the raw log, not the active path. Each is one half of an // intent→effect→settlement triple (pi's op state machine): the intent or // in-progress effect rides the side record, the settlement is an ordinary // chain entry. // // EntryInbox is a queued steering or follow-up message (Lane selects the // drain point). Written by Agent.Steer / Agent.FollowUp the moment the // caller enqueues, so a crash between queue and drain cannot lose human // input. Settled by EntryInboxDone when the loop delivers it into the // transcript as an EntryMessage. EntryInbox SessionEntryKind = "inbox" // EntryInboxDone settles an EntryInbox (Target = the inbox entry's ID). It // IS a chain entry: the fold's inbox scan reads it from the raw log, but // writing it through the save-point buffer keeps the delivered message and // its settlement in one atomic turn flush. EntryInboxDone SessionEntryKind = "inbox_done" // EntryAssistantFrame is a throttled snapshot of the assistant text // streamed so far this turn (pi's durable partial frames). Settled by the // turn's assistant EntryMessage; a trailing run of frames with no such // message is the draft the crashed turn had produced. EntryAssistantFrame SessionEntryKind = "assistant_frame" // EntryToolProgress is a throttled snapshot of one streaming tool call's // partial output (pi's tool progress checkpoint). Settled by the call's // tool-result EntryMessage; a trailing one tells recovery how far the // interrupted call reported getting. EntryToolProgress SessionEntryKind = "tool_progress" // EntryToolOutcome records one completed physical tool execution before the // turn can place its results in assistant source order. It is a side record: // a fast parallel sibling becomes durable without waiting for an earlier slow // call, while the eventual tool-result EntryMessage remains the canonical // transcript settlement. Recovery uses the outcome only when that settlement // is absent, so a normal run never duplicates a result. EntryToolOutcome SessionEntryKind = "tool_outcome" )
type SessionLeaseStore ¶
type SessionLeaseStore interface {
AcquireSessionLease(ctx context.Context, sessionID string) (leaseCtx context.Context, release func() error, err error)
}
SessionLeaseStore is an optional SessionStore capability for exclusive, renewable ownership of a resumed durable session. The returned context must be used for all resumed work and appends: distributed backends attach a fencing token to it and cancel it if renewal fails. Local stores may implement the same contract with an in-process lock.
type SessionNode ¶
type SessionNode struct {
Entry SessionEntry
ID string
ParentID string
}
SessionNode is one resolved node of the session tree: the entry plus its effective id/parent. Ids are synthesized ("#<index>") for legacy id-less entries, so every node is addressable by Rewind even in logs written before ids existed.
func SessionTree ¶
func SessionTree(log []SessionEntry) []SessionNode
SessionTree resolves a log into addressable nodes (log order). Use it to render the tree or to pick a Rewind target; ActivePath gives the branch a resumed run will actually see.
type SessionPlugin ¶
type SessionPlugin struct {
Store SessionStore
ID string
Resume bool
SeedDisabledTools []string
ProviderState *ProviderSession
ProviderSessionID string
}
SessionPlugin installs durable session logging into a Registry.
func (SessionPlugin) Register ¶
func (p SessionPlugin) Register(r *Registry) error
Register claims the session seam.
type SessionSandbox ¶
type SessionSandbox interface {
Sandbox
// CloseSession reaps the persistent container for id, if any. It is
// idempotent: closing an unknown or already-closed session is not an error.
CloseSession(id string) error
}
SessionSandbox is an optional capability a Sandbox backend may also implement to support persistent computer-use sessions. When req.Session is non-empty, Exec reuses one long-lived container keyed by that id so packages installed and files written by an earlier call are visible to a later one (e.g. `pip install python-docx` then a script that imports it). CloseSession tears the container down when the conversation ends; a backend that does not implement this interface simply runs every call ephemerally.
type SessionStore ¶
type SessionStore interface {
// Append records one entry; the store assigns its Seq. It MUST be safe for
// concurrent use on the same sessionID: a run's loop appends serially, but
// Steer/FollowUp enqueue and side-record writes race it from other
// goroutines, and a sibling process resuming the same log appends too. A
// store that assigns Seq by read-max-then-insert must resolve collisions
// (retry on a unique violation, or serialize per session) — a lost or
// duplicated Seq silently corrupts the fold.
Append(ctx context.Context, sessionID string, entry SessionEntry) error
// Log returns the full ordered entry log for a session.
Log(ctx context.Context, sessionID string) ([]SessionEntry, error)
}
SessionStore is the append-only durability seam (extends the working-memory MemoryStore conceptually; kept separate so a consumer can adopt durability incrementally). The store assigns Seq and never mutates a written entry. Append takes an immutable value snapshot: later changes through the caller's pointers or slices must not alter the stored record. Log likewise returns a snapshot whose mutation cannot rewrite the store.
type SessionWindowStore ¶
type SessionWindowStore interface {
// LogFrom returns the session's entries with Seq >= sinceSeq, in order.
LogFrom(ctx context.Context, sessionID string, sinceSeq int) ([]SessionEntry, error)
// CheckpointSeq reports the Seq of the newest completed EntryCompaction that
// carries BOTH Retained and State (0 when there is none), and whether the
// session contains any EntryLeafMove. Zero is unambiguous as "none" whatever
// a store's Seq origin: a compaction always follows the messages it
// summarizes, so it is never a log's first entry.
//
// Both facts are needed because both can defeat a window. Without State the
// suffix loses the model, tools, and goal the fold had accumulated; with a
// leaf move the newest checkpoint by Seq may sit on an ABANDONED branch, and
// resuming from it would continue work the log says was rewound away.
//
// A third fact defeats it the same way: an EntryInbox queued BEFORE the
// checkpoint and still unsettled (no EntryInboxDone naming it). The fold
// reads pending inbox items from the raw log, so a window that starts past
// one silently drops queued human input. A store that sees such an item
// reports seq=0 — the full read is the only correct answer.
CheckpointSeq(ctx context.Context, sessionID string) (seq int, branched bool, err error)
}
SessionWindowStore is an optional SessionStore capability: read the tail of a log instead of all of it.
Resume is the one operation whose cost grows with the whole history. A long run's log is mostly messages the newest checkpoint already stands for — on a 4,200-turn run, 10,600 entries and 8 MiB, of which the resume needs the last few dozen — and reading it whole makes crash recovery slower the longer the run got, which is exactly backwards.
The two methods are deliberately MECHANICAL. Neither decides whether a window is safe; both report facts, and LoadResumeLog (in the kernel) applies the rule. A store that answers these correctly cannot make a resume wrong, which is the property that matters when the alternative is every backend re-implementing the fold's preconditions.
type Skill ¶
type Skill struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Body string `json:"body,omitempty"`
Enabled bool `json:"enabled"`
}
Skill is a named, on-demand playbook authored as a SKILL.md (frontmatter + body). Selected by Description match (progressive disclosure) so only relevant skills enter context.
type SkillLoader ¶
SkillLoader fills selected skills with their full content only when needed. The runtime may pass nil when skills are already fully materialized.
type SteeringPlugin ¶
type SteeringPlugin struct {
Steer func(ctx context.Context) []Message
FollowUp func(ctx context.Context) []Message
PrepareNextTurn func(ctx context.Context, state TurnState) TurnState
}
SteeringPlugin installs mid-run steering queues into a Registry.
func (SteeringPlugin) Register ¶
func (p SteeringPlugin) Register(r *Registry) error
Register claims the steering seams.
type StepDecision ¶
type StepDecision struct {
// AdditionalContexts are prepended to the step's messages and persisted.
// Use it to tell the model something that became true between turns — a
// background job finished, an external state changed.
AdditionalContexts []Message
}
StepDecision is a StepInterceptor's answer. The zero value adds nothing.
type StepInfo ¶
type StepInfo struct {
Turn int
// BookkeepingTurns is the number of completed administrative turns refunded
// against MaxTurns. Turn itself remains monotonic for hooks and observers.
BookkeepingTurns int
// Model is the rung in use for this step.
Model string
// Usage is the run-to-date total.
Usage Usage
}
StepInfo describes the step about to run.
type StepInterceptor ¶
type StepInterceptor interface {
BeforeStep(ctx context.Context, info StepInfo) StepDecision
}
StepInterceptor runs at the top of each turn, before the provider request is assembled. Implement it to inject information the model needs for THIS turn.
type StopDecision ¶
type StopDecision struct {
// Continue re-opens the run instead of accepting the answer.
Continue bool
// Inject is the message explaining why, added to the conversation and
// persisted. Continue with nothing to inject would spin the loop against an
// unchanged conversation, so it is treated as accepting the finish.
Inject []Message
// Note is a short, user-facing progress line. The raw Inject text is
// internal rail scaffolding and is deliberately NOT shown to the end user.
Note string
// StopReason overrides the recorded reason when this extension gives up
// (a stalled goal, an exhausted guard).
StopReason string
}
StopDecision is a StopInterceptor's answer. The zero value accepts the finish.
type StopInfo ¶
type StopInfo struct {
// Final is the answer the model produced.
Final string
Turns int
// Tools is the run's tool trace, so a guard can check what evidence exists.
Tools []ToolTrace
// Attempt counts how many times THIS extension has already re-opened the
// run, so a guard can bound itself.
Attempt int
}
StopInfo describes a run that is about to finish normally.
type StopInterceptor ¶
type StopInterceptor interface {
TurnStopping(ctx context.Context, info StopInfo) StopDecision
}
StopInterceptor is consulted when the model produces a final answer and the run would end normally. Implement it to hold the run to a contract — a completion sentinel, a verification pass, an evidence requirement.
It is NOT consulted on a stop the run did not choose: a budget wrap-up, a tool-budget stop, a MaxTurns stop, an abort, or a terminal tool. Nudging those would wedge a run that is already being shut down.
Interceptors are consulted in registration order and the FIRST one that returns Continue wins; the rest are not consulted, so two guards cannot both inject into the same turn.
type StreamDecision ¶
type StreamDecision struct {
// Abort cuts the in-flight assistant message: the loop cancels the
// provider stream, discards the partial text, appends Inject to the
// conversation, and retries the turn's provider call from the same point.
// Abort with nothing to Inject would retry against an unchanged
// conversation — the model would emit the same tokens and abort again — so
// it is treated as "no opinion" and the stream completes.
Abort bool
// Inject is the correction the retried turn must see — the rule body the
// matched pattern violated. The LOOP appends it to the history and persists
// it like a steer, so a resumed run replays the conversation the retry was
// actually given.
Inject []Message
}
StreamDecision is a StreamInterceptor's answer. The zero value lets the stream continue untouched.
type StreamEvent ¶
type StreamEvent struct {
Type StreamEventType
Token string // set when Type == StreamToken
Tool *ToolTrace // set when Type == StreamTool
Note string // set for StreamProgress and textual tool updates
Card *ResultCard // set when Type == StreamCard
Question json.RawMessage // set when Type == StreamQuestion (the parked call's args)
Turn int
}
StreamEvent is one increment surfaced to a live viewer (the SSE chat endpoint) while a run is in flight. Native callbacks are serialized; background progress may arrive from child goroutines. Token/Tool/ToolExecUpdate are emitted by the core loop; Progress includes native compaction and run-owned child activity. Cards are supplied by consumers. Sinks must not reenter the same run's sink.
type StreamEventType ¶
type StreamEventType string
StreamEventType classifies an incremental event emitted during a streamed run.
const ( StreamToken StreamEventType = "token" // a text fragment of the assistant's answer StreamTool StreamEventType = "tool" // a completed tool-call trace StreamProgress StreamEventType = "progress" // a plain-language progress note (no tool identifier) StreamCard StreamEventType = "card" // a structured result card (stat | series) // Granular lifecycle events (pi's event vocabulary). They are additive: a // consumer that only reads token/tool/progress/card keeps working, while an // observability layer can reconstruct turn / message / tool-execution // boundaries. Emitted only on a streamed run (nil sink => none). StreamAgentStart StreamEventType = "agent_start" // run begins StreamTurnStart StreamEventType = "turn_start" // a reasoning turn begins StreamMessageStart StreamEventType = "message_start" // assistant message begins (before tokens) StreamMessageEnd StreamEventType = "message_end" // assistant message complete StreamToolExecStart StreamEventType = "tool_execution_start" // a tool call begins StreamToolExecUpdate StreamEventType = "tool_execution_update" // a streaming tool's partial output (P8) StreamToolExecEnd StreamEventType = "tool_execution_end" // a tool call finished (carries the trace) StreamTurnEnd StreamEventType = "turn_end" // the turn (reason + act) is complete StreamSavePoint StreamEventType = "save_point" // a turn's buffered durable writes were flushed atomically StreamAgentEnd StreamEventType = "agent_end" // run ends (any exit path) // StreamQuestion carries a parked call's validated arguments: a tool asked // the human a structured question and the run is now waiting on the answer. StreamQuestion StreamEventType = "question" )
type StreamInterceptor ¶
type StreamInterceptor interface {
InterceptStreamDelta(ctx context.Context, accumulated string) StreamDecision
}
StreamInterceptor observes the assistant's output WHILE it is being produced and may cut it off mid-token. It is the only extension point that fires on partial output: every other interceptor sees a finished artifact (a tool result, a batch, a final answer), which is too late for a rule whose whole purpose is that the model must never complete the pattern — a leaked secret, a forbidden phrase, a banned construct. Aborting mid-stream is what keeps the completed violation out of the transcript entirely.
The interceptor is handed the assistant text accumulated SO FAR this turn — the same buffer the loop is building — once per content delta, and again once on the non-streaming path with the completed response. It is called synchronously inside the delta loop, so a check must be cheap: a regex over the buffer, not a network call. Interceptors are consulted in registration order and the FIRST abort wins; the rest are not asked.
The loop retries the turn after an abort, so an interceptor MUST bound itself (a per-turn injection cap) or it spins the provider call forever. The loop backstops a runaway interceptor at maxStreamAbortsPerTurn and then lets the stream complete unmodified — pass-through, not failure, because a guard that can take the run down is worse than the output it was policing.
type StreamSink ¶
type StreamSink func(StreamEvent)
StreamSink receives display events during a run. A nil sink suppresses these notifications; native provider execution still consumes its stream.
type StreamingTool ¶
type StreamingTool interface {
RunStreaming(ctx context.Context, args string, emit func(partial string)) (string, error)
}
StreamingTool is an optional Tool capability (pi's tool_execution_update): a long-running tool may emit partial output as it works via the supplied emit callback, which the loop forwards to the stream sink so a viewer sees progress before the tool finishes. The returned string is still the authoritative final result (identical to Run's), and a tool that does not implement this runs through Run unchanged. emit is a no-op on a non-streaming run.
type StringTool ¶
type StringTool struct {
ToolName, Description string
Properties map[string]any
Required []string
Execute func(context.Context, map[string]string) (string, error)
}
StringTool declares a callback tool with string-valued JSON arguments. The host supplies only its schema and action; AgentCore validates and dispatches it through the same governance boundary as any other Tool.
func (StringTool) Name ¶
func (t StringTool) Name() string
func (StringTool) Schema ¶
func (t StringTool) Schema() ToolSchema
type Tool ¶
type Tool interface {
// Name is the stable identifier the model calls.
Name() string
// Schema advertises the tool to the model (JSON Schema parameters).
Schema() ToolSchema
// Run executes with validated JSON arguments and returns a result string
// (already truncated by the loop before reaching the model).
Run(ctx context.Context, args string) (string, error)
}
Tool is a single capability the agent can invoke. Implementations are provided by the consumer (host-injected): the user's Agent Definition may reference and permit tools but can never conjure new ones, keeping the capability surface under code control.
type ToolChoice ¶
type ToolChoice = protocol.ToolChoice
type ToolChoiceMode ¶
type ToolChoiceMode = protocol.ToolChoiceMode
type ToolContributor ¶
type ToolContributor interface {
// Tools returns this extension's tools for this run. Called once, after
// BeginRun, so the set may depend on run state (depth, durability).
Tools() []Tool
}
ToolContributor adds tools to the run's registry. Implement it to give the model a capability.
Contributing a tool is not granting it: the permission gate still decides whether the model may call it, unless the tool also declares itself self-gated (see SelfGated).
type ToolDenialReason ¶
type ToolDenialReason string
ToolDenialReason is why a tool call was refused (ToolTrace.Reason). Distinct from RunResult.StopReason — they share the "aborted" spelling by coincidence (a cancelled run stops with StopReason "aborted" AND remaining tool calls are denied with this reason). The JSON/wire value is the existing literal so rows already written keep classifying.
const ( // ToolDenialAborted is the loop's denial reason when a remaining call // is short-circuited because the run was cancelled (agentcore/loop.go). // ClassifyTool buckets this as ToolAborted; the loop itself also // compares against it to decide whether a result is settled enough to // persist. A typed constant so those two sites cannot drift from a typo. ToolDenialAborted ToolDenialReason = "aborted" )
type ToolGate ¶
type ToolGate struct {
CallID string `json:"call_id"`
Allowed bool `json:"allowed"`
Reason string `json:"reason,omitempty"`
Error string `json:"error,omitempty"`
}
ToolGate is the persisted gate outcome of one tool call the model requested. CallID matches ToolCall.ID. The LLM-call trace used to carry only the request (name + args), so FoldSteps had nothing to read and hardcoded Allowed: true — a replayed denial rendered as an allowed call, which is worse than missing data on a debugging surface. Empty ToolGates on a TurnRecord is the pre-column default; FoldSteps does not rewrite those as denied.
type ToolInterceptor ¶
type ToolInterceptor interface {
InterceptToolResult(ctx context.Context, call ToolCall, result string, runErr error) ToolResultDecision
}
ToolInterceptor wraps tool execution. It runs AFTER the permission gate and after the tool has executed, so it observes only calls that were actually allowed and run.
Interceptors are chained in registration order, each seeing the previous one's Result — a waterfall, so a size limiter and a redactor compose.
type ToolInvocation ¶
type ToolInvocation struct {
Trace ToolTrace `json:"trace"`
Executed bool `json:"executed,omitempty"`
}
ToolInvocation is the audit/accounting projection of a tool call initiated from inside another tool. Eval kernels use it for host-tool bridges: the nested call does not become a second provider-authored tool message, but it still has to survive in the outer call's durable outcome and RunResult.
type ToolInvoker ¶
type ToolInvoker interface {
InvokeTool(ctx context.Context, name, args string) (ToolOutput, error)
}
ToolInvoker is the run-owned capability for invoking another registered tool from inside a tool. Implementations must enter through Agent.runToolCall; a raw Tool.Run call would bypass validation, policy, hooks, credentials, result bounds, and idempotency. The capability exists only on contexts handed out by a live Agent run, so constructing a Tool directly does not grant delegation.
func ToolInvokerFrom ¶
func ToolInvokerFrom(ctx context.Context) (ToolInvoker, bool)
ToolInvokerFrom returns the run-owned nested invocation capability, when the current call is executing inside an Agent loop.
type ToolOutcomeRecord ¶
type ToolOutcomeRecord struct {
Message Message `json:"message"`
Trace ToolTrace `json:"trace"`
Invocations []ToolInvocation `json:"invocations,omitempty"`
Extra []Message `json:"extra,omitempty"`
Terminate bool `json:"terminate,omitempty"`
Executed bool `json:"executed,omitempty"`
}
ToolOutcomeRecord is the bounded, model-visible result of one completed tool call plus the run semantics that must survive a crash before source-order placement. It deliberately stores no raw/unbounded tool output: Message is the same truncated or spill-backed value the live model receives.
type ToolOutput ¶
type ToolOutput struct {
Content string
Parts []ContentPart
// Invocations records governed tools called from inside this tool. It is
// audit/control metadata, never provider-visible content. Eval uses it to
// preserve host-tool bridge traces in the outer call's durable outcome.
Invocations []ToolInvocation
// AdditionalContexts and Terminate propagate the same decisions a direct
// nested call would have produced, but the loop applies them only after the
// outer tool result so provider tool-call/result adjacency remains valid.
AdditionalContexts []Message
Terminate bool
}
ToolOutput is the optional structured result of a RichTool. Content remains the model-visible text result and is processed by the same interceptors, hooks, bounds, traces, and spill policy as Tool.Run. Parts are additive rich content and are bounded independently at the dispatch boundary; providers that cannot carry them degrade explicitly to text.
type ToolResultArchiver ¶
type ToolResultArchiver interface {
ArchiveToolResult(context.Context, ToolCall, string) (string, error)
}
ToolResultArchiver is an optional durable-output seam for request-only context rescue. It must save the complete text before returning a locator served by the run's existing retrieval tool. It grants no new effects.
type ToolResultDecision ¶
type ToolResultDecision struct {
// Result replaces the tool's output when Replace is true. Replacing is
// explicit rather than inferred from a non-empty string, so an interceptor
// cannot erase a result by returning a zero value.
// Replacing also tells the loop the result is already bounded, so its own
// default truncation is skipped — that is what lets a lossless bounding
// strategy exist at all, since the default is lossy and runs first
// otherwise.
Result string
Replace bool
// Meta annotates the trace without entering the model's context (for example
// a cache verdict). Empty leaves the trace unchanged.
Meta string
// ResultRef is an opaque handle that recovers content omitted from Result
// (for example a spill locator). It is persisted on the tool message and
// context-reduction paths preserve and surface it in replacement text. Keep
// unrelated trace metadata in Meta: the model may be shown ResultRef later.
ResultRef string
// AdditionalContexts are messages to add to the conversation because of
// this call. The LOOP appends them after ALL tool results in the batch —
// never interleaved, which would break tool-call/result adjacency — and
// persists them to the durable log.
//
// Persistence is the loop's job precisely so it cannot be forgotten: an
// extension that injects a message the model sees but the log misses would
// make a resumed run replay a different conversation. Plugins get the
// injection; the core keeps the invariant.
AdditionalContexts []Message
// Terminate ends the run cleanly after this batch (a terminal tool).
Terminate bool
}
ToolResultDecision is a ToolInterceptor's answer.
The zero value means "no opinion": the result passes through untouched. This matters — an interceptor that only wants to OBSERVE returns the zero value and cannot accidentally blank a result.
type ToolSchema ¶
type ToolSchema = protocol.ToolSchema
type ToolSet ¶
type ToolSet struct {
// contains filtered or unexported fields
}
ToolSet is an ordered registry of tools keyed by name.
func NewToolSet ¶
NewToolSet builds a registry from the given tools, preserving order.
func (*ToolSet) Schemas ¶
func (ts *ToolSet) Schemas() []ToolSchema
Schemas returns the advertised schemas in registration order.
func (*ToolSet) With ¶
With returns a COPY of the set with the given tools added, leaving the receiver untouched. The loop assembles a run's effective registry this way — host tools plus whichever built-ins the run enables (read_skill, spawn_subagent, read_spill, job_*, session_query) — so the shared Agent toolset is never mutated per run and two concurrent runs of the same definition cannot see each other's built-ins.
type ToolStrictness ¶
type ToolStrictness = protocol.ToolStrictness
type ToolTrace ¶
type ToolTrace struct {
// CallID is the provider's id for this specific invocation. It is what makes a
// trace addressable: two concurrent calls to the same tool differ in nothing
// else, so a consumer keying on name (or on array position, which shifts as
// the list grows) reconciles the wrong one onto the other. Empty only for a
// synthesized trace with no originating model call.
CallID string `json:"call_id,omitempty"`
Tool string `json:"tool"`
Args string `json:"args"`
Allowed bool `json:"allowed"`
Reason string `json:"reason,omitempty"`
Error string `json:"error,omitempty"`
ResultMeta string `json:"result_meta,omitempty"`
LatencyMS int64 `json:"latency_ms,omitempty"` // wall-clock of the tool execution (0 when not executed)
// IdempotencyKey is the framework-derived dedupe key handed to the tool
// (empty on runs without a durable session). Persisting it lets an external
// side effect be correlated back to the exact logical call that caused it.
IdempotencyKey string `json:"idempotency_key,omitempty"`
// SpillLocator is set when the result was too large for the context and its
// full text was saved to a spill artifact (plugins/spill). It makes the complete
// output recoverable from the trace long after the run, not just by the model
// mid-run — an oversized result is no longer lost to observability either.
SpillLocator string `json:"spill_locator,omitempty"`
}
ToolTrace is a persisted projection of one tool execution (§9 agent_tool_calls): tool name, validated args, whether it was allowed, and result metadata.
func (ToolTrace) DeniedAborted ¶
DeniedAborted reports whether this trace is the loop's cancel-denial. Historical rows carry the bare string "aborted"; the comparison is on that wire value.
type ToolsPlugin ¶
ToolsPlugin contributes tools to a Registry.
func ToolsFromSet ¶
func ToolsFromSet(ts *ToolSet) ToolsPlugin
ToolsFromSet builds a ToolsPlugin from an existing ToolSet.
func ToolsOf ¶
func ToolsOf(list ...Tool) ToolsPlugin
ToolsOf builds a ToolsPlugin from a list of tools.
func (ToolsPlugin) Register ¶
func (p ToolsPlugin) Register(r *Registry) error
Register adds the tools.
type TurnHook ¶
TurnHook observes a turn boundary. Unlike the StreamTurnStart / StreamTurnEnd stream events — which only reach a viewer on a *streamed* run — turn hooks fire on every run, so metering and audit do not depend on someone watching. Read-only: a return value is not threaded back.
type TurnInfo ¶
type TurnInfo struct {
Turn int
// Model is the rung actually in use for this turn.
Model string
// Usage is the run-to-date total at the boundary.
Usage Usage
// StopReason is set at turn end only (empty at turn start) and carries the
// provider's reason for the turn that just completed.
StopReason string
}
TurnInfo describes one reasoning turn at its boundary (pi's turn_start / turn_end). Usage is the run total accumulated so far, not this turn's slice.
type TurnRecord ¶
type TurnRecord struct {
NativeTrace json.RawMessage
SessionKey string
Depth int
Messages []Message // the request messages sent to the model this turn
Response string // assistant text returned
ReasoningBlocks []ReasoningBlock // opaque provider replay blocks returned with the assistant turn
ToolCalls []ToolCall // the tool calls the model requested this turn
// ToolGates is the gate outcome of each call in ToolCalls. Empty means the
// recording predates this field (or the turn requested no tools) — FoldSteps
// then keeps the historical default rather than inventing denials. See ToolGate.
ToolGates []ToolGate
Tools []string // advertised tool names this turn
StopReason string
Error string
TokensIn int
TokensOut int
CostUSD float64
}
TurnRecord is one recorded LLM call within a run — the unit the fold consumes. A consumer maps its persisted trace rows (storage.AgentLLMCall) onto this neutral shape so the fold itself imports no storage.
func ApplyLiveGates ¶
func ApplyLiveGates(records []TurnRecord, traces []ToolTrace) []TurnRecord
ApplyLiveGates overlays in-memory dispatcher verdicts onto TurnRecords whose ToolGates are still empty. Live explain folds at StepGate, which fires before persistTrace writes tool_gates_json; FoldSteps then treats empty gates as the historical Allowed:true default and a live denial renders as allowed until the run ends. This is the consumer mapping that closes that gap without a DB round-trip and without importing storage into FoldSteps (TurnRecord exists for that reason).
Records that already carry gates (replay, or a persistTrace that has landed) are left alone — live overlay must not rewrite persisted truth. A crash mid-run therefore degrades to today's empty-gates view rather than inventing denials. Pure: same inputs → same records.
The join key is ToolCall.ID / ToolTrace.CallID. Traces without a CallID cannot be paired and are skipped.
type TurnState ¶
TurnState is the per-turn save-point: the model, tools, and system prompt that will drive the next provider request. After each turn the loop hands the current state to a consumer's PrepareNextTurn hook, which may return a modified copy; the change applies to the next turn only and never mutates the in-flight request (pi's prepareNextTurn). Messages is supplied read-only for the hook to inspect — returning a different slice does not replace the loop's history.
Source Files
¶
- agent.go
- background.go
- compaction.go
- compose.go
- contextprune.go
- definition.go
- doc.go
- env.go
- extension.go
- faux.go
- fork.go
- goal_revision.go
- hooks.go
- image.go
- lab.go
- memory.go
- memsession.go
- native_run.go
- native_session.go
- permission.go
- pi_lifecycle.go
- pi_tools.go
- plugin.go
- prompt.go
- provider.go
- provider_session.go
- result.go
- run_lifecycle.go
- run_policy.go
- schema.go
- seams.go
- session.go
- session_tree.go
- skill_tool.go
- tool.go
- toolbridge.go
- tooldispatch.go
- turn.go
Directories
¶
| Path | Synopsis |
|---|---|
|
Code generated by testdata/generate-format-patterns.ts; DO NOT EDIT.
|
Code generated by testdata/generate-format-patterns.ts; DO NOT EDIT. |
|
Package host supplies reusable policy and checkpoint utilities around the native engine.
|
Package host supplies reusable policy and checkpoint utilities around the native engine. |
|
plugins
|
|
|
advisor
Package advisor reviews completed work and, optionally, work in progress at turn boundaries.
|
Package advisor reviews completed work and, optionally, work in progress at turn boundaries. |
|
ask
Package ask contributes the `ask` tool: the agent poses a structured question to the human — a prompt plus labeled options, optionally multi-select — and the run parks until the answer arrives.
|
Package ask contributes the `ask` tool: the agent poses a structured question to the human — a prompt plus labeled options, optionally multi-select — and the run parks until the answer arrives. |
|
finishguard
Package finishguard installs verify-on-stop: a bounded second look before a normal finish is accepted.
|
Package finishguard installs verify-on-stop: a bounded second look before a normal finish is accepted. |
|
goal
Package goal installs the run-level completion contract: a goal-gated run may only stop when it says so.
|
Package goal installs the run-level completion contract: a goal-gated run may only stop when it says so. |
|
jobs
Package jobs lets a tool go asynchronous.
|
Package jobs lets a tool go asynchronous. |
|
memory
Package memory installs the working-memory store used for recall across runs, and the model-facing curation tools that let the agent revise what it remembered: `learn` captures a reusable lesson, `memory_edit` updates or retracts a stored entry by id.
|
Package memory installs the working-memory store used for recall across runs, and the model-facing curation tools that let the agent revise what it remembered: `learn` captures a reusable lesson, `memory_edit` updates or retracts a stored entry by id. |
|
preset
Package preset composes agentcore's default agent out of the plugin packages.
|
Package preset composes agentcore's default agent out of the plugin packages. |
|
repeatguard
Package repeatguard breaks a run out of a tool-call loop by TELLING the model it is in one.
|
Package repeatguard breaks a run out of a tool-call loop by TELLING the model it is in one. |
|
sessionquery
Package sessionquery gives an agent authorized retrieval over its own durable session log.
|
Package sessionquery gives an agent authorized retrieval over its own durable session log. |
|
spill
Package spill keeps an oversized tool result out of the model's context WITHOUT destroying it.
|
Package spill keeps an oversized tool result out of the model's context WITHOUT destroying it. |
|
subagent
Package subagent installs spawn_subagent: self-forking and cross-agent delegation under shared depth and budget caps.
|
Package subagent installs spawn_subagent: self-forking and cross-agent delegation under shared depth and budget caps. |
|
todo
Package todo contributes a live run plan: a checklist the model writes for itself and that the loop pins into every request.
|
Package todo contributes a live run plan: a checklist the model writes for itself and that the loop pins into every request. |