Skip to main content
The worker is a long-running process that connects coding agents on your own infrastructure to BlobHub sessions. This page covers the handful of concepts you need before connecting it to your sessions: how the worker acts as a user, how it is organized into sections by job type, how it drives an agent per thread, and the control channel a user uses to hand work to the worker and get it back.

Worker process & identity

A single worker is one long-running process that authenticates to BlobHub as one BlobHub user — the identity recorded by blobhub-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 under sections 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

A session_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. Setting session_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:
  1. 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: thread and carries either an agent block or an instance.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.
  2. 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 own type rules it out for free. A modified object whose event carries type == "thread" still gets downloaded and classified, exactly as at attach, since only the full object can confirm whether it carries an agent block or instance.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.
Because steady state re-classifies from live event data rather than the cache, an alias that was cached as not-the-worker’s and later becomes a real work thread is picked up automatically on its next envelope write — the cache only ever saves a download, it never suppresses one permanently. See Filesystem Layout → 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 agent type (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.
See blobhub-worker start for both modes.

See also