@selfworking-ai/surface
v0.3.1
Published
Display server + protocol for agent CLIs — a locked prompt dock and a generative, composable, retained-mode canvas. LSP for agent consoles: Surface owns the surface, the runtime owns the fabric.
Maintainers
Readme
Surface
Surface owns the surface; the runtime borrows the fabric — LSP for agent consoles.
Surface is a display server + protocol for agent CLIs. It owns the surface — a locked prompt dock and a generative, composable, persistent canvas — and borrows the fabric (agents, memory, orchestration) from whatever agent runtime is plugged in via an adapter. Think LSP for agent consoles: Surface is the editor, each agent CLI is a language server, and capability negotiation lights up features.
It is the productized, runtime-agnostic generalization of the body prototype:
body was Claude-Code-only and immediate-mode (render(html) repainted every turn);
Surface is runtime-agnostic and retained-mode (the agent patches a persistent canvas).
What it is
- A kernel: a locked dock (the one trusted input) + a retained-mode canvas runtime + a component registry + a data broker + an identity/permission boundary + the protocol.
- Runtime-agnostic. Any agent CLI drives it through an
AgentAdapter. The kernel never reimplements agents. - Embeddable.
npm i @selfworking-ai/surface, callcreateSurface(...), get a live console in ~10 lines. - Zero-build. Vanilla HTML/CSS/JS, Node ESM, a single runtime dependency (
ws), tests onnode:test. Heavy add-ons are isolated build islands that commit a prebuilt artifact; the core never needs a bundler.
What it is not
- Not an agent platform. Surface writes no agent logic, orchestration, memory, or spawning — those are borrowed from the runtime.
- Not a chat transcript. The canvas is retained-mode and composable, not a scroll log.
- Not an API-key app. Auth flows through the user's local agent-CLI credentials. No Anthropic API keys.
- Not public-by-default. Loopback bind (
127.0.0.1) + WS origin allowlist, always.
60-second quickstart
Embed Surface in a host project:
import { createSurface } from "@selfworking-ai/surface";
import { echoAdapter } from "@selfworking-ai/surface/adapters/echo.mjs";
const surface = createSurface({ adapter: echoAdapter(), port: 5757 });
const { url } = await surface.listen();
console.log(`Surface live at ${url}`);Or boot one straight from the CLI (uses the echo adapter, or ./surface.config.js if
present):
npx surface devOpen the printed URL, type into the dock, and watch the echo adapter paint retained-mode
tiles — mount on the first turn, update-in-place after, so you can see the reconciler
patch rather than repaint.
Install
npm i @selfworking-ai/surfaceUpdate across projects with npm update — the wire protocol is versioned and negotiated
separately, so a host and a runtime on different protocol versions degrade gracefully
instead of corrupting state.
| Env var | Purpose |
|--------------------------|-------------------------------------------------------------|
| SURFACE_PORT / PORT | HTTP/WS port (default 5757). Browser opens localhost:PORT. |
| OPEN_BROWSER | 0 to skip auto-opening the browser on boot. |
| SURFACE_DIR | File-store directory (default ./.surface). |
Capability tiers (graceful degradation)
Surface speaks a thin core to every runtime, then lights up extra affordances by what the adapter advertises (the LSP model). An adapter declares its org tier:
- Tier A — native fabric. Durable agents + registry + messaging + spawn → full org mode.
- Tier B — session orchestration. Subagents within a turn → live delegation tree; durable employees emulated via the runtime's resume model.
- Tier C — single agent. No fabric → single-agent console, org features stay dark.
The kernel renders whatever the connected runtime supports and quietly hides the rest.
Features
- Retained-mode canvas — the agent patches a persistent workspace with
mount/update/remove/layoutops; a tiny reconciler diffs by component id. - Versioned protocol — every wire message carries a protocol version; mismatches are negotiated, not crashed.
- Locked prompt dock — the kernel's syscall interface and the single trusted input channel; locked for UX and security.
- Decision cards — permissions and branches surface as glass
askcards in the page and block on the user's tap, so headless turns never silently hang. - Time-travel — every turn commits a self-contained frame; scrub past dashboards
read-only and
recallone verbatim. - Three modes —
operator(full),team(scoped + attributable),visitor(generation OFF, tools NONE, curated pack only). - File store by default — frames + workspace persist as JSONL on disk; swap the
StorageProviderfor Postgres/Redis behind a kernel port. - Glass design system — a visionOS glass token system (lime accent, warm/cool backdrop, blur 36px / saturate 1.8, concentric radii). Registration rejects non-token values.
Identity & auth status (be honest about this). The identity/permission boundary is the kernel contract — provider ports, the containment grant rule (
component.caps ⊆ mode.grantable ⊆ principal.ceiling), the audit anchor, a real Google OIDC config-gated builder, and a mock-tested identity plane all ship. The end-to-end auth flow is not yet operable: there is no HTTP/authroute and no connection↔principal binding, the WebAuthn verifier intentionally throws (signature crypto is a deferred vetted island), and the OIDC path has not been exercised against live Google. Today Surface runs single-operator on loopback; multi-tenant authentication is architecture, not a working login.
Milestone status
Surface ships in independently shippable milestones. The full M0–M7 plan has shipped (v0.3.0); the current release is v0.3.1, an audit-fix release.
| Milestone | Scope | Status | |---|---|---| | M0 | Repo & rails: license, package, CI, docs skeleton, test harness | ✅ shipped (v0.1) | | M1 | Kernel core — retained-mode canvas + reconciler, versioned protocol, origin-allowlisted WS hub, file store, locked dock, echo adapter | ✅ shipped (v0.1) | | M2 | Adapter SDK + Claude Code adapter + capability negotiation | ✅ shipped (v0.2) | | M3 | Design system + components + data broker | ✅ shipped (v0.2) | | M4 | Provider plane + multi-tenant identity | ✅ shipped (v0.2) | | M5 | Packs (the app system) | ✅ shipped (v0.2) | | M6 | Org projection + a second runtime (prove agnosticism) | ✅ shipped (v0.3) | | M7 | Component-smith + gardener (signal-gated self-improvement) | ✅ shipped (v0.3) |
The M1 kernel core — a retained-mode canvas + reconciler, the Surface Protocol (versioned
envelope + negotiation), an origin-allowlisted 127.0.0.1 WS hub, a file-backed
StorageProvider, a locked dock with time-travel, and a hardcoded echo adapter — is enough
on its own for another project to npm i, mount, and get a live console; M2–M7 layer the
real runtime adapter, design system, provider plane, packs, org mode, and self-improvement on
top (see the CHANGELOG and the auth-status note above for what's operable vs.
scaffolding).
Documentation
- Getting started — install, embed, run, persist, update.
- Architecture — kernel vs userspace, the protocol, capability tiers.
- Protocol spec — the Surface Protocol reference.
- Security model — trust boundaries, modes, the locked dock.
- ADRs: adapter run-iterator · retained-mode canvas.
A zero-dependency node scripts/build-docs.mjs mirrors docs/**/*.md to styled HTML (an
isolated island; the core stays no-build).
Contributing
See CONTRIBUTING.md. The philosophy is zero-build, single-dep, and token-only; the gotchas in CLAUDE.md are tested invariants, not tribal lore.
Security
Loopback bind, origin allowlist, a locked dock as the single trusted input, and no model API keys. Report vulnerabilities per SECURITY.md.
License
MIT.
