Docs

How Ctrl AI works

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 /ctrl panel, 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. stripepaddle; resendpostmarkses
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 (stripepaddle) 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.json records 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 conform diffs 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 to ejected/, 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 conform should 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 internalsdocs/03-architecture.md (contributor-facing; being refreshed).
  • Per-part docs → each part ships SPEC.md (what it does) and seams.md (how to wire it).