~/.blobhub/, shared with
blobhub-cli. Everything else lives under ~/.blobhub-worker/ (mode 0700), divided
into one state tree per profile. YAML is used for every file a user might want to read or
edit.
This page covers the generic files. Per-section runtime state lives under a tree’s jobs/, in subdirectories
owned by each section’s job_type and documented on that job type’s filesystem page.
The two trees
~/.blobhub-worker/. A tree is named after where its key came from — a
stored profile, or a one-way digest of a directly supplied key — so two credentials can never share one. See
Authentication and Profiles for why that is keyed on origin rather than
on the resolved profile name.
Run blobhub-worker whoami to print the tree a given invocation resolves to. For a key-derived tree you will
need it: the digest is not guessable, and config.yaml has to be authored inside the tree before the first
start.
File-by-file
~/.blobhub/credentials.yaml — the shared store
Written by blobhub-worker login and by blobhub login alike, mode 0600. A default: naming one profile,
and a profiles: map:
INSECURE_CREDENTIALS_PERMISSIONS) — it is never repaired for you. An absent file is not an error: a key
supplied by --api-key or BLOBHUB_API_KEY needs no file at all.
identity.yaml — durable identity, per tree
The user this tree’s credential resolves to. Recorded on the tree’s first start if it is not there
already, and compared on every start after; a mismatch is USER_IDENTITY_MISMATCH.
Absence stopped being an error in v0.5.0. Because the store is shared,
blobhub login --profile ci can create
a profile this worker has never seen — refusing its first start would tell you to run a login you had just
run. IDENTITY_NOT_FOUND now means only that the file is present and unreadable.instance.yaml — the lock, per tree
Atomically created at start with O_EXCL and removed on clean shutdown.
start refuses with WORKER_ALREADY_RUNNING, naming the profile; if it is alive
but belongs to some other program — the PID was recycled — the lock is reclaimed the same as a dead one,
rather than blocking the tree forever. Because the lock lives inside the tree, two workers on different
profiles never contend — that is the point of the layout.
blobhub-worker instance ls|show|stop read this file (and the tree around it) to inventory and stop the
workers on this machine — see blobhub-worker instance.
config.yaml — sections
User-edited, one per tree. Full reference — generic blocks plus the per-section session block and per-agent
settings — is on the Configuration page. The shape of each section depends on its
job_type.
Because it lives inside the tree, --profile prod and --profile staging read different configurations and
watch different sessions. INVALID_CONFIG names the full path it looked at, which is the quickest way to
learn where a key-derived tree is.
logs/{instance_id}/worker.jsonl
Structured log, one JSON record per line. Captures preflight — including the worker_resolved record naming
the profile and tree — section dispatch (attach / refuse), transient API errors, and shutdown. Per-section
detail goes to a section-scoped log under that section’s jobs/ subtree.
This file is written only when the worker runs with --tui — the TUI redirects the log stream to keep the
terminal clean. In headless mode the same records go to stderr instead and no file is written. Log
rotation is not implemented; the file grows for the life of the instance.
The jobs/ subtree
Each running section gets a directory under its tree’s jobs/. The naming convention and the contents are
owned by the section’s job_type. Today the only job type is session_agent_harness, which lays out:
session_agent_harness nests one directory per /-separated
segment of a thread’s alias, so a section scoped to part of a session (session_prefix) ends up with
directories several levels deep under threads/.
Full reference: Session Agent Harness → Filesystem layout.
Migrating from a pre-profiles layout
Before v0.5.0 these files sat flat at the top of~/.blobhub-worker/, with the credential in its own
credentials.yaml there. The first start after upgrading moves them into profiles/default/ and the
credential into the shared store, printing what it moved. See
Authentication and Profiles for the two cases it refuses
and the one that needs a follow-up.

