Skip to content

Architecture

Steven Enamakel edited this page Sep 1, 2026 · 5 revisions

Architecture

Three crates, split by one question: does answering require waiting on something?

crates/
├── tinyhivemind-core/   the pure algebra. No async, no IO, no host types.
├── tinyhivemind/        the session runtime. Ports a host implements.
└── tinyhivemind-hive/   bounded group deliberation. Pure, opt-in.

If a question can be answered from its arguments, it lives in the core crate. If it has to await a read, a write, or a model call, it lives behind a port in the runtime crate. When the answer is unclear, the decision goes in core and the waiting goes in the runtime. That split is what makes the interesting logic testable without a fixture.

tinyhivemind-core

The algebra. Desks and membership, the roster, the mention grammar and its resolution, the responder ladder's decisions, and the fold that projects a shared transcript into one viewer's turn history.

module what it holds
chat conversation identity, and the four stored spellings that mean the default desk
desk desk records and the borrowed overlay fold behind DeskSet
dispatch bounded selection of at most one mentioned child turn
mention mention parsing and pure routing choices
roster borrowed agent and person identity snapshots
responder deterministic selection of one agent for one message
error the crate-wide Error and Result

This crate sits on the hot path of every agent turn, so it has to compile in a host's default build with no feature flags behind it. It may not depend on an async runtime, a transport, an HTTP client, a web framework, a SQL client, a git implementation, or anyhow. .github/scripts/assert-pure.sh asserts that in CI, and the enumerated list in that script is the source of truth.

The constraint is not only about compile cost. A crate that cannot call out cannot grow a path back into its host, which is the layering violation this repository exists to fix.

tinyhivemind

Everything that has to wait, expressed against ports a host implements.

module what it holds
session the SessionLog port, the paging walk, and attributed projection
threads a bounded, recency-ordered index of a desk's live threads
sharing continuous transcript sharing over a caller-owned watermark
responder the Selector port and model-assisted responder choice
dispatch the mention-dispatch edge and the MentionTurnQueue boundary
briefing ephemeral team initialization, and typed per-turn context

It depends on the core crate and re-exports it, so a host takes one dependency instead of two and the types crossing the boundary are the same types rather than structural twins.

Three ports, and nothing else, is the whole surface a host implements: SessionLog, Selector, and MentionTurnQueue.

tinyhivemind-hive

Bounded group deliberation. It answers a different question from the other two: not who responds to this message, but how a room reaches a decision.

module what it holds
trace the marker grammar and its read
salience recency decay, importance, and relevance
quorum standings, cross-inhibition, and the consensus predicate
attention the bid each member makes for the floor, and the argmax
episode the state machine and the visibility filter

It defines no port. An episode is step(state, transcript, roster, desks, policy) -> HiveStep, a fold over arguments the caller already holds, and the host does its waiting through the three ports it already implements. All arithmetic in it is fixed-point integer, so every payload derives Eq and every fold reproduces.

See Hive episodes.

What this repository will not grow

A second journal. Messages are addressed by sequence number across surfaces the host owns, including reactions, board cards, and run rows. A second append-only log could not be kept consistent with the first, so there is not one.

Host types. Nothing here may name a type from a consuming application. A snapshot or a borrowed view crosses the boundary. A callback into the host does not, because a callback seam is exactly how the original layering problem grew.

Fan-out. One message, one turn. See ADR 0002.

Releases. Every crate is publish = false. A consumer pins the repository as a submodule and takes the crates as path dependencies, so the pinned commit is the version and a tag would be a weaker second name for it.

Clone this wiki locally