An open app is one the public can prompt-edit without you losing sleep. Gates run before any spend, some parts of the UI are locked, a second model audits the diff, and the prompter pays. Here is what actually ships.
Most apps have two states of trust: you can edit it, or you cannot. An open app is a third thing. It is an app the public can prompt-edit, on purpose, without you losing sleep. The owner declares who may submit prompts, which prompts actually run, who pays for the AI, and which parts of the app can never be touched, all in policy.json next to the data rules. Here is how it works, and what shipped versus what is still on the board.
The idea
An open app is a Bounded app whose change surface is itself governed by policy. Think open-source licenses, except enforced automatically at the app level, with AI doing the edits and Bounded doing the guarantees. Two blocks in the policy do it: openApps (who may participate and how changes land) and boundaries.ui (what parts of the interface are off-limits). Neither adds anything the prover has to check. If they are absent, you get today's behavior: fail-closed to the owner.
policy.json
"openApps": {
"prompt": {
"submit": "hasRole(\"editors\") || get(/holders/@user.id/balance) > 0",
"run": { "expression": "get(/holders/@user.id/balance) >= 100" },
"limits": [ { "count": 1, "windowHours": 24 } ]
},
"lands": "variation",
"funding": { "mode": "prompter" }
},
"boundaries": {
"ui": [
{ "id": "checkout-lock",
"title": "Checkout can never be prompted away",
"match": { "paths": ["src/checkout/**"] },
"mode": "locked" }
]
}
Gates run before you spend a cent
The first thing a stranger's prompt hits is not the AI. It is two gates, written in the same rule language as your data rules:
- submit: may this person submit a prompt at all? An expression like
hasRole("editors") or get(/holders/@user.id/balance) > 0, or just "signed-in".
- run: a second gate, evaluated at accept time, before any spend. It can be an expression, a deployed app function, or both. Both must pass.
Only after both gates pass does anything cost money. The gates are fail-closed: if the expression errors or returns a non-boolean, the answer is deny. And openApps only ever adds access. The owner keeps base behavior even if the config cannot be read, and a conditional participant is denied the moment anything needed to evaluate their gate is unavailable.
On top of that are deterministic rate limits, per app and per user, enforced server-side: "1 change per 24 hours," with an unless clause to exempt, say, the top holder. The widget renders the verdict honestly: "You can submit 1 change per 24h," or "You don't meet this app's participation rule yet."
And by default, an accepted prompt from someone without edit capability produces a variation, a branch, not a direct change to the live app. Direct edits stay with capability holders. A stranger's good idea becomes something you can look at, not something already shipped.
Locked and guarded areas
This is the part I would want most if I were opening my own app: some things must never change, no matter how clever the prompt. boundaries.ui gives you two modes.
Lockeddeterministic
Any prompt edit that touches the matched files is refused in the sandbox before anything is committed or pushed. Fail-closed. Put your checkout in src/checkout/** and no prompt can rewrite the pay button.
Guardedaudited
A second model reviews the diff. Input is the boundaries plus the change; output is strict JSON listing any violations. One violation fails the job, nothing is pushed, and the spend is refunded.
The audit is itself fail-closed: if that second pass errors, the job fails rather than shipping an unreviewed diff. You mark a boundary from the widget. It collapses, the page gets a hover overlay, you click the element to protect, name it, pick locked or guarded, and save. That patches boundaries.ui in the deployed policy and redeploys through the normal proof-gated pipeline.
Who pays
Left unmanaged, an open app is a way for strangers to spend your money on AI. So funding is a declared mode:
- prompter (default): the person submitting the prompt pays for the compute it triggers.
- app: prompts draw from a per-app bucket the owner funds with a conserved transfer from their account. An exhausted bucket returns
402, never falling through to another payer.
- owner: the owner pays, but the per-app cap still reserves first, so a mob cannot drain the account past the cap.
What ships, and what does not yet
All of the above is live and tested on production: the openApps and boundaries validation, the submit and run gates, rate limits, funding modes and the app bucket, locked-path enforcement, the guarded audit pass, the boundaries widget and its element picker, the activity timeline, and act-as, which lets an owner voluntarily demote themselves to a viewer so their own prompts land like anyone else's. There is also a local bridge that lets the widget drive a coding agent on your own machine against the repo.
I will be honest about the edges, because this is the kind of feature where the edges are the whole risk. Full clone forks to a new app, mergeable back, are designed but not enforced yet. Server-persisted self-demotion is designed. Per-token AI metering and route-level (as opposed to file-level) deterministic enforcement are on the board, not in the build. Locked mode enforces on file paths today; selector and route matching is how you author a boundary, and deterministic route enforcement is the next piece.
Why bother
The default for "let people change my app" has always been "don't." Open apps make it a set of declarations instead of an act of faith: who can touch it, what they can touch, who pays, and what is welded shut. All fail-closed, all in one file, all enforced by the same engine that already proves the data rules. That engine is Bounded, and poof.new is built on it.
Something public is coming that leans all the way into this. When it is live, you will be able to prompt-edit a production app from your browser and watch the boundaries hold.