Crux Daemon · Developer guide
How to build on the Crux Daemon: extend it with packs, tools and sandboxed modules; connect it to external systems; ship what you build to other operators.
Every claim on these pages carries a file:line reference into this repository. Where a surface is declared but has no runtime, or is stubbed pending later work, it says so. Nothing here is aspirational.
In plain English
Extending the daemon means teaching it to do something it did not ship knowing how to do: talk to a system we have never heard of, run a piece of logic you wrote, react to a file changing on your disk. This guide covers the four mechanisms for that, from a purely declarative recipe the daemon only stores and hands to a client, through to code that runs sandboxed inside the daemon process itself.
The four differ mainly in where your code runs and therefore in how much trust the operator has to extend you. A declarative pack executes nothing and needs almost no trust. An external tool runs on your own infrastructure and the daemon merely calls it. A WASM extension runs inside the daemon process, which is the most powerful option and the one with the most machinery attached to it. Choosing between them is mostly a question of how much you need to run and where you would rather it ran.
Three rules run through all four, and they are the thing to internalise before writing anything. Installing something never enables it: a second, explicit operator action has to name what the code may do, and a pack that is installed but never granted does nothing at all, silently and deliberately. Artefacts are pinned by hash rather than fetched as "latest", so what runs tomorrow is what was reviewed today. And the capability surface is declared by the manifest, narrowed by the grant, and enforced as the intersection of the two on every call.
You will start here to pick a mechanism, then leave for the chapter that covers it. The three quickstarts below are the fastest way to see one work end to end; take one of those before reading the reference chapters, because the reference makes much more sense once you have watched the grant step refuse to let something run.
The thing people get wrong is assuming installation is the finish line. It is the halfway point. The single most common support question on this surface is why an installed pack is doing nothing, and the answer is almost always that it was never granted. That is the system working as designed, not failing.
Who this is for
| You are | Start at |
|---|---|
| An agent or SDK author calling the daemon | Quickstart A, then chapter 1 and chapter 10 |
| Shipping a dashboard or board other people install | Quickstart B, then chapter 8 and chapter 9 |
| Adding a tool the daemon can call | Quickstart C, then chapter 5 |
| Wanting in-process, sandboxed code | chapter 6 |
| Connecting a data source on a schedule | chapter 7 |
| An operator deciding what to allow | chapter 3, chapter 4, chapter 11 |
| Debugging | chapter 12 |
Reading map
| # | Chapter | Covers |
|---|---|---|
| 1 | Architecture | Planes, ports, data-dir layout, auth modes, scopes, error shape |
| 2 | Integration packs | The crux.integration.v1 manifest, every field, all nine entry kinds |
| 3 | Capabilities and grants | The 14 capabilities, the two grant models, operator posture |
| 4 | Signing and trust | Ed25519, the keyring, trust tiers, the community index, dev bypasses |
| 5 | External tools | HTTPS extensions: wire contract and the full enforcement pipeline |
| 6 | WASM extensions | The sandbox, the host ABI, hash pinning, resource limits |
| 7 | Connectors and sync | The scheduler, status facts, the vault watcher, GitHub and OpenAI |
| 8 | Studio packs and workspaces | crux.studio.v1, canonical JSON, build/verify, fact schemas, the crux.studio.index.v1 template library |
| 9 | Registry and publishing | Both rails end to end, author, sign, curate, sync, install, plus the CLI reference |
| 10 | MCP surface | How extension tools reach an agent's tool list |
| 11 | Security model | Enforced versus advisory versus not-yet-wired |
| 12 | Troubleshooting | Real error strings, and what each one means |
Three principles the whole surface follows
- Install is not enable. Every path needs a second, explicit operator action naming exactly what the code may do. A pack installed and never granted does nothing at all, silently and by design.
- Artefacts are pinned and content-addressed. Nothing is fetched "latest". The daemon verifies a hash your trust chain endorsed before any byte is persisted, and re-verifies a WASM module on every dispatch.
- Capability surface is declared, then enforced. The manifest declares what it wants, the grant decides what it gets, and the dispatcher enforces the intersection on every call.
Before you start
export CRUX_AGENT_TOKEN="…" # your token, or a scope list in dev mode
export CORECRUXD_HTTP_URL="http://127.0.0.1:14800"
curl -s http://127.0.0.1:14800/readyz # public, no auth
CORECRUXD_AUTH_MODE has no default, the daemon will not start without it (main.rs:307). For local development, dev_scopes lets a bearer token be the scope list (auth.rs:385):
export CORECRUXD_AUTH_MODE=dev_scopes
export CRUX_AGENT_TOKEN="admin:read admin:write facts:read facts:write query:read"
Quickstart A: call the daemon from your agent (10 minutes)
Goal: read and write memory, and see the MCP tool catalogue.
# 1. Write a fact.
curl -s -X PUT http://127.0.0.1:14800/v1/facts \
-H "Authorization: Bearer $CRUX_AGENT_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"entity":"demo::hello","key":"greeting","value":"world"}'
# 2. Read it back by entity.
curl -s "http://127.0.0.1:14800/v1/facts/entity/demo::hello" \
-H "Authorization: Bearer $CRUX_AGENT_TOKEN"
# 3. List a prefix.
curl -s "http://127.0.0.1:14800/v1/facts/list?entity_prefix=demo::&limit=20" \
-H "Authorization: Bearer $CRUX_AGENT_TOKEN"
Routes: http/mod.rs:685, :717, :719.
For MCP, point your client at the MCP listener, 127.0.0.1:14801, path /mcp (config.rs:817, console.rs:2327). Your tools/list will show the built-in tools plus any extension tools your passport has been granted (chapter 10).
Next: chapter 1 for the auth model and error shape.
Quickstart B: ship your first pack (10 minutes)
Goal: turn a Studio board into a portable, verifiable artefact.
# 1. Build a pack around a studio payload.
curl -s -X POST http://127.0.0.1:14800/v1/studio/pack/build \
-H "Authorization: Bearer $CRUX_AGENT_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"studio": {"schema":"crux.studio.v1","version":1,
"board":{"id":"my-board","doc":{"nodes":[],"links":[],"version":1}},
"designs":[],"settings":{"title":"My board"}},
"id": "my-board",
"name": "My Board",
"version": "0.1.0",
"publisher_passport_fpr": "p_6ca61039700c94bf57c2e158d282ef1e"
}' > pack.json
# 2. Verify it, schema, both hashes, signature verdict.
jq '{pack: .pack}' pack.json > verify-body.json
curl -s -X POST http://127.0.0.1:14800/v1/studio/pack/verify \
-H "Authorization: Bearer $CRUX_AGENT_TOKEN" \
-H 'Content-Type: application/json' --data @verify-body.json | jq '{ok, signature, studio}'
Both routes need only query:read and neither writes anything (studio_pack.rs:101, studio_pack.rs:434).
Unsigned is the expected result on a bare daemon, and the response's sign_instructions tells you the two ways forward: set CORECRUXD_STUDIO_SIGNING_KEY_HEX locally, or publish via a PR to integrations/community/, the canonical rail, which never needs a private key on a daemon.
A complete worked example is in-tree at integrations/community/studio-board-example/0.1.0/.
To go from one pack to a catalogue other operators install by id, publish it in a curator-signed crux.studio.index.v1 index and let them run corecruxctl studio sync then corecruxctl studio install <id>. The daemon pins your pack_sha256, requires the pack to be signed, and writes the board without ever overwriting one of theirs (chapter 8 §8.7).
Next: chapter 8, then chapter 9.
Quickstart C: wire an external tool (10 minutes)
Goal: have the daemon call your HTTPS service as an MCP tool.
# 1. Write the manifest.
cat > manifest.json <<'JSON'
{
"schema": "crux.integration.v1",
"id": "ext.demo.echo",
"name": "Echo",
"version": "0.1.0",
"publisher_passport_fpr": "p_dev_local",
"summary": "Echoes its arguments back.",
"entry": { "kind": "external_tool", "path": "tools/echo.json" },
"capabilities": ["facts:write"],
"network": { "allowed_hosts": ["127.0.0.1:8099"] },
"external_tool_endpoint": "http://127.0.0.1:8099/invoke",
"tools": [
{ "name": "ext.demo.echo.say",
"description": "Echo the args.",
"input_schema": { "type": "object" } }
]
}
JSON
# 2. Install it. Dev only: unsigned + plain HTTP both need explicit flags
# in the DAEMON's environment, not in this shell.
# CORECRUXD_EXTENSIONS_ALLOW_UNSIGNED=true
# CORECRUXD_EXTENSIONS_ALLOW_PLAIN_HTTP=true
curl -s -X POST http://127.0.0.1:14800/v1/extensions/register \
-H "Authorization: Bearer $CRUX_AGENT_TOKEN" \
-H "X-Corecrux-Passport-Id: p_operator" \
-H 'Content-Type: application/json' \
-d "{\"manifest\": $(cat manifest.json)}"
# 3. Grant a passport. Nothing is callable until this exists.
curl -s -X POST http://127.0.0.1:14800/v1/extensions/ext.demo.echo/grants \
-H "Authorization: Bearer $CRUX_AGENT_TOKEN" \
-H "X-Corecrux-Passport-Id: p_operator" \
-H 'Content-Type: application/json' \
-d '{"passport_fpr":"p_alice",
"allowed_tool_names":["ext.demo.echo.say"],
"allowed_prefixes_write":["demo::echo::"],
"rate_limit_per_min":30}'
# 4. Call it.
curl -s -X POST \
http://127.0.0.1:14800/v1/extensions/ext.demo.echo/tools/ext.demo.echo.say/invoke \
-H "Authorization: Bearer $CRUX_AGENT_TOKEN" \
-H "X-Corecrux-Passport-Id: p_alice" \
-H 'Content-Type: application/json' \
-d '{"args":{"hello":"world"}}'
Your service receives {tool, args, calling_passport_id, request_id} and must return {result, fact_writes?}. Writes outside allowed_prefixes_write are dropped and reported in drop_reasons, not an error, just a count.
Two things that trip people up on the first attempt: the tool name must start with ext. or MCP dispatch will never route to it (crux-mcp/src/tools/extensions.rs:179), and both dev flags belong in the daemon's environment, not your shell.
Next: chapter 5 for the full pipeline, then chapter 4 to sign it properly.
Related documents
- getting started, install paths and the first loop
- developer portal, base URLs, OpenAPI, SDKs
- API reference, route notes
- agent guide, budgets, sessions, handoffs
- architecture, daemon planes and crate boundaries
- threat model, trust boundaries and stated limitations
- error catalogue, daemon-wide error reference
- console workspaces, workspace and page fact schemas
- integrations/README.md, community publishing rules
Ground truth
- crates/corecruxd/src/config.rs:793, ports, data dir
- crates/corecruxd/src/auth.rs:385, dev-scope extraction
- crates/corecruxd/src/http/mod.rs:685, facts routes
- crates/corecruxd/src/http/mod.rs:667, studio routes
- crates/corecruxd/src/http/mod.rs:1061, extension routes
- crates/corecruxd/src/http/studio_pack.rs:96, pack build
- crates/corecruxd/src/http/studio_library.rs:116, template library browse
- crates/corecruxd/src/http/extensions.rs:121, extension register

