One thread per owner
One conversation shared by web, Telegram and WhatsApp. Its ID keys the workspace and chat session. Background sessions have their own browsers and saved Pi transcripts.
This page shows how nod's parts fit together and where each one lives in the code. Start here before you change nod or evaluate how it works.
The short version: nod is one Node process plus a few Docker containers on your computer.
The main chat session coordinates work. It answers short requests and starts a saved session for each independent task. Task questions, progress and results return to the same conversation. Task results do not need a second model call in the coordinator.
The tasks table is the task ledger. All sessions share the owner's workspace and memory, with separate saved histories and task folders. The server routes replies and delivers addressed owner updates to the correct session.
See Task sessions for the architecture diagram, records, context sharing and lifecycle.
One Node process serves the React client and runs the Fastify API, the agent runtime, the thread-drain worker and the schedulers. Docker containers run PostgreSQL, the workspace and the browser.
flowchart TD web([Web client]):::accent --> api[API and server features] channels([Telegram · WhatsApp]) --> api api --> db[(PostgreSQL<br/>and pg-boss jobs)] db --> worker[Thread-drain worker] worker --> runtime[Agent runtime] runtime --> pi[Pi session]:::accent runtime --> browser[Browser container]:::muted runtime --> workspace[Workspace container]:::muted
Every message, schedule fire and event becomes a row in PostgreSQL plus a thread-drain job. The worker picks up the job and asks the runtime to run a turn. The runtime opens a Pi agent session, and Pi calls tools that act in the workspace, the browser and your connected apps.
One thread per owner
One conversation shared by web, Telegram and WhatsApp. Its ID keys the workspace and chat session. Background sessions have their own browsers and saved Pi transcripts.
Everything is a turn
Messages, schedule fires, event wakes and resumes each run as a turn. Turns are never retried.
Policy lives on the server
The server checks app actions, Vault use and payments. The model's instructions sit on top of those checks, not in place of them.
A sandboxed computer
Commands run in a locked-down container. Provider keys never enter it or the browser.
scripts/check-architecture.mjs runs as part of pnpm run check. It fails when an import crosses a boundary:
| Folder | Can import from |
|---|---|
web |
web, shared |
shared |
shared |
ports |
ports, shared |
runtime |
runtime, ports, shared |
providers |
providers, ports, shared |
server |
server, ports, shared. Only composition.ts also imports runtime and providers. |
The same check keeps vendor packages (Pi, Composio, chat adapters and agent-browser) inside providers. It also stops runtime, ports, shared and web from reading process.env. Configuration belongs in src/server/composition.ts.