work · cybernetics
nanovsm
in the 1970s stafford beer asked a strange question: what is the minimum structure a system needs to stay alive in a changing world? his answer was the viable system model, five interlocking systems, the same pattern whether you're a cell, a company, or a country. nanovsm is that model compiled down to python plus a language model. cheap deterministic sensors watch the world, a typed judge audits every signal, and a policy synthesizer writes the decision, loading its identity from a single editable file. the claim the repo makes is the one worth testing: the engine is invariant across domains. only the sensors and that directive file change.
beer's systems, as code you can read
the canonical engine is about 240 lines, and it's literally divided by comment bands naming each of beer's systems. the whole loop is four calls, and it reads like the theory:
async def heartbeat() -> str:
raw = await sense() # S1: sensors fan out
surfaced = await audit(raw) # S3: judge each item
surfaced.sort(key=lambda x: x["importance"], reverse=True)
return await policy_brief(surfaced[:8]) # S5: write the brief
S1 is sensors, here an arxiv feed and a hacker news pull, both cheap, no model involved. S3 is the auditor that decides what's worth surfacing. S5 is policy, the part that decides, and it reads its directive from program.md at runtime. the readme puts it plainly: the engine is invariant, only the sensors and program.md vary. swap those two slots and the same machine that runs an ai-research radar becomes a personal operating system.
tool-use as structured output
the cleanest trick in the codebase is how the judge never parses model text. instead of asking the model for json and praying, it converts a pydantic schema into a forced tool call and reads the validated tool input back. no json.loads, no try/except around a hallucinated brace.
resp = await client.messages.create(
model=self._model,
tools=[{"name": tool_name, "input_schema": schema.model_json_schema()}],
tool_choice={"type": "tool", "name": tool_name}, # forced
messages=[{"role": "user", "content": content}],
)
for block in resp.content:
if block.type == "tool_use":
return schema.model_validate(block.input)
because it's generic over the schema, one judge serves the auditor, the surprise inspector, and the policy writer, each just hands it a different shape. the model's freedom is constrained at the door, so everything downstream is typed and safe.
a viable system doesn't survive by trying harder. it survives by being built so that drift gets noticed and pain gets heard.
variety, attenuated and amplified
beer's hardest idea is variety, the sheer number of states a system can be in. a viable system survives by attenuating the world's variety down to what it can handle, and amplifying its own where it must act. that isn't decoration here, it's the actual control flow. the coordinator (S2) is a sixty-second sliding-window rate limiter plus content-hash dedup, that's attenuation, throttling the firehose. the judge attenuates further by emitting only "surface" or "ignore." and then the algedonic channel, beer's pain-and-pleasure bypass, is the amplifier in reverse: a single signal above an importance floor skips every layer and reaches policy directly.
# the pain/pleasure bypass: one urgent signal jumps the queue
if verdict.importance >= self.importance_floor: # default 5
await self.emit(Algedonic(payload=verdict))
elif "URGENT:" in item.body:
await self.emit(Algedonic(payload=item))
a system that notices its own model going stale
the part i find genuinely beautiful is the intelligence system, S4, beer's "is our map still matching the territory?" channel. it computes the word frequencies of the directive and compares them against what's actually been surfacing lately. if a term the directive cares about has gone nearly silent in the real signal, it flags directive_stale; if a term keeps surfacing that the directive never mentioned, it flags directive_gap. the system can tell you that your own intentions have drifted from reality. and because S5 polls the directive file's modification time every few seconds, you can edit program.md and the new identity propagates live, no restart. the soul is hot-swappable.
the same engine, zero config
the life-ceo scenario proves the invariance claim. it's a fastapi server where the sensor is just whatever you paste into the browser, and the judge scores three daily binaries, physical, cognitive, structural. with no api key it boots in heuristic mode, plain keyword counting, no spend, and the reason field on every row tells you which mode classified it. export a key and the next request silently upgrades to a real model with vision. today's state isn't stored either, it's folded fresh from an append-only ledger each time, history stays immutable and the present is always derived.
what i love about beer is that he was really describing ihsan at the scale of a whole organism, a system arranged so that care and correction are structural, not heroic. you don't survive by trying harder, you survive by being built so that drift gets noticed and pain gets heard. nanovsm is my attempt to hold that idea in a few hundred lines you can actually run. الحمد لله.