regulator asks whether Stafford Beer's can run inside an autonomous agent: not as five chatbot personas, but as explicit functions, authority boundaries, typed channels, verification gates, and feedback loops. It ships its own to find out. Viable Agents, the course, builds it lesson by lesson. This page is the reference for what's actually running underneath it.
An agent harness built on Pi, with a typed VSM control plane layered on top of it. It is built against one recurring failure mode of autonomous agents: architectural drift that outruns anyone's ability to review it.
Stafford Beer's Viable System Model comes out of management cybernetics: it asks what any system (an organism, a company, an autonomous agent) needs structurally to keep operating as its environment keeps generating more variety than it can be made to predict. Beer's answer is five functions plus an independent audit: operations that touch the world (S1), coordination that keeps them from colliding (S2), control that decides what happens next (S3), intelligence that watches the environment and the future (S4), and identity that says what the system is and isn't allowed to become (S5), all checked by a function that never just takes the operators' word for it (S3*).
It matters for an agent harness because the model is not a deterministic component. It changes across versions, has no memory of last quarter's incident, and treats a prompt as advice it can ignore, and under pressure will. A harness that regulates entirely through prompt text has exactly as much regulatory variety as the words in that prompt, which is provably less than the variety of a real codebase, a real filesystem, or real money moving. VSM leaves the model's judgment in place and gives it a boundary it can't talk its way out of.
This is Beer's Viable System Model read as a harness architecture. Command flows down as attenuated instruction (grey), and evidence flows back up as amplified fact (teal). Units act on the domain. S3* audits S3 independently and verifies the domain itself at a revision, never through what S1 reports about it. S4 scans the environment on its own channel, arguing with S3 about now vs. next on S5's terms.
| Name | Function | regulator's interpretation |
|---|---|---|
| S1 — Operations | The units that do the actual work, touching the world directly. | Capability profiles a workload declares: implement, research, intelligence for software, bookkeeper and auditor for the ledger. Each is a grant over tool effects, and a workload in any domain can declare one. |
| S2 — Coordination | Keeps operations from colliding with or thrashing against each other. | Tool contracts, worktree isolation, leases with liveness, reintegration, anti-oscillation mechanisms |
| S3 — Control | Decides what happens next (dispatch, budget, recovery), facing inside the system, now. | The orchestrator: work contracts, dispatch, budgets, the recovery lattice, execution-state authority |
| S3* — Audit | Verifies operations' work independently, without trusting their own report of it. | Harness-run verification plus architectural/domain checks and independent audit findings |
| S4 — Intelligence | Watches the environment and the future and can raise a flag, but never replans on its own. | Read-only specialists and external/environmental intelligence feeding planning and reassessment |
| S5 — Identity/Policy | Declares what the system is, and isn't allowed to become. | Versioned product identity, architecture, domain model, invariants, and policy |
packages/
protocol/ VSM vocabulary + runtime schemas — messages, trace,
effects, profiles, registry, contracts, execution,
policy, recovery, audit, obligations, memory
core/ authority (write preflight), leases, thrash detector,
execution store, signal sink, budgets, recovery
router, effect journal, audit log, obligation ledger
and router, identity + memory stores, definition
check, registry check + REGULATORS.md / BOUNDARY.md
checks/ deterministic S3* checks: host-run verification
bound to a revision, the technical verdict
regulator/ the control plane: the `regulator` CLI, the S3 loop,
the stores, the host seam, and the definition —
registry (43 records), identity, profiles, policies,
workloads, the drift eval suite and its committed
reports; OPERATING.md says who runs what
regulator-pi/ the Pi host: the eleven session extensions, the
dispatcher, the S5 write gate, typed proposal /
audit-finding / uncertainty tools; `pi install`
control-room/ read-only page over the `regulator status` read
model: definition, instances, the unit inspector
docs/ the documentation site (concepts, guides,
reference), DEBT.md, decisions/, research/
vsm/ committed S5 artifacts for this repo itself
the course, Viable Agents, lives in its own repository and cites these by link
In the docs: The control plane · Definition, instance, domain · Architecture · Packages
When adding a feature to the harness, regulator walks this ladder from the top down: reach for the highest level that can actually do the job, and only fall further down when it can't. If you can write the check, don't write the paragraph.
In the docs: The control plane · Where each rule is enforced · Regulators · Enforcement boundary
What the system is (its invariants, its boundaries, the words it uses)
lives in four committed files under regulator/identity/. Every unit sees
them, rendered from the files on every run, so compaction can't drop them and a new
model can't reinterpret them from memory. Seeing them is only advice,
though, and a unit with a shell can reach the files. So identity is held the way the
mechanism hierarchy says to hold anything that matters: in layers, each catching what
gets past the one before.
AGENTS.md never reach it: the domain doesn't configure its regulator.write and edit is checked against the protected paths, through any spelling, traversal or symlink, and refused before it happens.identity-untouched at closeoutChanging identity is a separate path, on purpose. A unit may propose a change; the tool records the request and changes nothing. A person the interaction policy lets act as accepts it, as the only writer of an identity file, and may then promote it into the definition, so every later instance starts from it. The right to ask and the right to change are different mechanisms.
Secrets are watched the same way.
When regulator init installs an instance, it records the credential-looking values in the
repository's committed .env as canaries. Every tool result is scanned for them before
the model sees it: a canary is redacted and recorded as a critical finding. A canary the model
writes itself can't be unsaid, so that is recorded too. It measures whether an exposure path
exists. It only knows the values it was told about, and it doesn't watch the network.
In the docs: Protecting identity · The canary watch · Enforcement boundary
The orchestrator is nothing but the S3 loop (contract → dispatch →
verify → route → close), generic over workloads, with leases, budgets and attempts as
immutable records. It was built directly on bare Pi, one module per lesson: gates, effects,
profiles, leases, contracts, budgets, recovery, audit and authority, with no workflow
engine underneath it. The Pi-specific half lives in its own package, regulator-pi,
behind a host seam: the control plane never imports a host, it resolves one by name. Workload-specific concerns stay out of the loop entirely
and are declared in a workload definition instead, which is what keeps the loop itself small
enough to read in one sitting. How a unit runs through the
loop is drawn out on its own page.
Nothing but the S3
loop, generic over , with , budgets, and as immutable records.
All five stages exist in packages/regulator/src/controller.ts:
What would make a workflow engine heavy (fixed phases, milestone lifecycles, UAT, projections) is workload, and lives in a workload definition outside this loop. Recovery decisions cite a versioned policy, and is refused on anything but fresh, host-run evidence.
The software-development autoloop is the first , and the one the course builds against its fixture repository. It shows what it means to declare a workload rather than hard-code one:
six unit types, declared in workload/software-development.json, each naming:
implement may write src/ and test/; research is read-only by declared effectrun_checks, run_tests, and inherited-tests, the suite the unit inherited run against the unit's tree, so a test the unit deleted or weakened is a failing check — bound to the revision they ran againstresearch unit is routed into an on each unit it names, and at blocking severity holds them until a person or S3 dispositions it — never applied, never replanned. Operational memory is the other record: facts for later units, each with an expiry. Intelligence and memory ↗Every runs under ceilings the policy declares per unit type: tokens, cost, wall-clock and turns, and every unit under a ceiling on attempts. A guard in the session halts an attempt the moment a ceiling is crossed and refuses every tool with an effect from then on. The loop closes on the ledger, never on the transcript.
Each unit type also has a route: a primary model and declared fallbacks, in order. A provider failure moves to the next one, and nothing outside the route is ever tried. When the context fills, compaction keeps a deterministic block (the contract, the evidence so far, the files touched) so the unit never forgets what it was bound to.
A clean merge only proves no two units touched the same lines. So before every lands, the loop merges the branch in a temporary worktree and runs the tests on the merged tree. A tree that passes lands as the very commit that was checked. A tree that fails lands nothing: the unit repairs, and the base is never red.
Effects that can't be taken back, such as a message to the owner, go through an : intended before, committed after, and reconciled against the world on restart. A crash in between neither sends the message twice nor forgets it was sent.
In the docs: Running units · Work contracts · Host checks · Recovery, budgets and model routes · Coordination · Hosts
A suite declares the fixture, the tasks in order, the arms, the repetitions and the metrics it will report — before it runs. The control arm loads the plain harness; the treatment arm loads everything the workload declares; one ablation arm per regulator switches that one regulator off. The harness runs one fresh instance per arm and repetition through the real loop and grades every unit the same two ways.
| Graders | What they read | What they count |
|---|---|---|
| Outcome | the environment after the unit: the merged base, its tests, its comments | boundary violations, signature drift, vocabulary drift, rules written into prose, a test suite weakened |
| Trajectory | the records the control plane wrote while the unit ran | refusals, retries, escalations, invariant violations, attempts, tokens, turns, wall clock |
Pre-registered, and interpreted by a person.
The metrics are a closed list named in the suite, so a run cannot discover a favourable one afterwards. Arms are summarized with Student's t intervals and a lift is screened by interval overlap, never a p-value. And a report is not a report until someone says what it means: regulator eval without --interpretation writes a placeholder, and regulator check refuses a committed report that still carries it. The four committed reports ran scripted units — their numbers validate the graders, not a model — and one of them is the over-regulation case that changed the product: a check refused correct work over a word in a commit message, and the workload now declares that half advisory.
In the docs: Evidence about the regulators · Eval suites
Two things look alike from a prompt and are kept apart on purpose. is the environment and the future: a research unit, under its own read-only profile and budget, reports what it found with report_intelligence, and the signal becomes an obligation that holds the units it names until or a person dispositions it. Intelligence can stop work; it never replans on its own.
Memory is operational, and it expires.
A unit that learns something about this environment — a flag the tests need, a command that only works one way — records it with remember. The fact carries its provenance (the unit, the revision), a review-by date that cannot be more than ninety days out, and optionally the unit types it is for. Later units see current facts rendered as facts, scoped to their type; an expired fact is shown to nobody until a person reviews it. Nothing checks that a fact is true — the expiry is the pressure.
Neither is a rule.
A fact that turns out to be a rule, or a finding that should change what the system is, goes through propose_policy_change and waits for the decision. Memory never becomes identity by being repeated, and the drift suite measures the difference: rules written into a README count against a unit; facts recorded with an expiry do not.
In the docs: Intelligence and memory · Session tools
Every regulator in the control plane has a record, and the definition check refuses a gate that does not state what it fails to catch. The cards are the source of truth for what the system does and does not do; REGULATORS.md and the enforcement boundary, BOUNDARY.md, are rendered from them and never edited by hand.
| Field | What it commits to |
|---|---|
| purpose, absorbs | what the regulator is for, and the class of failure it exists to absorb — one sentence a reader can check against the code |
| mechanism | the level on the hierarchy (type, deterministic gate, typed tool, model judgment, prompt) and where it bites: a typed enforcement point — a session event, a tool's execute, a step of the loop, a subcommand or the definition check — from a closed list the check refuses to go outside |
| channels, scope, authority | what it consumes and emits, what it applies to, and what it may and may not do — the topology view is drawn from these |
| evidence | the tests that show it working; a source checkout verifies they exist |
| limitations | at least one, always: the routes around the gate. A limitation that names later work gets a row in the build's debt ledger in the same change |
| ablation, retirement | the switch that turns it off for an eval arm, and the condition under which a person would retire it — a control system that only grows is not regulating |
| ownership, introducedIn | who reviews it and by when — regulator review --due lists what is owed, and doctor fails on an overdue date — and the release that first shipped it |
Retirement is a decision, and the record stays. Two of the forty-four cards are retired: the first lexical write gate, superseded by the identity write gate, and the post-merge check, replaced by the pre-merge trial. Each keeps its history and says what replaced it, so the registry reads as the system's memory of itself, not only its present.
In the docs: The regulators · Enforcement boundary · Build debt
A projection of registry ∪ execution store ∪ regulatory state. It
owns no state of its own: every screen is read from records that already exist
elsewhere. packages/control-room serves this page today, over the
built by regulator status.
More of the page (units, leases and obligations for a live instance, the assurance and lifecycle views, a unit's full replay) is on the worked example and how it works.
In the docs: The control room · Evidence about the regulators
Two packages, then one command to declare the instance, one to check it, and one to run a unit. You need Node 22.19 or later and Pi 0.87.0 with a model provider key in its environment.
pnpm add -D @metacoding.io/regulator @metacoding.io/regulator-pi cd /path/to/your/repo # clean tree, on the branch units should land on regulator init --writable src/,test/ --protected vendor/ --by alice regulator doctor # the CI entry point: exit 1 on any problem regulator unit drive contracts/my-first-unit.json regulator unit show u1 · unit evidence u1 · obligations
init seeds the identity under regulator/identity/, writes the instance manifest, and commits. drive leases the unit, opens a session under the unit type's profile, runs the host checks at the unit's revision, and either closes and reintegrates or routes the failure under the recovery policy. What the policy cannot resolve becomes an obligation owed to you.
In the docs: Getting started · Declare who may answer · CLI reference · npm ↗
The loop runs units, and a person answers what the loop cannot decide. Everything below reads or dispositions a record, and every --by is checked against the interaction policy's people before anything is written.
| When | Command | What it does |
|---|---|---|
| before the first unit, and in CI | regulator doctor | checks runtime, git, the Pi pin, the definition, review dates, the manifest, a clean base, and what is owed and undelivered, then exits 1 on any problem |
| a unit needs work | regulator unit drive <contract> | the autoloop: run, verify, route, run again while the budget and the policy allow |
| something is owed | regulator obligations | what is open, to whom (S3, S5 or a person), at which severity, and which currently veto a unit |
| a unit asked a question | regulator answer <id> --by <who> --answer … | records the answer as the person's disposition, and the next attempt carries it. Silence is never consent |
| a finding or an escalation waits on you | regulator obligation resolve <id> --by <who> --disposition <d> --rationale … | closes it with one of the eleven dispositions: ack marks it seen, and escalate hands it to another consumer with the veto |
| a unit proposed a change to the identity | regulator identity accept|reject <id> --by <who> … | the S5 decision, by a person with actAsS5, the only writer of an identity file |
| a criterion no check can observe | regulator unit accept <id> <criterion> --by <who> | a human acceptance, recorded beside the technical verdict and never folded into it |
| a quiet instance | regulator watch --exec <cmd> | delivers what is owed to a person, reminds after the policy's interval, and forwards each new outbox line to your channel |
| once a month | regulator review --due | which regulator records are past their review date, and doctor fails on an overdue one |
| an accepted decision should outlive this instance | regulator identity promote <file>.md --by <who> … | carries the instance's accepted identity file back into the definition's seed |
| you want to see it | regulator status · regulator-control-room | the read model, and the read-only page over it |
In the docs: Running units · Obligations · Asking a person · Operating · The control room · Session tools
Four things are true today, and each runs end to end.
regulator init installs it into an existing repository under a declared manifest; doctor checks the instance before the first unit and in CI; the base is re-verified after every merge and a failure holds the whole instance; refused words are linted out; the outbox is watched; an identity decision promotes back into the definition.docs/DEBT.md tracks
every gap the build has taken on deliberately, one row per item: forty-one rows so far, most
already paid as later lessons closed them. What's still open, and worth knowing before
you rely on this:
bash sits outside the thrash detector, the effect journal, and the failure observer by design; real sandboxing is left to the operator, not built by the lab.--by is asserted, not authenticated. Disposition authority checks whether the named person may decide, not that the caller is that person; authentication is the deployment's to add.@metacoding.io/regulator (the control plane and CLI), regulator-pi (the Pi host), regulator-protocol, regulator-core, regulator-checks and regulator-control-room, tested without a model under CI on two Node versionsREGULATORS.md and BOUNDARY.md generated from the recordsIn the docs: Build debt · Roadmap · Changelog