Runtime Topology · v0.11.7

How the surfaces actually run together

The static crate map shows what depends on what. This is the runtime view: which processes exist, who hosts whom, and what is a shared central service versus a private, local-only one. The whole picture turns on one fact — the agent core is a shared object, not a shared server. Every surface is a host process that constructs one or more cores in-process; the HTTP/SSE server is just a thin facade wrapped around a core.

Private / loopback Shared / networked External adapter

The center of everything

One core, constructed in-process by whoever hosts it

vak_core::Core is the single object that owns a session's real state. It is not a daemon you connect to — each host builds its own via Core::new_with_trust, which resolves that workspace's trust, permission mode, and sandbox exactly as a local vak run would. Pooling or gateways can never grant a core more access than a local session in the same workspace already has.

vak_core::Core

The auditable, policy-gated agent runtime

Instantiated inside a host process — CLI, server, or desktop — never reached over a wire on its own.

ledger

Append-only JSONL session log on the local disk under the user's home — the source of truth every surface reads and writes.

permission engine

Every tool, worker, and flow clears the same gate before dispatch. Missing containment fails closed.

tool broker + sandbox

Filesystem, shell, and web effects run inside the brokered boundary the workspace resolved.

memory & checkpoints

Recall, worktrees, and workspace snapshots — all local, all reconstructable.

The processes

Who hosts a core — and how reachable it is

Four kinds of host process construct cores, plus the external chat adapters that never touch a core directly. The badge on each says whether that process is private to one user on the loopback interface, or a shared service bound to a network address.

vak (CLI)private · in-process

vak exec, plan, flow, eval build one core for the current workspace and call it directly — no network, no server. The session ledger is written to local disk and is fully reconstructable afterward.

hosts
1 core (cwd workspace)
reach
this invocation only
state
local JSONL ledger
vak-desktopprivate · loopback

The Tauri shell embeds vak-server on an ephemeral loopback port (127.0.0.1:0) guarded by a per-launch bearer token. The SolidJS webview is just an HTTP/SSE client of that private embedded server — same contracts as the networked one, reachable by nobody else.

hosts
embedded server + core(s)
reach
its own webview only
auth
loopback bearer token
vak serveshared · networked

The durable background service (macOS LaunchAgent / Linux systemd user unit). Boots vak-server on a real TCP address and exposes the full session / run / approval / transcript / diff / steering contract. With --gateway it also accepts inbound channel messages.

hosts
CorePool (many workspaces)
reach
web clients, IDEs, gateways
run as
foreground or service
vak-trayprivate · local

A menu-bar / system-tray companion that starts, stops, and checks the background service through vak-ops. It controls a host; it does not host a core of its own.

hosts
nothing — a controller
reach
local service lifecycle
channel bridgesexternal adapter

vak slack, vak discord, vak telegram are separate client processes. Each holds a bot token plus a gateway token, polls its chat platform, and forwards every message to the server as POST /gateway/inbound. They translate a chat platform into the gateway contract — they never construct a core.

hosts
nothing — a translator
talks to
vak serve --gateway
secrets
bot token + gateway token
web / admin UIserved by the server

The SolidJS admin console (and any web client) are pure HTTP/SSE consumers of a running server's endpoints — desktop's embedded one or a networked vak serve. They render state; they never hold agent state themselves.

hosts
nothing — a view
talks to
server endpoints

The gateway path

How a Slack message becomes an agent turn

Channels are the most involved path because the message originates outside vak entirely. It crosses from an external platform, through an adapter, into a persistent bound session on the shared server — and the reply travels back the same way.

1

A person posts in a channel

Slack / Discord / Telegram holds the conversation. The bridge process is subscribed to it with its bot token.

2

The bridge forwards it — POST /gateway/inbound

The adapter builds a validated {surface, chat, sender, text} payload and sends it to the server's gateway endpoint, authenticated with the gateway token. The real remote identity in chat/sender is what the allowlist and binding depend on.

3

The allowlist admits it, the binding resolves a session

gateway.chat_allowlist gates whether that surface:chat may reach a workspace at all. A persisted map (surface:chat) → session_id in <home>/gateway/bindings.json ties the conversation to a durable session that survives restarts.

4

The CorePool hands over the workspace's core

Keyed by (workspace, permission-override), the pool lazily starts or reuses the right core — the default workspace pinned warm, others idle-evicted. Each is built through Core::new_with_trust; an override can only ever reduce access, never raise it.

5

The turn runs — unattended, so it fails closed

Approval gates auto-deny and the denial is fed back to the model as a tool error. Messages that arrive mid-turn are queued as logged steering input and consumed between model steps — nothing typed while busy is dropped.

6

The reply returns to the channel

The bridge either long-polled the final assistant text or the server pushes it back through vak-delivery's durable outbox — the same audited record every other surface produces.

The dividing line

Shared central vs. private & local

Nothing phones home. The only thing that is ever "central" is a server process the user chose to run and bind to a network address; everything else is a local, per-user, per-invocation host.

Private & local

per-user · per-invocation · loopback

  • Session ledgers & memory — JSONL files under the user's home; never uploaded, always reconstructable.
  • CLI runs — one core, this invocation, no network at all.
  • The desktop app — its embedded server is loopback-only and token-guarded; no other client can reach it.
  • Credentials — provider keys from a gitignored .env, the user secret store, or the environment; stored 0600.
  • Trust resolution — every core resolves its own sandbox & permission mode from local config.

Shared central

opt-in · networked · multi-tenant

  • A networked vak serve — the one long-running host multiple clients and channels hit.
  • The CorePool — one server, many workspaces, each an isolated core with its own trust ceiling.
  • Per-conversation bindings — each chat gets its own persistent bound session; they don't bleed together.
  • The gateway endpoint — the single door channels enter through, guarded by allowlist + gateway token.
  • Still gated identically — a shared server grants no shortcut a local run wouldn't; unattended turns fail closed.
Key nuance

"Shared" is about isolation, not pooling risk. Two channels on the same workspace but different permission overrides get two distinct cores — a core's permission mode is single shared mutable state, so one channel could otherwise dictate the other's access. The pool key is the (workspace, override) pair precisely to keep them apart.

At a glance

Every component, by hosting

ComponentKindHosts a core?ReachabilityShared / private
vak exec / plan / flowCLI processYes — 1, in-processThis invocationprivate
vak-desktopTauri appYes — via embedded serverLoopback + tokenprivate
vak-desktop/ui · admin-uiSolidJS clientNo — a viewIts server's endpointsprivate
vak serveHTTP/SSE serviceYes — CorePoolNetwork addressshared
vak serve --gatewayGateway serviceYes — pooled per workspace+ /gateway/inboundshared
vak slack / discord / telegramChannel bridgeNo — a translatorChat platform ↔ gatewayexternal
vak-trayTray controllerNo — a controllerLocal service lifecycleprivate
session ledgers · memoryOn-disk state—Local home dirprivate