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

# Filesystem Layout

> The shared credential store, and what a profile keeps under ~/.blobhub-worker/

The worker touches two directories. Credentials live in `~/.blobhub/`, **shared with
[blobhub-cli](/cli/configuration)**. Everything else lives under `~/.blobhub-worker/` (mode `0700`), divided
into one **state tree** per [profile](/worker/auth). 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`](/worker/job-types) and documented on that job type's filesystem page.

## The two trees

```
~/.blobhub/
└── credentials.yaml                    # shared with blobhub-cli; mode 0600

~/.blobhub-worker/
├── profiles/
│   └── {profile}/                      # the key came from this stored profile
│       ├── identity.yaml               # durable: the user this profile resolved to
│       ├── instance.yaml               # this profile's lock (pid + instance_id)
│       ├── config.yaml                 # sections list; user-editable
│       ├── logs/{instance_id}/worker.jsonl
│       └── jobs/                       # per-section subtrees, owned by each section's job_type
└── keys/
    └── {digest}/                       # the key came from --api-key or BLOBHUB_API_KEY
        └── ... identical contents
```

**Nothing lives at the top level of `~/.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](/worker/auth#which-tree-a-run-uses) 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:

```yaml theme={null}
default: prod
profiles:
  prod:
    key: bhk_...
    url: https://api.blobhub.io/v1
  staging:
    key: bhk_...
    url: https://api.staging.example.com/v1
```

The worker refuses to start if the file is readable by group or other
(`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`.

```yaml theme={null}
user:
  user_id: usr_01J...
  name: Ada Lovelace
  email: ada@example.com
  recorded_at: 2026-05-27T12:34:56Z
```

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

### `instance.yaml` — the lock, per tree

Atomically created at `start` with `O_EXCL` and removed on clean shutdown.

```yaml theme={null}
instance:
  instance_id: ins_01J...
  pid: 12345
  host: laptop.local
  version: 0.5.0
  started_at: 2026-05-27T12:35:00Z
```

A stale file naming a **dead** PID is replaced on the next start. A **live** PID is checked further: if
it belongs to a worker, `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`](/worker/cli/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](/worker/configuration) page. The shape of each section depends on its
[`job_type`](/worker/job-types).

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:

```
profiles/{profile}/jobs/
└── session_agent_harness-{session_id}/
    └── ...
```

A job type's subtree isn't necessarily flat: `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](/worker/session-agent-harness/filesystem).

## 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](/worker/auth#upgrading-from-a-pre-profiles-worker) for the two cases it refuses
and the one that needs a follow-up.

## See also

* [Authentication and Profiles](/worker/auth)
* [Configuration](/worker/configuration)
* [Job types](/worker/job-types)
* [Session Agent Harness → Filesystem layout](/worker/session-agent-harness/filesystem)
