The shared mental model — for a developer evaluating Ctrl AI and for an AI agent working inside a Ctrl-locked repo. Read this once and the CLI, the
/ctrlpanel, and the lock all make sense.This doc describes mechanisms honestly: what the lock guarantees today, and what is still roadmap. If a sentence here ever stops matching the code, the code is right and this doc is a bug.
The one-sentence version
Ctrl AI vendors verified backend parts into your repo and locks them, so the infrastructure your AI agent depends on can't be changed without it showing up — as a red diff in your editor and a failed check in CI. The parts stay legible and accountable; your agent wires the seams and builds the product on top.
The value isn't the code (it's commodity). The value is that the backend stays legible and under control while an agent works fast on top of it.
The four nouns
Everything in Ctrl AI is one of these. Keep them straight and the rest follows.
| Noun | What it is | Example |
|---|---|---|
| Capability | An abstract contract — what a slice of backend does, pinned to a major version. A commons; vendor-neutral. | billing.subscription, email.transactional, auth.session |
| Part | A concrete, verified implementation of a capability. This is what gets vendored into parts/<name>/. |
the billing.subscription@1.2.0 part |
| Adapter / vendor | The swappable provider inside a part. One capability, one interface, N vendors. | stripe ↔ paddle; resend ↔ postmark ↔ ses |
| Seam | The single typed surface your app imports — parts/<name>/src/index.ts. The only legal way to use a part. Everything behind it is sealed. |
import { sendEmail } from "@parts/email.transactional" |
So: you pick capabilities, each resolves to a part, each part runs on a
chosen adapter/vendor, and your app touches it only through the seam. A
vendor flip (stripe→paddle) changes the adapter behind the seam; your app
code doesn't move.
The lock — what it is and what it actually guarantees
Every vendored part directory is content-hashed (parts.lock pins the hash;
each part carries an ATTESTATION.json recording the hash it was verified at).
Three things check it:
ctrlai guard— a pre-commit hook. Refuses a commit that changed a part's bytes without going through a sanctioned command.ctrlai audit— runs in CI. Re-hashes every part, fails the build on any drift, and checks attestations + wiring.ctrlai conform— also in CI. The spec-loop (next section).
What this guarantees today, honestly:
✅ Legibility / no silent drift. If anything edits a part's interior — most
often a helpful agent "fixing" something mid-task — the hash changes and guard
(local) and audit (CI) go red. The change cannot land quietly. This is
real, mechanical, and not downgradeable to a warning. It's the common, important
failure mode, and the lock stops it cold.
⚠️ Not yet cryptographic tamper-proofing. Today's attestations are
dev:unsigned and every trust artifact (parts.lock, ATTESTATION.json,
ctrl.spec.json) lives in the repo. A determined actor with write access who
also rewrites the lock and the attestation can re-bless an edited part. So the
guarantee today is "infra changes are legible and reviewable," not "an
adversary cannot alter a part." Closing that gap needs a root of trust outside
the repo — real signing, and server-side hash enforcement — which is the
cross-repo / team direction, not the in-repo free tier.
The honest framing: the lock is a strong, real tripwire against drift (the thing agents actually do), and the path to hard integrity is the cross-repo product.
The two promises, kept separate: Legibility (every infra change is a reviewable diff) is real in your repo today. Enforced integrity (a change can't be merged at all) needs an out-of-repo authority and is the team tier. We don't conflate them.
The spec-loop — "built to spec"
The lock protects the parts. The spec-loop protects your intent.
ctrl.spec.jsonrecords what you configured: which capabilities you chose and which vendor each runs on. It's the captured intent — the bus between the configurator, the CLI, and the panel.ctrlai conformdiffs intent against reality and reports "built to spec": every specified capability is installed, locked, on the vendor you picked, and not silently re-implemented by hand.
This is what catches an agent that, say, swaps your billing vendor or hand-rolls a capability instead of using the verified part. (Hand-roll detection today is import-based — it sees a part being bypassed by an SDK import; raw-HTTP reimplementations are a known blind spot we're widening.)
conform is the moat: not just "the parts are intact," but "the backend is the
one you asked for."
The /ctrl panel — control made visible
Every Ctrl app ships a local /ctrl route: the control panel for your backend.
- Read-only legibility: which parts are verified, what your agent can touch (nothing, behind the seam), whether the build is built to spec, and what each part costs to run.
- The editable vault: swap a vendor, add a capability, customize (eject), or remove — each runs the real operation locally, then shows you the git diff, re-locks, and re-runs conform. You watch your backend get rewritten safely: intent and code move together, so the lock stays green.
The panel's write actions are local-dev only — gated to loopback requests, so
a deployed app never rewrites its own tree from an HTTP call. (Set
CTRLAI_PANEL_ALLOW_REMOTE=1 only if you knowingly run dev on a remote box.) The
gate is header-based, so don't bind the dev server to 0.0.0.0 on an untrusted
network — see trust-and-security.md for the exact limits.
The lifecycle — from nothing to deployed
npx create-ctrl-app # a running, locked backend on a zero-config DB (pglite) — boots in seconds
↓
ctrlai add <capability> # vendor a verified part in (or `add <pack>` for a whole kit)
ctrlai wire <part> # scaffold the route/seam wiring
ctrlai doctor # is it actually wired and reachable?
↓
ctrlai provision db # a real Postgres on YOUR Neon, migrated, wired
ctrlai provision <part> # set the chosen vendor's credentials (working ≠ on-spec)
↓
ctrlai push # your repo on GitHub — CI runs `ctrlai audit` + `ctrlai conform` on every push & PR
ctrlai deploy # a live URL on YOUR Vercel, DB connected
Every rung lands on your accounts (your GitHub, your Neon, your Vercel). Ctrl AI never hosts your backend. The in-repo experience — parts, lock, panel, the whole loop above — is free. The cross-repo layer (a team dashboard across all your repos, private registries, server-enforced integrity, governance) is the paid tier.
For your AI agent — working inside a locked repo
If you are an AI agent in a Ctrl-locked repo, here is the contract:
parts/**is not yours to edit. Import only from a part's seam (parts/<name>/src/index.ts, usually aliased@parts/<name>). Everything behind the seam is sealed; editing a part's interior changes its hash and the build goes red. This is by design — you don't have to re-derive auth, billing, or webhooks; they're verified and done.- Wire seams, build the product. Your job is the app: routes, UI, business logic, and the glue that calls the parts through their seams.
- Change parts through the sanctioned verbs, never by hand:
ctrlai add(new capability),ctrlai upgrade(new version / vendor swap),ctrlai eject(take ownership — see below). These move the lock and the spec together, so the build stays green. - Eject is the honest exit. If a part genuinely can't meet a need,
ctrlai eject <part>moves it toejected/, drops its lock, and hands you the code to own. It's loud and reviewable on purpose — the legitimate way out, not a bypass. - Keep it built to spec. After any backend change,
ctrlai conformshould stay green. If you swapped a vendor, the new vendor needs credentials (ctrlai provision <part>) — on-spec is not the same as working.
The scaffolded AGENTS.md in each app is the in-repo, enforced version of this
contract.
What "verified" means (precisely)
When the panel or CLI says a part is verified, it means, today:
- its bytes match the attested content hash (no drift since it was issued), and
- the app is built to spec (the right capabilities, on the right vendors, not hand-rolled).
It does not yet mean a trusted third party cryptographically signed it —
attestations are dev:unsigned today; real signing (and server-side hash
enforcement) is the integrity work that belongs to the cross-repo tier. We say
exactly what we check, and nothing more.
Where to go next
- Anatomy of a part (contract, conformance, attestation, versioning) →
docs/02-part-specification.md(contributor-facing; being refreshed). - The registry, resolver, and CLI internals →
docs/03-architecture.md(contributor-facing; being refreshed). - Per-part docs → each part ships
SPEC.md(what it does) andseams.md(how to wire it).