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 SQLite log and artifact store (sqlite()).
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.
saveCase / save_case
Writes a conformance case directory in exactly the spec/conformance layout, so the same runners replay it: case.json (with expect and input.text), log.threads-ts.jsonl and log.threads-py.jsonl with matching expected.<impl>.json, artifacts/, model.json and stubs.json. The kind is stub: external effects replay from stubs.json and unmatched invocations fail closed. The case is built from typed values, so it is valid against case.schema.json by construction; core carries no JSON Schema evaluator, and both languages' tests validate every saved case against case.schema.json.
saveCase(name: string, options: {
expect: CaseExpectation;
externalEffects: "stub";
at?: EventId;
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 snapshot event the case restores. Omitted: the branch's latest fork point; a branch without one is no_snapshot_boundary.
dirstringdefault casesDirectory in the app repo; the case is <dir>/<name>/.
Returns an error value with one of these codes: invalid_request, no_snapshot_boundary, case_missing_dependency, egress_policy_unsupported.
pendingApprovals / pending_approvals
Open approval challenges on this branch, each with the tool call it guards and the suggested_rules an approver may keep. Python returns Ok or Err.
pendingApprovals(): Promise<readonly PendingApproval[]>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, 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.
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; otherwise it takes the branch lease for the one append. A lease another process holds is branch_busy and nothing is appended.
cancel(principal: Principal): Promise<Result<Appended>>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, branch_busy.
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.
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). 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 that never started counts nothing. Errors: a subagent's read error; log_corrupt when a finished subagent's log is missing, a subagent doesn't name the spawn that started it, or the tree loops; cost_overflow past 2^53-1 nanos. Messages name the path of child thread ids.
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[]>