agentcore

package
v0.0.0-...-6732be9 Latest Latest
Warning

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

Go to latest
Published: Oct 7, 2026 License: MIT Imports: 39 Imported by: 0

README

agentcore

The server uses the native Go engine by default. Execution lives in engine/, provider transcripts and transports in ai, and recording in telemetry. Application composition, credentials and persistence adapters stay in the consuming application.

The supported completion scope is Codex, Antigravity, Gemini and Claude Code. TypeScript source, worker transport, fixture generators and Bun build tooling have been removed. Go tests consume recorded JSON fixtures; source hashes and licenses remain in third_party/pi.

The root owns host composition, governed tool dispatch and plugins. All run entry points execute the native engine. Provider contracts live in ai/protocol so the facade can depend on the engine without an import cycle. Package boundaries follow ownership and dependency direction; no additional interface hierarchy is needed.

Native migration and consumer compatibility

The server and Soot use the native engine. The old root loop, configurable Driver, server legacy dispatch and runtime-selection flag are removed. Prompt, PromptStream, Continue and ContinueStream now require Config.NativeProvider. Continue accepts newly authored host input; resume provider history through RunNative and RunResult.NativeState. Durable execution uses the native session host. Recorded native fixtures and the integration suite run with ordinary Go tests. Native integration tests cover memory, compaction, todo and advisor. Native retry/fallback orchestration now lives in ai.FallbackProvider. The runtime binds durable selection and accounting to its callbacks. ai.NativeProvider owns native transport dispatch, and engine.StreamFn aliases the shared ai.StreamFn contract.

engine owns native scheduling and provider events. The root still supplies composition, governed tools and plugins. A new engine does not itself replace the host's context compaction or session policy.

The process bridge (NewPi, PiAgent) and its root-level configuration types (PiConfig, PiCallback, PiError) have been removed. Native consumers use engine.AgentOptions/engine.NewAgent; the application's JSON callback binding is owned by the consuming host configuration. Sessions now hold the concrete native agent, without the temporary worker/native function table or production string-command dispatcher. Recorded worker commands are decoded only by tests.

The earlier port also removed LabStepDiff, DiffStep, and the observe helpers AggregateRunSummaries/AggregateRunCoverage. Existing Plugin, Tool, LLMProvider, SessionStore and extension interface method sets are unchanged; no plugin package has been removed. Provider contracts are now owned by ai/protocol and re-exported by the root package while callers migrate. Native integrations use Hooks.PiContext for native transcripts and subagent.Settings.RunFork for native child runs.

The sibling Soot consumer uses New with Config.NativeProvider and RunNative for both case work and conversations. Its provider factory builds ai.NativeClient candidates under ai.FallbackProvider; the store atomically persists RunResult.NativeState with the public answer. Legacy context arrays are imported once as untrusted source facts, never replayed as provider history.

RunNative accepts an opaque prior checkpoint and newly authored user/system input. It composes the existing plugins and governed tools around engine.Agent, with native compaction and explicit-parent telemetry. Display Messages are output only. Durable sessions use the application's native session host for in-flight effects, leases and parked delegations. Ephemeral native subagents continue corrections from NativeState, preserving their original transcript.

Shared native compaction and transcript-checkpoint utilities live in host/; application persistence and credentials remain in the consumer. The same package owns host-input conversion and display projections. Native request observers receive the final transformed provider view; successful compaction emits a rebase before the request observation, and settled messages emit append observations. This keeps advisor and other existing RunObserver plugins informed without giving them control over native history. Integration tests exercise memory, todo, advisor and compaction through the native engine.

Native context summary progress reports each chunk's settlement, including a failed chunk. It does not claim that the entire compaction succeeded or that the next model request is ready. Chunk and model-request spans have individual timings in telemetry/export.Batch.SpanTimings. Async subagent display progress uses the run-owned observer in background.go, fenced on native run teardown.

The existing kernel is one flat package. The native Go replacement lives in engine/; the other subdirectories are ejectable plugins/ and black-box integration/ tests. The existing root package has a hard dependency rule in both directions:

Composition names no plugin and imports only shared runtime modules. Provider contracts live in ai/protocol, below the agent facade; the native loop lives in engine, and reusable host policy lives in host. Delete every package under plugins/ and this package still compiles, still runs, and still passes its tests — it just does less.

These boundaries are tested: boundary_test.go reads the package's own imports and fails on a plugin or application import. That is what makes the kernel publishable on its own and what makes plugins/README.md's ejectability claim true.

Everything structural on this page is enforced the same way, because a rule that only this file knows is a rule that drifts:

test holds
TestKernelNamesNoPlugin no plugins/ import from the root package
TestKernelRuntimeDependencies only AI, engine, host, telemetry and shared JSON dependencies
TestKernelTreeHoldsOnlyDeclaredBoundaries only the declared engine/, host/, plugins/, and integration/ boundaries sit below the root
TestNativeEngineNamesNoHost the native engine directly imports only AI/telemetry modules, the shared JSON value codec, and libraries; it cannot launch a subprocess or import host policy
TestEveryKernelFileJustifiesItself every root .go file has a row below
TestPluginsDoNotNameEachOther no plugin imports a sibling (except preset)
TestEveryPluginDocumentsItself every plugin folder has a README.md

The API-side version of this page — the boundary rules, the three kinds of contribution, and the layer map — is doc.go, so a reader who arrives through go doc gets it too.

Why the root is flat

The obvious tidy-up — agentcore/session/, agentcore/context/, agentcore/tools/ — is the wrong move here, because these files are not independent concerns that happen to sit together. They are one machine: the loop reads the limits, writes the log, dispatches the extensions, brackets the compaction, and stamps the idempotency key, all through unexported fields of one Agent. Splitting them into packages would either export that machinery (making it everyone's to break) or produce packages that can only be imported in one order — folders pretending to be boundaries.

Flat is not the same as unordered, though, and the distinction is where this package earns the choice. The layering is real — composition sits above the contracts, the contracts above the loop, the loop above the log — it is just carried by file rather than by folder, and by a rule about which direction job: tooldispatch.go is the trust boundary, turn.go is one turn against the model, compose.go is how an agent gets built. A file earns a split only when it starts needing "and" to describe a boundary, not a subject — the loop's own retry, schema validation, and idempotency stamps live inside the files that use them rather than beside them. doc.go lists the layers in order.

The real boundary is plugins/, and it is enforced above. So the organizing question for the root is not "which folder?" but:

Why is this file in the kernel and not a plugin?

There are exactly four answers, and every file below gives one. A file that cannot give one belongs in a plugin, or belongs nowhere.

answer means
loop the loop calls it directly on every run; there is no composition in which it is absent
contract a type the loop dispatches over, so a plugin outside this repo can implement it
log it reads or writes durable run state, and only the log's owner may
seam default the built-in behind a replaceable seam, so an agent composed with nothing still works

The files

Composition — how an agent gets built
file why core
doc.go contract — the package doc: the two boundary rules, the three kinds of plugin contribution, and the layer map below rendered where go doc can see it. No code.
provider.go contract — LLMProvider, ChatRequest/ChatResponse, Usage, and provider-neutral text/image ContentParts. The wire seam every model call goes through, kept small enough that an implementation is a translation layer and nothing more.
provider_session.go contract + seam default — provider-private conversation state with selective account-rotation reset, plus the bounded lease-aware in-process registry. The loop carries the session on every request; providers own the concrete records, and a cache miss may cost discovery but never change correctness.
plugin.go loop — Plugin, Registry, Priority. The composition surface itself: seam setters, additive contributions, per-plugin Unload.
compose.go loop — Build, BuildRegistry, ApplyConfig, and Limits/DefaultLimits: the run's bounds are chosen at composition, read every turn, and published to extensions through RunInfo. There is no composition in which a run is unbounded.
agent.go loop — the configured runtime instance (capability-bearing fields unexported on purpose, see fork.go) plus Agent.Describe(): what the agent is actually configured with after every default and override.
definition.go contract — AgentDefinition, Skill, SkillLoader, and the always-loaded byte cap that keeps the system prompt bounded.
seams.go seam default — ConfigPlugin, ModelPlugin, DefinitionPlugin, PolicyPlugin, ToolsPlugin, HooksPlugin, BudgetPlugin, SessionPlugin, CompactionPlugin, SteeringPlugin. Core adapters that claim seams during Build(...) without needing fake wrapper packages under plugins/.
The loop and its extension points
file why core
run_policy.go Shared native-run ceiling messages and bookkeeping classification.
turn.go loop — one turn against the model: capability shaping (including copy-on-write request-wide image budgets), ProviderError classification, same-rung retry, then escalation down the ladder, plus the streaming path. Retry lives with the loop, not in a provider, so failure behaviour cannot differ per vendor. That includes reading usage off the stream: a delta's Usage is a running total that may arrive at any point (Anthropic states input tokens before the first output token; OpenAI sends a usage-only chunk after the terminal one), so the turn keeps the newest non-zero value of each field rather than whatever rode Done. Getting it wrong is silent — the answer is still correct and only the number the budget gate meters on is zero.
tooldispatch.go loop — one tool call end to end: lookup → prepare → validate → gate → execute → bound → trace. The trust boundary, applied in exactly one place so it is unskippable rather than usually-called. Also the two context stamps a call carries: the idempotency key derived from (sessionID, toolCallID) — stable across crash-resume because both already survive in the log — and the provider-assigned tool-call id a spawn tool derives the child's deterministic session from.
result.go contract — RunResult, StreamEvent and the event vocabulary, ResultCard. The loop's output side, which consumers render and plugins observe. (ToolTrace sits with the code that fills it, in tooldispatch.go.)
background.go contract — run-owned background launcher and progress observer shared by independent plugins; no scheduler or task policy in core.
extension.go contract — every extension point (ToolInterceptor, StepInterceptor, StopInterceptor, RunObserver, ToolContributor, …) and the extensionSet the loop dispatches through. The file that forbids naming a plugin.
run_lifecycle.go contract + dispatch — optional settled-boundary controls, host command handlers and finalization before native checkpoints; no capability-specific state.
hooks.go contract — the lifecycle hook types and their dispatch, including the BeforeToolCall shape the permission gate is built from.
tool.go contract — Tool, optional additive RichTool, ToolSet, ArgPreparer, and the loop's own byte bounding. Ordinary text stays compatible while image parts can cross the neutral provider seam.
toolbridge.go contract + loop — the run-owned ToolInvoker capability for tools such as eval that need to call another registered tool. Nested calls re-enter the single dispatch trust boundary (schema, policy, credentials, hooks, bounds, tracing, idempotency), share the run's atomic execution budget, reject recursion, and persist as audit metadata without inventing provider-authored tool messages. Directly constructing a tool never grants this capability.
pi_tools.go host adapter — exposes a composed Agent's tools to the native Go engine through the existing dispatch boundary. Owns extension resources and the busy slot, supplies native tool definitions/results, and shares the execution budget with nested calls.
native_session.go Restores the native transcript from journal entries, retaining opaque messages and rejecting unsettled effects. Consumer workflow validation remains separate.
native_run.go Composed plugins and consumer checkpoints over the native engine; AI-owned retry/fallback.
pi_lifecycle.go Native-loop host boundary for composed prompt, step, batch, stop, and ceiling policies. Calls extension contracts while Pi owns scheduling and native history.
image.go loop — the central rich-image trust boundary: decode validation, MIME correction, pixel/input limits, provider-portable resize/recompression, aggregate byte budgeting, and coordinate mapping. Keeping it beside tool dispatch gives every rich tool identical laptop/server behavior without trusting each implementation to normalize correctly.
permission.go contract + seam default — Policy, Decision, and DenyAll. Default-deny is the kernel's, so a composition that forgets governance is not ungoverned.
prompt.go loop — system-prompt assembly, recall dedup, and provider-neutral prompt-cache breakpoints placed at the end of the request's append-only prefix (the persisted history), not on its final message. A ContextHook trailer is re-rendered every turn, so a prefix ending in one can never be read back: measured on a 300-turn run, 7 of 299 cache entries were still a prefix of the next request; anchoring before the trailer makes it 277 of 299 (the rest are the 22 compactions, which legitimately rewrite the window).
skill_tool.go loop — the read_skill built-in, registered by the loop whenever a definition carries skills. Progressive disclosure is part of the prompt, not an add-on.
schema.go loop — compiles OutputSchema at composition time and validates final text locally, so providers that ignore structured-output hints cannot silently violate the result contract.
Durable state — only the log's owner may touch these
file why core
goal_revision.go contract + log seam — a consumer-authored completion-condition change and its context-scoped commit boundary. Native tools commit before the plugin publishes the condition; goal policy, validation, and the revision tool stay in plugins/goal.
session.go log — SessionEntry kinds, SessionStore, reduce/recover, side records (EntryInbox/EntryInboxDone, EntryAssistantFrame, EntryToolProgress, EntryToolOutcome), idempotent parked-call answers, optional SessionLeaseStore ownership, and the goal as a fact about the run (written once, recovered on resume; what to DO about an unmet goal is plugins/goal). The log is the source of truth; run state is rebuilt by reducing it, never mutated in place. Chain entries form the session tree; side records capture intent and mid-turn granularity without forking the chain. A completed outcome is journaled before slower siblings finish, then superseded by its canonical source-order result. SessionBatchStore upgrades a turn save point from ordered-prefix compatibility to an atomic commit. The windowed read (SessionWindowStore, LoadResumeLog) lets the fold restart at a checkpoint, so a resume reads a suffix rather than a history — and the rule for when that is safe lives here, once, rather than in each backend.
session_tree.go log — the log is a tree: parent ids, branches, EntryLeafMove, Rewind. Reduce and recover walk only the active branch.
memsession.go seam default — in-process append-only SessionStore with atomic save-point batches and per-session resume ownership, so a laptop run is resumable and the log/concurrency invariants are checkable with nothing wired.
compaction.go log + seam default — WHEN to compact and the durable bracket around it, plus the goal pin that survives summarization, and Compactor/DefaultCompactor: WHAT replaces the old span, as a strategy with more than one right answer. The trigger is min(model window − output headroom, configured ceiling), re-derived per turn from the answering rung: the ladder routinely mixes models whose windows differ by 30x, and a ceiling too high for the current one means the loop never compacts before the provider rejects the request. The kernel knows no model's window — the rung carries it.
contextprune.go contract + seam default — ContextPruner is the optional deterministic first-stage contract; the built-in implementation batch-prunes superseded, stale, and bulky old tool results before paying for an LLM summary. It protects deep warm-cache prefixes, requires meaningful aggregate savings for age-only rewrites, and preserves recoverable artifact references. A replacement compactor brings its own policy; the loop still owns the durable bracket around every rewrite.
fork.go loop — everything that crosses an agent boundary: building a child from a parent's unexported fields (a delegation plugin cannot do this from outside without those fields becoming exported, which is exactly how a child ends up out-scoping its parent), delegation depth (a spawn plugin's recursion cap is only enforceable if the depth survives the hop), and the run's session id (a sub-agent shares its parent's provider and ctx, so without this tag the two are one undifferentiated stream to anything decorating the seam). All three ride ctx — the only thread that survives the hop.
lab.go log — the read model: one pure fold from recorded facts to ordered steps, so a live-stepped run and a replayed run read identically — and the same fold one level up as chapters: a run's compaction summaries ARE its table of contents, and dividing the fold at them is what makes a several-thousand-step run navigable rather than merely paginated.
Host capabilities — injected, never reached for
file why core
env.go execution contract — host-owned tool infrastructure: Env, the Sandbox, optional SessionSandbox and ProcessSandbox capabilities (persistent eval and LSP), session-id plumbing, and CredentialResolver. These are not agent plugins. The host binds sandbox backends to tools; the executor resolves credentials after gating. Env.Sandbox is diagnostic metadata, not an isolation switch.
memory.go contract — MemoryEntry, MemoryStore, Embedder, Cosine: cross-run recall as a seam the consumer backs. A nil store is valid; ranking degrades to keyword recall rather than failing.
faux.go seam default — the providers that make the loop testable with no network and no key: FauxProvider (scripted) and ReplayProvider (plays a recorded []TurnRecord and asserts the loop rebuilt the same request, so a transcript is a regression test). omp has no record/replay; this is agentray's.

Tests in the root

Root tests exercise loop-owned behaviour, not plugin policy. Two that look like plugin tests and are not, because the state is the kernel's:

  • budget_test.go — the graceful-stop protocol (one tool-free wrap-up turn, then budget_exhausted) is the loop's; BudgetPlugin in seams.go only supplies the ceiling.
  • session_test.go, compaction_test.go — the goal's persistence and its survival through compaction are the log's; plugins/goal owns the completion protocol and is tested there.

Black-box tests that import plugins now live in integration/. They prove capabilities against the real public loop without mixing consumer composition into the kernel's package-private test suite. A plugin-policy test still belongs next to the plugin itself.

Shared telemetry and native extensions

Supply NativeRun.Telemetry or carry an explicit parent with telemetry.WithContext. telemetry/export delivers completed span batches; telemetry/llm supplies model trace records and DB/file sink contracts. Model pricing is in ai. The former observe plugin, WrapProvider and raw provider response hook are removed. Native checkpoint validation replaces the old typed-journal invariant observer.

Go plugins use ExtensionFactory.BeginRun around the engine's hooks. NativeContextContributor adds a run-scoped request transform without changing authoritative history. Todo uses it with its per-run tool and checkpoint; memory contributes explicit memory_recall. Pi's TypeScript ExtensionAPI loader belongs to its coding-agent application, not to the ported agent engine.

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

View Source
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
)
View Source
const (
	InboxSteer  = "steer"
	InboxFollow = "follow"
)

Inbox lanes: which drain point an EntryInbox feeds.

Variables

View Source
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.

View Source
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.

View Source
var ErrNoPendingQuestion = errors.New("agentcore: no pending question")

ErrNoPendingQuestion reports that an answer does not match an open ask call.

View Source
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.

View Source
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

func Cosine(a, b []float32) float64

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

func DelegationDepth(ctx context.Context) int

DelegationDepth returns the current delegation depth (0 for a top-level run).

func IdempotencyKey

func IdempotencyKey(ctx context.Context) (key string, ok bool)

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 IsRetryable(err error) bool

func LaunchBackground

func LaunchBackground(ctx context.Context, tool, label string, run func(context.Context) (string, error)) (json.RawMessage, error)

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

func ReportProgress(ctx context.Context, note string)

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

func RunSessionFrom(ctx context.Context) string

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

func SandboxSessionFrom(ctx context.Context) string

SandboxSessionFrom reads the session id set by WithSandboxSession ("" when absent — the safe default that keeps execution ephemeral and unshared).

func StringProperties

func StringProperties(names ...string) map[string]any

StringProperties builds bounded, non-empty text arguments for a declaration.

func ToolCallID

func ToolCallID(ctx context.Context) (id string, ok bool)

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

func ToolInvocationScope(ctx context.Context) (string, bool)

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

func TruncateBytes(s string, maxBytes int) string

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

func TruncateMiddle(s string, maxBytes int) string

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

func WithDelegationDepth(ctx context.Context, depth int) context.Context

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

func WithRunProgress(ctx context.Context, progress func(string)) context.Context

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

func WithRunSession(ctx context.Context, id string) context.Context

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

func WithSandboxSession(ctx context.Context, id string) context.Context

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

func WithToolInvocationScope(ctx context.Context, invocationID string) context.Context

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

func Build(plugins ...Plugin) (*Agent, error)

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

func New(cfg Config) (*Agent, error)

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

func (a *Agent) AddChildUsage(u Usage)

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

func (a *Agent) Continue(ctx context.Context, history []Message, task string) (RunResult, error)

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

func (a *Agent) Describe() string

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

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

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

func (a *Agent) Fork(childSessionID string) *Agent

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

func (a *Agent) IsDurable() bool

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

func (a *Agent) Prompt(ctx context.Context, userInput string) (RunResult, error)

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

func (a *Agent) RunNative(ctx context.Context, input NativeRun) (RunResult, error)

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

func (a *Agent) SessionID() string

SessionID returns the durable log this agent writes to, or "" when the run is purely in-memory.

func (*Agent) Steer

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

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

type AgentEndHook func(ctx context.Context, res RunResult)

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

func NewAllowList(names ...string) *AllowList

NewAllowList builds an AllowList from the given permitted tool names.

func (*AllowList) Allow

func (a *AllowList) Allow(_ context.Context, call ToolCall) Decision

func (*AllowList) PermittedTools

func (a *AllowList) PermittedTools(_ context.Context, all []string) []string

type ArgPreparer

type ArgPreparer interface {
	PrepareArguments(raw string) string
}

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

type BeforeAgentStartHook func(ctx context.Context, start RunStart) RunStart

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

type BeforeToolCall func(ctx context.Context, call ToolCall) Decision

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) Name

func (BudgetPlugin) Name() string

Name identifies the plugin.

func (BudgetPlugin) Register

func (p BudgetPlugin) Register(r *Registry) error

Register claims the budget and step gates.

type CallRetrySafeTool

type CallRetrySafeTool interface {
	RetrySafeCall(call ToolCall) bool
}

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 CardPoint

type CardPoint struct {
	Label string  `json:"label"`
	Value float64 `json:"value"`
}

CardPoint is one point in a "series" card (e.g. a time bucket count).

type CardStat

type CardStat struct {
	Label string `json:"label"`
	Value string `json:"value"`
}

CardStat is one labeled metric row in a "stat" card.

type ChatDelta

type ChatDelta = protocol.ChatDelta

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) Name

func (CompactionPlugin) Name() string

Name identifies the plugin.

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

type ContextContributor interface {
	RunContext(ctx context.Context) context.Context
}

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

type ContextHook func(ctx context.Context, msgs []Message) []Message

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

type CredentialResolver interface {
	Resolve(ctx context.Context, args string) (string, error)
}

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

type Decision struct {
	Allow  bool
	Reason string
}

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.

func Allowed

func Allowed() Decision

Allowed is a convenience constructor for a permitted decision.

func Blocked

func Blocked(reason string) Decision

Blocked is a convenience constructor for a denied decision.

type DefinitionPlugin

type DefinitionPlugin struct {
	Definition AgentDefinition
	Limits     *Limits
	Env        *Env
}

DefinitionPlugin installs identity, limits, and host environment into a Registry.

func (DefinitionPlugin) Name

func (DefinitionPlugin) Name() string

Name identifies the plugin.

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.

func (DenyAll) Allow

func (DenyAll) PermittedTools

func (DenyAll) PermittedTools(context.Context, []string) []string

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

type Embedder = protocol.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

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

type HooksPlugin struct {
	Label    string
	Hooks    Hooks
	Priority Priority
}

HooksPlugin contributes consumer lifecycle hooks to a Registry.

func HooksOf

func HooksOf(h Hooks) HooksPlugin

HooksOf builds a HooksPlugin at PriorityDefault.

func (HooksPlugin) Name

func (p HooksPlugin) Name() string

Name identifies the plugin.

func (HooksPlugin) Register

func (p HooksPlugin) Register(r *Registry) error

Register adds the hooks.

type InboxItem

type InboxItem struct {
	ID      string
	Lane    string
	Message Message
}

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

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

func (m *MemorySessionStore) CheckpointSeq(_ context.Context, id string) (int, bool, error)

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

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

type Message = protocol.Message

func CloseDanglingCalls

func CloseDanglingCalls(messages []Message) []Message

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

type MessageEndHook func(ctx context.Context, msg Message)

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

type ModelCapabilityError struct {
	Provider string
	Model    string
	Feature  string
}

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) Name

func (ModelPlugin) Name() string

Name identifies the plugin.

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

func (h *PiToolHost) ControlPiRun(ctx context.Context, info StepInfo) (string, error)

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

func (h *PiToolHost) StartPiRun(ctx context.Context, task string) (string, error)

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

func ConfigPlugin(cfg Config) Plugin

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) Name

func (PolicyPlugin) Name() string

Name identifies the plugin.

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

func BuildRegistry(plugins ...Plugin) (*Registry, error)

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

func (r *Registry) AddHooks(p Priority, h Hooks)

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

func (r *Registry) AddTools(tools ...Tool)

AddTools contributes tools to the run's registry. Unlike a seam this is additive: many plugins may each contribute capabilities.

func (*Registry) Agent

func (r *Registry) Agent() (*Agent, error)

Agent assembles the composed Agent from a registry.

func (*Registry) ApplyConfig

func (r *Registry) ApplyConfig(cfg Config) error

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

func (r *Registry) Describe() string

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) Plugins

func (r *Registry) Plugins() []string

Plugins returns the names of registered plugins in registration order.

func (*Registry) Provider

func (r *Registry) Provider(seam string) string

Provider returns the seam owner for diagnostics ("" when unclaimed).

func (*Registry) SetBudgetGate

func (r *Registry) SetBudgetGate(fn func(ctx context.Context, u Usage) bool) error

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

func (r *Registry) SetCompactor(c Compactor) error

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

func (r *Registry) SetContextWindow(tokens int) error

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

func (r *Registry) SetEnv(e Env) error

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

func (r *Registry) SetEscalation(rungs []ModelRung) error

SetEscalation installs the ordered fallback ladder tried when the primary rung errors.

func (*Registry) SetFollowUpSource

func (r *Registry) SetFollowUpSource(fn func(ctx context.Context) []Message) error

SetFollowUpSource installs the follow-up queue, drained when the run would otherwise end.

func (*Registry) SetGoal

func (r *Registry) SetGoal(goal string) error

SetGoal declares the condition under which the run may stop.

func (*Registry) SetLimits

func (r *Registry) SetLimits(l Limits) error

SetLimits overrides the run's bounds (turns, tool calls, context, result size).

func (*Registry) SetMaxTokens

func (r *Registry) SetMaxTokens(n int) error

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

func (r *Registry) SetParallelToolCalls(enabled bool) error

SetParallelToolCalls configures the optional provider generation hint.

func (*Registry) SetPolicy

func (r *Registry) SetPolicy(p Policy) error

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

func (r *Registry) SetPromptCache(key, retention string) error

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

func (r *Registry) SetReasoningEffort(e string) error

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

func (r *Registry) SetSeedDisabledTools(names []string) error

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

func (r *Registry) SetSteeringSource(fn func(ctx context.Context) []Message) error

SetSteeringSource installs the mid-run steering queue, drained at the top of every turn.

func (*Registry) SetStepGate

func (r *Registry) SetStepGate(fn func(ctx context.Context, turn int) error) error

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

func (r *Registry) Unload(name string) error

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

func (r *Registry) UsePolicy(p Policy) error

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

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 Role

type Role = protocol.Role

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

type RunController interface {
	ControlRun(context.Context, StepInfo) (reason string, err error)
}

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

type RunFinalizer interface {
	FinalizeRun(context.Context, RunResult, error) error
}

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

type SandboxMount struct {
	Source   string
	Target   string
	ReadOnly bool
}

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) Name

func (SessionPlugin) Name() string

Name identifies the plugin.

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 Severity

type Severity string

Severity ranks a load Diagnostic.

const (
	SeverityWarning Severity = "warning"
	SeverityError   Severity = "error"
)

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

type SkillLoader func(ctx context.Context, ids []string) (map[string]string, error)

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) Name

func (SteeringPlugin) Name() string

Name identifies the plugin.

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) Run

func (t StringTool) Run(ctx context.Context, args string) (string, error)

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 ToolCall

type ToolCall = protocol.ToolCall

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

func NewToolSet(tools ...Tool) *ToolSet

NewToolSet builds a registry from the given tools, preserving order.

func (*ToolSet) Add

func (ts *ToolSet) Add(t Tool)

Add registers a tool, overwriting any existing tool of the same name.

func (*ToolSet) Get

func (ts *ToolSet) Get(name string) (Tool, bool)

Get returns the tool of the given name, if registered.

func (*ToolSet) Names

func (ts *ToolSet) Names() []string

Names returns tool names in registration order.

func (*ToolSet) Schemas

func (ts *ToolSet) Schemas() []ToolSchema

Schemas returns the advertised schemas in registration order.

func (*ToolSet) With

func (ts *ToolSet) With(tools ...Tool) *ToolSet

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

func (t ToolTrace) DeniedAborted() bool

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

type ToolsPlugin struct {
	Label string
	Tools []Tool
}

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) Name

func (p ToolsPlugin) Name() string

Name identifies the plugin.

func (ToolsPlugin) Register

func (p ToolsPlugin) Register(r *Registry) error

Register adds the tools.

type TurnHook

type TurnHook func(ctx context.Context, info TurnInfo)

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

type TurnState struct {
	Model    string
	Tools    *ToolSet
	System   string
	Messages []Message
}

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.

type Usage

type Usage = protocol.Usage

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.

Jump to

Keyboard shortcuts

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