apply creates the branch and writes both variables, then stops at the deploy barrier: the callback needs
the preview URL, which exists once the deploy finishes. The next apply (the one the deploy triggers in CI) finishes
it.
pnpm docs:gen runs this plan against the fake cloud below and writes what the CLI prints here.
Try it in 60 seconds
No Vercel, Neon or Clerk account needed:@sponson/sim is a local fake cloud with their APIs. You need Node.js 22
or later, git and curl. In one terminal, start it with a ready demo repository:
sim.envpointssponsonat the fake cloud with placeholder tokens, and makessponsonthe published CLI of the sim’s own version, through npx (a shell function, so a global install of another version cannot get in the way).planreads live state and prints the diff ofrelease.plan.yaml: a Neon branch to create, and aDATABASE_URLand a Clerk redirect that wait on it. Nothing is written.applycreates the branch, writes its connection string into Vercel’sDATABASE_URLby reference (the password is never printed), finds the preview deployment of this commit, and allows its URL as a Clerk redirect.- The same
applyagain: every lineunchanged, zero writes. - The
curlplays someone editingDATABASE_URLin the Vercel console. planreports the drift and blocks that line (x, exit 1):applywill not overwrite it without--reconcile.apply --destroyremoves everything the demo created, in reverse order, and warns thatDATABASE_URLwas changed outside Sponson before it deleted it. Ctrl-C in the first terminal stops the fake cloud.
scenarios/docs/try-it.test.ts runs these exact commands from this file on every CI run.
Why this exists
Agents can already change a repository. They cannot safely change the rest of a release. The code lands in Git. The things that have to move with it do not. A preview database lives in the database console. Environment variables live in the deploy console. OAuth callbacks live in an identity console. Feature flags live in a fourth. A person copies state between them by hand. An agent does the same thing with no shared record of what it intended, what it tried, and what a human approved. Existing tools each own one slice. Terraform and Pulumi plan resources, not the preview data and callback that belong to this release. Vercel and Railway deploy the app, not the secrets and flags that have to match it. Doppler and Infisical hold secrets, not the release those secrets belong to. A sequence of agent tool calls is not itself a record. A release needs its own file. Sponson is that file. The format is the product. A hosted approval inbox can come later. A new cloud does not.What it does
One release, one plan. Three commands.- Secrets are references.
{ secret: "env://NAME" }, or any other secret scheme. Every command resolves them,planandapply --destroyincluded (so a scheme backed by a secret manager’s CLI runs that CLI onplantoo), and registers each value for masking before anything is printed; onlyapplyhands values to an adapter. They are redacted from every byte of output. A literal that looks like a secret is rejected at parse time. - References cross lines.
{ from: db.connection_string }reads another line’s output. Outputs that only exist after an external event (a deploy) stop the run with statuspartial; the nextapplycontinues from there. Same command, no flags. - Drift is reported, never silently overwritten. Something changed in a console since the last apply?
plansays so;applyrefuses that line until you pass--reconcile. Something exists that the plan does not mention? It is listed and left alone. - Receipts are the agent’s memory. Each run writes what actually happened to an orphan branch of your repo, one per environment and scope (
sponson-receipts/<env>/<scope>, so unrelated pull requests never wait on each other): a ledger of every resource the scope owns (kept across failed, refused and crashed runs), what each line did, and which commits were applied. Creates are recorded before they are sent, so even a write whose response was lost is never forgotten. The agent reads the receipt, not its own last tool call. - Adopting is not copying.
sponson initadopts existing resources as{ keep: true }: Sponson takes over that they exist, keeps their live values, and never destroys them. - Production needs a human.
--env productionwithout--approved-byis refused before any adapter is touched, and so is any run with a line that writes to production (a Verceltarget: production, a LaunchDarkly environment that is production). The default environment is alwayspreview; nothing is inferred from a branch name. - Destroy is symmetric.
sponson apply --destroyremoves what Sponson created, in reverse order, and never touches resources it merely adopted. A resource Sponson created that someone edited since (changed drift) is deleted too, since the pull request is closing, but never silently: the output, the PR comment and the receipt (notes.changedOutside) name it. - What no API can do is a step for a person. A
manual.stepline (a Google OAuth redirect URI for the preview URL) shows its instructions as a todo, holds back the lines that depend on it until someone runsapply --confirm <line>, records who confirmed it and when, and asks for its undo on destroy. Agents show the step to a person; they never confirm it.
Supported providers
Sponson manages per-environment state at 64 providers in 13 categories, from deploy targets and databases to auth callbacks, feature flags, webhooks and DNS: 17 first-class ops and 68 recipe ops.sponson init detects 59 of them from a repository’s files and writes their lines.
docs/providers.md lists every one: what Sponson manages there, the op or recipe, the credential, what init detects it by and a line to start from.