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.
The big picture
┌──────────────────────── 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.vosobject that turns method calls into messages the host validates and executes.
App lifecycle
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 hookStep 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.
// 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)| Property | Value | Why it matters |
|---|---|---|
| Timeout | 30 seconds per call | Long operations must be chunked, with progress shown between chunks. |
| Concurrency | Requests are matched by id, so calls can overlap | Use Promise.all to parallelise independent reads. |
| Error shape | A message string becomes new Error(message) | err.code is not delivered — branch on err.message. |
| Host push | vos-fs-change | The one event the host sends unprompted, debounced by roughly 200 ms. |
// 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
| Blocked | Use instead |
|---|---|
External script, link and font URLs | Inline all CSS and JavaScript into the html field; use system fonts. |
Direct fetch, XHR and WebSocket | vos.net.fetch to declared origins. |
Remote img src | Fetch the bytes through vos.net and set a base64 data URI. |
| Access to the desktop DOM, other apps, or their data | Nothing — this is by design. Share via a user-visible folder. |
| Model provider APIs with your own key | vos.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
widthandheightare only the opening size; users resize windows freely.