Skip to main content
This page is the reference for the session_agent_harness job type: the error codes it can raise — in three classes beyond the generic process codes that fire before any section runs — and the v1 limitations specific to driving agents over a session.

Section validation

These fire during preflight, before the section is contacted. They exit the process with the code, the same way the generic process codes do. This static check can’t catch two sections naming the same session under different session_id spellings (one by uuid, one by alias) — that collision surfaces after resolution instead, as the section-level DUPLICATE_SECTION_TARGET below.

Section-level errors

One section fails to attach to its session; the failure is persisted in jobs/{job_id}/section.yaml.attachment.error and the section stops, while the worker keeps running all other sections.

Thread-level errors

A single thread transitions to failed, terminal until the user resets it (instance.state = "pending"). The error detail is persisted locally in jobs/.../threads/{alias}/thread.yaml (agent.error) — it is not written to the thread envelope. It surfaces to other clients as a thread_failed item in the worker activity thread.

Transient errors

Logged and retried with exponential backoff up to polling.backoff_max_ms. They surface in the TUI warning ribbon and do not trigger any state-machine transition on their own (a long-running transient may eventually present as a thread-level THREAD_POST_FAILED after exhaustion). Posting a thread item follows a stricter rule, because a repeated post is a duplicate item. Only an API_RATE_LIMITED or a failed connect is retried as is. Any other failure may have stored the item, so the worker reads the thread back for the item’s post_id before it posts again, and when that read-back cannot complete it does not post again at all. See Recovery → Network blips.

Limitations

These trade-offs are specific to session_agent_harness. For generic worker limitations (no hot config reload, single-process / single-machine), see Reference.

guarded is a guardrail, not a sandbox

A policy (permissions: guarded) catches string/glob-matched mistakes; it is not OS-level confinement, and of the three supported harnesses only codex has a sandbox underneath it — claude_code and antigravity have none, so on those two the policy is the only gate at all. See Job Session Object → Policy and guarded permissions for the full statement, the per-harness capability table, what codex’s sandbox refuses and escalates for a write + outside: work_folder rule (including disguised escapes like hard links, the .git/.agents/.codex carve-outs, and the no-sandbox-platform case), how antigravity’s gate runs as a hook in a separate process, and why an exec rule governs which commands run — never where they write — on any of the three. Read it before writing rules for a sensitive workspace, not after discovering a gap the hard way.

An unauthenticated agent CLI is not an activation failure

Activation checks that the configured executable resolves on PATH and nothing else. A CLI that is installed but not signed in passes that check: the thread goes active, the turn starts, and the agent prints whatever it prints when it needs credentials — for agy, an authorization URL — with no one to answer it. The turn then sits until the harness’s own print timeout, which the worker sets far beyond any plausible turn precisely so that ending a turn stays a caller’s decision. So the two install failures surface at opposite ends. A missing binary fails the dispatch immediately with AGENT_EXECUTABLE_NOT_FOUND, before a backend exists. An unauthenticated one fails nothing and produces a thread that looks busy. Sign each agent CLI in on the worker host before pointing a thread at it, and if a thread activates and then produces nothing, check the agent’s own authentication first.

Same-user posting collision

A human posting into a thread via the BlobHub web UI as the same user whose API key the worker uses is classified by the worker as “self” and ignored. The worker uses user_id as the only signal for filtering its own emissions out of the inbound stream; there is no per-post “posted by worker” flag in v1. Run the worker under a dedicated service-account user with its own API key so humans posting as themselves are seen as not-self and reach the agent.

No detach command

There is no blobhub-worker detach subcommand in v1. To free a session manually, delete the worker session object directly:
The running worker observes the deletion event and stops that section with SESSION_DETACHED_EXTERNALLY. Other sections continue.

Same-user worker races

Two worker installs sharing the same API key (same user_id) will race to overwrite each other’s worker marker silently — both will run and both will post. One worker install = one service-account user with one API key; don’t share credentials across machines.

Unavailable attachments never fail the thread

When a file attachment can’t be materialized — the download fails after 3 attempts, or the thread’s inbox itself is unusable (an escaping attachments.inbox warns inbox_invalid and disarms attachment materialization for that whole activation) — the worker doesn’t fail the thread or the turn. It posts a status / attachment_unavailable item naming what’s missing and why, tells the agent the same thing in its own prompt, and the turn proceeds without that file. See Thread Items → attachment_unavailable and Job Session Object → Attachments.

No agent retry on failure

When an agent crashes or fails, the worker records the error in the local thread.yaml, transitions the thread to failed, and posts a thread_failed activity item. It does not auto-retry. Inspect thread.log / the local agent.error.message, then update the envelope to set instance.state = "pending"; the worker observes the change and re-runs the activation.

Unresolved interactive prompts don’t survive restart

If the worker is restarted while a pending_prompt is unanswered, the prompt is cancelled with pending_prompt_resolved (reason: worker_restart). The agent re-asks on resume if it still needs the input.

Multi-question prompt answering is best-effort

For ask_user_question prompts with multiple questions, the plain-text answer parser is best-effort (line-prefix 1) ..., 2) ... format). Unparseable replies fall back to each question receiving the raw text as a free-text answer. Answer one question at a time when the agent asks several at once.

No native UI for thread metadata editing

To put a thread into pending state for the worker to pick up, you currently update the thread envelope’s value.thread.metadata directly via upload_session_object (REST or SDK) — for example:
There is no inline editor in blobhub-web for these fields in v1; a dedicated editor is a deferred fast-follow.

See also