A thread is handed to the worker and returned through its lifecycle state machine. The user drives the thread by settingDocumentation Index
Fetch the complete documentation index at: https://docs.blobhub.io/llms.txt
Use this file to discover all available pages before exploring further.
instance.state = pending; the worker takes over, runs the agent, and reports progress
back through the worker activity log. This page covers the dynamics — the states, the transitions, and
what the worker does in each. The static envelope shape and field-ownership rules live on the
Job Session Object page.
The control channel is instance.state on the thread envelope (value.thread.metadata), with the values
pending, active, completed, and failed. The user owns the workspace and agent settings and sets
instance.state to pending (hand off) or completed (clean stop), and resets failed → pending to
retry. The worker writes only instance.state, and only the values active and failed.
State machine
The user-driven states arepending (hand off), completed (clean stop), and the failed → pending
reset. The worker-driven states are active (activation succeeded) and failed (a thread-level error).
pending → active checklist (worker side)
When the worker sees a thread envelope with instance.state == "pending", it runs this sequence with
respect to the envelope:
- Validate
workspace.work_folder:- must be absolute → else
WORK_FOLDER_NOT_ABSOLUTE - must exist → else
WORK_FOLDER_NOT_FOUND - must be a directory → else
WORK_FOLDER_NOT_A_DIR - must be readable → else
WORK_FOLDER_NOT_READABLE
- must be absolute → else
- Validate
agent.typeis in the supported set (claude_code,codex) → elseAGENT_TYPE_UNSUPPORTED. - Validate the resolved
permissionsisautonomousorapproval→ elsePERMISSIONS_UNSUPPORTED. - Reserve a slot from the
concurrency.max_agentssemaphore. If saturated, the thread stays inpendinguntil a slot frees. - Spawn the configured agent with its working directory set to
work_folder. Record the resumableagent_session_idreturned by the SDK adapter (local-only). - Catch up: fetch
list_session_thread_items(created_since = items.last_consumed.created_at, ascending=true), drop self items, and feed the remaining user items increated_atorder to the agent’s first turn. - Write
instance.state = "active"on the envelope; persistthread.yaml(recording the localagent_session_idand clearing the local error). - Post a
thread_activeactivity-log item on theworkerthread.
failed with the corresponding error code; the
semaphore slot is not reserved.
Active behavior
While the thread isactive:
- Inbound — new thread items posted by other users (not the worker’s own user) are queued in
memory. Between turns the worker drains the queue, coalesces all queued items in
created_atorder into one prompt, and runs the next agent turn. The agent is never interrupted mid-turn. - Outbound — every agent emission becomes a thread item with a brief
content[0].textand structuredmetadata. See Thread items. - Interactive prompts — when the agent asks a question, the turn blocks and the worker posts a
pending_promptitem. See Interactive prompts. - Cursors —
items.last_consumedadvances only at a completed turn boundary, so a crash mid-turn replays the items the resumed agent hasn’t yet folded in.items.last_postedadvances per successful post.
active → completed (clean stop)
The user stops a thread cleanly by setting instance.state = "completed" on the envelope. The worker
observes the change, tears down the live turn loop, releases the concurrency slot, records the state
locally so a later restart does not try to recover the thread, and posts a thread_completed activity-log
item on the worker thread. completed is user-set — the worker never writes it.
active → failed triggers
| Trigger | Code |
|---|---|
| Agent process exited unexpectedly | AGENT_CRASHED |
| Agent executable missing on PATH | AGENT_EXECUTABLE_NOT_FOUND |
| Persistent 4xx posting items | THREAD_POST_FAILED |
| Item exceeds cap even after truncation | THREAD_ITEM_TOO_LARGE |
AGENT_CRASHED — there is no separate resume error code.
On transition to failed, the worker writes instance.state = "failed" on the envelope, records the
error in the local thread.yaml, releases the concurrency slot, cancels any active prompt, and posts a
thread_failed activity item that carries the error code and message. The error itself is not placed
on the envelope — it lives in thread.yaml locally and surfaces server-side only through the
thread_failed activity item.
failed → pending reset
To retry a failed thread, the user updates the envelope to set instance.state = "pending". The worker
observes the change, clears its local agent block, and re-runs the pending → active activation.
The worker never auto-retries a failed thread.

