Skip to main content

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.

These are the items a Job Session Object carries: every agent step the worker observes becomes one thread item, posted via post_session_thread_item. The content array carries a brief human-readable rendering; metadata carries the structured detail.

General shape

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

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.

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:
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