Skip to content

nest

the place inside humd where bees gather and cells live — defines the WorkerBee + ForagerBee traits, the Cell runtime, and the Nest pool

A nest is the space inside a humd where two kinds of inhabitants meet: bees (the askers / producers, connected over thrum) and cells (the live LLM subprocesses, spawned by worker bees). The nest itself isn’t a process or a model — it’s the meeting place. One humd, one nest.

This crate defines what lives in the nest and how:

  • WorkerBee trait — produces compute. Owns the spawn, the stdin pipe, the parsed-event stream, and the cell lifecycle.
  • ForagerBee trait — translates outside wires (OpenAI, Anthropic, custom HTTP) into thrum. Stub-only today; concrete impls live in hives/.
  • Cell struct — one live LLM subprocess. The compute itself.
  • Listener trait — what humd binds to a cell to receive parsed events for a particular sid.
  • Nest struct — the pool runtime. Owns the cells keyed by pool_key, dispatches stdin writes, evicts on idle, enforces max_procs.

humd today is a router: it doesn’t link WorkerBee impls in at build time. Instead, worker-bee binaries (under hives/) attach to humd over thrum and announce themselves with chi:"hello" carrying bee: ["worker"]. The Nest pool above is the Rust SDK for in-process embeddings — the canonical wire-attached path is the helper in hives/common/ (nest_common::serve_worker).

The three things — clearly

There are exactly three things this crate is about. They get confused constantly. The README will spell them out and then never let them blur:

wordwhat it isexample
nestthe space inside humd. Not a process, not a model. Where bees and cells coexist.one per humd
cellone live LLM subprocess that lives in the nest. The actual compute.the claude-cli child process
WorkerBee (Rust trait)the kind of compute — how to spawn it, what it speaks, when it’s ephemeral. Implementation.claude-cli, claude-repl, future openai-api

And the cohabitants from the other side of the thrum:

wordwhat it is
hivethe kind/contract a bee conforms to (defined in hives/ or hives/). Doesn’t run; doesn’t send anything.
beethe running instance. Declares its kinds on hello: bee: ["worker"], bee: ["forager"], or both.
nestlerthe bee, in the act of joining. Pre-acceptance. Awaiting the breath.
nestledthe bee, after joining. Same actor, registered, has a nestledId. Keeps asking throughout the connection.

Seven words, seven different referents. nestler → nestled is a state transition (one actor, two lifecycle stages); both are askers.

Where the nest fits

bees (outside)
│ thrum tones (NDJSON over Unix socket)
humd
├── ensemble ← cross-humd routing
└── nest ← THE SPACE (this crate)
├── nestled<1> ← bees, post-handshake
├── nestled<2>
└── cells ← live LLM subprocesses
├── cell<A> spawned by claude-cli WorkerBee
├── cell<B> spawned by claude-repl WorkerBee
└── cell<C> spawned by openai-api WorkerBee (hypothetical future)

The nest is BELOW ensemble (ensemble routes between humds; nest is local to one humd) and INSIDE humd. Nestleds and cells share it. chi traffic crosses between them: a nestled’s chi:"prompt" is routed to a worker bee’s cell; that cell’s chunks flow back to the nestled.

The WorkerBee trait

#[async_trait]
pub trait WorkerBee: Send + Sync {
fn ephemeral(&self) -> bool;
async fn spawn(&self, spec: SpawnSpec) -> Result<Cell>;
}

A WorkerBee is a recipe for a kind of cell. Two methods:

  • ephemeral() — does the pool evict this cell after each result? (PTY/REPL-style harnesses say yes; pipe-mode say no.)
  • spawn(spec) — turn a high-level SpawnSpec (sid, modelId, cwd, system prompt, MCP url, …) into a running Cell.

The trait says nothing about what the cell is. A WorkerBee impl might fork a local Claude binary, open an HTTPS connection to OpenAI’s API, load a llama.cpp model into the same address space, or return a canned-response mock for tests. The wire never sees the difference — all the worker has to do is produce a Cell whose stdin → events behaves correctly.

Cell

pub struct Cell {
pub pid: Option<u32>,
pub stdin: mpsc::Sender<String>, // push raw NDJSON to the cell
pub events: Arc<Mutex<mpsc::Receiver<Value>>>, // pull parsed JSON back
pub exited: tokio::sync::oneshot::Receiver<i32>,
pub ephemeral: bool,
pub kill: Arc<dyn Fn() + Send + Sync>,
}

One live LLM subprocess seen from the humd side. The stdin and events channels are the only contract — whatever’s behind them is the WorkerBee impl’s business.

The Nest pool

let nest = Nest::new(
NestConfig { max_procs: 8, idle_timeout: Duration::from_secs(300) },
pipe_worker, // Arc<dyn WorkerBee> — long-lived pipe-mode cells
pty_worker, // Arc<dyn WorkerBee> — ephemeral PTY/REPL cells
);

Two worker bees by convention: a pipe-mode one and a PTY-mode one. A pool that wants to colocate compute keeps these in-process. humd today doesn’t — it routes prompts to thrum-attached worker bees that live in their own processes.

Operations the daemon calls on the pool when it’s in use:

callwhat happens
nest.murmur(spec, prompt, listener)spawn-if-needed + write chi:"prompt" to stdin + bind listener for the sid
nest.reply(sid, tool_use_id, result)route a chi:"tool-result" reply to the right cell’s stdin
nest.interrupt(sid, request_id)inject control_cancel_request mid-turn
nest.fell(sid)tear the cell down — host called chi:"cleanup"

Why a trait, not a hard-coded path

hum is LLM-agnostic by design. The protocol (thrum), the mesh (ensemble), the drone, the bees — none of them know which model is behind the nest. WorkerBee is the seam where that decision lives, and where it gets swapped.

kind of cellwhat its worker spawnswhen to use
claude-cliclaude -p with stream-json over pipenormal model-CLI usage
claude-replclaude-cli in interactive REPL mode over a PTYnon-stream-json fallbacks, debugging
(future) openai-apiHTTPS to OpenAI’s API; chunks streamed via SSEuse GPT-4 from your humd
(future) ollama-locallocal LLM via Ollama’s CLI or HTTPrun open-weights models on a laptop

A new model harness adds one WorkerBee impl. No code in humd, thrum-core, ensemble, drone, or the wire needs to change.

What this crate is NOT

  • Not “compute” by itself. The compute is the cell. This crate is the space the cell lives in and the traits that define what kind of bee can produce / translate. Same nest word, distinct referents — nest is the room, cell is who lives there.
  • Not a thrum endpoint. Cells don’t speak thrum directly. humd reads cell events via Listener and writes the thrum side. Worker bees running as standalone processes write thrum themselves via hives/common/serve_worker.
  • Not a router. Nest picks the right cell for a sid; it doesn’t decide which humd handles which conversation. Ensemble owns that.

Layout

nest/
├── src/
│ ├── lib.rs # SpawnSpec, Cell, WorkerBee + ForagerBee traits,
│ │ # Listener trait, encode_* helpers
│ ├── pool.rs # Nest — the cell pool runtime
│ └── mock.rs # MockWorkerBee for sim tests
└── README.md

Concrete WorkerBee impls live under hives/:

  • hives/claude-cli/claude -p stream-json over pipe
  • hives/claude-repl/ — PTY-mode interactive fallback
  • hives/common/ — shared building blocks across worker impls (e.g. the serve_worker helper + the regex Classifier for drone’s context-loss detection)

See also

  • WIRE.md — the thrum protocol; see “The nest model” section for what the wire sees (and what it doesn’t) about nests and cells.
  • drone/ — the sentinel that observes the chunks coming out of a cell and scores channel health.
  • humd/ — the daemon that owns the Nest instance.
  • VOCABULARY — the canonical glossary; entries for nest, cell, hive, bee, worker, forager, nestler, nestled.