Documentation
ReferenceArchitecture

Architecture overview

Tabbify is two planes bolted together. The substrate is an event-sourced core: an append-only log is the source of truth, and services react to events rather than calling each other. The app-layer is what turns your code into a running service — a CLI, a registry, a public node, supervisors with per-app runners, the private mesh that wires them, and an auth service that signs who gets in. This page is the map; each subsystem has its own page linked below.

The substrate core

The spine is the transaction-node: an append-only event log where every state change — app_registered, app_started, secret_registered — is recorded before it takes effect. Recovery is replay; every mutation is auditable. The read-node builds projections off that log; the api-gateway terminates external webhooks and resolves secrets out of band; the original sandbox-supervisor subscribes to sandbox_spawn_requested and emits sandbox_log / sandbox_started back into the log. Nothing talks to a service directly — inbound traffic becomes an event, your app subscribes to a segment, and emits its result. See Core concepts and Services & capabilities.

Two honest caveats: the event stream is fire-and-forget (a late subscriber misses events; recent ones sit in a ring buffer), and the chat approval card is designed but not yet wired in the frontend.

The app-layer

A handful of pieces take a tabbify.toml to a reachable URL:

  • tclitcli deploy --remote hands your repo URL and commit to the node, which clones and builds it for you. The tabbify-io/deploy@v1 GitHub Action runs the same command on every git push. (Local-build deploy is not implemented; --remote is the path.)
  • registry — a mesh-only OCI registry (Zot behind a Rust wrapper), gated by tabbify auth, per-tenant namespaces. Built images land here, addressable only over the mesh.
  • supervisor + runner — a supervisor is a universal node that both builds and runs apps (capability tags: builder, firecracker). Its control plane spawns one detached tabbify-runner per app on [app_ula]:8730 and re-adopts living runners after a crash.
  • runtimes — there is exactly one runtime: Firecracker, booted from your OCI image. [build].kind = "docker" produces an image; the runner turns it into an ext4 rootfs and boots a microVM. (Legacy [runtime] / wasm fields parse for wire-compat but resolve to Firecracker — there is no per-app runtime choice.)
  • mesh — a userspace WireGuard overlay; every app gets a deterministic IPv6 fd5a:1f02:<blake3(uuid)>::1.
  • node — the public edge. It exposes REST + MCP and proxies /app/<uuid> over the mesh. Users reach it over HTTPS at https://api.tabbify.io (CloudFront); the node itself binds locally and is never addressed by raw IP or port.
  • auth — Ed25519 JWTs and node-join tokens; the coordinator trusts token claims, not a peer's self-asserted --tag.

The data path

A request from the public internet to a running app:

browser
  │  HTTPS
  ▼
api.tabbify.io  (CloudFront edge)
  │
  ▼
tabbify-node                 Bearer-auth, derive_app_ula(uuid)
  │  WireGuard tunnel (mesh)
  ▼
tabbify-runner  [fd5a:1f02:…::1]:8730
  │
  ▼
Firecracker microVM  (your OCI image's entrypoint)

The node derives fd5a:1f02:<blake3(uuid)[0:6]>::1 from the UUID and dials [app_ula]:8730 directly — there is no supervisor query and no uuid → supervisor cache. The mesh route is the discovery: when a supervisor starts an app it advertises the app-ULA, and the node simply computes the same address and dials it. Bodies stream both directions; an unreachable app is 502, a bad UUID is 400. See Routing & public access.

Deploys flow the other way: git pushdeploy pipeline → the node clones and builds your repo on a builder supervisor → the image is pushed to the mesh registry → a supervisor spawns the runner as a Firecracker microVM. Start there with the Quickstart, or run your own edge via self-hosting a node.

Honest scope

App-ULA direct binding is live: the node computes the app-ULA and dials it directly, with no supervisor lookup. The unified tabbify.toml is the manifest both tcli and the supervisor read; legacy [runtime] fields still parse but are ignored (everything resolves to Firecracker). Node auth is a Bearer token issued to you; its activation/version stores are still in-memory. NAT traversal goes direct → hole-punch → DERP relay; the relay (Stage 3) is live, so peers behind symmetric NATs reach the mesh through it.