Developer quickstart
Bring the full chain up locally from source — data source, query plane, gateway — and ask Claude Desktop a question.
View as .mdThis is the developer runbook — bringing the full chain up locally and asking Claude Desktop a question against real data. For the operator-grade walkthrough (Docker only, no Rust toolchain), see the operator quickstart.
This runbook drives one reference implementation — illustrative, not definitive. The canonical product is the role specification (read-only, one-way, outside copy); see the system architecture. The commands below are real for this reference build.
Prerequisites
- macOS or Linux dev host
- Docker + Docker Compose (for the data source)
- Rust toolchain (
rustup default stable, recent stable) mkcert(brew install mkcerton macOS) — only needed if you’re testing mTLS- Optional: Claude Desktop or another MCP-aware client
The chain
Claude Desktop ──MCP──▶ gateway ──HTTP/mTLS──▶ query plane ──▶ local data source
(off-box) (:8091) (the seam) (:8090, i3X) (Docker, :5001)
The gateway is the read-only front door. The query plane re-exposes the data source under a small, opinionated read API. Nothing in this chain has a write path to a device.
Step 1 — Bring up the data source
The data source runs as a Docker Compose stack (app + workers + Postgres + Redis). The one-time bootstrap handles secrets, schema migrations, the default org, and the API key the query plane uses:
make first-run
make first-run is idempotent. It populates services/witness/.env with dev secrets, brings up db/redis, runs alembic upgrade head, starts the app, provisions an org + API key, and writes WITNESS_API_KEY to ./.env. Confirm health:
make ps
curl -k http://127.0.0.1:5001/
Load sample data either by uploading a preset capture from services/witness/presets/ in the console at http://127.0.0.1:5001, or via the live-capture path if you’ve given the container an interface.
Step 2 — Bring up the query plane
In a new terminal at the repo root. make run-query-plane honors any env you’ve exported:
WITNESS_URL=http://127.0.0.1:5001 \
WITNESS_API_KEY="$(grep ^WITNESS_API_KEY= .env | cut -d= -f2-)" \
make run-query-plane
Verify connectivity:
curl -s http://127.0.0.1:8090/v1/info | jq
# expect capabilities.query.history = true (data source wired)
Step 3 — Bring up the gateway
QUERY_PLANE_URL=http://127.0.0.1:8090 \
AUDIT_LOG_PATH=/tmp/cf-audit.jsonl \
make run-gateway
Smoke check:
curl -s http://127.0.0.1:8091/health
curl -s http://127.0.0.1:8091/tools | jq '.tools | length'
Step 4 — Make a real query (HTTP path)
curl -s http://127.0.0.1:8090/v1/objects | jq '.result[].elementId' | head -10
ELEMENT_ID="<paste-an-elementId-from-above>"
curl -s -X POST http://127.0.0.1:8091/query \
-H 'content-type: application/json' \
-d "$(jq -nc --arg eid "$ELEMENT_ID" '{
request_id: "00000000-0000-0000-0000-000000000001",
intent: "get-current-state",
elementId: $eid,
maxDepth: 2
}')" | jq
Inspect the audit chain:
tail -1 /tmp/cf-audit.jsonl | jq
Step 5 — Connect Claude Desktop (optional, MCP path)
Run the gateway as a stdio subprocess instead of HTTP:
cargo build --release -p conversational-gateway
Edit ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"factory": {
"command": "/path/to/conversational-factory/target/release/conversational-gateway",
"args": ["--stdio"],
"env": {
"QUERY_PLANE_URL": "http://127.0.0.1:8090",
"AUDIT_LOG_PATH": "/tmp/cf-audit.jsonl"
}
}
}
}
(make run-gateway-stdio runs the same thing from source for a quick check.) Restart Claude Desktop. Ask: “Use the factory MCP server. What objects exist on Cell 5? Get the current state of one of them.”
Step 6 — Verify the audit chain
cat /tmp/cf-audit.jsonl | jq -s 'group_by(.tool) | map({tool: .[0].tool, count: length})'
The audit log carries: the natural-language question, the exact tool dispatched, the parameters, the downstream call(s), and the status returned to the AI. Every claim traces back to a read — there is no write path to trace.
Dev mode vs production
| Concern | Dev (this guide) | Production |
|---|---|---|
| Query plane transport | Plain HTTP loopback | mTLS with operator-issued certs |
| Gateway location | Same host as query plane | Off-box on operator workstation / adjunct host |
| Data source API key | Read from .env | Provisioned via signed-config artifact |
| Audit log path | /tmp/cf-audit.jsonl | /var/log/cf/gateway-audit.jsonl with rotation |
| Sample data | Loaded from presets | Real plant traffic |
Troubleshooting
Run make smoke-run to probe the full chain end-to-end. Common surprises:
make upexits withappcrashlooping — schema not migrated.make first-runrunsalembic upgrade head; re-run it.- Compose complains about missing required env — the five secrets in
services/witness/.env(DB_PASSWORD,SECRET_KEY,ADMIN_PASSWORD,ARTIFACT_ENCRYPTION_KEY,SMTP_FROM) are NOT NULL;make first-runwrites dev defaults. - Query plane reports
query.history: false—WITNESS_URLorWITNESS_API_KEYis wrong; the startup ping failed. get_current_statereturnsGoodNoDatafor everything — no data loaded yet; upload a preset capture in the console.tools/callreturnsaudit-failure— theAUDIT_LOG_PATHdirectory isn’t writable by the gateway process.- Gateway can’t reach query plane — port mismatch (default 8090) or one process bound to localhost only.
- Data source rejects the bearer token — key was regenerated but the env var still has the old value; re-source
.env.