model

package
v1.11.0 Latest Latest
Warning

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

Go to latest
Published: Jul 22, 2026 License: MIT Imports: 54 Imported by: 0

Documentation

Overview

Package model provides the ModelRegistry and claw-based NodeExecutor for resolving "provider/model-id" specs and executing LLM nodes.

Index

Constants

View Source
const (
	MemoryReadToolName  = "memory_read"
	MemoryWriteToolName = "memory_write"
	MemoryListToolName  = "memory_list"
)

Tool names surfaced to the LLM. Tracked as constants so prompts, skills, and validators can refer to them without drifting. They are stable across the FS/cloud MemoryStore backends.

View Source
const DefaultBackoffBase = time.Second

DefaultBackoffBase is the base duration for exponential backoff.

View Source
const DefaultMaxAttempts = 3

DefaultMaxAttempts is the default number of LLM call attempts (initial + retries).

View Source
const DefaultMaxAttemptsTransient = 6

DefaultMaxAttemptsTransient is the attempt budget for connectivity/transient failures. Larger than DefaultMaxAttempts so a brief internet/API outage is ridden out rather than aborting a long run: with a 1s base and the capped exponential backoff below, 6 attempts span roughly a minute of retrying.

Variables

View Source
var ErrCompactionUnsupported = errors.New("model: compaction not supported by executor")

ErrCompactionUnsupported is the sentinel ClawExecutor.Compact returns when the backend has no in-process conversation handle to drop. The runtime re-exports it (runtime.ErrCompactionUnsupported is an alias) so the engine's `errors.Is` check works without importing model directly. Lives here because runtime imports model, not the reverse.

View Source
var ErrTextBlockTooLarge = errors.New("aggregateStream: text/thinking block exceeded max size")

ErrTextBlockTooLarge signals that a streamed text/thinking block's accumulated content exceeded maxTextBlockSize.

View Source
var ErrToolInputTooLarge = errors.New("aggregateStream: tool_use input exceeded max size")

ErrToolInputTooLarge signals that a streamed tool_use block's accumulated input JSON exceeded maxToolInputJSONSize.

Functions

func AsyncAnswerText added in v1.1.0

func AsyncAnswerText(in *store.Interaction) string

AsyncAnswerText flattens the recorded answers of an answered async interaction. The canonical shape is a single AskUserQuestionKey string; other keys are rendered k=v so nothing is silently dropped.

func AsyncQuestionText added in v1.1.0

func AsyncQuestionText(in *store.Interaction) string

AsyncQuestionText extracts the question text of an async interaction.

func BuildSecretGuard added in v0.39.0

func BuildSecretGuard(ctx context.Context, wf *ir.Workflow, vars map[string]string) *secretguard.Guard

BuildSecretGuard assembles the per-run secret guard (Layer 0/1/2) from every value iterion can already see as sensitive:

  • the resolved per-run credentials carried in ctx (cloud BYOK keys),
  • host env vars whose name looks secret (the local-run path — same definition store.CaptureLaunchEnv uses), and
  • the workflow's declared `secrets:` values (Layer 1; the agent only ever sees their placeholder).

Returns nil when redaction is disabled via ITERION_SECRETS_REDACT=off, in which case every guard method is a no-op. Otherwise returns a non-nil guard even with zero known values, so the heuristic detector pass still scrubs unknown token shapes from the run's sinks.

func ClassifyStreamError

func ClassifyStreamError(body []byte) error

ClassifyStreamError parses a stream error event and returns the appropriate typed error (*ContextOverflowError or *APIError), or nil if the data is not a recognized error event.

func CollectAsyncAnswersText added in v1.1.0

func CollectAsyncAnswersText(ctx context.Context, rs store.RunStore, runID, nodeID string) (string, error)

CollectAsyncAnswersText formats every ANSWERED async question of a node (nodeID empty = whole run) into one human-readable block. Shared by the await_answers tool result (both backends) and the runtime's await-node output / resume re-injection so the agent always sees one shape.

func DefaultDangerousCommandPatterns

func DefaultDangerousCommandPatterns() []string

DefaultDangerousCommandPatterns returns conservative regexes that match obviously destructive shell invocations. The list is small on purpose: each entry should have very low false-positive rate against typical engineering workflows.

func FormatAsyncAnswerMessage added in v1.1.0

func FormatAsyncAnswerMessage(interactionID, question, answer string) string

FormatAsyncAnswerMessage renders the operator-message text delivered to the asking node's inbox when its async question is answered. One canonical formatter so claw mid-turn injection, claude_code hook drains, and the resume fold-in all show the same shape.

func KnownModelSpecs added in v0.39.0

func KnownModelSpecs() []string

KnownModelSpecs returns a representative set of model specs to list when the `iterion models` command is invoked without an explicit model. It spans the providers the resolver knows curated heuristics for (Anthropic Claude, the GLM family served via the Anthropic-compatible endpoint, and OpenAI) so the listing exercises every curated branch. It is a display convenience, not an exhaustive catalogue — any "provider/model-id" can be passed explicitly.

func LoopIterationFromContext added in v0.39.0

func LoopIterationFromContext(ctx context.Context) int

LoopIterationFromContext returns the loop iteration stamped by WithLoopIteration, or 0 when none was set (the natural default for nodes outside any loop).

func NewDefaultLifecycleHooks

func NewDefaultLifecycleHooks(_ EventHooks) *hooks.Runner

NewDefaultLifecycleHooks returns a Runner pre-configured with the hooks every iterion run should have on by default:

  • SafetyHook on PreToolUse: blocks dangerous bash commands (rm -rf /, fork bombs) before they run.

Tool-call observability (PostToolUse, PostToolUseFailure) is NOT wired here: the executor already emits OnToolCall via applyHooks with the correct node ID at every tool outcome (success, error, or hook-blocked). Adding a PostToolUse audit hook here would duplicate the same event under a placeholder node ID — see AuditHook docs.

Hosts that need to add or override hooks should construct their own runner via hooks.NewRunner and pass it via WithLifecycleHooks; this helper is a sane default for the common case.

The default patterns are hardcoded and known to compile, so this function never returns an error — but it preserves the error signature for symmetry with NewLifecycleHooks variants that take user-supplied patterns.

func NewSubagentRunner

func NewSubagentRunner(
	modelReg *Registry,
	toolReg *tool.Registry,
	eventHooks EventHooks,
	lifecycle *clawhooks.Runner,
	defaultModel string,
) func(ctx context.Context, input map[string]any) (string, error)

NewSubagentRunner returns a closure suitable as the executor for the claw `agent` tool. When the LLM dispatches the agent tool, the closure spins up a child conversation against the same model registry, filters the tool registry by AllowedToolsForSubagent, and returns the child's final text alongside its agent_id.

Hooks are propagated to the parent's event stream with a "subagent:<agent_id>" nodeID so a live test can observe the full sub-tree.

The returned executor never recurses into itself: the `agent` tool is filtered out of the child's tool set, regardless of the subagent type's allowlist. defaultModel is used when the input omits the model field; pass an empty string to force an explicit model on every call.

func NodeIDFromContext added in v0.39.0

func NodeIDFromContext(ctx context.Context) string

NodeIDFromContext returns the active node id set by WithNodeID, or "" when none is wired. "" means "run-scoped drain" — every queued message is delivered regardless of its NodeID tag (legacy behaviour).

func ParseModelSpec

func ParseModelSpec(spec string) (providerName, modelID string, err error)

ParseModelSpec splits "provider/model-id" into its components.

func ProviderlessModelID added in v0.39.0

func ProviderlessModelID(spec string) string

ProviderlessModelID strips the leading "<provider>/" off a claw model spec. claw's GenerationOptions.Model wants the bare model id while Registry.Resolve wants the full "<provider>/<model>" spec — direct structured callers (supervisors, the session-board curator) resolve the client from the full spec but pass the bare id on the request. Returns the input unchanged when it carries no provider prefix.

func RecordAsyncAnswer added in v1.1.0

func RecordAsyncAnswer(ctx context.Context, rs store.RunStore, publish func(store.Event), runID, interactionID, answer string) (*store.Interaction, string, error)

RecordAsyncAnswer is the shared trunk of every "operator answers an async question" surface (runview service, CLI): it records the answer on the pending interaction, appends + publishes interaction_answered, and returns the answered record with the canonical delivery text for the asking node's inbox. Callers own the delivery-specific tail (QueueMessage vs direct AppendQueuedMessage).

func RefreshModelSpecs added in v0.39.0

func RefreshModelSpecs(ctx context.Context) error

RefreshModelSpecs force-refetches the model-spec cache synchronously via the existing resolver, blocking until the fetch completes or fails. On success the on-disk cache (~/.iterion) and the in-process table are both updated; on failure the prior cache is left untouched and the error is returned. This is the `iterion models --refresh` path.

func RunIDFromContext

func RunIDFromContext(ctx context.Context) string

RunIDFromContext returns the run ID set by WithRunID, or "" when none is wired (e.g. unit tests that exercise the executor without the runtime engine).

func SafetyHook

func SafetyHook(patterns []string) (hooks.Handler, error)

SafetyHook blocks PreToolUse events whose top-level "command" input matches one of `patterns`. Returns an error listing every pattern that fails to compile so operators are not silently running without the safety net they configured.

func SchemaToJSON

func SchemaToJSON(schema *ir.Schema) (json.RawMessage, error)

SchemaToJSON converts an IR Schema to a JSON Schema (json.RawMessage) suitable for use with sdk.WithExplicitSchema.

func SetCredentialsLookup added in v0.39.0

func SetCredentialsLookup(fn func(ctx context.Context) (func(provider string) string, bool))

SetCredentialsLookup wires a per-ctx credentials lookup. The runner calls this at boot once with a closure that reads from secrets.CredentialsFromContext and returns the per-provider key. Idempotent; the latest call wins.

func SetOAuthDirLookup added in v0.50.0

func SetOAuthDirLookup(fn func(ctx context.Context) (func(kind string) string, bool))

SetOAuthDirLookup wires a per-ctx OAuth-dir reader. The runner calls this at boot with a closure over secrets.CredentialsFromContext(ctx).OAuthDir. Idempotent; latest call wins.

func ValidateOutput

func ValidateOutput(output map[string]any, schema *ir.Schema) error

ValidateOutput checks that output contains all required fields from the schema with compatible types. It does NOT attempt to repair or coerce invalid values — the node must fail explicitly on schema mismatch.

func WithLoopIteration added in v0.39.0

func WithLoopIteration(ctx context.Context, iter int) context.Context

WithLoopIteration returns a derived context carrying the 0-based loop iteration counter for the node about to execute. The runtime calls this immediately before Executor.Execute so backends can tag their log output as [NodeID#iter/...] for per-(node, iteration) filtering in the studio.

func WithNodeID added in v0.39.0

func WithNodeID(ctx context.Context, nodeID string) context.Context

WithNodeID returns a derived ctx carrying the active node id. The runtime engine calls this once per node execution, alongside WithRunID. Empty nodeID is a no-op (drain falls back to run-scope).

func WithRunID

func WithRunID(ctx context.Context, runID string) context.Context

WithRunID returns a derived ctx carrying the run ID. The runtime engine calls this once per node execution, before `executor.Execute`. ClawExecutor reads it via RunIDFromContext.

func WithSandboxRunnerSession added in v0.5.0

func WithSandboxRunnerSession(ctx context.Context, runID string, store *NodeSessionStore, sink SessionCaptureSink) context.Context

WithSandboxRunnerSession is the runner-side entry point (V2-4) that hooks a freshly-built session store + capture sink into ctx so the in-runner ClawBackend mirrors every saved snapshot back across the IPC. The runner constructs the store via NewNodeSessionStore and the sink as a thin wrapper over the delegate.EnvelopeWriter.

Exported (where [withRuntimeContext] stays unexported) because the runner lives in `cmd/iterion` which can't reach package-private helpers — and we don't want every executor caller to discover a "sink" parameter they'd have to thread nil through.

func WithTemplateData

func WithTemplateData(ctx context.Context, td *TemplateData) context.Context

WithTemplateData returns a derived ctx carrying the template data snapshot. The runtime engine calls this once per node execution before invoking executor.Execute. Passing nil clears the snapshot.

Types

type APIError

type APIError struct {
	Message     string
	StatusCode  int
	IsRetryable bool
}

APIError represents a non-overflow API error.

func (*APIError) Error

func (e *APIError) Error() string

type AssistantTextInfo added in v0.39.0

type AssistantTextInfo struct {
	Text string
	// Iteration is the 0-based loop iteration (see LLMRequestInfo.Iteration).
	Iteration int
}

AssistantTextInfo carries one chunk of assistant narration (mid-turn prose), passed to the OnAssistantText hook. Fired per assistant text block for claude_code and per tool-bearing step for claw.

type AsyncAskBinder added in v1.1.0

type AsyncAskBinder interface {
	BindAsyncAsk(ctx context.Context, runID, nodeID string) AsyncAskHook
}

AsyncAskBinder constructs a per-(run,node) AsyncAskHook. Returning nil disables async asks for that run (the tools then error explicitly).

type AsyncAskHook added in v1.1.0

type AsyncAskHook interface {
	// Post persists a pending Kind=async Interaction and emits
	// human_input_requested{async:true}. Returns the interaction ID.
	Post(ctx context.Context, q delegate.AsyncQuestion) (string, error)
	// Pending lists the node's still-unanswered async questions
	// (oldest first).
	Pending(ctx context.Context) ([]delegate.PendingAsync, error)
	// CollectAnswers formats every answered async question of the node
	// into one human-readable block (the await_answers success result).
	CollectAnswers(ctx context.Context) (string, error)
}

AsyncAskHook is one node's async-question surface.

type AttachmentInfo added in v0.7.0

type AttachmentInfo struct {
	Name             string
	Path             string // absolute host path; empty in cloud unless prefetched
	OriginalFilename string
	MIME             string
	Size             int64
	SHA256           string
	// PresignURL, when set, is invoked lazily the first time a
	// `.url` reference is resolved. Returns "" when no presigner
	// is wired (e.g. unit-test stubs).
	PresignURL func() (string, error)
}

AttachmentInfo is the resolved per-attachment view consumed by template references. Built once per node execution from the store's AttachmentRecord plus a lazy presign hook so prompts that never touch `.url` don't pay the URL signing cost.

func (AttachmentInfo) URL added in v0.7.0

func (a AttachmentInfo) URL() (string, error)

URL evaluates the presign hook. Each call re-runs the hook because AttachmentInfo is stored by value in TemplateData.Attachments and cache mutation wouldn't be visible across map lookups.

type AttachmentWriter added in v0.39.0

type AttachmentWriter interface {
	WriteAttachment(ctx context.Context, runID string, rec store.AttachmentRecord, body io.Reader) error
}

AttachmentWriter is the optional capability the Browser pane uses to persist binary captures (PNG/JPEG screenshots) as run attachments reachable through `GET /api/runs/:id/attachments/:name`. The production EventEmitter is `*store.FilesystemRunStore` (or its Mongo equivalent), both of which already implement WriteAttachment. Mocks/tests that pass an emitter without this method silently skip screenshot capture.

type CapabilitySource added in v0.39.0

type CapabilitySource string

CapabilitySource records where a resolved capability value came from.

const (
	// SourceAggregator means the online model-spec aggregator (models.dev,
	// cached under ~/.iterion) had an entry for the model and was merged over
	// the curated fallback.
	SourceAggregator CapabilitySource = "aggregator"
	// SourceCurated means the curated static table in capabilities.go was the
	// sole source — the aggregator lacked the model, was disabled, or offline.
	SourceCurated CapabilitySource = "curated"
)

type ClawBackend

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

ClawBackend implements delegate.Backend by calling GenerateTextDirect and GenerateObjectDirect against api.APIClient. It wraps the direct LLM path into the unified Backend interface.

func NewClawBackend

func NewClawBackend(registry *Registry, hk EventHooks, retry RetryPolicy, opts ...ClawBackendOption) *ClawBackend

NewClawBackend creates a new ClawBackend.

func (*ClawBackend) Execute

func (b *ClawBackend) Execute(ctx context.Context, task delegate.Task) (delegate.Result, error)

Execute implements delegate.Backend.

When the run is sandboxed (task.Sandbox != nil), the call is forwarded to the iterion-claw-runner sub-process inside the container — see [executeViaSandboxRunner]. This keeps the LLM's in-process tool execution (Bash, file edits) inside the sandbox rather than escaping to the host. The unsandboxed path below is the historical in-process implementation.

V1 limitations of the sandbox-routed path are documented on delegate.IOTask and in docs/sandbox.md: no MCP servers, no mid-tool-loop ask_user resume.

type ClawBackendOption

type ClawBackendOption func(*ClawBackend)

ClawBackendOption configures a ClawBackend at construction time.

func WithBackendLifecycleHooks

func WithBackendLifecycleHooks(r *hooks.Runner) ClawBackendOption

WithBackendLifecycleHooks installs an in-process hook runner fired around tool execution and at session end. A nil runner is a no-op.

func WithInbox added in v0.39.0

func WithInbox(b InboxBinder) ClawBackendOption

WithInbox wires an InboxBinder into the backend so the generation engine drains the operator chatbox between tool iterations.

func WithMemoryStore added in v0.39.0

func WithMemoryStore(ms knowledge.MemoryStore) ClawBackendOption

WithMemoryStore injects a non-default workspace-memory backend (e.g. the cloud Mongo store). nil leaves the filesystem default.

type ClawExecutor

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

ClawExecutor implements runtime.NodeExecutor by routing LLM calls through pluggable Backend implementations (claw, claude_code, codex, etc.).

func NewClawExecutor

func NewClawExecutor(registry *Registry, wf *ir.Workflow, opts ...ClawExecutorOption) *ClawExecutor

NewClawExecutor creates a ClawExecutor for a given workflow.

func (*ClawExecutor) Close

func (e *ClawExecutor) Close() error

Close releases resources held by the executor, including MCP server connections and the per-run host secret-file tempdir (host runs only — sandbox runs never populate it).

func (*ClawExecutor) Compact

func (e *ClawExecutor) Compact(ctx context.Context, nodeID string) error

Compact satisfies the runtime.Compactor structural interface.

ClawExecutor maintains a session-per-node store of messages accumulated during the previous attempt. On Compact, the pure CompactSessionPure helper from claw-code-go is applied to that list — the next retry's claw backend prepends the (now smaller) list to its opts.Messages so the LLM sees a summarised history instead of the full pre-overflow conversation.

When no session is wired (non-claw backends, or a node that has never been executed) Compact returns ErrCompactionUnsupported, the recovery dispatcher logs the gap, and the retry runs without special treatment — the same behaviour as before session tracking existed.

func (*ClawExecutor) EffectiveBackendName added in v0.50.0

func (e *ClawExecutor) EffectiveBackendName(node ir.Node) string

EffectiveBackendName exposes the exact runtime resolution to safety checks. In particular, callers must not reimplement this chain and accidentally miss launch overrides, environment defaults, or credential-based auto-detection.

func (*ClawExecutor) EvictRun

func (e *ClawExecutor) EvictRun(runID string)

EvictRun drops every per-node session belonging to the given run. The runtime engine calls this when a run terminates (success, terminal failure, or cancellation) so a long-lived executor shared across runs does not leak session state from failed nodes.

func (*ClawExecutor) Execute

func (e *ClawExecutor) Execute(ctx context.Context, node ir.Node, input map[string]any) (map[string]any, error)

Execute implements runtime.NodeExecutor.

func (*ClawExecutor) ExecuteHumanLLMForInteraction

func (e *ClawExecutor) ExecuteHumanLLMForInteraction(
	ctx context.Context,
	nodeID string,
	ni *ErrNeedsInteraction,
	fields ir.InteractionFields,
) (answers map[string]any, needsHuman bool, err error)

ExecuteHumanLLMForInteraction handles delegate interaction requests by creating a synthetic HumanNode from the original node's InteractionFields and calling executeHumanLLM. The questions from the ErrNeedsInteraction become the input, and the interaction schema is synthesized from the question keys.

Returns:

  • answers: LLM-generated answers for each question
  • needsHuman: true if the LLM decided to escalate (llm_or_human mode only)
  • err: any error from model execution

func (*ClawExecutor) ExecuteReviewCompanion added in v0.39.0

func (e *ClawExecutor) ExecuteReviewCompanion(ctx context.Context, node *ir.HumanNode, systemText, userMessage string) (map[string]any, error)

ExecuteReviewCompanion drives a review gate's companion LLM (interaction: review). Given a pre-resolved system prompt (the companion's authored contract) and a user message (the diff context + dialogue transcript + the human's latest reply, assembled by the runtime), it returns a structured result carrying:

  • "message" — the next test-walkthrough message to show the human
  • "needs_human_input" — false when the companion is satisfied it can conclude
  • plus every field of the review node's output schema (the verdict: decision / confidence / blockers / …)

The companion never has tools — it reasons over the change and the conversation. systemText is resolved by the caller (the runtime, which holds rs.vars/outputs), so this layer only performs the LLM call.

func (*ClawExecutor) ExfiltratesTo added in v0.39.0

func (e *ClawExecutor) ExfiltratesTo(s, host string) bool

func (*ClawExecutor) LifecycleHooks

func (e *ClawExecutor) LifecycleHooks() *hooks.Runner

LifecycleHooks returns the runner installed via WithLifecycleHooks (nil if none). It is intended for backends that need to forward the runner into their own generation paths.

func (*ClawExecutor) MCPHealthCheck

func (e *ClawExecutor) MCPHealthCheck(ctx context.Context, servers []string) error

MCPHealthCheck verifies that the listed MCP servers are reachable by connecting and sending an MCP ping. Should be called before execution starts to fail fast on misconfigured servers.

func (*ClawExecutor) MaterializeForHost added in v0.39.0

func (e *ClawExecutor) MaterializeForHost(s, host string) string

MaterializeForHost / ExfiltratesTo / SecretsInspectActive let the engine use the executor's guard as the egress rewriter for the sandbox proxy's TLS-inspection mode (Layer 2), via a structural interface — so the runtime needn't import pkg/backend/secretguard.

func (*ClawExecutor) ScrubOutput added in v0.39.0

func (e *ClawExecutor) ScrubOutput(output map[string]any) map[string]any

ScrubOutput satisfies runtime.SecretScrubber: it returns a redacted deep copy of a node's output for the (observational) node_finished event stream, never mutating the live output (which feeds downstream nodes and the resume checkpoint). Nil-safe via the guard.

func (*ClawExecutor) SecretsInspectActive added in v0.39.0

func (e *ClawExecutor) SecretsInspectActive() bool

SecretsInspectActive reports whether the run has known secrets worth inspecting egress for. Egress TLS inspection only pays its cost (CA minting + trust injection) when there is something to substitute or protect.

func (*ClawExecutor) SetAskUserEndpoint added in v1.9.0

func (e *ClawExecutor) SetAskUserEndpoint(endpoint, token string)

SetAskUserEndpoint installs the per-run gateway-reachable ask-user MCP URL + token (started with the sandbox) so the executor can wire Task.AskUserHTTPEndpoint/AskUserRunToken for sandboxed interactive nodes (ADR-082 Phase 3). Called by the engine after the sandbox starts; empty values leave the sandboxed ask-user transport unwired.

func (*ClawExecutor) SetBoardEndpoint added in v0.39.0

func (e *ClawExecutor) SetBoardEndpoint(endpoint string)

SetBoardEndpoint installs the per-run gateway-reachable board MCP URL (started with the sandbox) so the executor can wire Task.BoardHTTPEndpoint/BoardRunToken for sandboxed board-cap nodes (C082). Called by the engine after the sandbox starts; no-op endpoint "" leaves sandboxed board-emit disabled.

func (*ClawExecutor) SetPresetFocus added in v0.39.0

func (e *ClawExecutor) SetPresetFocus(prompt string, skills []string)

SetPresetFocus records the selected launch-time preset's prompt bias and relevant-skill hints. The engine calls this at run start (and on resume) for the `--preset <name>` selection; every LLM node then renders a "## Focus" section in its system prompt (see executeBackend). The prompt is stored raw and {{vars.X}}-resolved per node. Empty args clear the focus. Must be called before Execute; not safe to call concurrently.

func (*ClawExecutor) SetRepoRoot added in v0.39.0

func (e *ClawExecutor) SetRepoRoot(dir string)

SetRepoRoot updates the source-of-truth repository root. The engine calls this once per run, alongside SetWorkDir, so memory specs that opt into `project_root: true` resolve their scope under the operator's main repo even when the run executes from a worktree or dispatcher workspace. Empty string means "no project root captured" — memory specs that require it will fall back to WorkDir's encoded key.

func (*ClawExecutor) SetRunExtraEnv added in v1.1.0

func (e *ClawExecutor) SetRunExtraEnv(env []string)

SetRunExtraEnv installs run-level process-environment additions (KEY=value entries) applied to every host-spawned command of the run. The engine calls this once per run, before the first node executes, from host devbox provisioning (pkg/runtime/devbox_host.go); the same happens-before as SetSandbox makes a mutex unnecessary.

func (*ClawExecutor) SetSandbox added in v0.4.0

func (e *ClawExecutor) SetSandbox(run sandbox.Run)

SetSandbox installs the live sandbox handle on the executor. The engine calls this once per run, after [resolveAndStartSandbox] returns. Subsequent tool node and backend invocations consult the handle to route through the sandbox transparently.

Passing nil clears the previous handle (used between runs).

func (*ClawExecutor) SetSkillHints added in v0.39.0

func (e *ClawExecutor) SetSkillHints(hints map[string]string)

SetSkillHints records the name→description map of skill-library skills that resolved in the library at run start (from the runtime mirror). Every LLM node then renders a "## Skills" section for the subset it references (node `skills:` ∪ workflow default). Nil/empty clears the hints. Must be called before Execute; not safe to call concurrently.

func (*ClawExecutor) SetVars

func (e *ClawExecutor) SetVars(vars map[string]any)

SetVars merges run-level workflow variables into the executor's vars map. Keys present in vars override the matching default seeded from wf.Vars at construction time; keys absent from vars retain their default. Must be called before Execute.

func (*ClawExecutor) SetWorkDir

func (e *ClawExecutor) SetWorkDir(dir string)

SetWorkDir updates the working directory for backend subprocesses (claude_code, codex) and tool node shell exec. The engine calls this at run start when `worktree: auto` produces a per-run worktree path that wasn't known at executor construction time. Safe to call before Execute; not safe to call concurrently with an in-flight Execute.

type ClawExecutorOption

type ClawExecutorOption func(*ClawExecutor)

ClawExecutorOption configures a ClawExecutor.

func WithArtifactFilesDir added in v0.50.0

func WithArtifactFilesDir(dir string) ClawExecutorOption

WithStoreDir sets the dispatcher store root forwarded to capability-gated backend tools (currently the board MCP server). Backends translate this to the ITERION_STORE_DIR env var on spawned MCP children. WithArtifactFilesDir sets the run's artifact_files scratch dir, exported to host tool-node subprocesses as ITERION_ARTIFACT_FILES_DIR (sandboxed runs receive it from the container env instead).

func WithBackendRegistry

func WithBackendRegistry(dr *delegate.Registry) ClawExecutorOption

WithBackendRegistry sets the backend registry on the executor. When set, nodes with a `backend` property are executed via the named backend instead of the default claw backend.

func WithBoardRegister added in v0.39.0

func WithBoardRegister(fn func(caps []string, sourceIssueID string) string) ClawExecutorOption

WithBoardRegister installs the per-node board MCP token minter (server path). The closure registers a token for the node's board capabilities with the server's BoardMCPTokenRegistry and returns it; the executor pairs it with the board endpoint on each sandboxed board-cap node. sourceIssueID is the ticket owning the run, so the HTTP transport can auto-stamp parent_id on board.create like the other two transports.

func WithBotID added in v0.39.0

func WithBotID(botID string) ClawExecutorOption

WithBotID sets the stable bot identity used to qualify structured visibility=bot memory spaces. When unset, NewClawExecutor falls back to Workflow.Name.

func WithCompressOverride added in v0.39.0

func WithCompressOverride(mode string) ClawExecutorOption

WithCompressOverride sets the run-level compression override (CLI --compress / studio Launch toggle): on|ultra|off, or "" for "unset, defer to DSL/env". It is the highest-priority input to rewrite.Resolve.

func WithDefaultBackend

func WithDefaultBackend(name string) ClawExecutorOption

WithDefaultBackend sets the workflow-level default backend.

func WithEventHooks

func WithEventHooks(h EventHooks) ClawExecutorOption

WithEventHooks sets observability callbacks on the executor.

func WithExecutorAsyncAsk added in v1.1.0

func WithExecutorAsyncAsk(b AsyncAskBinder) ClawExecutorOption

WithExecutorAsyncAsk installs the async-ask binder on the executor. Tasks built for interaction: async nodes then carry the PostAsyncQuestion / PendingAsyncQuestions / CollectAsyncAnswers closures consumed by both backends.

func WithExecutorInbox added in v0.39.0

func WithExecutorInbox(b InboxBinder) ClawExecutorOption

WithExecutorInbox installs the operator-chatbox binder on the executor. Every Task built by executeBackend / executeLLMRouterUnified then carries an InboxDrain closure so CLI-based backends (claude_code) can drain queued messages from inside their PostToolUse / Stop hooks. The claw backend's own copy is wired separately via WithInbox on the backend (set together in runview/executor.go); both share the same StoreInboxBinder so the run's queue is the single source of truth.

func WithExtraClosers added in v0.50.0

func WithExtraClosers(closers ...io.Closer) ClawExecutorOption

WithExtraClosers registers auxiliary resources (nil entries ignored) to be released when the executor is closed — e.g. the native board store opened for board.* MCP tools, whose fsnotify watcher would otherwise leak a goroutine + inotify fd per BuildExecutor call. Close() aggregates their errors.

func WithLifecycleHooks

func WithLifecycleHooks(r *hooks.Runner) ClawExecutorOption

WithLifecycleHooks installs an in-process lifecycle hook runner. When set, the runner is consulted around every tool execution (PreToolUse, PostToolUse, PostToolUseFailure) and at session end (Stop). Build the runner once via hooks.NewRunner, register callbacks with runner.Register(event, handler), then pass it here.

A nil runner disables the integration (default).

func WithLogger

func WithLogger(l *iterlog.Logger) ClawExecutorOption

WithLogger sets a leveled logger for the executor.

func WithMCPManager

func WithMCPManager(m *mcp.Manager) ClawExecutorOption

WithMCPManager sets the generic MCP manager used to lazily discover MCP tools.

func WithModelOverrides added in v0.39.0

func WithModelOverrides(o ModelOverrides) ClawExecutorOption

WithModelOverrides sets the launch-time per-node/-group backend+model+ provider overrides (studio Launch dropdowns / CLI --model/--backend). They take precedence over the node's DSL backend:/model:. Empty is a no-op.

func WithPermissionOverride added in v0.39.0

func WithPermissionOverride(mode string) ClawExecutorOption

WithPermissionOverride sets the run-level permission-gate override (CLI --permission / studio Launch): off|ask|deny, or "" to defer to DSL/env. Highest-priority input to the mode precedence (override > node > workflow > env > off).

func WithPermissionRules added in v0.39.0

func WithPermissionRules(allow, ask, deny []string) ClawExecutorOption

WithPermissionRules adds run-level permission rules (CLI --permission-allow / --permission-ask / --permission-deny). They are additive on top of the workflow-level rule lists.

func WithRetryPolicy

func WithRetryPolicy(rp RetryPolicy) ClawExecutorOption

WithRetryPolicy sets the retry policy for transient LLM errors.

func WithRewriteChain added in v0.39.0

func WithRewriteChain(chain *rewrite.Chain) ClawExecutorOption

WithRewriteChain sets the active rewriter chain (the enabled rewriter plugins, rtk by default), wired from the plugin registry at startup. Nil disables compression regardless of mode.

func WithSecretGuard added in v0.39.0

func WithSecretGuard(g *secretguard.Guard) ClawExecutorOption

WithSecretGuard installs the per-run secrets scrubber. Shared with the event hooks; the executor uses it to redact node_finished output (runtime.SecretScrubber) and to materialise ${secret.X} placeholders at tool/shell exec. A nil guard disables secrets protection.

func WithSourceIssueID added in v1.0.0

func WithSourceIssueID(issueID string) ClawExecutorOption

WithSourceIssueID stamps the ticket that owns this run so board.create can auto-link spawned children (parent_id / spawned_from).

func WithStoreDir added in v0.39.0

func WithStoreDir(dir string) ClawExecutorOption

func WithToolPolicy

func WithToolPolicy(p tool.ToolChecker) ClawExecutorOption

WithToolPolicy sets the tool execution policy on the executor. When set, every tool call is checked against the allowlist before execution. A denied tool produces an explicit error.

func WithToolRegistry

func WithToolRegistry(tr *tool.Registry) ClawExecutorOption

WithToolRegistry sets the unified tool registry on the executor.

func WithWorkDir

func WithWorkDir(dir string) ClawExecutorOption

WithWorkDir sets the working directory for backend subprocesses. When set, backend nodes will run their CLI in this directory.

type CompactInfo

type CompactInfo struct {
	// BeforeMessages is the message count before compaction.
	BeforeMessages int

	// AfterMessages is the message count after compaction.
	AfterMessages int

	// RemovedMessageCount is reported by claw's CompactionResult.
	RemovedMessageCount int
}

CompactInfo is passed to the OnCompact hook when the running tool-loop message history is shrunk by claw's pure-function compactor between iterations. Emitted only when compaction actually fired (no event when the transcript was short enough to skip).

type ContextOverflowError

type ContextOverflowError struct {
	Message string
}

ContextOverflowError indicates the prompt exceeded the model's context window.

func (*ContextOverflowError) Error

func (e *ContextOverflowError) Error() string

type DelegateInfo

type DelegateInfo struct {
	BackendName        string        // e.g. "claude_code", "codex"
	Duration           time.Duration // subprocess wall-clock time
	Tokens             int           // estimated total tokens consumed
	ExitCode           int           // process exit code
	Stderr             string        // captured stderr output
	RawOutputLen       int           // byte length of raw stdout
	ParseFallback      bool          // true if structured output fell back to text wrapper
	FormattingPassUsed bool          // true if two-pass execution was used (tools + schema)
	Error              error         // non-nil for OnDelegateError
	Attempt            int           // 1-based retry number (for OnDelegateRetry)
	Delay              time.Duration // backoff delay (for OnDelegateRetry)
}

DelegateInfo describes a backend execution attempt, passed to backend hooks.

type ErrNeedsInteraction

type ErrNeedsInteraction struct {
	NodeID    string
	Questions map[string]any // question_key → question text
	SessionID string         // delegate session ID for re-invocation
	Backend   string         // delegate backend name (empty for claw direct)

	// Conversation is the persisted backend-specific conversation history
	// captured at the pause point (claw: marshalled []api.Message). The
	// runtime relays this opaque blob into the checkpoint so that resume
	// can rehydrate the LLM's mid-tool-loop state instead of restarting
	// from system+user prompts. Backends that cannot persist conversation
	// state (CLI: claude_code, codex) leave this nil.
	Conversation json.RawMessage
	// PendingToolUseID is the ID of the tool_use block in Conversation
	// that is awaiting an answer. Required when Conversation is non-nil.
	PendingToolUseID string
}

ErrNeedsInteraction is returned by the executor when a delegate or LLM signals that it needs user input to continue. The runtime engine should handle this by pausing (interaction: human), auto-responding (interaction: llm), or deciding (interaction: llm_or_human) based on the node's InteractionMode.

func (*ErrNeedsInteraction) Error

func (e *ErrNeedsInteraction) Error() string

type EventEmitter

type EventEmitter interface {
	AppendEvent(ctx context.Context, runID string, evt store.Event) (*store.Event, error)
}

EventEmitter is the subset of store.RunStore used by the event bridge.

type EventHooks

type EventHooks struct {
	OnLLMRequest    func(nodeID string, info LLMRequestInfo)
	OnLLMPrompt     func(nodeID string, systemPrompt string, userMessage string)
	OnLLMResponse   func(nodeID string, info LLMResponseInfo)
	OnLLMRetry      func(nodeID string, info RetryInfo)
	OnLLMStepFinish func(nodeID string, step LLMStepInfo)
	// OnAssistantText fires with each chunk of assistant narration —
	// mid-turn prose the conversation views render as the agent
	// "talking" while it works. claude_code fires it per streamed
	// assistant text block; claw derives it from tool-bearing steps in
	// the store hooks layer.
	OnAssistantText func(nodeID string, info AssistantTextInfo)
	// OnLLMTurnCapture fires once per claw tool-loop iteration after
	// the conversation has been augmented with this step's
	// assistant + tool_results blocks. The runtime persists the
	// snapshot as a store.TurnCheckpoint anchored at (run, node,
	// loop_iter, turn) — the load-bearing primitive for the
	// fork-from-here UX and the per-node timeline. Conversation is
	// an opaque []byte (JSON-encoded []api.Message) so EventHooks
	// stays neutral to the wire format.
	OnLLMTurnCapture func(nodeID string, info LLMTurnCaptureInfo)
	OnLLMCompacted   func(nodeID string, info LLMCompactInfo)
	OnToolStarted    func(nodeID string, info LLMToolStartedInfo)
	OnToolCall       func(nodeID string, info LLMToolCallInfo)
	// OnToolNodeResult is called for direct tool nodes (not LLM tool loops)
	// with full input/output content for detailed logging.
	OnToolNodeResult func(nodeID string, toolName string, input []byte, output string, elapsed time.Duration, err error)

	// Delegation lifecycle hooks.
	OnDelegateStarted  func(nodeID string, backendName string)
	OnDelegateFinished func(nodeID string, info DelegateInfo)
	OnDelegateError    func(nodeID string, info DelegateInfo)
	OnDelegateRetry    func(nodeID string, info DelegateInfo)
	// OnProviderFallback fires once each time a node's provider
	// fallback chain falls through from a failed provider to the next
	// one (see the DSL `provider: "a,b,c"` chain). It is purely
	// observational — the run continues transparently against the next
	// provider — and lets the studio / Prometheus exporter surface that
	// a credential route was exhausted without the run itself failing.
	OnProviderFallback func(nodeID string, info ProviderFallbackInfo)

	// OnNodeFinished fires after a node's executor returns successfully.
	// The output map carries iterion's conventional usage keys (`_tokens`,
	// `_cost_usd`, `_model`) so observers (e.g. the Prometheus exporter)
	// can attribute cost and tokens per-node without re-parsing the event
	// log.
	OnNodeFinished func(nodeID string, output map[string]any)
}

EventHooks allows the executor to emit observability events back to the caller.

func ChainHooks

func ChainHooks(a, b EventHooks) EventHooks

ChainHooks composes two EventHooks so callbacks registered on either side run in order (a then b) for every event. Either side may leave any callback nil; the result keeps the non-nil one without an extra closure.

func NewStoreEventHooks

func NewStoreEventHooks(ctx context.Context, emitter EventEmitter, runID string, logger *iterlog.Logger, guard *secretguard.Guard, observers ...func(store.Event)) EventHooks

NewStoreEventHooks returns EventHooks that emit store events for a given run and log emoji-rich console output via the provided logger. The logger controls which content fields are included in events:

  • debug+: prompts, response text
  • trace: tool call inputs/outputs, tool call details

ctx is captured by the returned hook closures: filesystem stores ignore it but cloud (Mongo) stores honor cancellation/timeout. The hook lifetime is bounded by the engine.Run call that constructed it. observers (ADR-046) fire on every persisted event emitted through the backend-hook layer — the dispatcher's stall-heartbeat seam for the high-frequency tool events that bypass the engine's WithEventObserver.

type FinishReason

type FinishReason string

FinishReason indicates why generation stopped.

const (
	FinishStop          FinishReason = "stop"
	FinishToolCalls     FinishReason = "tool-calls"
	FinishLength        FinishReason = "length"
	FinishContentFilter FinishReason = "content-filter"
	FinishError         FinishReason = "error"
	FinishOther         FinishReason = "other"
)

Canonical FinishReason constants.

type GenerationOptions

type GenerationOptions struct {
	// Model is the model ID (e.g., "claude-sonnet-4-6").
	Model string

	// System is the system prompt (plain string form).
	System string

	// SystemBlocks, when non-empty, takes precedence over System and is sent
	// as the Anthropic array-form `system` field. This is the only way to
	// attach `cache_control` markers to system content for prompt caching.
	SystemBlocks []api.ContentBlock

	// Messages is the initial conversation history.
	Messages []api.Message

	// Tools available for the model to call.
	Tools []GenerationTool

	// MaxSteps is the maximum number of tool-loop iterations (default 10).
	MaxSteps int

	// ForceInitialToolUse compels the model to call a tool before it may
	// emit a final answer: while no tool has been called yet, the tool loop
	// pins tool_choice to "any" (mapped to the provider's required/any
	// form), reverting to auto once the first tool call lands. This is the
	// agentic-parity lever for backends/models that, under the default
	// "auto" tool_choice, skip provided tools and answer from priors — the
	// exact failure that let a claw+gpt-5.5 reviewer produce an ungrounded
	// verdict (0 tool calls) while claude_code read the files. No-op when
	// Tools is empty. Set by the claw backend for tool-equipped agent/judge
	// nodes; harmless for models that already use their tools.
	ForceInitialToolUse bool

	// MaxTokens is the maximum tokens per response (default 8192).
	MaxTokens int

	// Temperature controls randomness (nil = provider default).
	Temperature *float64

	// ExplicitSchema is the JSON schema for structured output (GenerateObjectDirect).
	ExplicitSchema json.RawMessage

	// SchemaName is the name for the synthetic structured-output tool (default "structured_output").
	SchemaName string

	// ProviderOptions are provider-specific options (e.g., reasoning_effort).
	ProviderOptions map[string]any

	// MaterializeSecrets, when non-nil, swaps secret placeholders for
	// their real values in agent-emitted tool input immediately before
	// execution (Layer 1). The placeholder form is what hooks/events
	// persist, so the real secret never reaches the store.
	MaterializeSecrets func(string) string

	// CompactThresholdRatio overrides the default compaction trigger as a
	// fraction of the model's context window. 0 falls back to the built-in
	// default (0.85). Values outside (0, 1] fall back to the default.
	CompactThresholdRatio float64

	// CompactPreserveRecent overrides the default count of recent messages
	// kept verbatim during compaction. 0 falls back to the built-in default
	// (4). Values < 0 fall back to the default.
	CompactPreserveRecent int

	// OnRequest is called before each StreamResponse call.
	OnRequest func(RequestInfo)

	// OnResponse is called after each StreamResponse aggregation completes.
	OnResponse func(ResponseInfo)

	// OnStepFinish is called after each tool-loop step completes.
	OnStepFinish func(StepResult)

	// OnTurnCapture is called once per loop iteration after the
	// conversation has been augmented with the step's assistant message
	// (and tool_results, when the step contained tool calls). The
	// snapshot is a defensive copy the callback may persist or hand off
	// to a goroutine. Used by the runtime to write per-turn
	// TurnCheckpoint artifacts that anchor the fork-from-here UX
	// without disturbing the live message slice. Nil disables turn
	// capture.
	OnTurnCapture func(turn TurnCaptureInfo)

	// OnToolStarted is called immediately before each tool executes,
	// once the PreToolUse lifecycle hook (if any) has allowed the call.
	// Mirrors OnToolCall but carries no duration/error.
	OnToolStarted func(ToolCallInfo)

	// OnToolCall is called after each tool execution.
	OnToolCall func(ToolCallInfo)

	// OnBeforeCompact fires only when in-loop compaction is about to
	// shrink the history. The callback may return a modified slice
	// (e.g. with an injected session-memory user turn) that feeds the
	// summariser; the live history keeps the originals. Returning nil
	// is a no-op.
	OnBeforeCompact func(messages []api.Message) []api.Message

	// OnCompact is called once after each in-loop compaction round
	// that actually shrunk the message history. No-op compactions
	// (transcript too short) do not fire the callback.
	OnCompact func(CompactInfo)

	// OnContextCompactRetry fires when a model call was REJECTED for
	// exceeding the backend's real context window and the loop reacted by
	// force-compacting the history and retrying. afterMessages is the
	// post-compaction message count; targetTokens the budget it shrank to.
	// Lets the backend surface the recovery as an llm_retry event.
	OnContextCompactRetry func(attempt int, err error, afterMessages, targetTokens int)

	// Hooks, when non-nil, is consulted around tool execution and at
	// session end. PreToolUse fires before each Execute and may Block
	// (the tool returns a synthetic refusal). PostToolUse fires after
	// successful Execute, PostToolUseFailure fires on error. Stop
	// fires once when the generation loop exits (success or failure).
	Hooks *hooks.Runner

	// Inbox, when non-nil, plumbs the run's operator-message inbox
	// into the tool loop. After each tool-results turn the engine
	// calls Inbox.Consume (transitioning the previous round's
	// delivered messages to "consumed") and then Inbox.Drain
	// (returning new operator texts to append as a synthetic user
	// turn). Delivery is cooperative — between iterations, never
	// mid-stream — mirroring Claude Code CLI's queued-message
	// semantics. Nil disables the inbox plumbing.
	Inbox InboxHook

	// Permission, when enabled, is the tool-permission gate evaluated
	// before each tool executes (the anti-prompt-injection boundary).
	// Allow → execute; Deny → a synthetic refusal tool_result; Ask →
	// the loop aborts with a *delegate.ErrAskUser so the run pauses and
	// the human approves (claw's half of cross-backend parity with
	// claude_code's PreToolUse permission hook). Nil/disabled = no gate.
	Permission *permission.Policy
}

GenerationOptions configures a direct generation call.

type GenerationTool

type GenerationTool struct {
	// Name is the tool identifier.
	Name string

	// Description describes what the tool does.
	Description string

	// InputSchema is the JSON schema for the tool's input parameters.
	InputSchema json.RawMessage

	// Execute runs the tool with the given JSON input and returns the result text.
	Execute func(ctx context.Context, input json.RawMessage) (string, error)
}

GenerationTool describes a tool available during direct generation. It replaces sdk.Tool for the native generation engine.

type InboxBinder added in v0.39.0

type InboxBinder interface {
	Bind(ctx context.Context, runID string) InboxHook
}

InboxBinder constructs a per-run InboxHook. The runtime supplies a store-backed implementation; tests can plug in a stub. Returning nil disables the inbox plumbing for that specific run.

type InboxHook added in v0.39.0

type InboxHook interface {
	Consume(ctx context.Context)
	Drain(ctx context.Context) []string
}

InboxHook is invoked by the generation tool-loop between iterations: Consume marks the previous round's delivered messages as consumed, Drain returns any new operator-typed texts to inject before the next LLM call. Both run cooperatively at safe boundaries — never mid-stream.

type KeyedProviderFactory added in v0.39.0

type KeyedProviderFactory func(modelID, apiKey string) (api.APIClient, error)

KeyedProviderFactory builds an APIClient given an explicit API key. Used by ResolveWithContext when a per-run BYOK plaintext is available — the result is NOT cached (multi-tenant safety).

type LLMCompactInfo

type LLMCompactInfo struct {
	BeforeMessages      int
	AfterMessages       int
	RemovedMessageCount int
	// Iteration is the 0-based loop iteration (see LLMRequestInfo.Iteration).
	Iteration int
}

LLMCompactInfo describes a mid-tool-loop compaction round, passed to the OnLLMCompacted hook. Emitted when claw's pure-function compactor shrinks the running message history before the next StreamResponse call so long agentic loops on small-context models do not silently hit context_length_exceeded.

type LLMRequestInfo

type LLMRequestInfo struct {
	Model           string
	MessageCount    int
	ToolCount       int
	ReasoningEffort string
	Timestamp       time.Time
	// Iteration is the 0-based loop iteration the node is executing in,
	// threaded through by applyHooks. Drives the [node#iter/claw] console
	// tag so the studio's per-(node,iteration) Logs tab can associate the
	// line; without it every claw line stuck at #0 (the hook closures
	// captured a base context, not the live per-iteration one).
	Iteration int
}

LLMRequestInfo describes an LLM request, passed to the OnLLMRequest hook.

type LLMResponseInfo

type LLMResponseInfo struct {
	Latency          time.Duration
	InputTokens      int
	OutputTokens     int
	CacheReadTokens  int
	CacheWriteTokens int
	FinishReason     string
	Error            error
	StatusCode       int
}

LLMResponseInfo describes an LLM response, passed to the OnLLMResponse hook.

type LLMStepInfo

type LLMStepInfo struct {
	Number           int
	Text             string
	ToolCalls        []ToolCallEntry
	FinishReason     string
	InputTokens      int
	OutputTokens     int
	CacheReadTokens  int
	CacheWriteTokens int
	// ReasoningTokens is an approximate count of extended-thinking tokens
	// (re-encoded from the thinking text); ThinkingMs is the wall-clock spent
	// in thinking blocks. Both are 0 when the step produced no thinking.
	ReasoningTokens int
	ThinkingMs      int
	// Thinking is the extended-thinking text for this step (empty when the
	// step produced no thinking).
	Thinking string
	// Iteration is the 0-based loop iteration (see LLMRequestInfo.Iteration).
	Iteration int
}

LLMStepInfo describes a single step in a multi-step LLM generation, passed to the OnLLMStepFinish hook.

type LLMToolCallInfo

type LLMToolCallInfo struct {
	ToolName  string
	InputSize int
	// ToolUseID correlates this completion event with the matching
	// tool_started event. The studio merges the two by this id so the
	// post-execution event (duration + error) lines up with the
	// pre-execution event (structured input payload for whitelisted
	// tools). Empty when the path can't provide one — the merge then
	// falls through and the card renders with whatever data the
	// tool_called event alone carries.
	ToolUseID string
	// Output is the string the tool returned to the LLM. The hooks layer
	// truncates and persists it on the tool_called event so the studio's
	// per-node Tools tab can render in+out the way Claude Code does.
	// Empty when the backend can't surface a result (e.g. tool blocked
	// pre-execution by a lifecycle hook, unknown tool dispatch).
	Output   string
	Duration time.Duration
	Error    error
}

LLMToolCallInfo describes a tool call execution, passed to the OnToolCall hook.

type LLMToolStartedInfo added in v0.39.0

type LLMToolStartedInfo struct {
	ToolName  string
	InputSize int
	// ToolUseID correlates start↔completion when the same node fires
	// multiple parallel tool calls (e.g. claude_code's assistant message
	// with several tool_use blocks). Empty when the path can't provide
	// one (direct tool nodes, claw single tool loop).
	ToolUseID string
	// Input is the raw JSON arguments the LLM produced for this call.
	// May be empty when the backend cannot surface it. The hooks layer
	// persists it (truncated) on the tool_started event so the studio's
	// per-node Tools tab can render parameters (command, file_path,
	// todos, …) for every tool — symmetric with the post-execution
	// `output` field on LLMToolCallInfo.
	Input json.RawMessage
	// Iteration is the 0-based loop iteration of the node firing this
	// tool call (see LLMRequestInfo.Iteration). Populated so the plan-
	// capture hook can tag each TodoWrite snapshot with the pass it came
	// from; 0 for direct tool nodes / paths that don't carry one.
	Iteration int
}

LLMToolStartedInfo describes a tool call about to execute, passed to the OnToolStarted hook. Emitted immediately before the tool runs so observers can render an in-flight indicator (e.g. the studio's "Running <tool>" footer) while waiting for completion.

type LLMTurnCaptureInfo added in v0.39.0

type LLMTurnCaptureInfo struct {
	Step             int
	Text             string
	ToolCalls        []ToolCallEntry
	FinishReason     string
	InputTokens      int
	OutputTokens     int
	CacheReadTokens  int
	CacheWriteTokens int
	// Iteration is the 0-based loop iteration (see LLMRequestInfo.Iteration).
	// Threaded so TurnCheckpoint.LoopIter is correct — it previously came
	// from a stale captured context and was always 0.
	Iteration int
	// Backend names the executor that produced this turn ("claw" or
	// "claude_code"). Used by the store-backed hook to set the
	// matching TurnCheckpoint.Backend field. Empty defaults to "claw"
	// for back-compat with paths that don't fill it explicitly.
	Backend string
	// SessionID is the claude_code CLI session id captured at the end
	// of a delegate call. Empty for claw (no session-id concept) and
	// for claude_code paths that didn't surface one. The Fork API
	// passes it to `claude --resume <id> --fork-session` for the
	// claude_code rehydration path.
	SessionID string
	// contains filtered or unexported fields
}

LLMTurnCaptureInfo describes one captured tool-loop turn, passed to the OnLLMTurnCapture hook. Carries the per-step metadata (finish_reason, tool calls, usage) plus the JSON-encoded conversation snapshot the runtime persists as a store.TurnCheckpoint.MessagesRef for the Fork API rehydration path.

Conversation is the JSON-encoded []api.Message slice taken at the natural end-of-iteration boundary — the very state the next LLM call would observe if the loop continued. Treat as immutable.

func (LLMTurnCaptureInfo) MarshalConversation added in v0.39.0

func (i LLMTurnCaptureInfo) MarshalConversation() json.RawMessage

MarshalConversation encodes the captured message slice as JSON, on demand. Returns nil when the turn carried no conversation (the claude_code path, or an SDK that didn't surface one). Errors are folded into a nil return so the store-backed hook treats them as "snapshot unavailable" rather than aborting the run.

type ModelCapabilities

type ModelCapabilities = llmtypes.ModelCapabilities

ModelCapabilities describes what features a model supports. Re-exported from llmtypes to keep the model package API stable.

type ModelOverrides added in v0.39.0

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

ModelOverrides is an ordered set of launch-time per-node/per-group backend+model+provider overrides, populated from the studio Launch dropdowns or the CLI --model / --backend flags. The zero value applies nothing, so a run with no overrides behaves exactly as before.

A selector matches a node by (most specific first):

  • exact node id ("reviewer_claude")
  • id glob ("reviewer_*", "fix_*") — path.Match syntax
  • node-kind keyword ("agent", "judge", "router")
  • "*" every LLM node (run-level default)

For each field (backend/model/provider) the most specific matching rule that sets a non-empty value wins; ties resolve to the later rule. Because resolution is per-field, `--model 'reviewer_*=X'` and `--backend '*=claw'` compose (the reviewer gets model X on backend claw). Overrides sit at the TOP of the resolution chain — above the node's DSL backend:/model: — because the operator is explicitly re-targeting the bot at launch without editing the .bot.

func ParseModelOverrides added in v0.39.0

func ParseModelOverrides(modelFlags, backendFlags []string) (ModelOverrides, error)

ParseModelOverrides builds a ModelOverrides from repeatable CLI flag values. Each element of modelFlags/backendFlags is a "selector=value" (or a bare "value" targeting every node). Returns a descriptive error on a malformed element so the run fails fast instead of silently ignoring a typo.

func (ModelOverrides) Empty added in v0.39.0

func (o ModelOverrides) Empty() bool

Empty reports whether no rules are configured.

func (ModelOverrides) ForNode added in v0.39.0

func (o ModelOverrides) ForNode(id string, kind ir.NodeKind) NodeModelOverride

ForNode resolves the effective override for a node id + kind. Returns an empty override when nothing matches (the caller then keeps the node's DSL values). Resolution is per-field: the most specific matching rule setting a non-empty value for that field wins.

func (*ModelOverrides) SetBackend added in v0.39.0

func (o *ModelOverrides) SetBackend(selector, backend string)

SetBackend adds a backend directive for the selector.

func (*ModelOverrides) SetModel added in v0.39.0

func (o *ModelOverrides) SetModel(selector, model string)

SetModel adds a model directive for the selector.

func (*ModelOverrides) SetProvider added in v0.39.0

func (o *ModelOverrides) SetProvider(selector, provider string)

SetProvider adds a provider directive for the selector.

type NodeModelOverride added in v0.39.0

type NodeModelOverride struct {
	Backend  string
	Model    string
	Provider string
}

NodeModelOverride is the resolved launch-time override for a single node: which backend / model / provider to use instead of the node's DSL values. Empty fields mean "leave the node's resolved value unchanged".

func (NodeModelOverride) Empty added in v0.39.0

func (o NodeModelOverride) Empty() bool

Empty reports whether the override carries no directive (all fields blank).

type NodeSessionStore added in v0.5.0

type NodeSessionStore = nodeSessionStore

NodeSessionStore is the public alias the runner uses to construct a store (the unexported alias preserves the in-package call-sites).

func NewNodeSessionStore added in v0.5.0

func NewNodeSessionStore() *NodeSessionStore

NewNodeSessionStore exposes the internal store constructor for the claw runner sub-binary (V2-4).

type ObjectResult

type ObjectResult[T any] struct {
	// Object is the deserialized result.
	Object T

	// Text is the raw text that was parsed.
	Text string

	// Steps contains results from each generation step.
	Steps []StepResult

	// TotalUsage is the aggregated token usage across all steps.
	TotalUsage Usage

	// FinishReason indicates why generation stopped.
	FinishReason FinishReason
}

ObjectResult is a typed wrapper around a text generation result that includes a deserialized object of type T (for structured output / GenerateObject).

func GenerateObjectDirect

func GenerateObjectDirect[T any](ctx context.Context, client api.APIClient, opts GenerationOptions) (*ObjectResult[T], error)

GenerateObjectDirect generates structured output by injecting a synthetic tool with the given schema and forcing the model to call it. The tool_use input is parsed as the result object of type T.

type PlanWriter added in v0.39.0

type PlanWriter interface {
	AppendPlanSnapshot(ctx context.Context, runID string, snap store.PlanSnapshot) (store.PlanSnapshot, bool, error)
}

PlanWriter is the optional capability stores satisfy for persisting the chronological plan snapshots agents produce via their TodoWrite (claude_code) / todo_write (claw) tool. When present, the tool-started hook captures each snapshot (filesystem → runs/<id>/plans/, Mongo → run_plans collection). The hook skips the capture when the capability is missing rather than failing the LLM call. In cloud mode the runner's metricsEmitter wrapper forwards this interface to the inner Mongo store (see pkg/runner metricsEmitter.AppendPlanSnapshot), otherwise the plain `emitter.(PlanWriter)` assertion below would hide it. See store.PlanStore for the format + dedup semantics.

type ProviderFactory

type ProviderFactory func(modelID string) (api.APIClient, error)

ProviderFactory creates an APIClient for a given model ID. The factory is called once per unique model ID; results are cached.

type ProviderFallbackInfo added in v0.39.0

type ProviderFallbackInfo struct {
	BackendName string // backend that ran the chain (e.g. "claude_code")
	From        string // provider hint that just failed ("" = auto)
	To          string // provider hint about to be tried next
	FromModel   string // effective model the failed provider ran (per-element override or node baseline)
	ToModel     string // effective model the next provider will run
	Attempts    int    // retry attempts spent on the failed provider
	Err         error  // the hard failure that triggered the fall-through
}

ProviderFallbackInfo describes a single fall-through within a node's provider fallback chain, passed to the OnProviderFallback hook.

type Registry

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

func NewRegistry

func NewRegistry() *Registry

NewRegistry creates a model registry pre-loaded with built-in providers.

func (*Registry) Capabilities

func (r *Registry) Capabilities(spec string) (ModelCapabilities, error)

Capabilities returns the capabilities of the model identified by spec. Capabilities are derived from a static table keyed by provider and model family.

func (*Registry) Register

func (r *Registry) Register(providerName string, factory ProviderFactory)

Register adds a provider factory under the given name. Calling Register with an already-registered name replaces the factory and invalidates any previously cached entries for that provider, so subsequent Resolve calls go through the new factory.

func (*Registry) Resolve

func (r *Registry) Resolve(spec string) (api.APIClient, error)

Resolve parses a model spec ("provider/model-id") and returns the corresponding APIClient, creating it via the provider factory if not already cached.

Concurrency: the registry mutex is only held while looking up or creating the per-key cache entry — never during the factory call. Concurrent Resolve calls for the same spec rendezvous on the entry's sync.Once so the factory runs exactly once; concurrent calls for different specs run their factories in parallel.

func (*Registry) ResolveWithContext added in v0.39.0

func (r *Registry) ResolveWithContext(ctx context.Context, spec string) (api.APIClient, error)

ResolveWithContext is the BYOK-aware variant of Resolve.

When ctx carries per-run secrets.Credentials with a non-empty key for the requested provider, this method bypasses the cache and constructs a fresh APIClient using the override key. This is crucial in cloud mode: a single runner pod serially handles runs for many tenants, so caching one tenant's API key against a modelID would leak it across tenants.

When ctx has no credentials (local mode, or no BYOK configured), this falls through to Resolve(spec) which uses the env-var fallback + per-process cache.

type RequestInfo

type RequestInfo struct {
	// Model is the model ID.
	Model string

	// MessageCount is the number of messages in the request.
	MessageCount int

	// ToolCount is the number of tools available.
	ToolCount int

	// ReasoningEffort is the resolved reasoning_effort spec sent on the
	// request, when set ("low", "medium", "high", "xhigh", "max"). Empty
	// when the node did not request a reasoning level.
	ReasoningEffort string

	// Timestamp is when the request was initiated.
	Timestamp time.Time
}

RequestInfo is passed to the OnRequest hook before a generation call.

type ResolvedCapabilities added in v0.39.0

type ResolvedCapabilities struct {
	Provider      string           `json:"provider"`
	Model         string           `json:"model"`
	Spec          string           `json:"spec"`
	Source        CapabilitySource `json:"source"`
	Reasoning     bool             `json:"reasoning"`
	ToolCall      bool             `json:"tool_call"`
	Temperature   bool             `json:"temperature"`
	ContextWindow int              `json:"context_window"`
}

ResolvedCapabilities is the CLI-facing resolution result: the same ModelCapabilities the runtime would compute, plus the resolution source.

func ResolveCapabilities added in v0.39.0

func ResolveCapabilities(provider, modelID string) ResolvedCapabilities

ResolveCapabilities resolves capabilities for an explicit provider + model ID, exactly as the runtime does via capabilitiesForModel(), and additionally reports whether the dynamic aggregator contributed (Source). It performs no blocking network fetch — call RefreshModelSpecs first to force one.

func ResolveSpec added in v0.39.0

func ResolveSpec(spec string) (ResolvedCapabilities, error)

ResolveSpec resolves a "provider/model-id" spec string. It returns an error (suitable for surfacing as a user-input error) when the spec is malformed.

type ResponseInfo

type ResponseInfo struct {
	// Latency is the time from request to response.
	Latency time.Duration

	// Usage is the token consumption for this call.
	Usage Usage

	// FinishReason indicates why generation stopped.
	FinishReason FinishReason

	// Error is non-nil if the call failed.
	Error error

	// StatusCode is the HTTP status code (0 if not applicable).
	StatusCode int
}

ResponseInfo is passed to the OnResponse hook after a generation call completes.

type RetryInfo

type RetryInfo struct {
	Attempt    int           // 1-based retry number (attempt 1 = first retry)
	Error      error         // the error that triggered this retry
	StatusCode int           // HTTP status code if available
	Delay      time.Duration // backoff delay before this retry
}

RetryInfo describes a retry attempt, passed to the OnLLMRetry hook.

type RetryPolicy

type RetryPolicy struct {
	// MaxAttempts is the total number of attempts (1 = no retry). Default: 3.
	MaxAttempts int
	// MaxAttemptsTransient is the attempt budget for connectivity/transient
	// failures (network blips, upstream 5xx). Default: 6. Falls back to
	// max(MaxAttempts, DefaultMaxAttemptsTransient) so it is never smaller
	// than the standard budget.
	MaxAttemptsTransient int
	// BackoffBase is the base delay for exponential backoff. Default: 1s.
	BackoffBase time.Duration
}

RetryPolicy controls automatic retry on transient LLM errors.

func RetryPolicyFromEnv added in v0.39.0

func RetryPolicyFromEnv() RetryPolicy

RetryPolicyFromEnv builds the in-executor per-node retry budget from the environment, falling back to the built-in defaults (DefaultMaxAttempts / DefaultMaxAttemptsTransient) for any dimension left unset. This is the LAYER-1 knob: it bounds how many times a transient backend failure (rate-limit, session-limit, idle-watchdog, network/5xx) is retried IN-EXECUTOR with exponential backoff before the failure bubbles up to a run-level failed_resumable (which the LAYER-2 auto-resume then handles).

The env values are RETRY counts (they exclude the initial attempt), so ITERION_NODE_MAX_TRANSIENT_RETRIES=8 yields 9 attempts. A value of 0 means "no retry" (fail-fast); a negative or non-numeric value is ignored (keeps the default) rather than silently disabling retries.

  • ITERION_NODE_MAX_TRANSIENT_RETRIES → the connectivity/transient budget (network blips, upstream 5xx, rate-limit, idle hang). Default: 5 retries (6 attempts).
  • ITERION_NODE_MAX_RETRIES → the standard budget for deterministic-but- retryable errors (signal kill). Default: 2 retries (3 attempts).

type ScreenshotDirective added in v0.39.0

type ScreenshotDirective struct {
	Path       string
	URL        string
	ToolCallID string
}

ScreenshotDirective is one parsed `[iterion] preview_screenshot=...` line. Path is host-absolute; URL and ToolCallID are optional.

type SessionCaptureSink added in v0.5.0

type SessionCaptureSink interface {
	// Capture is called after the local store has saved the snapshot.
	// Implementations should not block — the caller is on the LLM
	// loop's hot path.
	Capture(runID, nodeID string, snapshot []byte)
}

SessionCaptureSink mirrors per-(runID, nodeID) message snapshots out of the local store into a side channel as they are saved. V2-4 uses it on the runner side: the sink emits delegate.EnvelopeSessionCapture envelopes so the launcher's host nodeSessionStore stays in sync with the runner's, enabling CompactAndRetry to compact the LATEST history rather than the last-known-good one. The host side leaves this nil — it owns the canonical store directly.

type StepResult

type StepResult struct {
	// Number is the 1-based step index.
	Number int

	// Text generated in this step.
	Text string

	// ToolCalls requested in this step.
	ToolCalls []ToolCall

	// FinishReason for this step.
	FinishReason FinishReason

	// Usage for this step.
	Usage Usage

	// Thinking is the extended-thinking (reasoning) text produced during
	// this step; empty when the step produced no thinking.
	Thinking string
}

StepResult describes a single generation step in a tool loop.

type StoreAsyncAskBinder added in v1.1.0

type StoreAsyncAskBinder struct {
	Store   store.RunStore
	Publish func(store.Event)
	// OnPosted, when set, is invoked after each successfully posted
	// question (e.g. to ring a notification surface). Optional.
	OnPosted func(runID, interactionID string)
}

StoreAsyncAskBinder is the production AsyncAskBinder, backed by a store.RunStore + an event-broker publish callback (same pair as StoreInboxBinder — local mode passes EventBroker.Publish, cloud mode nil and the change stream surfaces transitions).

func (*StoreAsyncAskBinder) BindAsyncAsk added in v1.1.0

func (b *StoreAsyncAskBinder) BindAsyncAsk(_ context.Context, runID, nodeID string) AsyncAskHook

BindAsyncAsk returns the hook scoped to (runID, nodeID), or nil when the binder is not configured for this run.

type StoreInboxBinder added in v0.39.0

type StoreInboxBinder struct {
	Store store.RunStore
	// Publish receives each user_message_* event after store-side
	// persistence. Local mode passes EventBroker.Publish; cloud mode
	// passes nil (the Mongo change-stream surfaces transitions).
	Publish func(store.Event)
}

StoreInboxBinder is the production InboxBinder, backed by a store.RunStore + an event-broker publish callback. The hooks it returns reuse the shared store.DrainPending / store.MarkConsumed helpers so the runtime's pauseAtHuman drainer and the agent-loop drainer emit identical event payloads.

func (*StoreInboxBinder) Bind added in v0.39.0

func (b *StoreInboxBinder) Bind(ctx context.Context, runID string) InboxHook

Bind returns the hook scoped to runID, or nil when the binder is not configured for this run.

type TemplateData

type TemplateData struct {
	// Outputs is the per-node output map. Keys are node IDs.
	Outputs map[string]map[string]any

	// LoopCounters is the current iteration count per loop name
	// (1-indexed once incremented by the engine).
	LoopCounters map[string]int

	// LoopMaxIterations is the declared `as <name>(N)` upper bound
	// per loop name, sourced from ir.Workflow.Loops.
	LoopMaxIterations map[string]int

	// LoopPreviousOutput is the snapshot of the source node's output
	// at the previous traversal of each loop's edge — i.e. one
	// iteration behind the current one. Nil on the first iteration.
	LoopPreviousOutput map[string]map[string]any

	// Artifacts is the publish-name → output map for artifacts that
	// have been produced so far in this run.
	Artifacts map[string]map[string]any

	// RunID is the current run identifier, exposed to prompts as
	// `{{run.id}}`.
	RunID string

	// Attachments maps the workflow's attachment names to their
	// resolved per-run metadata. Populated from Run.Attachments at
	// the start of every node execution. Resolvers expose:
	//
	//	{{attachments.<name>}}        → host filesystem path
	//	{{attachments.<name>.path}}   → host filesystem path (explicit)
	//	{{attachments.<name>.url}}    → presigned URL (lazy: filled on demand)
	//	{{attachments.<name>.mime}}   → sniffed MIME
	//	{{attachments.<name>.size}}   → byte length as decimal string
	//	{{attachments.<name>.sha256}} → hex SHA-256
	Attachments map[string]AttachmentInfo
}

TemplateData carries runtime state needed to resolve prompt template references in the `outputs.*`, `loop.*`, `artifacts.*`, and `run.*` namespaces. The runtime engine populates a snapshot of its current state before each node execution and attaches it via WithTemplateData so the executor can render prompt bodies that reference upstream outputs, loop counters, and previous-iteration snapshots.

All fields are read-only views — callers must not mutate them.

func TemplateDataFromContext

func TemplateDataFromContext(ctx context.Context) *TemplateData

TemplateDataFromContext returns the TemplateData attached by WithTemplateData, or nil when none is set. Resolvers tolerate nil by leaving cross-namespace refs (`outputs.*`, `loop.*`, etc.) unresolved — callers that don't wire the runtime context still get `input.*` and `vars.*` resolution.

type TextResult

type TextResult struct {
	// Text is the accumulated generated text.
	Text string

	// ToolCalls requested by the model in the final step.
	ToolCalls []ToolCall

	// Steps contains results from each generation step.
	Steps []StepResult

	// TotalUsage is the aggregated token usage across all steps.
	TotalUsage Usage

	// FinishReason indicates why generation stopped.
	FinishReason FinishReason

	// Messages is the final accumulated conversation list, including
	// the initial input plus every assistant turn (with tool_use
	// blocks) and tool_result message produced during the loop. Hosts
	// that maintain a per-node session — e.g. for compaction-aware
	// retries — capture this slice and feed it back as opts.Messages
	// on the next attempt.
	Messages []api.Message
}

TextResult is the final result of a text generation call.

func GenerateTextDirect

func GenerateTextDirect(ctx context.Context, client api.APIClient, opts GenerationOptions) (*TextResult, error)

type ToolBlobWriter added in v0.39.0

type ToolBlobWriter interface {
	WriteToolBlob(ctx context.Context, runID, toolUseID, kind string, body []byte) (int64, error)
}

ToolBlobWriter is the optional capability filesystem stores satisfy for the per-tool-call sidecar I/O persistence path. When present, tool inputs/outputs exceeding `toolInlineThreshold` are written through it and the event carries a small head preview + a ref instead of the full body. Mongo (cloud) stores don't satisfy it today; the hook layer falls back to inline truncation in that case.

type ToolCall

type ToolCall struct {
	// ID is a unique identifier for this tool call.
	ID string

	// Name of the tool to invoke.
	Name string

	// Input is the JSON-encoded arguments.
	Input json.RawMessage
}

ToolCall represents the model's request to invoke a tool.

type ToolCallEntry

type ToolCallEntry struct {
	Name  string
	Input json.RawMessage
}

ToolCallEntry describes a single tool call within a step.

type ToolCallInfo

type ToolCallInfo struct {
	// ToolName is the name of the tool that was called.
	ToolName string

	// InputSize is the byte length of the tool input JSON.
	InputSize int

	// ToolUseID correlates start↔completion when multiple parallel tool
	// calls share a node. Populated for OnToolStarted; the post-execution
	// OnToolCall path leaves it empty (existing callers don't need it).
	ToolUseID string

	// Input is the raw JSON arguments passed to the tool. Populated for
	// OnToolStarted so observers can render the tool's target (URL, file
	// path, query, …) and persist structured input for whitelisted tools.
	// Empty on the post-execution OnToolCall path to avoid duplicating
	// payloads in events.jsonl.
	Input json.RawMessage

	// Output is the string the tool returned to the LLM. Populated only on
	// the post-execution OnToolCall path; empty on OnToolStarted. The
	// hooks layer persists it (truncated) on the tool_called event so the
	// studio's per-node Tools tab can render the in/out pair the way
	// Claude Code does.
	Output string

	// Duration is how long the tool execution took.
	Duration time.Duration

	// Error is non-nil if the tool execution failed.
	Error error
}

ToolCallInfo is passed to the OnToolCall / OnToolStarted hooks before (started) and after (completed) a tool executes.

type TurnCaptureInfo added in v0.39.0

type TurnCaptureInfo struct {
	// Step is the 1-based step index, matching StepResult.Number.
	Step int
	// Result mirrors the StepResult passed to OnStepFinish.
	Result StepResult
	// Conversation is the snapshot the runtime persists. Treat as
	// immutable — the generation loop reuses the underlying slice
	// after the callback returns. Callbacks that hand the snapshot
	// off to a goroutine MUST take their own copy first (the
	// generation loop already does so before invoking the hook).
	Conversation []api.Message
}

TurnCaptureInfo is the payload of the OnTurnCapture hook fired by the generation loop after every step completes (and after its tool results have been appended, when applicable). It carries everything the runtime needs to write a per-turn store.TurnCheckpoint plus, optionally, a sibling messages.json blob used as the rehydration source for the Fork API.

Conversation is a defensive copy of the messages slice at the natural end-of-iteration boundary — the very state the next LLM call would be made from if the loop continued. For the final step (no follow-up tool calls), the slice is augmented in-snapshot with a synthetic assistant text message so the snapshot is forkable too.

type TurnWriter added in v0.39.0

type TurnWriter interface {
	WriteTurn(ctx context.Context, t *store.TurnCheckpoint) error
}

TurnWriter is the optional capability filesystem stores satisfy for the per-LLM-turn snapshot persistence path. Each tool-loop iteration completing inside the claw backend (or a delegate-call boundary for claude_code) is persisted as a store.TurnCheckpoint so the studio's timeline + the Fork API have a stable anchor. Mongo (cloud) stores don't satisfy it today; the hook layer skips the write when the capability is missing rather than failing the LLM call.

type Usage

type Usage struct {
	InputTokens      int
	OutputTokens     int
	TotalTokens      int
	ReasoningTokens  int // approximate count of extended-thinking tokens (re-encoded text)
	CacheReadTokens  int
	CacheWriteTokens int
	ThinkingMs       int // wall-clock spent in thinking blocks, milliseconds
}

Usage tracks token consumption for a request.

Jump to

Keyboard shortcuts

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