architecture
Node (Local Daemon)
What runs on your local PC: the shared TS adapter, the local Agent Service, and the sync engine.
What Node is
Node is the local daemon. It runs as a long-lived process on a machine you control. Internally it has three responsibilities:
- State. It owns the current state of every active Thread, the cached project metadata, and a queue of pending intents.
- Agent execution — behind the Agent Service. Node is two layers: a shared TS adapter that owns the outbound relay WebSocket + wire protocol, and a local Agent Service it calls over
localhostHTTP/SSE. The adapter no longer embeds an agent; the Agent Service runs it. The Agent Service is polyglot — the TypeScript reference implementation ships first, but any language can register a node by honoring the HTTP contract. - Sync. It talks directly to Git (or Perforce, depending on the project) to commit, push, pull, and resolve.
Why state lives here
Two reasons.
First, this is where the truth is. The files on disk are here. The agent’s working memory is here. Putting state anywhere else creates a synchronization problem we don’t need.
Second, this is where the work is. Compute, tooling, dependencies — they all live on this machine. Putting state somewhere else and compute here creates network round-trips for every state read.
Why a service boundary, not an embed
Earlier drafts embedded a single Claude Code instance directly in the daemon. Splitting agent execution behind a localhost HTTP/SSE contract buys two things. First, polyglot: the runtime is no longer hard-wired — Claude, Codex, or any process that speaks the contract can be the agent, and the user switches per turn like a model selector. Second, clean layering: the adapter owns the network edge (one outbound WebSocket to the relay) and the Agent Service owns execution, so neither has to know the other’s internals. We compose on top of the vendor agent SDKs inside the Agent Service, where swapping them out is a local concern.
Node’s outbound API
// illustrative — not the actual interface
class Node {
// intent comes in
async receive(intent: Intent): Promise<void>
// state changes broadcast out
on(event: 'state', cb: (s: State) => void): void
on(event: 'token', cb: (t: TokenStream) => void): void
}
State changes go out as broadcasts to the relay; the relay fans out to subscribed clients. The wire format follows the Soulstream-compatible event vocabulary (thread_start, user_prompt_submit, pre_tool_use, post_tool_use, text_delta, thread_end, recap) — see tech_architecture.md §3 for the schema.
Recap on Thread end
When a Thread ends (Node observes the CC Stop hook and emits thread_end), Node generates a Recap — a short text summary of what happened — by calling a Manifoldone-managed LLM gateway. Only the summary text leaves the machine; raw tool inputs and outputs do not. The token cost is absorbed by Manifoldone (it’s not billed against the user’s own Claude account). The architecture rationale is in the tech reference.
What runs Node under the hood
In the early beta the adapter is a Node.js 20+ process (the projects/node package) and the reference Agent Service is @manifoldone/agent-service, bound to 127.0.0.1:8790. Both run as a headless background service (systemd/launchd) so a machine can be a node with no desktop app present. TypeScript throughout; a thin in-memory state store for the adapter.
Naming note
Node was previously called “Pylon” in earlier drafts (a name inherited from the Estelle prior art). We renamed it for clarity: Node is what most distributed-systems readers expect, and it removes the RTS-game associations that were getting in the way for first-time readers. The technical model is unchanged.
Concurrency on a single Node is bounded by the MANIFOLDONE_MAX_CONCURRENT_THREADS env var (default 3). Raise it on bigger hardware; Phase 2 introduces a separate cap policy for Project-owned Nodes.