Worker process & identity
A single worker is one long-running process that authenticates to BlobHub as one BlobHub user — the identity recorded byblobhub-worker login. Every API call the worker makes (reading
sessions, posting thread items, updating envelopes) is made as that user, with that user’s access.
Because the worker filters its own emissions out of the inbound stream using only user_id, posts made by
the same user the worker runs as are treated as the worker’s own and are not forwarded to the agent. Run the
worker under a dedicated service-account user with its own API key so that humans posting into a thread as
themselves are seen as not-self and reach the agent.
Sections & job types
The unit of organization is a section: one entry undersections in config.yaml describing a single
unit of work the worker should run. Each section names a job_type, the discriminator that selects what the
section attaches to and how it behaves at runtime.
session_agent_harness is the only job type in v1. See Job Types for the discriminator
and how new job types are added.
Sessions, threads & the agent
Asession_agent_harness section attaches to one BlobHub session. Inside that session it discovers
thread-typed session objects and treats each one as a unit of work — a “job”. For each thread it hands off,
the worker drives one coding agent (Claude Code or Codex) in that thread’s work_folder on the worker host,
streaming the agent’s output back as thread items and feeding inbound posts to the agent.
One agent runs per active thread. The session is shared; the worker marks its attachment with a single
worker session object that doubles as an activity log.
Namespace scoping & discovery
By default a section watches an entire session. Settingsession_prefix (see
Configuration) narrows that to one slice of the session’s object namespace —
useful when a session holds far more than this worker’s threads — an application is free to keep graphs,
records and other objects in the same namespace, and the worker should never touch those or spawn an agent
for one.
Discovery is list, then watch:
- At attach, the section pages every session object under the prefix and classifies each alias it
hasn’t seen before: an alias is the worker’s own work when its object is
type: threadand carries either anagentblock or aninstance.state. Everything else — a graph, a record, a published artifact — is not the worker’s, and that verdict is cached on disk so a later restart never downloads it again. - In steady state, the section watches the session’s event stream (filtered to the prefix) instead of
re-listing: a modified object whose event carries
type != "thread"is added straight to the not-mine set with no download at all — the event’s owntyperules it out for free. A modified object whose event carriestype == "thread"still gets downloaded and classified, exactly as at attach, since only the full object can confirm whether it carries anagentblock orinstance.state. Either way, an item posted to a known alias is delivered to that thread, and an item posted to an alias the worker never registered is dropped — no download, no agent spawned for someone else’s thread.
not_mine.yaml for where the
classification cache lives on disk.
The control channel
A user hands a thread to the worker, and takes it back, through one field on the thread session object’s envelope:instance.state.
The user sets
pending to hand off, sets completed to stop the worker cleanly, and resets failed → pending
to retry. The worker writes only active and failed. It never writes completed. See
Handoff for the full state machine.
Agents, models, effort, permissions
A thread can specify which agent runs and how. The user-facing knobs are the agenttype (claude_code or
codex), an optional executable, model, effort (low | medium | high, or a harness-specific
passthrough string), and permissions.
permissions controls the approval gate — three rungs, each answering “who decides” differently:
guarded needs no human in the loop for whatever its rules cover, without handing out the blank check
autonomous does — but a policy is a guardrail against mistakes, not a sandbox. See
Job Session Object → Policy and guarded permissions for the
rule model and its limits before relying on it for a sensitive workspace.
These knobs resolve through three tiers — the thread’s own agent.* settings, then the worker’s
agents.<type> config, then the codebase default — with the most specific winning per field. See
Configuration for where you set them.
Concurrency & run modes
concurrency.max_agents in config.yaml caps how many agents run at once across all sections. A thread that
goes pending while the worker is at capacity waits for a slot to free before activating.
The worker runs in two modes:
- Headless (default) — structured JSON log lines go to stderr; suitable for systemd, a container
supervisor, or
tmux. --tui— a live Textual dashboard; logs are redirected to a file instead of stderr.
blobhub-worker start for both modes.

