Skip to main content
Everything Vendo builds is an 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

  1. Tree only, instant and jailed.
  2. Tree plus server functions reached with fn: references.
  3. Server-computed tree returned from a function.
  4. A machine-served HTTP app.
The agent escalates when needed. Users do not choose a rung. The last working surface remains available while the next rung builds. Rung 1 create and tree-only edit calls run without an approval card because they cannot reach host tools, cannot perform egress, and render in the jailed UI surface. Edits that add server behavior stay approval-gated. See Effective risk resolution. The instant payload dispatches by 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 of data-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 to ui: "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.