AppDocument with format vendo/app@1. Every
user owns their own copy. Import, fork, sharing, and publishing mint a fresh
app_ id, so artifacts carry no data, grants, or authority.
The ladder
- Tree only, instant and jailed.
- Tree plus server functions reached with
fn:references. - Server-computed tree returned from a function.
- A machine-served HTTP app.
formatVersion. V0 registers only
vendo-genui/v1. Unknown formats render a contained notice. Generated
components run inside an iframe with connect-src 'none' by default. An
approved version of an app can also render in the host page — see
In-client venue and approvals for how approvals
are recorded, how the version hash is pinned, and how new versions drop back
to the jail.
Rung 4 uses the sandbox provider URL directly. While a snapshot resumes, the
UI shows a dimmed, non-interactive cover. Machine code reaches host tools only
through VENDO_PROXY_URL with a short-lived VENDO_RUN_TOKEN.
Secrets and outbound requests
Secret values never enter the machine. Each declared secret name is injected as an opaque handle (vendo-secret:<name>:<nonce>); app code reads
process.env.STRIPE_KEY normally and never learns the value. Real values
resolve only inside the runtime’s egress proxy, only for hosts on the app’s
declared egress allowlist, after an SSRF guard on the resolved IP. Reflected
secrets are stripped from responses before they return to app code.
App code does not call the proxy itself. The runtime writes a fetch shim into
every machine and loads it at boot: outbound fetch() to an external host is
rewritten into POST {VENDO_PROXY_URL}/egress authenticated by the run token,
so plain fetch with the secret env var in a header or body just works.
Internal requests are never rewritten — relative URLs, the proxy itself, and
loopback (the app’s own $PORT server) pass straight through. The shim
activates only when VENDO_PROXY_URL and VENDO_RUN_TOKEN are present.
A request the proxy refuses (non-allowlisted host, private address) surfaces
as an ordinary fetch error. One limitation to know: clients outside the Node
fetch path — raw sockets, non-Node runtimes, binaries the app spawns — skip
the shim, so those integrations do not authenticate. They also cannot leak:
everything they can send is the opaque handle, because the machine never held
a value.
Progressive generation
Create and open stream the generated tree as the model produces it. Complete nodes paint as soon as they close, and later partial updates reconcile into the same view — the stock ai-SDK client replaces the payload in place instead of appending duplicates. Nodes whose generated source has not arrived yet render as skeletons and swap in when their source closes. Queries embedded in the tree resolve concurrently as they stream, with per-query error containment so one failing query does not block the rest of the view. You do not need to change your client. Existing consumers ofdata-vendo-view
parts see progressive frames followed by the final tree under a single stable
part id.
Invisible graduation to rung 4
When a tree app graduates to rung 4, the first served version renders the identical kept tree — users see no visible change. The runtime writes a served-app scaffold to the machine before the model’s files, boots the scaffold’s static server, captures a cover screenshot, then snapshots. The document only flips toui: "http" after the edit persists. If any step
fails, the previous rung keeps serving and the document is untouched.
The scaffold serves the kept tree from /tree.json and any document-level
generated components from /components.json. It mounts them through the
same renderer used before graduation, so component identity and jailed
iframe boundaries are preserved. Tree references of the form fn:<name>
become POST /fn/<name> requests to the app’s own server on $PORT. The
graduated app owns those routes, and the model’s post-graduation edits
supply the handlers.
App server code follows a fixed boot convention on every rung that runs
one. The machine boots /app/start.sh when present, otherwise
/app/server.js. The server listens on $PORT and answers
POST /fn/<name> with the fn: envelope. Before every snapshot on rungs
2 and 3, the runtime confirms the server is actually listening. If
nothing serves, the edit fails and the previous rung keeps serving.
A few behaviors are specific to the served page and worth knowing when
writing prompts or diagnosing a graduation:
- Nodes that reference host-source components degrade to a contained “unknown component” notice, because host components cannot ship inside a served app. Prewired and generated components render identically.
- The served page renders with the tree’s at-rest data. Progressive query re-resolution, which happens while the host still renders the tree, does not run once the app is serving itself.
- Non-
fn:host actions are refused with a structured error outcome and make no network calls. Mount failures surface as a visible alert instead of a blank page.