How does Daemion work?
Daemion is a local-first agent system: a persistent gateway running on your machine, a 6-substrate kernel that handles everything from storage to streaming, and a universal extension model where every capability is data — not code.
System overview
Phone / Browser (PWA)
│
│ HTTPS via Tailscale (or localhost)
▼
┌──────────────────────────────────────────┐
│ Gateway :3001 (default) │
│ │
│ HTTP/WebSocket API │
│ │
│ ┌────────────────────────────────────┐ │
│ │ 6 Kernel Substrates │ │
│ │ Extension │ Context │ Execution │ │
│ │ Trigger │ Storage │ Presentation│ │
│ └────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────┐ │
│ │ Local Storage │ │
│ │ SQLite · Wiki · Filesystem │ │
│ └────────────────────────────────────┘ │
└──────────────────────────────────────────┘
│
│ Agent SDK (not child_process)
▼
Runtime model path
The frontend is a static PWA served from Vercel — just a window into your local system. All messages, threads, extensions, and config live in SQLite on your machine. The gateway serves everything; the frontend never talks to an external database.
What is the gateway?
The gateway is a local HTTP/WebSocket server (port 3001 by default). It is the single entry point for all client communication — every message, thread list, extension CRUD, job run, and streaming response goes through it.
Core API surface:
| Method | Path | What it does |
|---|---|---|
GET | /health | Health check, no auth required |
POST | /chat | Send a turn, get a streaming response |
GET | /threads | List conversation threads |
GET | /threads/:id/turns | Turns for a thread |
POST | /threads | Create a thread |
POST | /run/:job | Execute a job by name |
GET | /extensions | List all extensions |
POST | /extensions | Create or update an extension |
DELETE | /extensions/:id | Remove an extension |
POST | /reseed | Re-sync built-in extensions from disk (no restart needed) |
WS | /stream | Streaming turns and tool-call events |
The API data model uses “turns” throughout — not “messages.” A turn is one exchange unit in a thread.
The gateway is local-first. Remote access goes through Tailscale when needed, which preserves the “lives on your machine” model instead of turning Daemion into a hosted service. Bearer token auth is required for all endpoints except /health.
What are the 6 kernel substrates?
The substrates are the OS primitives. Everything else — jobs, agents, commands, themes — plugs into them as extensions.
1. Extension Substrate
The meta-substrate. Registers, validates, loads, and manages all 12 extension types. Extensions are stored as JSON/YAML in SQLite — not compiled code. The agent can create extensions at runtime through chat.
See Extending Daemion for the full extension model.
2. Context Substrate
How the agent knows things. Inspired by Recursive Language Models — externalizes context rather than bulk-loading everything into the prompt.
Per request, the Context Substrate:
- Loads the last 10–15 turns from SQLite (always present, full text)
- Searches the knowledge substrate in parallel (compiled wiki, raw sources, and history surfaces)
- Provides the agent with history and knowledge tools such as
search_history,get_thread,list_threads,find_relevant,search_all,knowledge_search, andknowledge_read
Older turns are queryable on demand — the agent retrieves them when it detects it needs them. This keeps prompts lean while preserving access to full history and durable knowledge.
3. Execution Substrate
How the agent does work. Manages model selection, tool access, budgets, turn limits, streaming, cancellation, and concurrency.
| Request type | Model | Max turns | Budget |
|---|---|---|---|
| Chat (quick) | sonnet | 10 | $0.50 |
| Chat (complex) | sonnet/opus | 25 | $5.00 |
| Job execution | configurable | 30 | $5.00 |
| Build task | sonnet | 50 | $10.00 |
All Claude invocations use the Agent SDK — never child_process (known hang bug #771). The SDK provides streaming, tool access, and session management.
4. Presentation Substrate
How output appears in the UI. Renders all content types, streams tokens, shows tool calls as collapsible step indicators, and handles interactive elements (approve/deny buttons, forms).
Content pipeline: Agent output → type detection → renderer selection → display
Custom renderers are extensions of type renderer — a proposal-card renderer, a diff renderer, etc.
5. Trigger Substrate
What causes things to happen. Evaluates conditions and fires the appropriate response.
| Type | Fires when |
|---|---|
message | User sends a turn |
command | User types /command |
cron | Time-based schedule |
watch | File changes on disk |
webhook | HTTP request received |
event | Internal event fires |
chain | Another extension completes |
6. Storage Substrate
All data persists locally. No cloud database.
| Backend | Stores |
|---|---|
| SQLite | Turns, threads, extensions, config, metrics, costs |
| Knowledge wiki + raw files | Compiled durable knowledge, raw captured sources, agent-specific wiki spaces |
| Filesystem | Project files, images, attachments, job outputs |
What is the extension model?
Everything that isn’t the kernel is an extension. There are 12 types:
| Type | What it is |
|---|---|
command | Input handler (/, @, !, #) |
theme | Visual identity (colors, fonts) |
job | Autonomous work unit |
renderer | Custom content display component |
integration | External service connection (GitHub, Slack, Vercel) |
action | Per-turn contextual action (copy, edit, regenerate) |
widget | Dashboard UI component |
app | Embedded Vite application |
artifact | Agent-created output (code file, document) |
capability | Agent skill or behavior |
control | System configuration (budget limits, model defaults) |
agent | Persistent agent identity |
Extensions are data stored in SQLite — they don’t require compilation or deployment. The primary way to create one is by asking Daemion in chat. Agent-created extensions start disabled and require your approval to activate.
POST /reseed re-syncs built-in extensions from disk without restarting the gateway process.
See Extending Daemion for full schema, lifecycle, and examples.
How does a message flow end to end?
1. You type a message in the PWA
2. Frontend sends POST /chat {"thread_id": "thr_01abc123", "content": "..."}
via Tailscale (or localhost) to the gateway
3. Gateway receives the request and authenticates the bearer token
4. Context Substrate assembles knowledge:
a. Load last 10–15 turns from SQLite (always present)
b. Search the knowledge substrate for relevant compiled or raw context (parallel)
c. Check for extension-provided context (integrations, workspace state)
5. Execution Substrate invokes the agent (Agent SDK):
a. Select model from request metadata or thread default
b. Apply budget and turn limits based on detected complexity
c. Stream tool calls + text tokens back via WebSocket /stream
6. Presentation Substrate formats output:
a. Tool calls appear as step indicators ("Reading file...")
b. Text streams token-by-token
c. Code blocks get syntax highlighting on completion
d. Final turn stored to SQLite
7. Frontend displays the streamed response
For jobs (autonomous, no user turn):
1. Trigger fires (cron schedule, file watch, event chain)
2. Engine loads the job definition (extension of type "job")
3. Context Substrate assembles job-specific context
4. Execution Substrate invokes the agent with the job prompt
5. Output routed: file write, proposal, notification, or other configured destination
6. If job has chains → trigger the next job
What events does the WebSocket send?
The WebSocket at WS /stream sends 12 event types. All events are JSON with a type field:
{ “type”: “connected”, “threadId”: “thr_01abc123” } { “type”: “start”, “messageId”: “trn_07xyz456”, “model”: “claude-sonnet-4-5” } { “type”: “text-delta”, “messageId”: “trn_07xyz456”, “delta”: “Hello” } { “type”: “tool-start”, “messageId”: “trn_07xyz456”, “tool”: “Read”, “input”: ”…” } { “type”: “tool-end”, “messageId”: “trn_07xyz456”, “tool”: “Read”, “output”: ”…” } { “type”: “finish”, “messageId”: “trn_07xyz456”, “costUsd”: 0.003, “durationMs”: 1240 } { “type”: “error”, “messageId”: “trn_07xyz456”, “error”: “budget exceeded” } { “type”: “stopped”, “messageId”: “trn_07xyz456” } { “type”: “warning”, “text”: ”…” } { “type”: “extension-changed”, “extensionId”: “ext_09def789” } { “type”: “thread-updated”, “threadId”: “thr_01abc123” }
The frontend reconstructs the full turn from streamed events. Tool calls render as collapsible step indicators in the Presentation Substrate.
What are the storage backends?
SQLite is the primary store — turns, threads, extensions, config, and cost metrics all live there. Path defaults to ~/.daemion/daemion.db, overridable via DAEMION_DB_PATH.
Knowledge in the current product is provided through the compiled wiki, raw source capture, and searchable history surfaces. That is the memory model users should expect today.
Filesystem access is provided via GET /filesystem/ls and GET /filesystem/search. Both take a path param that defaults to os.homedir() — the agent can browse your home directory. Scope this appropriately for your threat model.
Common questions
daemion start runs the gateway only. The background service (launchd/systemd) runs the full daemon.HEARTBEAT.md, checks ambient state, and replies HEARTBEAT_OK if nothing needs attention, or sends a notification if something does. It's how Daemion maintains ambient awareness without polling.What can go wrong
Gateway unreachable from phone — Tailscale must be connected on both devices. The gateway binds to 127.0.0.1 only; Tailscale routes correctly when both devices are on the same Tailscale network. Check tailscale status on both.
401 {"error": "unauthorized"} — The bearer token is missing or expired. Re-pair the device: run daemion start, scan the QR code, and let the new token replace the old one in localStorage.
Filesystem endpoint returns homedir contents unexpectedly — GET /filesystem/ls defaults to os.homedir() when no path param is provided. Always pass an explicit path if you’re using this endpoint programmatically.
What’s next?
- Full Setup Guide — Tailscale, background service, and environment setup
- The Kernel — deep dive into each substrate
- Extending Daemion — create jobs, agents, and commands as extensions