Core concepts
Tabbify is an office for your AI: you describe an outcome in chat, the AI writes the code, and the platform runs it as a real service. This page is the mental model that ties the pieces together — what an office holds, how an app becomes a running process, how those processes find each other, and where the human stays in the loop.
Offices and apps
An office is the bounded namespace you own: its apps, services, secrets, and approvals. The chat is your control plane; everything below is runtime.
An app is a workload identified by a UUID v7 — its permanent address, stable
across every deploy. You don't publish artifacts by hand. The deploy flow is a
git push: GitHub Actions runs the tabbify-io/deploy action, which calls
tcli deploy --remote. Tabbify clones your repo, builds it on its own machines,
and runs the result — no Docker on your laptop, no object-storage upload, no
registry login. (You can also run tcli deploy --remote straight from a terminal
if you prefer; see the CLI reference and
Quickstart.)
A single tabbify.toml is the source of truth — [app]
metadata, [build], [runtime] resource limits and lifecycle, and any
[[deploy]] placement targets.
The runtime
There is exactly one runtime: a Firecracker microVM built from an OCI image.
The build always produces an image (your Dockerfile → OCI image → ext4 rootfs),
and that image boots in its own tiny VM. There is no runtime to pick — [build]
is always kind = "docker" and the app always runs as Firecracker:
[build]
kind = "docker" # build the Dockerfile into an OCI image
[runtime]
lifecycle = "on_request" # start on first visit, sleep when idle
memory_mb = 256
vcpus = 1
Older manifests sometimes carry a [runtime].type (such as wasm-http or
docker) or a [[deploy]].runtime field. Those are tolerated for backward
compatibility but ignored — they do not select an alternate runtime.
Supervisors carry mesh capability tags (for example firecracker, which requires
/dev/kvm, and builder), and the node validates that a target can run an app
before placing it there. See The Runtime and
Runtimes.
The private mesh
Apps do not listen on the open internet. They join a WireGuard overlay where each app gets a deterministic IPv6 address derived from its UUID:
fd5a:1f02:<blake3(uuid)>::1
The address is stable across restarts and host migrations, so one service always finds another at the same place — no discovery dance. A supervisor spawns a detached runner process per app, and each runner is itself a first-class mesh peer on that UUID-derived address — not just a socket the supervisor opened. The node proxies your requests to it across the mesh. Full detail in Private Mesh and Routing.
Events and approvals
State changes are recorded as an append-only event log — app_registered,
app_started, secret_registered. The log is the source of truth: recovery is
replay, and every API mutation flows through it for audit.
Two honest caveats today: the event stream is fire-and-forget (a subscriber that
connects late misses an event; recent ones sit in a ring buffer), and the
approval card — the chat widget that lists requested capabilities like
telegram:send before anything runs — is designed but not yet wired in the
frontend. Secrets never reach the prompt: they live in an encrypted store and are
injected as environment, never written to the log. See
Services & capabilities.