Multi-project as the only multi-tenancy
Most platforms grow tenancy along the user axis. Supernova runs on one machine, for one person, and dropped human multi-tenancy deliberately (2026-06-20): “isn’t really designed to be shared on a single machine … safe to assume all humans have sudo” (Matt). The tenancy that survived is the one a builder platform actually needs — isolation between the projects it builds, so that several codebases can be worked concurrently without their coordination state bleeding together.
Who the tenants are
Human multi-tenancy is dropped, not deferred: no human-tenant principal model, no per-human routing identity, no privilege separation to model between people who all have sudo anyway. If you want to share, there are exactly two modes, and never a third:
| Mode | What is shared | Isolation boundary |
|---|---|---|
| Share the instance | Collaborators are assigned into its namespaces | The project boundary |
| Share the product | Independent instances coordinate through the repo and PRs | Git itself |
“Live builder DBs never cross instances. Databases do not merge; the design ensures they never need to.” One nuance: the system is single-human but chat is not — chat carries user identity for multiple humans. Participation is not tenancy.
Instances, namespaces, subjects
The model was ratified in a live session (2026-06-10) [supernova design]. A running instance hosts one namespace per project it is building. A namespace is the builder’s working memory about a subject, never an organ of the subject — “product-truth ships with the deliverable; builder-truth lives in the instance’s database.” Agents belong to the instance and are assigned to namespaces; asking “where does an agent live” is a type error, the way asking where a PM “lives” is — they work for the company and get assigned to a project. There are exactly two coordinator levels: a root coordinator allocating across projects, per-project coordinators allocating within. Matt’s test for whether any coordination layer earns its seat: “Allocation value is largely ‘un-bottleneck the critical path of top level agents.’”
flowchart TD
RC["root coordinator"] -->|"allocates across projects"| CA["coordinator, project A"]
RC -->|"allocates across projects"| CB["coordinator, project B"]
AG["instance agents"] -->|"assigned, never resident"| NA["namespace A"]
AG -->|"assigned, never resident"| NB["namespace B"]
CA -->|"allocates within"| NA
CB -->|"allocates within"| NB
NA -->|"subject"| SA["repo A: code, specs, product wiki"]
NB -->|"subject"| SB["repo B"]
NA -.->|"builder-truth"| DB[("instance builder DB")]
NB -.->|"builder-truth"| DB
The platform’s own next version is an ordinary subject: instance vN hosts a namespace whose subject is vN+1’s codebase. “Never develop on the instance you are running.” Promotion reconciles by replacement, never merge.
Three truth tiers
Every artifact gets sorted into one of three tiers, chosen per artifact rather than per repo:
| Tier | Contents | Committed? |
|---|---|---|
| Project repo | Code, specs and the R# DAG, product wiki, team practice docs | Always |
.supernova/ | Thin agent definitions, team templates, project tool config, project.yaml | Yes, if the team shares Supernova |
| Builder DB | Task rows, runs, session mappings, heartbeats, team state, reconcile state | Never |
The middle tier generalizes the .github/ / .vscode/ convention: present means rich integration, absent means the repo is just a repo. project.yaml doubles as the cwd project-context marker — context resolution runs a fixed precedence (session’s bound namespace → explicit flag → cwd marker) and then fails loudly: no silent global default that lets a command mutate the wrong project’s state.
The tier boundaries encode a specific scar. The v1 builder kept coordination in a second repo, and Matt’s verdict was blunt: “this split has been a nightmare and caused endless torment” (2026-06-10). The post-mortem found two distinct causes, neither of them separation itself: the wrong medium (tasks as markdown in a shared git checkout is a database implemented as a filesystem race) and the wrong boundary (specs are product-truth, coupled line-by-line to source via @R# annotations, yet were exiled with the coordination). Fixes: builder-truth moves to a real database; specs return to the project repo. The standing rule: “never implement coordination as a second repo, and never exile the product’s own documentation along with it.” A pleasant consequence is that the “builder wiki” nearly vanishes — just a root portfolio view — so there is no second wiki and no wiki-reconciliation problem.
What may cross a boundary
The task system is unified — one schema, lifecycle, gate, and dispatch codebase — but the task space is partitioned per namespace. Cross-namespace machine-consumable edges are banned and rejected at write time, in both the project layer and the issue layer. The rationale is spin-off-ability: the moment a task in one project can block a task in another, neither namespace is independently dispatchable or extractable.
The only sanctioned crossing is the pointer-task: an obligation assigned to an agent whose subject lives elsewhere, carrying gating_edge: false, never consumed by gates or dispatchers. Matt’s canonical example: “‘hey nova get XYZ landed in duva’ … it’s mostly there to remind nova when they call ‘am next’ what they need to be doing” (nova is an instance agent, duva a sibling project’s namespace, and am the predecessor platform’s CLI). Close conditions cite observable outcomes in the target namespace, with evidence. And this yields the portfolio for free: “the root namespace’s pointer-task list IS the portfolio graph” — no separate project-graph machinery. Chat got the sibling rule (2026-06-23): explicit @-suffix addressing like onyx@duva is the sanctioned crossing; implicit cross-namespace resolution stays banned.
Structured reconciliation
Product-truth changes outside the builder’s actions — pulls, other builders, human edits, promotion — while builder-state references it: tasks keyed to R#s, coverage numbers, in-flight worktrees. Matt: “we really need reconciliation to be structured and that process to be clear and documented.” So it is a defined pipeline, not a vibe: four triggers (post-merge/post-pull VCS events carrying old and new heads, post-promotion, a scheduled drift sweep that makes missed events self-healing, and on-demand), a purely mechanical scope computation (git diff plus the R# parser — “no model judgment in scope computation”), and an eight-class taxonomy mapping each change class to a builder-state action. An R# whose text changed unchecks and flags dependent tasks needs-reconcile; a removed R# flags its orphan annotations; a moved spec re-points mechanically; conflict markers halt reconciliation for that file.
stateDiagram-v2 [*] --> clean clean --> pending: vcs event or drift sweep detects delta pending --> reconciling: reconcile runs reconciling --> clean: all change classes applied reconciling --> conflicted: merge conflict markers found conflicted --> reconciling: human resolves and reruns
The invariants are the interesting part. Reconciliation is deterministic and idempotent (same diff in, same builder-state delta out; re-run is a no-op). It “never mutates product-truth” — it reads truth and adjusts builder-state only. It is never destructive to builder-state either: tasks get flagged, never auto-closed, auto-reassigned, or silently edited. It is loud by construction — a silent reconcile is a defect. And a conflicted namespace blocks auto-dispatch of new work while in-flight work continues; the refusal is a denial in workflow failure-class terms — a designed branch, not an error — while the conflict halt itself is a deterministic failure: the same diff can never auto-resolve, so it parks for a human. A former reactor that auto-filed issues from reconcile findings was deleted and covered with refusal regressions: automation surfaces findings; judgment principals — humans or agents holding judgment authority, never the automation itself — decide.
The CUJ registry
The project system’s second half exists because of a documented failure mode: on 2026-06-09 an audit found every top-level branch of the plan was infrastructure — “income had no node.” The CUJ registry is where “what is all of it for” lives: critical user journeys with an executable scenario reference each (D12: CUJs are “validated by running workflows through the e2e testing system”). The test system is the sole producer of exercise telemetry; the manual CLI verb for exercising a CUJ was removed by design, so self-incremented counts are impossible by construction. Definition-of-done is a measured pass — every registered CUJ must show a real, passed, batched run — “the platform’s done-ness is computed against measured runs, not declared.”
Where this stands
The model is ratified design [supernova design]; the live v1 builder still runs single-project on the old two-repo layout this design exists to retire. The project Rust system is real code with most requirements closed on named unit and integration evidence — reconcile proven against real git diffs, dispatch gating and the cross-namespace ban enforced at write time — and the builder dogfoods the registry on its own repo as row #1 [built in-repo]. Two requirements are openly ready, not done: namespace teardown and the transactional outbox, pending the final single-store database (current stores are local-JSON scaffolding, named as such). Per-project containers — a primary container for shared services, one container per project swarm enforcing that project’s permissions (ruled 2026-06-21, superseding one-container-per-instance) — are ruled, not built. See decisions as case-law for how those supersessions are recorded.
What this costs
- Multi-project runs at n=1. One registered project. Namespaces, the edge ban, pointer-tasks, and reconciliation are designed and unit-proven but have never hosted two real concurrent projects. The load-bearing claim is untested at its actual load.
- The isolation is software-only today. Until per-project containers exist, the namespace boundary is enforced by write-time rejections, not the container boundary the design leans on.
- The two unclosed requirements are the hard ones. Teardown and the outbox are exactly the transactional parts — sibling receipts bound to one transaction, registry mutation and audit event committing together — still sitting on local JSON proofs.
- Reconciliation’s conservatism is a queue. Never-destructive means
needs-reconcileflags accumulate for judgment principals to triage, and aconflictednamespace stops new dispatch until someone acts. Monitor SLA alerts mitigate; the human-in-the-loop bottleneck is by design. - Dropping human multi-tenancy is a real bet. “All humans have sudo” forecloses shared-machine collaboration; sharing is instance-level trust or git-level independence, nothing in between.
- It is a lot of machinery for one user. Three truth tiers, a reconcile state machine, an outbox, checkpointed teardown. The justification rests entirely on v1’s documented torment — which is at least a documented reason, not a hypothetical one.