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

# Worker Session Object

> Affinity lock and activity log for a worker-attached session

When a `session_agent_harness` section attaches to a BlobHub session, it creates a session object
whose alias is literally `worker`. This object serves two purposes at once:

1. **Affinity lock** — the value's `metadata.user.user_id` identifies which BlobHub user owns the
   session for this worker; a different user is refused with `SESSION_OWNED_BY_DIFFERENT_USER`.
2. **Activity log** — the value is a `thread`-typed session object, so the worker can post items
   into it as it operates. The activity log is visible to the user (rendered by the standard
   thread widget) and forms an audit trail.

## Value shape

```yaml theme={null}
type: thread
thread:
  attributes:
    name: worker
    description: BlobHub worker activity log
  metadata:
    user:
      user_id: usr_01J...
    instance:
      instance_id: ins_01J...
      version: 0.1.0
      started_at: 2026-05-27T12:35:00Z
      last_seen_at: 2026-05-27T12:36:30Z
      status: attached          # only value written in v1 (informational)
```

`metadata.user.user_id` is the **only** field consulted for affinity. Everything under
`metadata.instance` is informational and changes on every `start`. The `status` field only ever
carries `attached` in v1 — no other status value is written. On clean shutdown the worker leaves the
object as it is (`last_seen_at` simply goes stale) and refreshes it on the next attach.

## Attach algorithm

On `blobhub-worker start`, each section runs this sequence:

```mermaid theme={null}
flowchart TD
    A["download_session_object (alias = worker)"] --> B{object exists?}
    B -->|absent / 404| C["create — upload with our identity"]
    B -->|present / 200| D{valid and owned by us?}
    D -->|value.type is not thread| E["refuse — SECTION_WORKER_OBJECT_MALFORMED"]
    D -->|user_id is not ours| F["refuse — SESSION_OWNED_BY_DIFFERENT_USER"]
    D -->|yes| G["re-upload a fresh instance block"]
    C --> H["post attached item, persist section.yaml status = attached"]
    G --> H
```

A refused section is recorded in `section.yaml.attachment` with `status: refused` and the relevant
`error.code` / `error.message`. The worker process keeps running other sections — only the refused
section is stopped.

## Heartbeat

While running, the worker periodically refreshes `metadata.instance.last_seen_at` via
`upload_session_object` on the `worker` object. This is **informational only** — it does not drive
takeover. A second worker attempting to attach with a different `user_id` is refused even if
`last_seen_at` is fresh.

## Detachment

There is no `blobhub-worker detach` command in v1. To free a session manually:

```bash theme={null}
# via REST: delete_session_object(session_id, alias="worker")
```

If the worker is running and observes the `session_object_deleted` event for the `worker` alias, it
stops that section with `SESSION_DETACHED_EXTERNALLY` and tears down all agents for that section
cleanly. Other sections continue.

## Activity-log items

The worker posts items into its own `worker` thread as significant events happen:

| `metadata.type`     | When posted                                | metadata extras                           |
| ------------------- | ------------------------------------------ | ----------------------------------------- |
| `attached`          | section attach succeeded                   | (none)                                    |
| `thread_registered` | worker first observed a thread envelope    | `thread: {alias}`                         |
| `thread_active`     | `pending → active` for a thread            | `thread: {alias, agent_session_id}`       |
| `thread_failed`     | `* → failed` for a thread                  | `thread: {alias, error: {code, message}}` |
| `thread_completed`  | `active → completed` (user-set clean stop) | `thread: {alias}`                         |
| `thread_removed`    | thread envelope was deleted                | `thread: {alias}`                         |
| `thread_recovered`  | thread resumed on worker restart           | `thread: {alias, agent_session_id}`       |

Each item's `content[0].text` is a short human-readable rendering of the same event.

## Strict-exclusive ownership

The affinity model is strict-exclusive at the `user_id` level. There is no auto-takeover, no
heartbeat-based override, and no detach command in v1. If two worker installs share the **same**
API key (same `user_id`), they will race to overwrite each other's `instance` block silently —
recommended workaround is one worker = one service-account user. See
[Reference](/worker/session-agent-harness/reference).

## See also

* [Handoff](/worker/session-agent-harness/thread-handoff)
* [Introduction](/worker/session-agent-harness/overview)
* [`upload_session_object`](/blob-types/workflow/operations/upload-session-object) — creates or refreshes the marker.
