Repository navigation
Hive language
A hive package is a hive written down as data: its roles, seats, desks, goals,
policies, and memory layout. tinyhivemind-lang reads a package, checks it,
and turns it into the core values a host needs to start the hive. Because the
whole definition is data, an editor can edit its files directly and the host
can check, evaluate, and archive that change. Hive language editing
covers that part. Hive gallery walks through the seven example
packages.
This page describes the format and the three calls that handle it: parse,
validate, and lower. The accepted behavior is in the
language specification,
and the boundary decision is
ADR 0032.
The crate never touches the file system. A package is a
BTreeMap<String, String> that maps a relative path to the file's contents,
and the host builds that map however it likes: from a directory, a database
row, or a test's include_str!. parse turns the map into a typed Package,
validates it, and returns it.
The crate stays pure for the same reasons core does. It reads no files, runs no
agents, calls no model, and owns no evaluation, archive, or memory store.
lower produces the inputs a host needs to create seats, and the host decides
when to activate them. The crate does not name OpenHuman types either. That
conversion lives in tinyhivemind-openhuman's language module, so any host
can use the language. It is in the pure_crates list that
.github/scripts/assert-pure.sh checks.
review-board/
├── hive.json # the manifest: roles, seats, desks, goals, policies
├── constitution.json # host-pinned caps and edit authority
├── memory.json # memory identities and bindings, never contents
├── roles/author.md # one charter per role
├── roles/reviewer.md
├── seats/author.md # one prompt per seat, optional JSON frontmatter
├── seats/reviewer-a.md
├── seats/reviewer-b.md
└── context/*.md # shared documents split into stable items (optional)
parse sorts the keys it is given into three groups:
-
hive.json,constitution.json, andmemory.jsonare required. Each is decoded into its typed section. - Every other key ending in
.md, in any letter case, is kept inPackage::documents. That includes Markdown no manifest entry references, such as aREADME.md. It is accepted and kept, so it counts toward the package's content identity. - Every other file is dropped. The gallery uses this:
script.jsonandsim.jsonbelong to thehive_viewexample, not to the package.
Every key has to be a safe relative path, including keys that end up dropped.
A path fails if it is empty, starts with /, contains \ or a control
character, has an empty, ., or .. segment, or contains :.
The examples on this page come from the
review-board
package, trimmed where marked. Every field is required except the nullable
ones such as model, which may be left out, and unknown fields are rejected.
{
"$schema": "https://tinyhumans.ai/schemas/hive/v1",
"version": 1,
"id": "review-board",
"name": "Review board",
"roles": [
{ "id": "author", "charter": "roles/author.md" },
{ "id": "reviewer", "charter": "roles/reviewer.md" }
],
...
}$schema and version identify the wire version. Any other value fails with
Error::Schema. The URI is a name and is never fetched. id is the package
identity the host uses for activation and lineage. A role points to its charter
in roles/.
"seats": [
{
"id": "reviewer-a",
"label": "reviewer-a",
"role": "reviewer",
"prompt": "seats/reviewer-a.md",
"template": "hive-view",
"config": { "max_iterations": 8, "temperature": 0.0 },
"model": null,
"tools": { "allow": ["file_read"], "deny": ["shell"] },
"mcp": [],
"skills": []
}
],A seat fills one role and has one prompt under seats/. template names a
host factory and must not be blank. config is non-secret host
configuration, passed through untouched. model, tools, mcp, and skills
go to the host's seat factory. No credential ever appears here.
"desks": [
{
"id": "board",
"name": "Review board",
"description": "Decide whether the retry-policy change merges",
"members": ["author", "reviewer-a", "reviewer-b"],
"responder_mode": "auto"
}
],
"goals": [],
"missions": [],
"topology": [],
"referrals": [],
"context": [],A desk is core's own Desk value, in its own wire form; see
Desks and rosters. Every member must be a declared seat,
listed once. review-board leaves the rest empty. The other packages fill
them in:
-
Goals have a description,
acceptancecriteria the host evaluates, andsubgoals. The decomposition must be acyclic.research-debatesplits averdictgoal intosurveyandcritique. -
Missions bind a role to the goals it owns, such as
{"id": "decide", "role": "moderator", "goals": ["verdict"]}. -
Topology and referrals are directed
{"from", "to"}desk links. Topology is a graph and may have cycles;math-swarmchains five desks in a ring. See Cross-desk referral. -
Context lists shared documents under
context/(see below).
"policies": {
"episode": {
"turn_budget": 9, "round_width": 3, "revealed_width": 3,
"blind_round": true, "dominance_cap": 50, "repetition_cap": 3,
"distance": "sequence",
"quorum": { "threshold": 2, "window": 30, "require_grounded": true },
"weights": { "recency": 5, "importance": 30, "relevance": 20, "half_life": 20 }
},
"routing": {
"minimum_confidence": 0, "high_impact_minimum_confidence": 0,
"clarification_threshold": 1000000, "high_impact_threshold": 1000000,
"round_width": 4, "choice_option_limit": 8
},
"approval": { "enabled": false, "default": "deny", "rules": [], ... },
"aside": { ... }, "referral": { ... }, "digest": { ... }, "brevity": { ... },
"directory": { ... }, "exchange": { ... }, "division": { ... },
"conduct": { "child_turn_wall": 6, "turn_wall": 60 }
},
"coordinator": {
"round_width": 4,
"conduct_policy": { "child_turn_wall": 6, "turn_wall": 60 },
"broadcast_budget": null,
"retention": { "settled_episodes": null, "delivered": null,
"interrupted": null, "pending_per_agent": null }
}policies reuses core's policy structs in their own wire form: episode is
EpisodePolicy, routing is the semantic routing policy,
and so on. All eleven sections are required, and so is every non-nullable field inside
them. A package that wants the defaults still writes them, so the file says
exactly what will run. The
routing probabilities are integer parts per million, where 1000000 is
certainty and 250000 is one in four. coordinator holds host-neutral
settings that the OpenHuman adapter copies field by field into its
coordinator options.
{
"hard_constraints": [],
"max_turns": 60,
"max_width": 8,
"permitted_edits": [],
"read_only_memories": [],
"protected_namespaces": [],
"contamination_canaries": [],
"evaluation": null,
"acceptance": null,
"telemetry": null
}The host supplies the constitution, and no edit can change it. max_turns and
max_width cap every turn budget and round width in the manifest.
permitted_edits lists the patch classes
an editor may use, and review-board permits none. read_only_memories pins
memory identities, protected_namespaces fences off memory no seat may reach,
and contamination_canaries are strings no edit may introduce.
hard_constraints, evaluation, acceptance, and telemetry are opaque.
The crate carries them and blocks edits to them, but what they mean is the
host's business. self-improving fills every field in.
review-board has no memory ({"root": "team:hive", "identities": [], "bindings": []}), so this section uses
memory-guild.
{
"root": "team:guild",
"identities": [
{ "id": "archivist-notes", "namespace": "team:guild/agent:archivist",
"kind": "agent", "lifecycle": "hive", "read_only": false, "fork_parent": null },
{ "id": "charter", "namespace": "team:guild/project:charter",
"kind": "pool", "lifecycle": "hive", "read_only": true, "fork_parent": null },
{ "id": "scout-a-notes", "namespace": "team:guild/project:lore/agent:scout-a",
"kind": "agent", "lifecycle": "run", "read_only": false, "fork_parent": "lore" }
],
"bindings": [
{
"seat": "archivist",
"identity": "archivist-notes",
"reads": [
{ "at": "team:guild/agent:archivist", "inherit": false, "descendants": false },
{ "at": "team:guild/project:lore", "inherit": false, "descendants": true },
{ "at": "team:guild/project:charter", "inherit": false, "descendants": false }
],
"recall_at": ["session_start", "rejoin", "compaction"],
"budget_chars": 4000,
"remember": ["observation", "outcome", "note"]
}
]
}An identity is either an agent memory or a shared pool. Its lifecycle is
run (one run only) or hive (it persists across runs). It can be read-only,
or a fork beneath a parent identity. A binding gives one seat one identity.
reads sets the namespaces the seat may read, recall_at sets when the host
recalls, and budget_chars bounds the recall. remember limits which entry
kinds the seat may write. The package never holds remembered contents; the
host's memory store does.
A role charter or seat prompt is plain Markdown.
You review for correctness. Re-run the failing case in your head before you
speak; `!support #merge ^N` only when a cited message shows it passes.The markers in the prompt are the trace grammar. The language doesn't interpret them. It only hands the prompt to the host.
A seat prompt or role charter can repeat its manifest fields as frontmatter.
memory-guild's seats/archivist.md:
---
{"id":"archivist","role":"archivist","memory":"archivist-notes","budget_chars":4000}
---
You are the archivist. Recall the lore pool and the pinned charter before you
speak, cite the decision record you rely on, and remember outcomes, not chatter.Frontmatter is optional and only JSON is accepted. It is a JSON object between
an opening ---\n, which must be the document's first four bytes, and the
first \n---\n. The manifest is the source of truth, and frontmatter may only
repeat it:
- A seat's frontmatter may hold any field of its manifest seat (
id,label,role,prompt,template,config,model,tools,mcp,skills) and its memory binding settings (reads,recall_at,budget_chars,remember, plusmemoryfor the bound identity id). Each value must equal the manifest's value. - A role's frontmatter may hold only
idandcharter. - A document that opens with
---\nand never closes it, or whose frontmatter is not a JSON object, is rejected.
Each of these failures is Error::Frontmatter. Lowering strips frontmatter,
so the host gets only the prompt body.
A shared context document is split into items by stable markers. From
research-debate's context/brief.md:
# Embedding index brief
<!-- item:latency -->
Rebuilding the index takes 41 seconds on the reference corpus; loading a cached
copy takes 1.2 seconds.
<!-- item:staleness -->
The corpus changes on roughly one run in five. A cache keyed on the corpus
digest is invalidated exactly then.An item id uses ASCII letters, digits, -, and _, and must be unique within
the document. An item's body runs from its marker line to the next marker. An
editor can add, replace, or remove one item without rewriting the rest of the
document, which keeps context that has built up over many runs intact.
Each document has to sit in the directory for its purpose. A seat's prompt
must be under seats/, a role's charter under roles/, and a context path
under context/, and all of them end in .md. That stops an edit class from
reaching a document through another class's directory.
parse validates before it returns. A host that builds a Package some other
way, such as the whole-package JSON form below, must call validate itself.
lower validates first anyway. Each check fails with a typed Error. The
messages below are what the gallery packages produce after one deliberate
change.
| Check | Error | Example message |
|---|---|---|
| A required file or referenced document is absent | MissingDocument |
missing document seats/author.md |
| JSON does not match the schema, including unknown fields at any depth | Json |
invalid json in hive.json: unknown nested schema field |
Wrong $schema or version
|
Schema |
unsupported package schema or version |
An empty or duplicate id among roles, seats, desks, goals, missions, desk members, memory identities, namespaces, or bindings, or a blank template
|
Identity |
invalid seat identity reviewer-a |
| A role, seat, desk, goal, or memory id that does not resolve | Reference |
unresolved role reference auditor |
| An unsafe path, or a document outside its purpose directory | Path |
unsafe relative path roles/author.md |
| A bound or constitution cap broken | Bound |
invalid policy bound episode.width |
| A goal that is its own ancestor | GoalCycle |
cyclic goal decomposition at <id> |
| A malformed memory namespace | Namespace |
invalid memory namespace <ns> |
| A protected, out-of-root, inheriting, or wrongly pinned memory reach | MemoryAccess |
forbidden memory access team:other |
| Malformed or duplicate context item markers | Context |
invalid context item markers in <path> |
| Frontmatter disagreeing with the manifest | Frontmatter |
frontmatter disagrees with manifest for author |
A few rules are worth calling out:
-
Widths and budgets.
turn_budgetmust be nonzero and at mostmax_turns.round_widthmust be nonzero and at mostrevealed_width, which in turn must be at mostmax_width. This is stricter than core, where the default blind width is 4 and the revealed width is 1, so a package has to choose compatible widths explicitly. Division, routing, and coordinator widths are also capped bymax_width, and the conduct turn walls bymax_turns. These caps apply whatever edit class produced the package. -
Probabilities. A routing probability above
1000000fails to decode (invalid json in hive.json: probability exceeds 1000000). -
Unknown fields. serde's
deny_unknown_fieldscatches unknown keys at the top level. A second pass compares the decoded value with its input to catch unknown keys nested inside reused core policies. The one exception is"responder_mode": "lead": core omits the lead mode when it serializes, so writingleadexplicitly is accepted. -
Memory. Namespaces use the tinymemory shape: up to eight
kind:idparts joined by/, wherekindis one ofagent,team,user,ws,project, orsource. Every identity and every read must be insideroot. Anagentidentity's last part must beagent:<id>. A fork must sit beneath its parent's namespace. An identity the constitution pins must be declaredread_only. No identity or read may touch a protected namespace, andinherit: trueis always rejected because it would reach tinymemory's global root.validate_namespaceandvalidate_pathare exported for hosts that want those checks alone.
lower(&package) validates, then returns a LoweredHive, a snapshot the host
can activate:
| Field | What the host gets |
|---|---|
id, name
|
The package identity and display name |
desks |
Core Desk values, ready for the roster
|
candidates |
One routing RouteCandidate per seat: the role filled in, the role's charter body as the description, and tools.allow as capabilities |
seats |
One LoweredSeat per seat: the factory template and config, the prompt body without frontmatter, model, tools, MCP, skills, and the resolved memory binding |
policies, coordinator
|
The core policies and coordinator settings, unchanged |
goals, missions, topology, referrals
|
Kept as declared, for the host to install |
context |
Each declared context path with its full contents |
memory |
The whole memory spec, without contents |
A seat with a memory binding gets a LoweredMemoryBinding. Its agent_id is
the text after the namespace's last :, and its root is the text before the
last /, or the spec's root when there is no /. In memory-guild, the
archivist's team:guild/agent:archivist becomes agent archivist under
team:guild, and scout-a's fork team:guild/project:lore/agent:scout-a
becomes agent scout-a under team:guild/project:lore. The OpenHuman adapter
accepts an agent identity only when its namespace is exactly
<root>/agent:<agent_id>.
The host must enforce the lowered memory constraints when it registers a runner; lowering only passes them along. Activating a lowered snapshot is the host's decision. It happens between turns or sessions, and a turn already in flight keeps the snapshot it captured.
Package also serializes as one JSON object with four fields: manifest,
constitution, memory, and documents. A host can store or ship a package
that way. live_self_edit writes one, and the OpenHuman live_language
example reads it back. Reading it with serde_json skips parse's Markdown
filter and its nested unknown-field pass, so call validate (or lower)
before using it. Validation re-checks every document path.
The hive_view example does the file reading the library won't do. It loads a
package directory into the map, parses it, lowers it, and then renders it or
runs it through core's hive::step, offline or with --live against a real
model. A package of several desks runs one floor per desk, linked only by
referral along its declared links (see Hive gallery):
H=crates/tinyhivemind-lang/examples/hives
cargo run -p tinyhivemind-lang --example hive_view -- $H/review-board # text tree
cargo run -p tinyhivemind-lang --example hive_view -- $H/review-board --mermaid # flowchart
cargo run -p tinyhivemind-lang --example hive_view -- $H/review-board --json # one JSON object
cargo run -p tinyhivemind-lang --example hive_view -- $H/review-board --run # play an episode--run with no model replays review-board's script.json:
review-board on desk board — 3 seats, budget 9 turns, rounds of 3
round 1 (Deliberate, Blind)
author salience blind saw 1/1 !propose #merge ^1 The retry policy caps attempts ...
reviewer-a salience blind saw 1/2 !question ^1 Does the cap also apply to idempotent reads ...
reviewer-b salience blind saw 1/3 !propose #revise ^1 I want a load test before this merges.
round 2 (Deliberate, Full)
author salience full saw 4/4 !evidence ^2 Rollback is a config flag ...
reviewer-a dissent full saw 4/5 !support #merge ^2 The 200/200 run covers the failing case ...
reviewer-b salience full saw 4/6 !support #merge ^5 The rollback flag answers my load concern ...
round 3 (Commit, Full)
author addressed full saw 7/7 !commit #merge Recording the decision.
converged on #merge
3 rounds, 7 turns
saw 1/2 means the seat's projected view held one of the two lines in front of
it. The opening round is blind, so no one sees their peers' work in progress.
A package without a script runs a seeded default script instead, and a
package with sim.json runs the math simulation. --run --frames streams
NDJSON frames, --run --trace out.jsonl writes core telemetry, and --seed,
--round-width, --turn-budget, and --problem change the run. The example's
README
pins the wire format.
The lab viewer drives the same binary from a browser:
cd examples/lab/viewer
npm install
npm run dev # http://localhost:8099?view=hive opens the gallery, and ?hive=<name> opens one package in the
player, for example http://localhost:8099/?hive=review-board. The player
draws desks as boxes and seats as nodes, with topology as solid arrows and
referrals as dashed ones. It replays rounds with speech bubbles and quorum
standings, and shows exactly which lines each seat saw. &seed=N,
&round_width=N, &turn_budget=N, and &problem=<id> rerun it with
different settings. ?compare=math-panel,math-swarm&seeds=N opens the
Compare tab. The server has no authentication, so keep the port on
localhost. Its
README
covers the API and its bounds.
tinyhivemind is GPL-3.0-only. Built by @senamakel.
Start here
The algebra
- Shared medium
- Desks and rosters
- Mentions
- Cross-desk referral
- Transcript projection
- Threads
- Recall
- Private asides
- Responder ladder
Hive mechanics
- Hive episodes
- Trace grammar
- Transactive memory experiment
- Episode policy
- Benchmarks
- Benchmarks: live rooms
- Benchmarks: delegation
- Benchmarks: private exchange
- Benchmarks: scale
Hive language
Working on it
Reference