> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blobhub.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Introduction

> session_agent_harness: drive coding agents against BlobHub session threads

`session_agent_harness` is one of the [job types](/worker/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](/worker/session-agent-harness/worker-object).
2. **Discovers thread session objects** in the session and runs the
   [handoff state machine](/worker/session-agent-harness/thread-handoff) 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](/worker/session-agent-harness/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](/worker/session-agent-harness/interactive-prompts).
5. **Resumes cleanly after restart** — replays missed items, re-attaches the agent session,
   cancels stale prompts. See [Recovery](/worker/session-agent-harness/recovery).

## Identity

The worker acts on behalf of a single BlobHub user. At [`login`](/worker/cli/login), the worker
validates the API key against `GET /v1/users/me` and records the returned `user_id` in
`~/.blobhub-worker/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](/worker/session-agent-harness/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)`. One worker process can run any number of sections —
each runs independently. Two sections targeting the same session in the same config are refused
with `DUPLICATE_SECTION_TARGET`.

Configuration reference (including a full working example):
[Configuration](/worker/configuration).

## 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](/worker/session-agent-harness/worker-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:

```yaml theme={null}
workspace:
  work_folder: /absolute/path/to/repo   # required
agent:
  type: claude_code                     # required (or codex)
instance:
  state: pending                        # the control channel
```

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:

```mermaid theme={null}
stateDiagram-v2
    [*] --> pending: user sets instance.state = pending
    pending --> active: worker validates and spawns the agent
    active --> completed: user sets instance.state = completed
    active --> failed: agent crash / terminal error
    failed --> pending: user resets
```

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](/worker/session-agent-harness/thread-object). State-machine dynamics:
[Handoff](/worker/session-agent-harness/thread-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](/worker/session-agent-harness/thread-items). Interactive prompts:
[Interactive prompts](/worker/session-agent-harness/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](/worker/filesystem).

## See also

* [Job Session Object](/worker/session-agent-harness/thread-object) — object shape, ownership, settings tiers.
* [Configuration](/worker/configuration) — full working example with one section.
* [Filesystem Layout](/worker/session-agent-harness/filesystem) — per-section/per-thread on-disk state.
* [Handoff](/worker/session-agent-harness/thread-handoff) — the lifecycle state-machine dynamics.
* [Recovery](/worker/session-agent-harness/recovery) — what survives restarts and what doesn't.
* [Reference](/worker/session-agent-harness/reference) — error codes and limitations.
* [Thread session object reference](/blob-types/workflow/operations/post-session-thread-item)
