Skip to main content
session_agent_harness is one of the job types the worker supports. A section with this job_type attaches the worker to one BlobHub session, watches that session’s thread session objects, and — when the user marks a thread as pending — spawns a coding agent (Claude Code or Codex) inside the configured workspace and bridges items between the agent and the thread. The job type uses no new BlobHub backend endpoints — it speaks the existing session-object and thread-item commands as the user whose API key the worker carries.

What a section does

For each session_agent_harness section in config.yaml, the worker:
  1. Claims a per-session worker object — the affinity lock and activity log; see The worker session object.
  2. Discovers thread session objects in the session and runs the handoff state machine for each — when a user marks a thread instance.state = "pending", the worker validates the workspace and spawns the configured coding agent.
  3. Bridges items:
    • Outbound — for each agent emission (text, tool call, tool result, thinking, status, turn end, pending prompt), posts a thread item with brief text plus structured metadata. See Thread items.
    • Inbound — coalesces other users’ thread items into the next agent turn.
  4. Supports interactive prompts — when the agent pauses for input, posts a pending_prompt item and resolves the user’s plain-text reply back to the agent. See Interactive prompts.
  5. Resumes cleanly after restart — replays missed items, re-attaches the agent session, cancels stale prompts. See Recovery.

Identity

The worker acts on behalf of a single BlobHub user. At login, the worker validates the API key against GET /v1/users/me and records the returned user_id in the profile’s own identity.yaml. Every API call the worker subsequently makes is attributed to that user. Recommendation: run the worker under a dedicated service-account user with its own API key. If a human is logged in as the same user and posts to the same thread via the web UI, the worker classifies those posts as its own and ignores them — see Reference. The worker also carries an instance_id, generated fresh on every start. The instance_id is informational (logs, dashboard) and is not an affinity key.

Sections

A session_agent_harness section targets exactly one BlobHub session via (org_id, blob_id, revision_id, session_id)session_id may be a uuid or a session alias, resolved once at attach — and, optionally, one slice of that session’s object namespace (session_prefix). One worker process can run any number of sections — each runs independently. Two sections targeting the same (session, prefix) are refused with DUPLICATE_SECTION_TARGET. Configuration reference (including a full working example): Configuration. Namespace scoping and how the worker discovers work under a prefix: Concepts.

The worker session object

When a section attaches to a session, the worker creates (or claims) a session object whose alias is worker. Its value is a thread-typed session object whose metadata carries:
  • metadata.user.user_id — the affinity key. A different worker (different user_id) refuses to attach with SESSION_OWNED_BY_DIFFERENT_USER.
  • metadata.instance.* — the currently running instance (id, version, started_at, last_seen_at, status). Informational only.
The worker also posts items into this thread as it operates — attached, thread_active, thread_failed, detached, etc. They form a visible worker activity log in the session. There is no blobhub-worker detach command in v1. To free a session, delete the worker session object via the API: delete_session_object(session_id, alias="worker"). The running worker observes the deletion and stops that section cleanly. Full shape and rules: The worker session object.

Threads and the agent state machine

Inside a session, the user creates thread session objects (one per task the agent should drive). The user populates each thread’s metadata with a workspace + an agent block, then sets instance.state = "pending" to hand it off:
A thread may also carry an artifacts block (publish a local output folder into the session namespace each turn) and a policy block (the rule set a guarded thread is gated against). Both are covered in full on Job Session Object. The worker watches session events, picks up pending threads, validates the workspace, spawns the coding agent inside work_folder, and drives instance.state through the canonical state machine: The worker writes only instance.state, and only the values active and failed. The user owns workspace.* and agent.* and sets instance.state (pending to hand off, completed to stop cleanly, reset failed → pending to retry). The agent resume pointer (agent_session_id) and any error detail are local to the worker — they never appear on the wire envelope; a failure surfaces server-side as a thread_failed item in the worker activity log. Full object shape and field ownership: Job Session Object. State-machine dynamics: Handoff.

Items in / items out

While a thread is active, two flows run in parallel:
  • Inbound — the worker watches session events for session_thread_item_posted{alias}. New user items are dropped through a self-filter (the worker’s own posts are ignored), coalesced into the next agent turn, and handed to the agent’s prompt.
  • Outbound — each agent emission (text segment, tool call, tool result, thinking, status, turn-end, pending prompt) is translated into a thread item with brief text in content and structured detail in metadata. The full untruncated payload is always written to a local log for recovery if the item is truncated to fit the 350 KB per-item budget.
Full item shapes: Thread items. Interactive prompts: Interactive prompts.

Process model

session_agent_harness sections run in the same single worker process, on a single asyncio event loop. The dashboard (Textual) is opt-in via --tui. By default the worker runs headless with structured JSON logs to stderr, suitable for systemd or a container. Single-instance enforcement is generic — see Filesystem layout.

See also