A personal AI company · city hall
Build the city before the brains.
Beh.dev is one human principal, a permanent staff of machine scientists and engineers, and infrastructure owned and operated end to end. It began with no real model at all — on purpose. The first one now runs on the laptop's GPU; the city around it is still being built.
If the city works with fake brains, it will work with real ones. If it only works with real ones, we never built a city — we built a wrapper around someone else's API. Charter · the one-sentence test
- Subject
- city.goals.>
- Verdict
- —
- Cycle
- 000
Thesis 01
A research organisation is not a pile of models.
It is a set of roles, rules, records and resources. Build those four correctly and the models become a swappable component — which is the whole reason this city starts with a stub where its intelligence should be.
- roles
- A citizen is defined by a declarative spec, not a prompt buried in code. A role states its purpose, its permitted tools, its data clearance, its budget, and who reviews its output. Changing the workforce is editing YAML, not rewriting a program.
- rules
- Nothing is enforced by convention or good intentions. Admission policy rejects non-compliant workloads. NetworkPolicy makes exfiltration a routing impossibility rather than a promise. Budgets are enforced at the gateway, so a runaway agent stops rather than bills.
- records
- Every run is replayable or it did not happen. Inputs hashed, config and seed captured, every model output kept by its hash, the whole causal chain queryable — and replayable — months later. A scientist that cannot show its work is a random number generator with good manners.
- resources
- The substrate is a detail we deliberately refuse to hard-code. Today the city is one laptop: a real model on its GPU, and a stub for everything else. Tomorrow it runs on rented GPUs. That transition must be a configuration change, because any architecture that makes it a migration is the wrong architecture.
The city 02
The metaphor is load-bearing.
Every district is a real component with a real job, not a naming flourish. If a district cannot name the software that implements it, it does not exist — and a component without a district is one nobody has placed.
The last column says how far each one is. Designed: written down. Built: it exists and runs. Verified: a test that can fail is green. Most of the city is still designed.
| District | Function | What it actually is | State |
|---|---|---|---|
| Land & utilities | Compute, storage, power | k3s cluster, CSI storage classes, resource quotas | designed |
| Roads & post | How citizens reach one another | NATS JetStream — subjects, queues, KV, object store | designed |
| Citizens | The scientists and engineers | Agent pods: an image + a role spec + an identity + a budget | designed |
| Workplaces | Where work happens | Sandboxed Jobs and Argo Workflows, default-deny networking | designed |
| Foundry | The source of thought | model-gateway → dummy / local / frontier. The local model runs on the host: Docker on macOS cannot reach the GPU | stub, model verified gateway designed |
| Records office | What was done, and what is true | MLflow runs, MinIO artifacts, the gateway ledger, OTel traces | designed |
| Library | Accumulated knowledge | Postgres + pgvector: papers, results, prior art, agent memory | designed |
| Border control | What may leave, carrying what | Egress policy at the gateway; NetworkPolicy at the pod | designed |
| Civil registry | Who is who | ServiceAccounts, projected tokens, a registry of outside callers | designed |
| Planning office | What the city should look like | Flux, reconciling the cluster from Git | designed |
| Permit office | What may be built at all | Kyverno admission: unlabelled, privileged or unapproved workloads are refused | designed |
| Strongroom | Where the keys are kept | SOPS + age in Git, Vault + External Secrets in the cluster, the Keychain on the host | designed |
| Observatory | What is happening, and how fast | Prometheus, Loki, Tempo + OpenTelemetry, Grafana | designed |
| Front door | How the human reaches the city from anywhere | A tailnet address → the companion, which checks every caller and every skill call | checks verified daemon designed |
| City hall | Direction, budget, veto | The human — approval queues, dashboards, kill switches | designed |
Citizens 03
An agent is four things.
An image, a role spec, an identity, and a budget. Nothing else distinguishes one citizen from another — which is what makes the workforce editable rather than rewritable.
| Role | Job | Review gate |
|---|---|---|
| principal | Turns goals into decomposed, prioritised tickets | human |
| scientist | Designs experiments, forms hypotheses, interprets results | referee · 3 rounds |
| engineer | Writes and fixes the code an experiment needs | referee · 3 rounds |
| referee | Adversarial review. Can reject and return work. | principal |
| librarian | Ingests papers and data into pgvector; answers retrieval | — |
| ops | Watches cluster health; raises findings the principal tickets | human |
| scribe | Turns accepted runs into readable reports | human |
| companion | Talks with the human; the only citizen reachable from outside the machine | human, per effect |
The Referee is deliberately adversarial and cannot be the author of what it reviews. Self-approving agents produce confident nonsense at scale. Architecture · L4 citizens
That separation is not a guideline. It is a field in the spec, and the spec is what will run once the dispatcher that reads it is built. The scientist cannot publish a result at all: it can only ask for review, so its work reaches the principal and the human through a verdict or not at all.
# Deliberately cooler than the Scientist: this role is not here to be creative. model: { primary: beh-reason-lg, temperature: 0.2 } clearance: internal tools: - name: vector.search - name: mlflow.read # read-only: a referee cannot edit the record - name: artifact.get denied: [ artifact.put, mlflow.log, code.execute, ledger.write, net.egress.internet ] constraints: # … never_review_own_authorship: true must_cite_evidence: true # a verdict without evidence is itself rejected # … messaging: subscribe: [ "city.review.requests.>" ] publish: [ "city.review.verdicts.>" ] outputs: required: [ verdict, evidence, severity, required_changes ] verdict_enum: [ accept, revise, reject ]
Operating 04
Seven principles that decide arguments.
Each one exists to settle a class of decision in advance, so the same debate is not had twice.
Fake the intelligence, never fake the interface.
Stub backends return deterministic, seeded, cost-accounted responses through the exact API a real model serves — tool calls and structured output included. Contracts are exercised from day one.
One door per trust domain.
Every model call and every web search crosses model-gateway. One
chokepoint is auditable; twelve SDKs scattered through agent code are not. The
laptop and the cluster are separate domains, so each gets one door, and
restricted data never passes between them.
Portability is a labelling discipline.
Workloads select an abstract tier, never a machine — one label key per tier, so a laptop node can wear several hats. In the cloud those hats are separate node pools. The base manifests are identical; each environment differs by one reviewed overlay.
Determinism is a research instrument.
Same seed, same inputs, same outputs. That makes stub mode a load-testing and correctness tool rather than a placeholder. A real model is only as deterministic as its environment, so its outputs are kept and replayed rather than re-run.
Least privilege by construction.
An agent gets exactly the subjects, buckets, tables and egress it declares. The default is nothing.
The human is a scheduled component.
Approval gates are part of the pipeline with defined SLAs and queues, not interruptions to it.
Name the reason, or park it.
Before a component is added, it names the requirement it serves. If it serves none, it is parked — Kafka, OpenShift and Backstage are, for now.
- Run A / Run B
- 0000 / 0000
- Seed A / B
- 20260921 / 20260921
- Divergence
- 0.000
Two runs, one seed, one scheduler. The divergence readout is
measured rather than asserted, and it can fail: perturb run B — give it the next
seed — and it leaves zero at once; restore it and both runs restart on one seed
and hold at zero. That is the property dummy-inference gives the
foundry. Recorded on 10 October 2026: the same verdict request, sent twice,
returned content hash 3cf31417ed54cfdf both times; with the seed one
higher it returned af053a5574dccabc.
The real model held too, measured the same day: a greedy answer came back identical with and without a second request sharing its batch. That holds on this laptop and this build, which is why the records keep outputs rather than trusting a re-run.
It is already shipped once. Juju, the company’s second product, runs its speech model at build time rather than at runtime — every line is reviewed before a child can hear it, and what reaches the device is a folder of audio files that is identical every session.
Border control 05
One door, two locks.
Every artifact, dataset, prompt and agent carries a
data-class label, and admission policy rejects any workload that
omits it. The gateway resolves (class, backend) and refuses illegal
pairs with a logged, traced denial. A caller can raise a request's class, never
lower it.
the companion, on the host
accounting · audit · on the host
Lock one is the gateway. Lock two is the network: agent namespaces are default-deny, reaching only Postgres, MinIO, NATS and the gateway's one port. On the laptop, where no pod rule reaches, lock two is an outbound firewall rule for the daemons — Docker lets every container reach the host's loopback, so loopback alone is no lock. An agent that somehow acquires an API key still has no route to use it. Today this is designed; the companion's admission checks are built and tested, and the rest is not built yet.
| Class | Meaning | May reach frontier APIs? |
|---|---|---|
| public | Already public, or intended to be | yes |
| internal | Ours, unpublished, low harm if leaked | yes, with redaction |
| confidential | Ours, real harm if leaked | no — local models only |
| restricted | Harm to people if leaked | never leaves the machine |
For that data the difference between a policy and a
promise is somebody's safety, so it is enforced at two independent layers on the
assumption that either one may fail.
Secops · why restricted exists
Budget is a safety control, not accounting hygiene. An agent that loops is functionally a denial-of-wallet attack against its owner. Ceilings per run, per ticket and per day, priced in GPU time, because one laptop decodes about two million tokens a day and that is what is scarce. Three review rounds, then a human decides. The city and the companion have separate budgets, so a runaway agent cannot silence the companion. Stub mode reports realistic token counts, so all of it is tested at €0.
And three independent records, because a single log an attacker can edit is a comfort, not a control: the gateway ledger, the traces, and immutable run records.
Decisions 06
Twelve decisions settled. Ten corrections proposed.
Written down as ADRs so they can be argued with later by someone holding the reasoning, rather than re-litigated from memory. Some already have been: an October review found the design and the repository disagreeing in ten places. Each correction is an ADR of its own, proposed until the principal accepts it, and none rewrites the decision it corrects.
| ADR | Decision | The cost accepted |
|---|---|---|
| 0001 | k3s as the city substrate, three nodes | ~1.2 GB and real complexity — single-node hides affinity, eviction and drain bugs until cloud day |
| 0002 | The OpenAI API shape as the one internal contract | Vendor-specific features are inexpressible; the gateway grows a typed passthrough when one is genuinely needed |
| 0003 | NATS JetStream as the city bus | Less ubiquitous than Kafka — irrelevant for one person, a real cost if that changes |
| 0004 | Abstract tier labels, never machine names — mechanism corrected in 0013 | The most consequential decision in the repository. Laptop→GPU becomes a label and a route, not a migration |
| 0005 | All model traffic through one gateway — one per trust domain since 0014 | A single point of failure, accepted because it is also the single point of audit |
| 0006 | Stack chosen for what it teaches | Components that are cheap to run but absent from practice have negative value |
| 0007 | Inference runs on the host, not in the cluster | The gateway moved with the GPU and left GitOps, so it needs its own provenance |
| 0008 | A ternary 27B as the first local model | A pinned fork: a stock binary loads the weights and writes fluent nonsense, so verification asserts on meaning |
| 0009 | Provenance is a field on every message | Every layer that touches a message must carry it, memory included |
| 0010 | The tailnet is how applications reach the companion | A dependency on Tailscale's control plane |
| 0011 | The products are the city's first citizens | Products generate unbounded work that feels like learning and is not |
| 0012 | Three deploy targets; edge products stay at the edge | Two operating models in one head |
| ADR | Correction · proposed | What it fixes |
|---|---|---|
| 0013 | One label key per tier | A label key holds one value, so one node could not wear two tiers; every agent pod would have stayed Pending |
| 0014 | The host is its own trust domain | Docker lets every container reach the laptop's loopback; the model server now demands a key |
| 0015 | Provenance is computed from context | A tool call inherits the least trusted words that produced it; the test no longer labels the attack by hand |
| 0016 | The journal arrives labelled | The companion cannot read Selfwise's journal; entries come inside a request, each marked untrusted |
| 0017 | Work reaches the human only through a verdict | The scientist could publish straight to results, past the referee |
| 0018 | Budgets priced in GPU time, in two domains | The old ceilings measured re-read prompts, not GPU use, and one runaway could silence the companion |
| 0019 | Records are replayable, not reproducible | A hash cannot replay a run, and a re-run only reproduces on the hardware that was measured |
| 0020 | kube-router until L7, then Cilium | The config and the docs named different network plugins, and one cannot be swapped live |
| 0021 | Flux owns the city | Two GitOps controllers were both described as owning it, and the kill switch would have been undone |
| 0022 | Selfwise is edge-native | Its production is a static site and a Worker; the plan to run it in the cluster could not have built it |
Where it is 07
Honestly, at the beginning.
The architecture is written and most decisions are made. The cluster is not running yet; one real model is. Publishing the milestone it is actually on is cheaper than explaining a status light that cannot go red.
First come the platform labs the city is built from — secrets, observability, delivery, recovery. M0 follows them.
The city runs. Fake scientists complete a real research loop end to end, watchable in a dashboard.
after the labsThe loop produces something the principal would not have written, and the record proves how it got there.
plannedA real model drops into the foundry, and no citizen's spec changes. It runs and is verified; it had to run on the host, so the gateway moved with it, and that gateway is not built yet.
partlyThe city outgrows the laptop and moves to GPUs by editing config.
plannedThe city produces work that leaves the building: papers, services, artifacts published here.
plannedA green suite that cannot go red proves nothing. The rule every milestone is held to