kernel, grip, guests

Architecture

Thetis is one native binary and a handful of WebAssembly components it loads. The binary holds every real capability; the components hold everything the system is able to change about itself.

The orchestrator owns everything real

One native binary is the trusted kernel. It owns the network, the filesystem, the database, and the build toolchain, and hands guests narrow, mediated slices of them through the contract in wit/thetis.wit.

Guests hold no ambient authority. A guest cannot open a socket, read a file, or reach the model API on its own. Everything it can observe or affect is an import declared in the contract, answered by host code the guest never sees.

That split is what makes the rest possible. The parts that can rewrite themselves are the parts that hold no authority of their own, so a rewrite changes behaviour without ever widening reach.

Hold on through every form

The grip is wit/thetis.wit, the boundary between the trusted orchestrator and every hot-loaded guest. Its own header states the rule the system is built on: guests hold no ambient authority, and everything they can observe or affect is an import declared there.

An imports list is therefore a complete description of a world. A tool sees two interfaces and nothing else:

world tool {
  use types.{tool-manifest};

  import sys;
  import sandbox;

  export describe: func() -> tool-manifest;
  export invoke: func(
    session-id: string,
    args-json: string,
    config-json: string,
  ) -> result<string, string>;
}

A tool is handed its own [tools.<name>] block from thetis.toml as config-json, or {} when it has none. It never sees another tool's settings, nor anything else in the configuration.

Changing this file forces a rebuild of every guest, so it is orchestrator-owned: the dev kit refuses agent writes that touch it. The agent can rewrite what it does. It cannot quietly rewrite what it is allowed to do.

Three kinds of guest

The guests are aspects, the forms the system takes. Three kinds plug into the orchestrator:

AspectSourceWhat it does
agentagents/agent-coreRuns the agentic loop: rehydrate, prompt, stream, dispatch tools, check for nudges
gateway/webgateways/gateway-webServes the chat UI and owns the wire protocol
tool/<name>tools/<name>One tool each, scaffolded and edited by the agent

Each kind builds against its own world. The agent exports handle-turn, a health probe, describe, and list-tools; a gateway exports asset serving, client-message handling, and event rendering; a tool exports describe and invoke. Those exports are what the validation gate later smoke-tests.

A fresh store every call

Guests are instantiated per call in a fresh store. Nothing carries over in guest memory from one invocation to the next, because there is no instance left standing to carry it.

That is what makes hot swapping safe. A turn already in flight finishes on the component it started with, and the next call picks up the new one. There is no moment where half a conversation runs on old code and half on new, and no need to drain traffic before an activation.

The process picture has the same shape. The gateway owns the root checkout and the ref namespace, and spawns one worker per live conversation, each running against that conversation's own git branch (conv/<short-id>) checked out in its own worktree. A branch is materialized lazily at the conversation's first message, pinned to trunk's head at that moment, so empty conversations cost nothing. Workers are children of the gateway — one that finds itself orphaned exits on its own — and a worker can even be a different binary, when its branch has rebuilt the kernel itself.

The repository, top to bottom

thetis.toml             configuration; every path below is one of its settings
wit/thetis.wit          the host/guest contract — changing it rebuilds every guest
crates/thetis           the kernel: loader, pipeline, branches, watchdogs, web
agents/agent-core        the agent's own source, which it can rewrite
gateways/gateway-web     chat UI and wire protocol
skills/<name>.md         instruction sets you can attach to a conversation
tools/<name>             tools the agent scaffolds for itself
templates/tool-template  what new_tool starts from
worktrees/               per-conversation checkouts, one per branch
artifacts/               content-addressed build cache (component + smoke verdict)
data/thetis.redb        sessions and the event log

Read it as three bands. thetis.toml and wit/thetis.wit are the two files that govern everything else, one for settings and one for the contract. crates/thetis is the native kernel, and it is the only Rust here that does not run in a sandbox.

Below that sit the aspects: agents/agent-core, gateways/gateway-web, and one directory per tool, with templates/tool-template as the starting point new_tool copies. Every one of those paths is a setting in [paths], so the layout is a default rather than a hard-coded truth.

artifacts/ and data/thetis.redb are the memory. Artifacts are a content-addressed build cache, keyed by the source tree that produced each component and shared across every branch — the same tree builds once, wherever it is checked out. The database holds sessions and the event log. Both are on the protected list, which stops the system deleting its own state by accident; the source history itself lives in git, on trunk and the conversation branches.

The agent remembers nothing between turns

The agent is stateless across turns. It rehydrates from the session log every time, rebuilding the model's context from an append-only record of user messages, assistant messages, tool invocations and results, nudges, system notes, modifications, and incidents.

The log is the conversation. What the agent held in memory during the last turn is gone, and nothing depends on its still being there.

Resume after a restart follows from that rather than being built on top of it. A turn cut short leaves a log; coming back means running a turn again against a log that now records the interruption. There is no checkpoint format, no in-flight state to serialize, and nothing to reconcile between what the process believed and what was written down.