Skip to main content
This page covers the per-section files the session_agent_harness job type writes under the profile’s own jobs/. For the generic layout (identity.yaml, the shared credentials file, instance.yaml, config.yaml, logs/{instance_id}/worker.jsonl) see Filesystem Layout.

Tree

The agent runs with cwd = workspace.work_folder; the worker keeps no separate agent-session directory on disk. Session resume is SDK-managed and keyed by agent_session_id (recorded in thread.yaml). job_id is deterministic: session_agent_harness-{session_id}, always keyed on the resolved session id — even when config.yaml names the session by alias (see Configuration). This guarantees stable paths across restarts, reinstalls, and config edits — and namespaces this job type away from any future ones.

Nested thread state for path aliases

A thread’s alias can itself contain /, which is what an application with a path-structured namespace hands off, e.g. projects/<pid>/items/<iid>/runs/<n>/thread. Each /-separated segment becomes one nested directory, so that alias lands at:
A flat alias like my-feature-x (no /) lands exactly where it always has: threads/my-feature-x/thread.yaml. Both shapes coexist under the same threads/ root without collision — directory and file names are just alias segments, so the state for alias a and the directory for alias a/b don’t conflict (threads/a/thread.yaml vs. threads/a/b/thread.yaml).

File-by-file

section.yaml — per-section runtime state

Persisted by the worker. Recovery on next start re-reads this.
session.session_id here is recorded exactly as configured — a uuid, or an alias if that’s how config.yaml names it. It is not necessarily the resolved id used to derive job_id (the directory name one level up): if you configure an alias, this field keeps showing that alias while the enclosing directory is always named after the resolved uuid.

not_mine.yaml — classification cache

An attach-time optimisation: the set of aliases under this section’s watched prefix that were downloaded once and found not to be the worker’s own work (a graph, a record, a published artifact — anything that isn’t a thread-typed object carrying an agent block or an instance.state). A later attach skips the download for every alias already in this set.
This is purely a cache and is never consulted in steady state: once attached, the section re-classifies from the type each session_object_modified event carries, so a stale entry here can delay picking up a newly-relevant alias by at most one attach, and can never suppress it permanently. Every artifact alias the worker publishes itself (see Job Session Object → Artifacts) is added here as it’s uploaded — artifacts are the dominant volume in a busy namespace, so this is what keeps a cold attach to a long-lived instance bounded. See Concepts → Namespace scoping & discovery for the full discovery model this cache supports.

logs/section.log

Section-scoped JSON-line log: poll cadence, event dispatch, thread discovery.

The local outbox — inside work_folder, not under jobs/

When a thread’s envelope carries an artifacts block, the worker creates <work_folder>/<artifacts.source> (with parent directories) the moment that thread activates — so the folder exists before the agent’s first turn looks for it. Unlike everything else on this page, the outbox lives inside the repository checkout (work_folder), not under ~/.blobhub-worker/profiles/{profile}/jobs/. That is a constraint, not a layout preference: under approval or guarded, Codex runs its shell and file-edit tools inside a workspace-write OS sandbox scoped to work_folder, so a write outside it doesn’t happen silently — it escalates into a fileChange approval request first, the same request an out-of-workspace write anywhere else would trigger. An unattended publish step can’t depend on someone answering that request every turn, so the worker sidesteps the question entirely by keeping the outbox inside the work_folder the sandbox already grants. The worker makes the folder it creates self-ignoring: it writes a .gitignore containing a single * into the outbox root. work_folder is typically a git checkout, so without this, scratch output would show up in git status and could be swept into an agent’s own git add -A. A single * ignores the directory’s entire contents — including the .gitignore file itself — needs no cooperation from the repository’s own ignore rules, and works no matter what artifacts.source turns out to be. Writing it is idempotent and confined to the outbox root the worker itself created; the worker never deletes, moves, or rewrites a file already there. See Job Session Object → Artifacts for the publish contract and what happens to the files once they’re in this folder.

threads/{alias}/thread.yaml — per-thread state

The worker’s local, authoritative-for-recovery view of a thread, plus the inbound/outbound item cursors. This local layout differs from the wire envelope: it keeps workspace.agent_type and agent.state, and the agent_session_id resume pointer and error block are local-only — they never appear on the wire. The mapping is documented on Job Session Object.

threads/{alias}/logs/thread.log

Captures the full untruncated agent stdio + tool I/O. When the worker truncates a thread item to fit the 350 KB budget, the local log retains the full payload — see Thread Items.

Why both server-side metadata and thread.yaml?

  • The server-side envelope is authoritative for handoff. Anyone with API access (web UI, SDK, another tool) reads it to see the agent state machine.
  • thread.yaml is authoritative for local recovery. The worker writes it before any externally observable side-effect, so a crash-restart finds a truthful local record.

See also