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

# Thread Items

> Content and metadata shape for items the worker posts

These are the items a [Job Session Object](/worker/session-agent-harness/thread-object) carries:
every agent step the worker observes becomes one thread item, posted via
[`post_session_thread_item`](/blob-types/workflow/operations/post-session-thread-item). The
`content` array carries a brief human-readable rendering; `metadata` carries the structured
detail.

## General shape

```yaml theme={null}
content:
  - type: text                       # content-item type (text | json | image later)
    text: <brief human-readable rendering>
metadata:
  type: text | tool_call | tool_result | thinking | status | turn_end | pending_prompt | pending_prompt_resolved
  # plus type-specific fields below
```

`content[].type` and `metadata.type` are in different scopes — content-item shape vs. event shape —
and don't collide. Consumers (renderers, exporters, the future native widget) dispatch on
`metadata.type`.

The presence of `metadata.type` is the **distinguishing mark** vs. plain user-posted items, which
have no `metadata.type` set.

## Item types (per-thread)

### `text`

The agent's user-facing text output.

```yaml theme={null}
content:
  - type: text
    text: |
      Looks like the issue is in `parseToken` — it doesn't handle the new prefix.
metadata:
  type: text
```

A `text` item carries no `metadata` beyond `type` — the full output is `content[0].text`. If an item
exceeds the budget, the generic truncation path excerpts `content[0].text` and sets `metadata.truncated`
(see the truncation rules below); there is no separate full-text copy for `text` items.

### `tool_call`

The agent invoked a tool. Posted before the result so live tailing shows the call landing.

```yaml theme={null}
content:
  - type: text
    text: "Bash → npm test"
metadata:
  type: tool_call
  tool:
    name: Bash
    invocation_id: tu_01J...
    input:
      command: npm test
      description: Run the test suite
```

### `tool_result`

The tool's result for the matching `invocation_id`.

```yaml theme={null}
content:
  - type: text
    text: "→ ✓ src/parser.test.ts (12 ok) (3 lines)"
metadata:
  type: tool_result
  tool:
    invocation_id: tu_01J...
    is_error: false
    output: |
      ✓ src/parser.test.ts (12 ok)
      Test Files  1 passed (1)
      ...
```

A `tool_result` carries no tool `name` — renderers fold a `tool_call` / `tool_result` pair by
`invocation_id` and take the name from the `tool_call`. The brief is `→ <first output line> (N lines)`.

### `thinking`

Internal reasoning blocks (Claude Code). The brief `content.text` shows the first \~120 characters;
the full reasoning is in `metadata.text`.

```yaml theme={null}
content:
  - type: text
    text: "[thinking] Considering the user's request and looking at the parser code..."
metadata:
  type: thinking
  text: "Considering the user's request and looking at the parser code, the prefix handling..."
  full_text_length: 1842
```

### `status`

Progress / status pings (e.g. "Compacting context"). Carries a machine-readable token.

```yaml theme={null}
content:
  - type: text
    text: "Compacting context..."
metadata:
  type: status
  status: compacting_context
  detail: "Reducing 38k → 12k tokens"
```

### `turn_end`

Marker that the agent has finished its current turn. Useful for renderers to draw a turn separator
and for monitoring.

```yaml theme={null}
content:
  - type: text
    text: "Turn complete"
metadata:
  type: turn_end
  stats:
    input_tokens: 12480
    input_tokens_cached: 9600
    output_tokens: 1322
    duration_ms: 8421
```

`input_tokens` is the total input the model processed this turn (cached + uncached);
`input_tokens_cached` is the portion served from the prompt cache. Both adapters (Claude Code and
Codex) report the same breakdown.

### `pending_prompt` and `pending_prompt_resolved`

Used for the interactive-prompt round-trip. See
[Interactive prompts](/worker/session-agent-harness/interactive-prompts).

## Self-filter

Every item the worker posts carries the worker's `user_id` (because BlobHub stamps the calling
user on every post). The worker filters out items where `user_id == self.user_id` when consuming a
thread — they are echoes of its own emissions and must not be re-fed to the agent.

This has a documented consequence: a human posting from the same user account is also filtered.
See [Reference](/worker/session-agent-harness/reference).

## 350 KB truncation

The worker enforces a **350 KB** budget (`350_000` bytes) on each serialized thread item and
truncates large payloads before posting.

```
1. Always write the full untruncated payload to
   jobs/{job_id}/threads/{alias}/logs/thread.log before posting.
2. Build the candidate item dict (content + metadata).
3. budget = 350 KB
   if json.dumps(item) <= budget: post; done.
4. Identify the largest truncatable string fields, in order:
     metadata.tool.output, metadata.tool.input,
     metadata.text, content[0].text
   For each, replace with: <head 4 KB> + "…[truncated <N> bytes; see <local-log-path>]…" + <tail 2 KB>.
   Set metadata.truncated = true.
   Append the field name to metadata.truncated_fields.
   Set metadata.local_log = "<job_id>/threads/{alias}/logs/thread.log".
5. If still > budget after truncating all fields above, drop them entirely (keep markers + field list).
6. If still > budget (very rare), transition the thread to failed with THREAD_ITEM_TOO_LARGE.
```

After truncation, a posted item looks like:

```yaml theme={null}
content:
  - type: text
    text: "→ added 1452 packages in 38s (4210 lines)"
metadata:
  type: tool_result
  truncated: true
  truncated_fields: [tool.output]
  local_log: session_agent_harness-ses_01J.../threads/my-feature-x/logs/thread.log
  tool:
    invocation_id: tu_01J...
    is_error: false
    output: |
      <first 4 KB>
      …[truncated 412992 bytes; see <local-log-path>]…
      <last 2 KB>
```

The `local_log` field is relative to `~/.blobhub-worker/jobs/`. The full untruncated payload is
always recoverable from the worker's local disk.

## See also

* [Job Session Object](/worker/session-agent-harness/thread-object) — the object these items belong to.
* [Handoff](/worker/session-agent-harness/thread-handoff)
* [Interactive](/worker/session-agent-harness/interactive-prompts)
* [`post_session_thread_item`](/blob-types/workflow/operations/post-session-thread-item)
* [`list_session_thread_items`](/blob-types/workflow/operations/list-session-thread-items)
