Thread
A thread positioned at one branch.
Every method reads or appends through the store; none needs the agent in memory.
Properties
idThreadIdrequiredThe thread's id: pass it to openThread to get a handle again later.
branchBranchIdrequiredThe branch this handle reads and appends to.
storeStorerequiredThe log and artifact store: sqlite() or postgres().
Methods
timeline
Every step with the exact request the model saw; fork points marked.
timeline(): Promise<Result<Timeline>>Returns an error value with one of these codes: log_corrupt, unsupported_format, unsupported_critical_event.
branches
Every visible branch of this thread, with its parent branch and fork seq. A branch still being forked, or whose fork failed, is not listed.
branches(): Promise<readonly BranchInfo[]>forkPoints / fork_points
Eligible snapshot events only. TypeScript returns the list itself; Python returns Ok or Err.
forkPoints(): Promise<readonly ForkPoint[]>fork
A new branch restored into an isolated sandbox. Continue it with agent.run(input, {thread: child}).
fork(point: EventId | ForkPoint, options?: {
mode?: "live" | "stub";
knowledge?: "pinned" | "current";
}): Promise<Result<Thread>>pointEventId | ForkPointrequiredWhere to fork: the event_id of an eligible snapshot, or a ForkPoint from forkPoints(). Any other event is no_snapshot_boundary.
mode"live" | "stub"default livestub runs the new branch without live side effects: every mediated call is answered from what the original branch recorded after the fork point. Stubbing works in Python only today; in TypeScript a run on a stub fork is live. A stub-mode run on a live model that declares hosted tools is refused with ConfigError hosted_tool_unsupported, because hosted calls can't be stubbed.
knowledge"pinned" | "current"default pinned"pinned": the new branch searches knowledge as it was at the snapshot. "current": it searches the live knowledge base.
Returns an error value with one of these codes: sandbox_required, no_snapshot_boundary, snapshot_expired, snapshot_missing, snapshot_restore_failed, snapshot_manifest_mismatch, resource_unknown, egress_policy_unsupported, artifact_missing, artifact_corrupt.
saveCase / save_case
Saves one completed turn, the first included, as a case in the spec/conformance layout: case.json (with expect, input.text and rubric), log.threads-ts.jsonl and log.threads-py.jsonl (the branch through the event before the turn) with matching expected.<impl>.json (the recorded turn's events), artifacts/, model.json, stubs.json (effects), sandbox.json (read-only results by tool, args_hash and occurrence), extensions.json (hook decisions and recall) and line0.json (the recorded prompt prefix, for drift). No sandbox snapshot is needed: runEvals reruns it from recorded results only, so a case from any sandbox provider is portable. The case is built from typed values, valid against case.schema.json by construction; both languages' tests validate every saved case against it.
saveCase(name: string, options: {
expect: CaseExpectation;
externalEffects: "stub";
at?: EventId;
rubric?: readonly string[];
dir?: string;
}): Promise<Result<SavedCase>>namestringrequiredThe case's directory name under dir: lowercase letters, digits and hyphens, starting with a letter or digit. Anything else is invalid_request.
expectCaseExpectationrequiredWhat a replay of the case must show; a case needs at least one must assertion, else invalid_request. See CaseExpectation.
externalEffects / external_effects"stub"requiredThe effects policy is required; stub is the only one.
atEventIdThe user_input of the turn to save; default the last completed turn; a snapshot event id means the turn after it. A turn that isn't completed is invalid_request.
rubricreadonly string[]Criteria a live eval's judge checks the current agent's work against; each passes or fails, and the case passes only when all do. 1 to 20 criteria of 1 to 500 characters, else invalid_request. Omitted: the case has no criteria of its own.
dirstringdefault casesDirectory in the app repo; the case is <dir>/<name>/.
Returns an error value with one of these codes: invalid_request, case_missing_dependency, egress_policy_unsupported.
export
Writes this branch's resolved chain and every artifact it names to path as a portable bundle: log.jsonl, artifacts/<sha256> and bundle.json, the manifest written last. importThread stores one in any other store, so a thread moves between machines whole. path must not exist: an existing file or directory is path_exists and nothing is replaced. Export claims the path with an exclusive mkdir and fsyncs what it writes, so a crash leaves a directory without bundle.json, which import refuses as incomplete rather than storing half a thread.
export(path: string): Promise<Result<ExportedBundle>>pathstringrequiredThe bundle directory to create. It must not exist.
Returns an error value with one of these codes: branch_not_found, log_corrupt, unsupported_format, unsupported_critical_event, artifact_missing, artifact_corrupt, path_exists, io_error.
replay
Re-renders every recorded model request of this branch from the log and checks it byte for byte against what was sent: the system prompt against its settings epoch, then the stored request bytes, then the rendered request of the events before it. No model or tool calls, no sandbox, no appends; a fork includes its parent's prefix. The first failure comes back with the failing request's seq; a branch with no model request replays trivially. Import ran this check once; replay() re-runs it against the code running now, so run it over real threads in CI to prove an upgrade still reproduces them.
replay(): Promise<Result<void>>Returns an error value with one of these codes: log_corrupt, unsupported_format, unsupported_critical_event, prefix_changed, request_hash_mismatch, artifact_missing, artifact_corrupt.
pendingApprovals / pending_approvals
Open approval challenges on this branch, each with the tool call it guards, the suggested_rules an approver may keep, and the reason it asks when one was recorded. A log that can't be read is an error, never an empty list.
pendingApprovals(): Promise<Result<readonly PendingApproval[]>>Returns an error value with one of these codes: log_corrupt, unsupported_format, unsupported_critical_event.
approve
Grants an open approval challenge: appends approval_granted and resumes the branch if it was parked on it. Each challenge takes one answer: a second is approval_duplicate, and one after it expires is approval_expired.
approve(challengeId: Uuid, principal: Principal, options?: {
rememberRule?: PermissionRule;
}): Promise<Result<Appended>>challengeId / challenge_idUuidrequiredThe challenge_id of an open approval, from pendingApprovals() or the approval_requested event.
principalPrincipalrequiredThe approver answering, recorded as the event's actor. It must be one of the agent's approvers, else forbidden.
rememberRule / remember_rulePermissionRuleOne of the challenge's suggested_rules, also recorded as an allow rule so matching calls later in the thread need no approval. Any other rule is invalid_request. Omitted: the approval covers this one call.
Returns an error value with one of these codes: forbidden, not_found, invalid_request, approval_mismatch, approval_expired, approval_duplicate, branch_busy.
deny
Refuses an open approval challenge: appends approval_denied, so the tool call does not run, and resumes the branch if it was parked on it. Each challenge takes one answer.
deny(challengeId: Uuid, principal: Principal, options?: {
reason?: string;
}): Promise<Result<Appended>>challengeId / challenge_idUuidrequiredThe challenge_id of an open approval, from pendingApprovals() or the approval_requested event.
principalPrincipalrequiredThe approver answering, recorded as the event's actor. It must be one of the agent's approvers, else forbidden.
reasonstringFree text recorded on approval_denied, for example why the call was refused. Omitted: no reason is recorded.
Returns an error value with one of these codes: forbidden, not_found, approval_mismatch, approval_expired, approval_duplicate, branch_busy.
answer
Answers an open ask_user question, recorded as that call's result, then resumes the branch. An unanswered question closes after 24 hours with the error result no answer.
answer(callId: CallId, answer: string | readonly string[], principal: Principal): Promise<Result<Appended>>callId / call_idCallIdrequiredThe call_id of the open ask_user call. A call that isn't waiting for an answer is no_open_question.
answerstring | readonly string[]requiredThe answer text, or the chosen options as a list. With options, each must be an option or its 1-based number (literal text only when an option is itself a number), compared after trimming whitespace and lowercasing ASCII letters; a multi_select reply may also separate choices with commas or line breaks. The recorded answer is each chosen option's offered spelling, joined with newlines in first-reply order. Anything else is invalid_answer, with the options in its message; without options any non-blank text is accepted.
principalPrincipalrequiredWho answers; it must be the principal whose input started the turn, else forbidden.
Returns an error value with one of these codes: forbidden, no_open_question, invalid_answer, branch_busy.
resolveParked / resolve_parked
A person settles a side effect whose outcome is unknown and that parked the branch, then the branch resumes. Use it when the effect can't be checked automatically.
resolveParked(effectKey: string, resolution: "assume_done" | "assume_not_done", principal: Principal): Promise<Result<Appended>>effectKey / effect_keystringrequiredThe parked effect's key, <branch_id>:<call_id>, as listed in the parked result's pending. A key that isn't parked is not_parked.
resolution"assume_done" | "assume_not_done"required"assume_done": record that the effect happened, so it never runs again. "assume_not_done": let it run again, accepting the risk that it happens twice.
principalPrincipalrequiredThe person settling it, recorded as the event's actor. It must be one of the agent's approvers, else forbidden.
Returns an error value with one of these codes: forbidden, not_parked, branch_busy.
cancel
Durable cancel_requested; unsettled effects park. Tree-wide: every descendant subagent without agent_finished gets cancel_requested{scope: tree} too, recursively, and the parent records each child's agent_finished{cancelled} before its own cancelled. A control appends through the run's own writer when this process runs the branch, or takes the branch lease for the one append (Appended). When another process holds the lease it writes a durable inbox control item instead, which the holder applies at its next step boundary or dispatch fence (CancelAccepted, with the item's key). Authority is checked before either is written.
cancel(principal: Principal): Promise<Result<Appended | CancelAccepted>>principalPrincipalrequiredWho takes this action; recorded as the event's actor and authorized by the host.
Returns an error value with one of these codes: forbidden.
compact
Records a compaction request (compaction_requested) while the thread is idle; the thread's next run summarizes everything up to the request before its first model request, then answers from the summary. The outcome, compacted or compaction_failed, is in the timeline, not an error of compact(). branch_busy: a turn is in progress, one parked on an approval or a question included; try again when it ends. invalid_request: instructions is an empty string. invalid_transition: a compaction is already requested, or there is nothing to compact yet. branch_not_runnable: an inspection-only branch.
compact(principal: Principal, options?: {
instructions?: string;
}): Promise<Result<Appended>>principalPrincipalrequiredWho takes this action; recorded as the event's actor and authorized by the host.
instructionsstringExtra guidance for the summary, for example what to keep; non-empty. Omitted: the default summary instructions.
Returns an error value with one of these codes: forbidden, invalid_request, branch_busy, branch_not_runnable, invalid_transition.
setModel / set_model
Changes the model for this thread's next requests, recorded as settings_changed{reason: user}. before_model_switch doesn't run for it: that hook gates only automatic switches (a fallback and its revert). forbidden: the principal can't act on this thread, such as another tenant's.
setModel(settings: SettingsChange, principal: Principal): Promise<Result<Appended>>settingsSettingsChangerequiredThe model to switch to, with optional model_params and settings. The host resolves it to the model of that name in the agent's model or fallback list.
principalPrincipalrequiredWho takes this action; recorded as the event's actor and authorized by the host.
Returns an error value with one of these codes: forbidden, invalid_transition, branch_busy.
setOutputStyle / set_output_style
Switches later replies to one of the output styles pinned with the agent's config. The style's text is appended as a trusted instruction after the prompt prefix (injected{source: output_style}), so the system prompt and the prompt cache are kept. The latest style applies; to go back, set a style that says so. Idle only: branch_busy while a turn is in progress. not_found: the pinned config defines no style of that name. branch_not_runnable: an inspection-only branch.
setOutputStyle(name: string, principal: Principal): Promise<Result<Appended>>namestringrequiredA key of the agent's output_styles. An unknown name is not_found.
principalPrincipalrequiredWho takes this action; recorded as the event's actor and authorized by the host.
Returns an error value with one of these codes: forbidden, not_found, branch_busy, branch_not_runnable.
setMode / set_mode
Changes the branch's permission mode, recorded as mode_changed and used by later permission decisions. Switching to bypass is invalid_transition unless the agent's permissions set allow_bypass.
setMode(mode: PermissionMode, principal: Principal): Promise<Result<Appended>>modePermissionModerequiredThe new mode: plan, dont_ask, default, accept_edits or bypass.
principalPrincipalrequiredWho takes this action; recorded as the event's actor and authorized by the host.
Returns an error value with one of these codes: forbidden, invalid_transition, branch_busy.
usage
Token totals over this branch (a fork counts its parent's prefix). A response whose counts the provider didn't report, or that would take a total past 2^53-1, is counted in unknown_responses: never as zero and never out of range.
usage(): Promise<Result<UsageTotals>>Returns an error value with one of these codes: log_corrupt, unsupported_format, unsupported_critical_event.
cost
What this thread spent, in nano-units of its pinned currency (USD for agent()), with a conservative upper bound; null when its pinned policy has no currency or no model list (a pinned currency with an unpriced model gives an incomplete cost instead). With tree: true it also counts every subagent at any depth, each from its own log, in the root's currency (else the first priced subagent's); null when no readable thread in the tree has a currency, which means unknown, never zero, even when a subagent's log is missing. complete and bounded become false when some spend can't be counted: a thread priced in another currency, or an unpriced thread that called a model. A subagent with no log counts nothing but makes the total incomplete and unbounded, since nothing proves it spent nothing. Errors: a subagent's read error; log_corrupt when a finished subagent's log is missing (except a cancel's record of a child it never created: cancelled, usage unknown), a subagent doesn't name the spawn that started it, or the tree names a thread twice; cost_overflow past 2^53-1 nanos. Messages name the path of child thread ids. With a team, tree: true also walks every member the lead started, model- or operator-started and nested teams too; a member still starting counts zero and leaves complete true (it has made no model request), and a member whose branch is missing after its starter received its task notification, or whose parent is not the lead's member_started or thread_started, is log_corrupt.
cost(options?: {
tree?: boolean;
}): Promise<Result<Cost | null>>treebooleandefault falseAlso count every descendant subagent, each from its own log.
Returns an error value with one of these codes: log_corrupt, unsupported_format, unsupported_critical_event, cost_overflow.
cacheBreaks / cache_breaks
Turns whose prompt-cache reads dropped sharply, each with its likely cause. Judged by the pinned policy.context.cache_ttl_ms, else the default (300000 ms), so always a list.
cacheBreaks(): Promise<Result<readonly CacheBreak[]>>Returns an error value with one of these codes: log_corrupt, unsupported_format, unsupported_critical_event.
todos
The agent's current todo list: the latest one it wrote on this branch, or empty. TypeScript returns the list itself; Python returns Ok or Err.
todos(): Promise<readonly Todo[]>children
Every subagent this thread started, in start order, with its final status or running. TypeScript returns the list itself; Python returns Ok or Err.
children(): Promise<readonly Child[]>