Host

The host is the native shell. It opens the window. It places tiles inside that window, gives each tile a webview, hands running programs their surfaces, and dispatches their substrate operations to the engine. It does not write interface — everything a user sees is produced by a program.

The host is written in Rust against tao (cross-platform windowing) and wry (cross-platform webview). These are the libraries that underlie Tauri; the host uses them directly, without adopting the Tauri framework's app-level conventions. Our shape — one window with many tiled webviews, each its own program — fits these primitives more naturally than Tauri's one-webview-per-window default.

The engine and substrate are Rust crates linked into the host binary. The host calls them directly — there is no separate engine process and no inter-process hop between host and engine. VM programs (tool programs running inside their VM) are spawned by the engine and reach it over stdio JSON-lines.


What the Host Does

What the Host Does Not Do

The host stays small. Rust, a few hundred lines at the core, oriented around window + webviews + the wry IPC dispatch surface. This is not an aesthetic preference — it's the line that keeps interface authorship in the language the substrate's self-description already describes, rather than in a second language that has to keep up.


Program as Interface

Interface and tool are one kind of thing. A program is a chunk with an executable and a runtime declaration — nothing distinguishes a program that renders a read tile from a program that touches the filesystem beyond what their bodies declare.

The pilot supports two runtimes:

Programs of both runtimes use the same SDK surface (scope, commit, run, awaitRun, subscribe); only the transport differs. A complex UI that needs both DOM rendering and direct system access is built as a composition of two programs — a webview program and a vm program — bound by their shared scope, communicating through the substrate. Compositions are the substrate's native shape for what other systems call "islands": independent interactive regions, each with its own runtime, glued by shared state.

The pilot ships a TypeScript SDK only. First-party VM programs use #!/usr/bin/env bun so they can import the TS SDK directly; programs in other languages would need their own SDK speaking the same JSON-lines protocol. That is out of scope for the pilot, in scope for the horizon.

Every interface element is a program: sidebar, tabs, command palette, read tile, program runner. The host composes their outputs. When the user opens a read tile, a webview program is running. When the user brings up the command palette, a webview program is running as an overlay. The claude agent is a VM program; its output appears as session chunks the UI programs read.

When future runtimes land (host-rendered DOM from a VM program, GPU-canvas, terminal, native widgets), they become new runtime values. See research/runtimes-and-surfaces.md for the topologies considered and what's deferred.


The Composition Types

All in the ui namespace. Seeded by bootstrap. The host reads these chunks to render.

ui/session
  spec: { propagate: true, accepts: ['tab', 'process'] }
  body: { name?, current-tab? }
  — The outer container. Restorable, shareable. Any process placed on the
    session as instance becomes sidebar-visible. No separate pin archetype —
    session membership is sidebar presence.

ui/tab
  spec: { propagate: true, accepts: ['tile'] }
  body: { name? }
  placements: on session (instance)
  — The root of a tile tree. Workspaces are tabs; one term, one archetype.

ui/tile
  spec: { ordered: true }
  body:
    split node:  { direction: 'horizontal'|'vertical', ratio }
    leaf node:   (empty; mount expressed through placement)
  placements:
    on tab or parent-tile (instance, seq chooses split side)
    on engine/process (relates — "this leaf displays this running process")

ui/overlay
  body: { anchor: 'session'|'tab'|'tile' }
  placements:
    on engine/program (relates — overlay content)
    on anchor target (relates)

ui/recipe
  spec: { propagate: true, accepts: ['tile'] }
  body: { name?, description? }
  — A tile subtree preserved as a template. Spawning clones the structure
    under a chosen root — a whole tab, or a single tile within an existing
    tab. The recipe itself persists separately from any spawned instance.

A recipe, when spawned, produces a composition: a container process visible as one unit in the sidebar, a nested tile structure visible as one rounded card on the board (with inner tiles separated by borders rather than padding). Collapsing the container stops its children. Composition is the live form; recipe is the saved template. Spawning instantiates fresh processes from the template; the recipe itself is unchanged.

Compositions are how complex UIs that mix DOM and capabilities get built. A program that needs both a designed UI and direct system access is a composition of a webview program and a VM program, bound by their shared scope. Visually they can read as one designed surface — the host renders inner tiles with no padding when the composition wants seamlessness — even though they're independent runtimes.


View Modes as Lenses

The pilot ships tabs — the geometry described below. That is one lens on what the substrate holds, not the only one. A zoomable canvas is another lens on the same chunks; further geometries are reachable too. Because the host is built by programs themselves, a view mode is just another program working over the composition types — not a different host. Tabs go first; new lenses are additive, not forks.

Tile Geometry

Binary split tree. Same primitive the earlier pilot used; the model survived the redesign because it is the right one.

The host walks the tree of the active tab, positions webviews as rectangles inside the window, draws rounded-corner cards around each leaf. A composition (a container process with nested tiles) renders as an outer rounded card; its inner tiles render with borders only. Tiling never happens inside a webview. The host owns every rectangle.

Overlays

An overlay is a program that renders above the normal tile composition. Its anchor scope determines how far it spans:

A program can freely anchor an overlay to its own tile because the write boundary already includes the tile. Anchoring higher (tab, session) requires a program whose write boundary reaches that far — unusual and underexplored. The specifics of overlay-escalation semantics remain open.


Visual Language

The window is a quiet canvas:

In the sidebar, the same chunk is shown two ways based on its process state. A running process is a card, with the same rounding and shadow as a tile. A completed or failed process is flat — just its content directly on the background. The visual language distinguishes life from rest without a label.

Future visual refinements — glow around cards derived from their content pixels, native backdrop blur, compositor-level effects — live on the direction list. For the pilot, CSS backdrop-filter covers the aesthetic; native pixel effects come later.


Transport

One hop. One protocol shape.

A webview program calls the SDK; the SDK serializes the call and posts it through wry's IPC channel via window.__wry_ipc.postMessage(<json>). The host registers WebView::set_ipc_handler per webview at mount time; each invocation parses the JSON, attaches a Context { process_id } from the host's webview→process registry, calls the matching engine function, and resolves the call by injecting webview.evaluate_script("__sdk.resolve(<id>, <payload>)").

Unsolicited events from the engine ride the same channel in the other direction: webview.evaluate_script("__sdk.event(<payload>)"). The SDK distinguishes responses (id + result|error) from events (event field) by message shape on the JS side. See pilot/engine.md for the end-to-end push chain.

The host does not interpret substrate operations — it dispatches them. VM programs (tool programs running inside their VMs) speak the same protocol shape over stdio JSON-lines; the engine reads their stdout directly without going through the host.

SDK surface

A program imports the SDK and calls the substrate operations it needs directly. Webview programs render with their DOM library of choice — react-dom/client for React, anything else otherwise; the SDK has no rendering concerns.

import { scope, commit, run, awaitRun } from '@night/sdk'
import { useScope } from '@night/sdk-react'
import { createRoot } from 'react-dom/client'

function ReadTile() {
  const scopeId = /* from host-provided mount context */
  const data = useScope([scopeId])
  return <div>{/* render data */}</div>
}

createRoot(document.getElementById('root')!).render(<ReadTile />)

Operations the SDK exposes (in @night/sdk):

React hooks live in a separate package, @night/sdk-react. The starting hook is useScope(ids) — calls scope for initial data, registers a subscribe, re-fetches on scope_changed, unsubscribes on unmount. A richer hook vocabulary may emerge through use. Full surface in pilot/sdk.md.


Authoring Programs

Two shapes for the two kinds.

Webview program (runtime: 'webview'). A TSX entry that renders its component tree directly. The host loads the program's bundled JS into a webview that already has <div id="root"></div> in the page. No shebang — the file is bundled to JS by the build pipeline, not run directly.

import { useScope } from '@night/sdk-react'
import { createRoot } from 'react-dom/client'

function MyProgram() {
  const data = useScope([/* ... */])
  return <div>{/* ... */}</div>
}

createRoot(document.getElementById('root')!).render(<MyProgram />)

The program is a substrate chunk with body.executable pointing at the bundle and body.runtime: 'webview'. When the host runs the program, it creates a webview, loads the bundle, the JS runs createRoot(...).render(...) against the host-provided root.

VM program (runtime: 'vm'). An executable file with a shebang. Runs as a standalone process inside its own VM. The shebang determines the interpreter and what APIs the program has access to. The pilot's first-party VM programs use #!/usr/bin/env bun because the SDK is TypeScript:

#!/usr/bin/env bun
import { scope, commit, awaitRun } from '@night/sdk'

const args = await scope([process.env.PROCESS_ID!])
// ... do work, call APIs, write to substrate ...
process.exit(0)

The program is a substrate chunk with body.executable pointing at the script and body.runtime: 'vm'. When the host runs the program, the engine spawns the script inside its VM with stdio attached. Other interpreters (Python, Ruby, anything installed in the VM that can speak the JSON-lines protocol) become viable when an SDK for that language exists.

Lifecycle differs by kind.

State lives in the substrate. Programs use the substrate directly via scope and commit (and useScope for reactive reads in webview programs) for anything that needs to persist. There is no separate state-persistence API. Per-run state that must separate from shared-program state is passed as a typed argument to run.

Process identity. Each run of a program is a distinct process chunk with a distinct id. Two processes of the same program with the same arguments can coexist — they are different chunks. The sidebar disambiguates them with program name + args + some visual suffix (timestamp, index, or user-assigned name — the scheme is open UX).


The sidebar is the session's view of itself. Its items are processes placed instance on the current session, plus whatever the session explicitly holds.

History of what has been run is reachable without a dedicated scope-history chunk. Processes themselves are the history — the process-history of a program is the set of all its past runs, available via substrate scope.


Command Palette

A program with runtime: 'webview' and an ui/overlay placed on the current session. Opened by a leader key the host catches and forwards. Sources: available commands, programs in the system, recent processes, substrate search.

Not a host feature. Just another program, living as an overlay.


What Is Open


Directory

To be built (Rust):

To be built (TypeScript):

Existing:

The Rust port lands db, engine, and host as one binary (three crates in a workspace). See pilot.md.