hiveloomdocs

Architecture

hiveloom starts from a simple definition: agent = model + harness. It confines that agent to one job and makes its success provable. The harness declares the task, tools, context, loop policy, budgets, guardrails, and verification as one versioned execution boundary. A capable builder agent or a human generates that boundary; a small, cheap model executes inside it; the runtime decides whether the result satisfies the contract; and run traces become memory for a gated evolution loop.

This is behavioral task confinement rather than a virtual-machine security boundary. The exact guarantees and limits are described in task-confinement.md.

The pieces

                         hiveloom
  ┌────────────────────────────────────────────────────────────────┐
  │  spec/            the harness contract (pydantic) + YAML loader   │
  │    schema.py      every field/type/default — single source of     │
  │                   truth for schema, template, explain, generator  │
  │    loader.py      YAML <-> spec (round-trip safe), code-hook       │
  │                   import + signature checks                        │
  │    annotate.py    JSON schema, annotated template, `explain`       │
  │  catalog.py       catalog entries: tools/guardrails/validators/    │
  │                   policies/compaction/hooks/eval components         │
  │  ext.py           the open catalog: ExtensionAPI, pack/user/harness │
  │                   extension discovery, provider registry, model     │
  │                   pricing, blueprints — see docs/extending.md       │
  │                                                                     │
  │  construct.py     init/set/add/remove — the ONE path that builds   │
  │                   a harness (CLI + generator both call it),         │
  │                   validate-and-rollback per step                    │
  │  trust.py         trust store gating foreign harness folders        │
  │                                                                     │
  │  runtime:                                                           │
  │    models/        ModelProvider ABC → Claude | OpenAI-compat | Fake │
  │      capabilities declared/live probes, identity policy, cache      │
  │      router.py    which model is current, and which provider serves │
  │                   it — mid-run hot-swap at a turn boundary          │
  │    tools/         registry (active/deferred) + sandboxed builtins; │
  │                   ToolRegistry owns the MCP sync/async bridge       │
  │    context/       assembly, budgeting, compaction, spill, skills   │
  │    guardrails/    Allow/Block/Halt hooks (frozen from evolution)    │
  │  private.py       effective RunBoundary: one private-state view      │
  │  confine.py       OS confinement for spawned processes: bwrap /     │
  │                   sandbox-exec + rlimits, scrubbed env, timeouts    │
  │  egress.py        provider + external-tool outbound screening       │
  │    events.py      lifecycle event bus (spec `hooks:` + ambient)     │
  │    verify/        validators = the reward signal                    │
  │    loop/          engine + pluggable policies (react | plan | …)    │
  │      control.py   RunControl: stop · steer · switch playbook/model  │
  │    skills.py      progressive-disclosure SKILL.md folders           │
  │    runner.py      assemble + drive a run; `hiveloom run [--stream]` │
  │    serve.py       stdlib HTTP wrapper: POST /runs, GET /healthz     │
  │                                                                     │
  │  fork.py          re-enter a finished run at a model call; lineage  │
  │                                                                     │
  │  logging/         trace.py (append-only JSONL) + hive.py (SQLite)   │
  │      journal.py   the fold events → context state, + chain verify   │
  │                                                                     │
  │  generate/        strong model → construction plan → construct.py   │
  │                   (+ blueprints: house-style prompt fragments)      │
  │  evolve/          analyzer (Hive) → propose → gate → apply          │
  │    proposals.py   queue: create/list/get/apply/reject (Hive-backed) │
  │  package.py       portable <name>-<hash>.zip (+ Dockerfile, packs)  │
  │  serve/           HTTP surfaces (non-production)                    │
  │    simple.py      `hiveloom serve`: stdlib /runs + /healthz         │
  │                   — the rest is `hiveloom control-plane`, see       │
  │                   docs/control-plane.md:                            │
  │    keys.py        ed25519 keypairs, compact-JWT sign/verify         │
  │    auth.py        authorized-keys store, bearer verification        │
  │    runslots.py    bounded run concurrency for POST /run             │
  │    app.py         Starlette app: auth + spec lock + endpoints       │
  │  guide.py         AGENTS.md + skills/, packaged; `hiveloom guide`   │
  │  cli.py           Typer CLI over all of the above                   │
  │                                                                     │
  │  devtools/                                                          │
  │    ui/            the workbench — published to npm as               │
  │                   `hiveloom-workbench`; see docs/workbench.md       │
  └────────────────────────────────────────────────────────────────┘

Data flow of a run

hiveloom run ./h --input notes.txt
  │
  ├─ load spec, resolve code hooks (fail fast)
  ├─ resolve effective RunBoundary (including per-run path overrides)
  ├─ build: tool registry · guardrails · verifiers · context manager · trace
  │
  ├─ AgentLoop (react):
  │     before_model_call guardrails  ─┐
  │     context.assemble (compact?)    │  loops until: completion +
  │     provider.complete  ────────────┤  verify pass · max_turns ·
  │     after_model_response guardrails│  guardrail Halt
  │     dispatch tool_use → registry   │
  │       (before/after_tool_call)     │
  │     append tool_results ───────────┘
  │     on completion → verify → retry_with_feedback | success/fail
  │
  ├─ TraceWriter appends every step, hash-chained, to
  │    .hiveloom/traces/<run_id>.jsonl
  └─ auto-ingest the journal into the Hive

With sequential_steps, object steps sit above the same loop rather than creating another execution path. The policy filters the registry before each phase, blocks hidden calls again at dispatch, tracks required successful calls, and applies per-step model/tool limits. step_started, step_violation, step_completed, and step_failed events enter the journal; final bounded receipts are returned on RunResult.steps and indexed as Hive run_steps.

Verification receives a separate, run-local evidence view. The loop records only allowed calls after dispatch and builds a bounded VerificationContext from redacted tool inputs/results, step receipts, and declared artifacts. Each verifier gets its own deep copy, so extension code cannot change the evidence seen by another verifier. The context is not rebuilt from a trace and never includes a prior run. grounded_references uses this view to compare selected scalar output references with configured tool-result paths.

Truncated turns

A no-tool response ending at max_tokens is incomplete. While turns remain, the loop keeps the partial response in context and feeds back the truncation. It preserves the operator's configured output budget. Three consecutive truncations, or exhaustion of loop.max_turns, end recovery. The partial output still passes through output hooks, guardrails, and verification; passing verification establishes success, otherwise the run reports truncated (CLI exit 4). Runs without verification also report truncated in this case.

The journal records turn_truncated, and the Hive indexes output_truncated friction for evolution. An operator can raise the configured budget through hiveloom set model.max_tokens VALUE --dir HARNESS --json when appropriate; the model's registered output limit is a capability, not permission for the runtime to increase that budget automatically.

A run's identity is the run_id, and it is the only execution identity across the CLI, the API, the Hive, the journal, and the workbench. Branching a run always produces a derived run rather than a grouping above it.

RunControl (loop/control.py) is the operator seam into a run already in flight: stop, addressable steering messages, a playbook switch, or a model swap, all applied at the next turn boundary, where no model call or tool is outstanding. hiveloom serve, the control plane, and the workbench are clients of it, not privileged paths around it.

Logging as memory (the journal and the Hive)

Every run is an append-only JSONL journal with a common envelope (run_id, harness_name, harness_version_hash, seq, timestamp, prev) and typed events (run_started, context_append, context_system, context_tools, model_call, model_response, tool_call, tool_update, tool_result, tool_spilled, guardrail_triggered, hook_triggered, hook_error, context_compaction, playbook_switch, model_swap, provider_egress_redacted, provider_egress_blocked, tool_egress_blocked, network_access_decision, verification_result, run_finished).

Three properties make it more than a log, and each buys something concrete:

  • Progressive. The conversation is appended once, message by message. A model_call references the folded context (context_head, system_hash, tools_hash, messages_hash) instead of re-snapshotting it, so the file is linear in turns rather than quadratic. logging/journal.py is the fold back to state, and it is the single implementation trace --materialize, fork, and the workbench all share.
  • Tamper-evident. Each line carries prev, the sha256 of the line before it. hiveloom trace --verify walks the chain and names the first break.
  • Self-describing. run_started carries the dumped spec plus a path -> sha256 manifest of every behavioural file, so the harness that ran is reconstructible from the record — which is what makes forking possible at all.

fork.py turns that record back into somewhere to start from: it rebuilds the harness that actually ran, verifies it against the manifest, folds the conversation to a chosen model call, and writes an experiment under <harness>/.hiveloom/forks/<name>. See journal.md.

The Hive is a SQLite index over those journals (~/.hiveloom/hive.db, $HIVELOOM_DB to override), ingested idempotently by run_id. It answers, per version hash, the questions evolution needs: success rate / cost / turns, the most common failure verdicts and guardrail triggers, and the N most recent failed traces with their verifier feedback. It also carries lineage (parent_run_id, forked_at_seq), the run's task for search, and its model_path — runs whose model moved mid-flight are held out of the per-version fitness bucket and reported separately, because they did not execute the harness as declared.

Identity is deliberately split before evidence reaches evolution:

  • schema_version says which harness document contract was parsed;
  • the behavior hash covers the validated spec and every referenced behavior file, including playbook prompts;
  • the execution fingerprint adds the runtime, requested and effective models, run-only overrides, input digest, model path, and lineage.

Renaming legacy version to schema_version is a document migration, not a behavior change. The behavior hash normalizes that one spelling transition so old Hive buckets stay usable.

The Hive also derives a normalized friction index from redacted journal events. A recovered output validation failure or tool retry remains separate from final success and can be queried by category, component, model, time, and recovery state. The index stores bounded summaries and fingerprints, not raw tool or model payloads.

External scorers can attach immutable numeric observations to an indexed run. These RunMetric records remain separate from binary deferred outcomes and join to execution provenance through run_id. Aggregation groups by metric name, source, scope, unit, and direction, so a case score cannot silently mix with a run or eval score. Every group reports its sample and missing-value counts; raw traces are not needed to regenerate the aggregate.

An eval document resolves outside the runtime loop. Its dataset loader creates held-out EvalCase values; the normal harness path produces a public RunResult; only then do scorers receive the case, result, verification context, and artifacts. Scorer failures have their own receipts and cannot rewrite the run status. Content digests cover the eval document, loaded dataset, and scorer implementations before a batch starts.

The native eval runner expands that resolved contract into deterministic case and repetition cells. It checkpoints an atomic manifest below HIVELOOM_HOME/evals, keeps traces in the same durable managed tree, and revalidates eval, harness, adapter, effective-model, and case identities before resume. Infrastructure attempts have distinct run IDs; completed harness outcomes and scorer receipts are never retried or conflated.

Every manifest checkpoint also replaces its eval-cell snapshot in the Hive in one transaction. Reporting joins those cells to immutable run_metrics, so aggregate JSON and paired comparisons are reproducible after trace retention prunes the journals. Pairing uses hashed case identity plus repetition; cost sources remain separate and unmatched cells stay visible.

Configured evolution objectives select bounded numeric history by metric name, source, scope, and unit. Aggregation never mixes units or recorded directions. Runs are grouped into explicit cohorts by behavior hash and requested/effective provider and model; individual execution fingerprints and bounded evidence run IDs remain in the receipt. When eval case and repetition keys exist, the same evidence also carries paired cohort comparisons. Metric metadata and raw traces do not enter this path. Missing values remain missing, and floors or ceilings are reported as hard violations rather than folded into a weighted score.

When explicitly enabled, evolution uses those rows as incident anchors and selects a bounded window from each validated journal. It re-applies structured redaction before truncation and budgeting; a missing or retention-pruned journal falls back to its indexed summary. Proposals store an evidence digest and selection receipt, never a second copy of the selected payloads.

Evolution and the safety boundary

hiveloom evolve reads the Hive's clustered failures, asks a strong model for a minimal mutation, then gates it in code:

  • guardrails, model, logging.redact, extensions, hooks, mcp_servers, confinement, egress, evolution.auto_propose, evolution.reflect, evolution.trace_excerpts, evolution.objectives, and the memory budgets (schema.ALWAYS_FROZEN) — plus any path the harness lists as frozen — can never be changed;
  • accepted changes must fall within the harness's mutable set;
  • regenerated code hooks always require explicit human approval.

Applied mutations bump # evolved: N, re-validate, and are recorded in the Hive under a new version hash. stats compares version buckets; a measured assessment or paired evaluation is needed to establish that a mutation helped. The runtime and packager fingerprint both the YAML spec and its referenced local code, schemas, extensions, and skills, so a validator or hook edit also creates a distinct version bucket.

The end-to-end deploy-anywhere-keep-evolving loop (run in prod on a cheap model → collect in-folder traces → evolve deliberately on a dev/CI box → redeploy → judge by version hash) is documented in deploying-and-evolving.md.

Research above the execution and evolution engines

research/ composes the existing runner, eval manifests, signal analysis, and evolution gate. It has a separate charter and program state; it does not add another harness document format or let the director write the live spec.

Component Responsibility
charter.py, program.py Validate allowed search space and effects, snapshot c0, freeze identities and splits
engine.py, planning.py Advance units, run staged paired measurements, decide keeps, stack compatible winners, stop and confirm
execution.py Resolve allow/sandbox/replay/deny policy and tighten each candidate registry
budget.py, ledger.py Pool accounting and a verifiable event chain
director/ Packaged director harness with typed evidence and experiment tools
concepts.py, contract.py, judges.py, questions.py Draft/approve evaluation criteria, independent sealed cases, judge trust, and bounded human labels
service.py Frontend payloads, charter templates, background jobs, progress, and restart recovery

Candidate identities isolate their Hive measurements from production. The engine owns state and scoring; the director operates through permitted tools. Promotion merges kept spec changes with live edits and queues a normal proposal. The workbench reads this same service and opens executor case journals in Trace. See Research programs for the lifecycle and limitations.

Design invariants

  • One construction path. CLI and generator both go through construct.py; the loader is the single enforcement layer.
  • Schema is the single source of truth. The JSON schema, annotated template, explain, and the generator meta-prompt are all derived from spec/schema.py.
  • The folder is the harness, not the runtime. A harness is portable and versionable; pip install hiveloom (plus any packs named in hiveloom.lock) provides the engine wherever it lands.
  • Catalog-as-truth, and the catalog is open. Builtin and extension-registered entries appear in hiveloom catalog; declared MCP tools are discovered dynamically with hiveloom mcp list-tools; extensions register entries through hiveloom.ext and the generator sees them immediately (see extending.md).
  • Extensions widen choice, never the evolution gate. Registered providers, policies, and hooks expand what a human or generator may pick; the frozen paths and human code approval are untouched, and foreign folders are trust-gated before their code loads.
  • The record is checkable, not merely written. Append-only is a claim the hash chain lets a reader test, and "we cannot tell" (a pre-1.0 trace with no chain) is reported as a distinct answer from "it was tampered with".
  • Evidence is bucketed by what actually executed. A version hash covers the spec and its referenced local code; an external model override is separated from ordinary spec fitness. Models selected through the spec's playbooks remain part of that spec's behavior.
  • Safety invariants live in code, not convention — see docs/spec.md and evolve/evolver.py.