Threads AI

Thread

A thread positioned at one branch.

Every method reads or appends through the store; none needs the agent in memory.

A handle returned by the library.

Properties

idThreadIdrequired

The thread's id: pass it to openThread to get a handle again later.

branchBranchIdrequired

The branch this handle reads and appends to.

storeStorerequired

The 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 | ForkPointrequired

Where 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 live

stub 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>>
namestringrequired

The case's directory name under dir: lowercase letters, digits and hyphens, starting with a letter or digit. Anything else is invalid_request.

expectCaseExpectationrequired

What a replay of the case must show; a case needs at least one must assertion, else invalid_request. See CaseExpectation.

externalEffects / external_effects"stub"required

The effects policy is required; stub is the only one.

atEventId

The 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 cases

Directory 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>>
pathstringrequired

The 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_idUuidrequired

The challenge_id of an open approval, from pendingApprovals() or the approval_requested event.

principalPrincipalrequired

The approver answering, recorded as the event's actor. It must be one of the agent's approvers, else forbidden.

rememberRule / remember_rulePermissionRule

One 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_idUuidrequired

The challenge_id of an open approval, from pendingApprovals() or the approval_requested event.

principalPrincipalrequired

The approver answering, recorded as the event's actor. It must be one of the agent's approvers, else forbidden.

reasonstring

Free 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_idCallIdrequired

The call_id of the open ask_user call. A call that isn't waiting for an answer is no_open_question.

answerstring | readonly string[]required

The 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.

principalPrincipalrequired

Who 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_keystringrequired

The 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.

principalPrincipalrequired

The 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>>
principalPrincipalrequired

Who 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>>
principalPrincipalrequired

Who takes this action; recorded as the event's actor and authorized by the host.

instructionsstring

Extra 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>>
settingsSettingsChangerequired

The 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.

principalPrincipalrequired

Who 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>>
namestringrequired

A key of the agent's output_styles. An unknown name is not_found.

principalPrincipalrequired

Who 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>>
modePermissionModerequired

The new mode: plan, dont_ask, default, accept_edits or bypass.

principalPrincipalrequired

Who 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 false

Also 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[]>
Edit on GitHub

On this page