Core concepts

Runtime architecture

A VibeOS app is one HTML document running in a sandboxed iframe, talking to the operating system over a message bridge. Understanding that shape explains every rule in the rest of these docs.

sandboxed iframepostMessage RPChost-side permission checks

The big picture

text
┌──────────────────────── VibeOS desktop (host) ────────────────────────┐
│                                                                       │
│   Window manager · Files · installed grants · AI gateway · net proxy   │
│                                  ▲                                    │
│            vos-request           │           vos-response             │
│            vos-event             │                                    │
│  ┌───────────────────────────────┴────────────────────────────────┐   │
│  │  sandboxed iframe  (your .vibe app, one inline HTML document)  │   │
│  │                                                                │   │
│  │     window.vos  →  fs · ai · net · app                         │   │
│  │     events      →  vos-ready · vos-fs-change                   │   │
│  └────────────────────────────────────────────────────────────────┘   │
└───────────────────────────────────────────────────────────────────────┘
  • The host is the VibeOS desktop. It owns the window manager, the filesystem, the installed permission grants, the AI gateway and the network proxy.
  • Your app is a single inline HTML document with no direct access to any of that — the iframe is sandboxed and its network is locked down by CSP.
  • The SDK is the only bridge: an injected window.vos object that turns method calls into messages the host validates and executes.

App lifecycle

text
1. User opens your app
2. Host loads the installed manifest and the stored permission grant
3. Host builds a Content-Security-Policy from permissions.network
4. Your html document is rendered into a sandboxed iframe
5. Host injects window.vos, then fires the "vos-ready" event
6. Your code runs — every window.vos.* call is an RPC to the host
7. Host may push "vos-fs-change" whenever the filesystem changes
8. Window closes (or minimises, per closeBehavior) — no close hook

Step 5 is why boot order matters

Your scripts may execute before or after the SDK is injected, and vos-ready fires only once with no replay. Always check window.vos first and fall back to the event — see the SDK overview.

There is no close or before-close hook. closeBehavior: "minimize" keeps your document alive in the background; "close" discards it. Either way, save on a short debounce so nothing is pending when the window disappears.

The RPC bridge

Every SDK method is a remote procedure call over postMessage with a request id, answered by a matching response.

javascript
// Conceptually, every SDK method is this:
window.vos.fs.readFile("home/Documents", "note.md")

//  app  ──▶ host   { type: "vos-request", method: "fs.readFile",
//                    args: ["home/Documents", "note.md"], requestId }
//
//  host: re-read stored grant → validate path → perform read
//
//  host ──▶ app    { type: "vos-response", requestId, result: "# Hello" }
//                  or { type: "vos-response", requestId, error: "Access denied: …" }
//
//  → Promise resolves with the result, or rejects with new Error(error)
PropertyValueWhy it matters
Timeout30 seconds per callLong operations must be chunked, with progress shown between chunks.
ConcurrencyRequests are matched by id, so calls can overlapUse Promise.all to parallelise independent reads.
Error shapeA message string becomes new Error(message)err.code is not delivered — branch on err.message.
Host pushvos-fs-changeThe one event the host sends unprompted, debounced by roughly 200 ms.
javascript
// 1. Round trips cost. Batch reads instead of calling per item in a loop.
const entries = await window.vos.fs.readDir(dir);          // one call
const files = await Promise.all(                            // parallel, not serial
  entries.filter((e) => e.type !== "folder")
         .map((e) => window.vos.fs.readFile(dir, e.name)),
);

// 2. Nothing is synchronous. There is no window.vos.fs.readFileSync.
// 3. Every call is independently permission-checked, so a grant change
//    takes effect immediately — even mid-session.

The trust boundary

The iframe is treated as untrusted. This single rule explains most of the platform's behaviour:

  • The manifest inside your HTML is never consulted at runtime. The host reads the grant the user approved from its own storage on every call, so an app cannot widen its own scope by editing anything it controls.
  • Paths are re-resolved and re-checked host-side — traversal attempts, reserved directories and other apps' sandboxes are rejected regardless of what the app asks for.
  • Network URLs are re-validated against the approved allow-list by the proxy, not just by the browser's CSP.
  • There is no permission API. Nothing in the SDK can change install state, permissions or another app. The only path to a wider grant is a user-gated install or upgrade.

Read the security model

The secure sandbox page covers the isolation guarantees in more depth, and permissions covers the grant model.

What the sandbox blocks

BlockedUse instead
External script, link and font URLsInline all CSS and JavaScript into the html field; use system fonts.
Direct fetch, XHR and WebSocketvos.net.fetch to declared origins.
Remote img srcFetch the bytes through vos.net and set a base64 data URI.
Access to the desktop DOM, other apps, or their dataNothing — this is by design. Share via a user-visible folder.
Model provider APIs with your own keyvos.ai.chat, which runs on the user's plan.

Designing for the model

  • Render first, load second. Draw the shell immediately, then hydrate from the filesystem — the first read is a round trip.
  • Treat the filesystem as shared. Other apps and the user can change your files; watch for changes instead of caching forever.
  • Debounce writes. One write per keystroke is the fastest way to a sluggish app and an unhappy reviewer.
  • Assume permissions can be narrower than requested. Read what you were granted from app.getInfo() and adapt the UI.
  • Design responsively. Manifest width and height are only the opening size; users resize windows freely.