System Architecture (Canonical Topology)
This skill is the single source of truth for joelclaw system wiring.
Freshness notes:
2026-08-23 recall cutover: memory_observations and the system-log JSONL/slog path are retired. Production joelclaw recall composes lane-separated flowing reflections, flowing observations, and curated Brain pages. Every request has explicit scope and access. Chorus/Rhizomatic is parked with no briefing injection or live claims; service stop still needs steering sudo. Claude auto-memory is a pointer index, not a content store.
2026-07-10 topology: the old Panda-centric mental model is migration debt. Current responsibility lanes:
Central: Flagg/Mac Studio. It is authoritative for agent-mail and Run capture ingress; verify each remaining service family during migration.
Relay: Panda account-bound leftovers and explicit decommission blockers.
Satellite: Blaine/other capture clients, connectors, outboxes, and transcript backup freshness.
Treat older “Panda is the whole system” wording as stale unless re-verified against live receipts.
Use it for:
- "why did this run / not run"
- "which worker handles this function"
- "what is listening on port X"
- "how does event Y flow"
- "where does this Run/capture/memory record go"
- "is this Central, Relay, satellite, or shadow runtime work"
- full-stack routing/debug across CLI → Inngest → workers → gateway → telemetry
Ground-Truth Scope + Evidence Snapshot
This document is grounded in direct reads of:
apps/docs-api/src/index.ts
packages/restate/Dockerfile
packages/restate/src/index.ts
packages/restate/src/workflows/dag-orchestrator.ts
packages/agent-execution/src/microvm.ts
packages/system-bus/src/serve.ts
packages/system-bus/src/inngest/functions/index.host.ts
packages/system-bus/src/inngest/functions/index.cluster.ts
packages/system-bus/src/inngest/client.ts
infra/worker-supervisor/src/main.rs
~/Library/LaunchAgents/com.joel*.plist
k8s/* (all files)
infra/pds/values.yaml
packages/gateway/src/daemon.ts
packages/gateway/src/channels/*.ts
~/.joelclaw/gateway/AGENTS.md
~/.joelclaw/gateway/.pi/settings.json
~/.local/caddy/Caddyfile
~/.colima/default/colima.yaml + colima status --json
packages/cli/src/cli.ts, packages/cli/src/config.ts, packages/cli/src/inngest.ts
packages/system-bus/src/observability/* (key files: emit.ts, otel-event.ts, store.ts)
packages/telemetry/src/emitter.ts
packages/system-bus/src/lib/pi-output.ts
packages/inference-router/src/tracing.ts
CONTEXT.md
docs/gateway.md
docs/inngest-functions.md
docs/runbooks/satellite-rig-setup.md
docs/runbooks/flagg-gate5-staged-migration.md
infra/central/README.md
docs/prd-rhizomatic-network-canary.md
infra/central/launchd/*.plist.template
scripts/joelclaw-capture-session.ts
scripts/joelclaw-capture-codex-session.js
- ADRs in
~/Vault/docs/decisions/ (required + topology-adjacent)
- canonical OTel events plus durable Brain
.svx receipts (the former system-log JSONL is archived and retired)
Related docs verified
docs/architecture.md — Restate/Firecracker runtime + workload execution flow
docs/deploy.md — Restate worker deploy + auth/identity/PVC procedures
docs/cli.md — workload command tree + runtime bridge
docs/observability.md — not inspected in this update
Refresh receipt: 2026-06-15
This refresh folds in work from:
- Central vocabulary + Project Thread docs (
6b3a1b05, CONTEXT.md, docs/gateway.md)
- Flagg Central scaffold and Gate 5 migration runbooks (
6e02a6cd, d36b52f2, infra/central/*)
- worker-hosted Run capture (
f06501a8, docs/inngest-functions.md, packages/system-bus/src/serve.ts)
- Inngest SDK hardening + connect-mode recovery (
d7dd7788, 6c5d2a8e)
- Talon paging/debounce hardening (
c03edc5c, 1f086cfb, d26351cf)
- satellite rig setup for Blaine/Flagg (
bc5738f3, 9cc02f6e)
- historical Rhizomatic/Chorus network canary (
6bebf5b1, docs/prd-rhizomatic-network-canary.md), parked by the 2026-07-17 decision
0) Current Operator Map
The old mental model was "Panda is joelclaw." That is no longer precise enough.
Use these terms:
| Term |
Meaning |
Current truth |
| Network |
Users + Machines coordinated by one Central |
Logical boundary, not the tailnet/k8s cluster |
| Central |
single authoritative joelclaw service for the Network |
Flagg is authoritative for agent-mail and Run capture; verify remaining service families individually during migration |
| Central host target |
Machine consolidating Central responsibilities |
Flagg / Mac Studio, machine_id=mac-studio-central |
| Relay Machine |
machine that hosts account-bound/local-hardware-bound relays while delegating state to Central |
Panda becomes this after cutover; satellites stay thin |
| Satellite Machine |
thin local Pi/Codex/Claude runner with capture/search/repair hooks |
Blaine and Flagg bootstrap through scripts/setup-satellite-rig.sh |
| Run |
one captured agent invocation |
raw JSONL + metadata first, SQLite FTS is the live search index |
| Conversation |
sibling Run label for an interactive context |
not the source of truth |
| Project Thread |
private #brain-joel operator workroom for a bounded objective |
coordination only; does not authorize public replies |
Current authority split (verified 2026-07-10):
- Flagg is authoritative for agent-mail and Run capture ingress. The agent-mail daemon binds Flagg loopback; Blaine and Panda use SSH connector LaunchAgents so every
joelclaw mail client reaches the same mailbox without exposing the service on the tailnet.
- Panda is migration debt plus Relay responsibilities. Its independent agent-mail daemon and Talon are removed. A reboot-survivable SSH connector now binds Panda IPv4 loopback
127.0.0.1:3111 and forwards legacy /api/runs and /webhooks ingress to Flagg. The legacy system worker still owns the IPv6 listener until its system LaunchDaemon is booted out with sudo.
- Satellites stay thin. They run Pi/Codex/Claude, local capture hooks, and connectors to Central. Do not install independent stateful Central services on a satellite without a specific reason.
- SQLite indexes Runs. NAS/local Run blobs are the source of truth;
sessions.db is the compact live FTS index. The retired Typesense runs_dev and run_chunks_dev collections must not be recreated.
- Flagg splits book search from the operational Typesense node. The system LaunchDaemon on
127.0.0.1:8108 holds operational projections. The user LaunchAgent com.joelclaw.typesense-books on 127.0.0.1:8110 holds docs and docs_chunks_v2. DOCS_TYPESENSE_URL routes book readers and writers. A tailnet-only TCP forward exposes 8110 to Blaine.
- The book node is not a replica. A Typesense replica would copy every collection and repeat the same memory and startup cost. The separate process gives book indexing its own failure and restart boundary.
Cutover rule: avoid split-brain. Panda and Flagg must not both accept authoritative writes for the same Central service family. Gate 5 permits shadow smoke tests and migration rehearsal, but authority flips only inside an approved freeze/cutover window.
1) Physical Topology
Legacy Central snapshot: Panda
Mac Mini "Panda" (host macOS)
├─ launchd services (gateway, worker supervisor, caddy, talon, agent-mail, etc.)
├─ Colima VM (driver: VZ, arch: aarch64, runtime: docker, VM IP: 192.168.64.2)
│ └─ Talos node: joelclaw-controlplane-1 (k8s v1.35.0, internal IP 10.5.0.2)
│ ├─ namespace: joelclaw
│ │ ├─ inngest (StatefulSet + NodePort 8288/8289)
│ │ ├─ redis (StatefulSet + NodePort 6379)
│ │ ├─ typesense (StatefulSet + ClusterIP 8108)
│ │ ├─ restate (StatefulSet + NodePort 8080/9070/9071)
│ │ ├─ system-bus-worker (Deployment + ClusterIP 3111)
│ │ ├─ restate-worker (Deployment + ClusterIP 9080; full agent image + Firecracker)
│ │ ├─ dkron (StatefulSet + ClusterIP 8080)
│ │ ├─ docs-api (Deployment + NodePort 3838)
│ │ ├─ livekit-server (Deployment + NodePort 7880/7881)
│ │ ├─ bluesky-pds (Deployment + NodePort 3000)
│ │ └─ minio (StatefulSet + NodePort 30900/30901)
│ └─ namespace: aistor
│ ├─ aistor operator (Deployments: adminjob-operator, object-store-operator)
│ └─ aistor-s3 object store (StatefulSet + NodePort 31000/31001)
├─ Caddy reverse proxy (tailnet HTTPS fan-in)
├─ Gateway daemon (embedded pi session)
├─ Firecracker substrate (requires Colima nestedVirtualization=true for /dev/kvm; OFF by default — unstable under load)
└─ NAS "three-body" (NFS tiers per ADR-0088)
Flagg shadow / next Central target
Mac Studio "Flagg" (host macOS; target Central host)
├─ system tailscaled path required for cutover
├─ Central Service Account: joelclaw:staff
├─ service root: /Users/Shared/joelclaw/
│ ├─ services/{redis,typesense,inngest,minio}/
│ ├─ backups/central/
│ ├─ logs/central/
│ └─ src/joelclaw/ (service-owned checkout)
├─ shadow Compose stack (not authoritative)
│ ├─ Redis 7-alpine
│ ├─ Typesense 30.1
│ ├─ Inngest self-hosted
│ ├─ Restate 1.6.2 (Docker named volume for data)
│ └─ MinIO smoke surface
├─ system LaunchDaemon templates
│ ├─ com.joelclaw.central.colima
│ ├─ com.joelclaw.central.compose
│ ├─ com.joelclaw.central.health
│ └─ com.joelclaw.central.nas-mounts
├─ Chorus/Rhizomatic (parked historical canary)
│ ├─ no session briefing injection and no live claims
│ ├─ com.joelclaw.chorus-rhizomatic may remain loaded only until steering completes the sudo stop
│ └─ old 4821/7331 endpoints are historical troubleshooting context, not active dependencies
└─ NAS "three-body" proof path
├─ /Volumes/nas-nvme -> three-body:/volume2/data
└─ /Volumes/three-body -> three-body:/volume1/joelclaw
Flagg Gate 4 is complete: shadow Central recovered after hard reboot with no GUI login. Gate 5 is not complete until Flagg owns Central state, workers, endpoints, and verification while Panda is frozen as rollback-only.
Known runtime endpoints
- Colima VM IP:
192.168.64.2 (colima status --json)
- Kubernetes API (stable operator tunnel):
https://127.0.0.1:16443
- Talos API (stable operator tunnel):
127.0.0.1:15000
- Tailnet hostnames seen in config:
panda.tail7af24.ts.net (Caddy routes)
pds.panda.tail7af24.ts.net (PDS values)
flagg.tail7af24.ts.net (Mac Studio shadow / target Central host)
blaine.tail7af24.ts.net (satellite)
- Current live Run capture URL for satellites:
https://panda.tail7af24.ts.net/api/runs
- served by Panda host system-bus worker on
localhost:3111
- do not use
http://panda:3000 or http://panda.tail7af24.ts.net:3000; Panda has no durable Central web listener there.
Tailscale mesh state
tailscale status --json failed in this environment: UNKNOWN — needs manual verification
2) Process Inventory (Long-Running)
Herdr launch-domain split
com.joelclaw.herdr-server is the default interactive server. It runs as a per-user LaunchAgent in gui/<uid> so pane descendants inherit the Aqua bootstrap namespace and can reach user Keychain services.
com.joelclaw.herdr-system-server is the boot-safe automation server. It remains a system LaunchDaemon and owns only the named system Herdr session.
- Never install the default server as a system LaunchDaemon. A
UserName on a LaunchDaemon changes the Unix identity, not the launchd bootstrap namespace. Native macOS clients in those panes cannot resolve the user's com.apple.securityd.xpc service.
- The installer and cutover contract lives in
infra/install-herdr-default-launchagent.sh. The durable rationale is .brain/resources/herdr-launch-domain-contract.svx.
Host launchd inventory (Panda live Central snapshot)
Snapshot source: launchctl print gui/$(id -u)/<label> and plist inspection.
| Launchd label |
State |
PID (snapshot) |
Role |
Ports / endpoints |
com.joel.system-bus-worker |
running |
75292 |
Host worker supervisor (worker-supervisor) |
supervises child bun on 3111 |
com.joel.restate-worker |
retired / rollback-only |
— |
Historical host Restate wrapper (scripts/restate/start.sh) |
superseded by deployment/restate-worker on 9080 |
com.joel.gateway |
running |
81275 |
Gateway daemon (packages/gateway/src/daemon.ts) |
WS :3018, Redis bridge |
com.joel.caddy |
running |
9347 |
Reverse proxy |
3443, 5443, 6443, 7443, 8290, 8443, 9443 |
com.joel.talon |
running |
96359 |
Infra watchdog |
health 127.0.0.1:9999 |
com.joel.agent-secrets |
running |
98048 |
Secret lease daemon |
no public port |
com.joel.imsg-rpc |
running |
61110 |
iMessage JSON-RPC socket daemon |
Unix socket /tmp/imsg.sock |
com.joel.kube-operator-access |
running |
varies |
stable kubectl/talos operator tunnel |
local 16443 (kube), 15000 (talos) |
com.joel.voice-agent |
running |
71887 |
voice agent runtime |
local 8081 |
com.joel.local-sandbox-janitor |
scheduled |
(launchd timer) |
ADR-0221 local sandbox janitor (scripts/local-sandbox-janitor.sh → joelclaw workload sandboxes janitor) |
logs in /tmp/joelclaw/local-sandbox-janitor.{log,err} |
com.joelclaw.agent-mail |
spawn scheduled |
(none in launchctl snapshot) |
agent-mail MCP HTTP service |
observed listener 127.0.0.1:8765 (python process) |
com.joel.colima |
not running |
— |
startup helper for Colima |
n/a |
com.joel.k8s-reboot-heal |
not running |
— |
periodic k8s heal script |
n/a |
com.joel.system-bus-sync |
not running |
— |
sync guard watcher |
n/a |
com.joel.gateway-tripwire |
not running |
— |
gateway tripwire script |
n/a |
com.joel.content-sync-watcher |
not running |
— |
fs watch -> content/updated event |
n/a |
com.joel.vault-log-sync |
not running |
— |
Vault log sync watcher |
n/a |
Flagg Central launchd scaffold
Source: infra/central/README.md, infra/central/launchd/*.plist.template, and docs/prd-rhizomatic-network-canary.md.
These labels are part of the Flagg Central shadow/cutover scaffold. They are not proof that Flagg is authoritative.
| Launchd label |
Domain |
Role |
Ports / endpoints |
com.joelclaw.central.colima |
system LaunchDaemon |
starts the dedicated joelclaw-central Colima/Docker substrate as service infrastructure |
Docker socket under /Users/joelclaw/.colima/joelclaw-central/docker.sock |
com.joelclaw.central.compose |
system LaunchDaemon |
starts the shadow Central Compose stack |
Redis, Typesense, Inngest, Restate, MinIO bound to 127.0.0.1 by default |
com.joelclaw.central.health |
system LaunchDaemon |
bounded health + recovery state machine |
health.sh can invoke recover.sh --all after repeated degraded passes |
com.joelclaw.central.nas-mounts |
system LaunchDaemon |
mounts/verifies Flagg NAS tiers |
/Volumes/nas-nvme, /Volumes/three-body |
com.joelclaw.chorus-rhizomatic |
system LaunchDaemon |
Parked historical canary; no briefing injection or live claims; stop pending steering sudo |
Old endpoint 127.0.0.1:4821/mcp; old satellite tunnel 127.0.0.1:7331 |
Flagg reboot acceptance rule: Central is not eligible for cutover until infra/central/scripts/reboot-proof.sh passes from another machine after hard reboot with no GUI login.
Process supervision behavior: worker-supervisor
Source: infra/worker-supervisor/src/main.rs
- Default config:
- worker dir:
~/Code/joelhooks/joelclaw/packages/system-bus
- command:
bun run src/serve.ts
- port:
3111
- health endpoint:
/api/inngest
- sync endpoint:
/api/inngest (PUT)
- health interval: 30s
- restart after 3 consecutive health failures
- restart backoff: 1s → 30s max
- Pre-start kills stale process on port 3111.
- Runs host import preflight before spawn:
bun --eval "await import('./src/inngest/functions/index.host.ts');"
- on failure, skips spawn and retries with exponential backoff
- Loads env from
~/.config/system-bus.env plus leased secrets.
- Forces
WORKER_ROLE=host for the supervised host worker.
- Emits OTEL events via CLI on supervisor failures/restarts:
worker.supervisor.preflight.failed
worker.supervisor.worker_exit
worker.supervisor.health_check.restart
Worker supervision split note
- Talon is running (
com.joel.talon), but host worker is still launched via com.joel.system-bus-worker -> worker-supervisor.
- ADR + system-log indicate Talon can defer worker supervision during coexistence.
Kubernetes process inventory
Node
joelclaw-controlplane-1 (Talos v1.12.4, k8s v1.35.0, internal IP 10.5.0.2)
Core services
| Service |
Workload kind |
Service type |
Service port(s) |
NodePort(s) / exposure |
Role |
| Inngest |
StatefulSet inngest |
NodePort (inngest-svc) |
8288, 8289 |
8288, 8289 |
Event API + connect ws |
| Redis |
StatefulSet redis |
NodePort |
6379 |
6379 |
Queue/state/pubsub |
| Typesense |
StatefulSet typesense |
NodePort |
8108 |
8108 via Colima/Lima host publish |
Search + telemetry store |
| Restate |
StatefulSet restate |
NodePort |
8080, 9070, 9071 |
8080, 9070, 9071 |
Durable workflow ingress + admin + metrics |
| system-bus-worker |
Deployment |
ClusterIP |
3111 |
in-cluster only |
Cluster-role worker (12 functions) |
| restate-worker |
Deployment |
ClusterIP |
9080 |
in-cluster only |
dagOrchestrator + dagWorker + queue drainer in full agent image |
| docs-api |
Deployment |
NodePort |
3838 |
3838 |
PDF/docs API + agentic search + taxonomy graph |
| dkron |
StatefulSet |
ClusterIP (dkron-svc) + headless peer svc (dkron-peer) |
8080, 8946, 6868 |
in-cluster only; operator access via short-lived CLI-managed tunnel |
Distributed cron scheduler for Restate pipelines |
| livekit-server |
Deployment (Helm) |
NodePort |
80, 7881 |
7880 (for svc port 80), 7881 |
LiveKit signaling + rtc tcp |
| bluesky-pds |
Deployment (Helm-managed) |
NodePort |
3000 |
3000 |
AT Proto PDS |
| minio |
StatefulSet |
ClusterIP + NodePort |
9000, 9001 |
30900, 30901 |
Legacy local S3-compatible runtime |
aistor-s3-api (aistor ns) |
NodePort service (operator-managed) |
NodePort |
443, 9000 |
31000 (+ dynamic management NodePort) |
AIStor S3 API (TLS + management) |
aistor-s3-console (aistor ns) |
NodePort service (operator-managed) |
NodePort |
9443 |
31001 |
AIStor web console |
Restate / Firecracker runtime note
deployment/restate-worker is the current durable execution worker. The image bundles Bun + Node + pi + codex, the full repo checkout, and 76 symlinked skills.
- Runtime auth/identity come from
secret/pi-auth and configmap/agent-identity, which recreate /root/.pi/agent/auth.json plus the joelclaw identity chain inside the pod.
- Firecracker is enabled in-pod via privileged access to
/dev/kvm on Colima VZ. The /dev/kvm hostPath mount uses type "" (optional) so the pod starts without it when nestedVirtualization is off.
- Persistent microVM assets live on PVC
firecracker-images, mounted at /tmp/firecracker-test for kernel, rootfs, and snapshot files.
- Retry caps (2026-03-17): dagWorker maxAttempts=5, dagOrchestrator maxAttempts=3. Prevents Restate journal poisoning from infinite retries after code changes or infrastructure failures.
- Colima stability: nestedVirtualization is OFF by default (crashes VM under Docker build load). Toggle ON only for Firecracker testing sessions, then toggle OFF. See k8s skill for recovery procedures.
Control-plane access
- kube API exposed locally at
127.0.0.1:16443 via com.joel.kube-operator-access (ssh -S none -o ControlPath=none -L 16443:10.5.0.2:6443)
- Talos API exposed locally at
127.0.0.1:15000 via the same daemon (ssh -S none -o ControlPath=none -L 15000:10.5.0.2:50000)
- NodePort/runtime app ports still come from Colima/Lima forwarding; the operator daemon exists specifically because the direct host-published 6443 path was not boring after the rebuild
3) Worker Architecture (Role Split + Registration)
Source files:
packages/system-bus/src/serve.ts
packages/system-bus/src/inngest/functions/index.host.ts
packages/system-bus/src/inngest/functions/index.cluster.ts
packages/system-bus/src/inngest/client.ts
Role model
WORKER_ROLE parsed as host (default) or cluster.
- Registered function set is role-dependent:
- host uses
hostFunctionDefinitions
- cluster uses
clusterFunctionDefinitions
Ground-truth counts
- Measured from the imported definition arrays on 2026-07-29:
- Host function set: 163
- Cluster function set: 14
gateway/external-delivery-canary is host-only. It appends synthetic
message.requested events and watches the canonical event log. It does not run
in the gateway driver, transport daemon, or gateway session.
- Cluster subset functions:
agent/approval-request, agent/approval-resolve
todoist-comment-notify, todoist-task-completed-notify, todoist-task-created-notify
github-workflow-run-completed-notify, github-package-published-notify
webhook-subscription-dispatch-generic
webhook-subscription-dispatch-github-workflow-run-completed
memory/observe-session
queue/observer, queue/observer-requested
swarm-orchestrator, swarm-agent-exec
App registration isolation
From inngest/client.ts:
- app id resolves to:
system-bus-host when role is host
system-bus-cluster when role is cluster
- explicit
INNGEST_APP_ID overrides role-derived id.
This prevents host and cluster workers from overwriting each other’s function graphs.
serveHost behavior
From serve.ts:
- host role default
serveHost: http://host.docker.internal:3111
- cluster role default
serveHost: unset (connect-mode default)
INNGEST_SERVE_HOST overrides either role.
INNGEST_CONNECT_MODE=1|true|yes starts inngest/connect with instanceId=system-bus-<role>-<hostname> and maxWorkerConcurrency=8.
/api/inngest now explicitly allows only GET, POST, and PUT; PATCH, OPTIONS, and DELETE return 405 with Allow: GET, POST, PUT.
/api/inngest logs bounded request summaries for failed or ambiguous POST/PUT callbacks, including safe query/debug keys and body shape, not raw huge payloads.
- Bun server
idleTimeout=255 because registration PUTs can exceed the default 10s while the self-hosted runtime is under cron/backlog pressure.
Kubernetes cluster worker manifest sets:
INNGEST_BASE_URL=http://inngest-svc:8288
INNGEST_SERVE_HOST=http://system-bus-worker:3111
TYPESENSE_URL=http://typesense:8108
- image:
ghcr.io/joelhooks/system-bus-worker:d7dd7788 (recorded after Inngest SDK advisory hardening)
Panda host worker config should advertise an SDK callback URL the Inngest pod can actually reach. Current docs call out INNGEST_SERVE_HOST=http://100.93.201.72:3111 on Panda. Do not assume host.lima.internal or host.docker.internal works from inside Talos unless a live pod-to-host probe proves it.
Registration mechanics
- Worker exposes
GET|POST|PUT /api/inngest.
- Worker sends a delayed self-sync
PUT /api/inngest ~5s after startup.
worker-supervisor also performs startup PUT sync.
Host is primary today
From index comments + function lists:
- ADR-0089 transition: host remains authoritative for broad function ownership.
- Cluster is intentionally limited to cluster-safe subset (12 functions).
4) Event Flow (CLI → Inngest → Worker → Completion)
Canonical flow: joelclaw send
- CLI
joelclaw send <event> calls Inngest.send().
Inngest.send() POSTs event JSON to:
${INNGEST_URL}/e/${INNGEST_EVENT_KEY}
- default:
http://localhost:8288/e/<key>
- 2026-06-18: Pi
memory-enforcer observe events must follow this keyed Event API shape too; never POST to bare /e/. The extension resolves INNGEST_EVENT_KEY from env/local env files and skips observe emission when no key is available.
- Inngest server persists the event and resolves matching function triggers.
- Inngest dispatches function steps to the worker app graph that owns that function ID:
- host app (
system-bus-host) for 101-host set
- cluster app (
system-bus-cluster) for 12-cluster subset
- Worker handles callbacks via
/api/inngest (Hono + inngest/hono handler).
- Each
step.run result is memoized by Inngest; next step executes when prior completes.
- Completion/failure is queryable via GraphQL (
/v0/gql) and CLI commands (runs, run, event, events).
Run capture flow: Machine hook → Central /api/runs → memory/run.captured
- Runtime-native hook captures only the new Run slice:
- Pi extension for Pi
- Claude Code Stop hook via
scripts/joelclaw-capture-session.ts
- Codex hook/helper via
scripts/joelclaw-capture-codex-session.js
- server-side runtimes can call capture inline instead of shelling through a hook.
- The hook posts JSON to Central:
- current live URL for satellites:
POST https://panda.tail7af24.ts.net/api/runs
- Panda serves this through the host system-bus worker on
localhost:3111
- auth:
Authorization: Bearer <~/.joelclaw/auth.json token>
packages/system-bus/src/serve.ts validates the bearer token against the persistent local SQLite registry ~/.joelclaw/capture-auth.db. The worker refreshes this four-row registry from Typesense machines_dev in the background every five minutes. Known-token requests never wait for Typesense; an unknown token gets one bounded migration lookup.
- Worker writes the raw source of truth through
@joelclaw/memory#writeRunBlob:
- default dev store:
~/.joelclaw/runs-dev/<user>/<yyyy-mm>/<run-id>.jsonl
- companion metadata includes
user_id, machine_id, agent_runtime, parent/conversation IDs, tags, byte count, and SHA-256.
- Worker emits
memory/run.captured to Inngest.
packages/system-bus/src/inngest/functions/memory/run-captured.ts appends Run metadata, chunks, and FTS rows to SQLite sessions.db in one transaction.
- If POST fails from a Machine, the hook writes the POST body into
~/.joelclaw/outbox/; the Machine does not become Central just because capture is temporarily offline.
- If raw blobs exist but SQLite is stale, recover Inngest/worker registration first, then backfill with
scripts/backfill-session-index.ts. Do not replay thousands of memory/run.captured events casually.
- The public Vercel app is not a Run ingress or search surface. Its
POST /api/runs and POST /api/runs/search routes return 410. Machine hooks must use the Central system-bus endpoint, and operators must use joelclaw sessions search.
Recall flow: scoped request → CLI composition root → lane-preserving result
- Interactive callers run in a trusted GitHub checkout or pass both
--project
and --workstream. Automatic callers send one exact
ComposedRecallRequestV1 through joelclaw recall --request-file - on
private stdin.
- Every request names principal, purpose, allowed privacy tiers, supersession
policy, and independent limits for reflections, observations, and curated
pages. The deprecated
--limit flag is ignored.
- The CLI reads the pinned flowing-memory artifact and curated
critical.db
projection, then returns three separate lanes: flowing-reflections,
flowing-observations, and curated-pages. Never merge their scores.
- Unavailable lanes return typed envelopes and exit 3. The gateway shows a
generic unavailable marker. Optional Inngest enrichment records redacted
telemetry and returns no context. Restate returns a generic degraded marker.
- Use
joelclaw sessions search "<query>" --source both --extract for exact
Run/session wording, commands, and transcript evidence. Composed recall is a
broad memory surface, not raw transcript search.
typesense-recall is config-selected rollback only. Status reports it as a
visible degraded warning without turning global health red.
Accepted cluster decision (2026-08-23): automatic recall inside the
system-bus and Restate cluster images is intentionally disabled/degraded after
retiring old observation recall. packages/recall is present for module
resolution, but the images do not receive a local semantic store or compiled
joelclaw recall binary. Callers must take their explicit optional-enrichment
degrade path. This is not a hidden deployment dependency. Re-enable cluster
recall only after a separately reviewed secure remote flowing-read substrate
exists.
Queue flow: joelclaw queue emit → Restate drainer → durable dispatch
- CLI
joelclaw queue emit <event> persists a QueueEventEnvelope into Redis stream joelclaw:queue:events and indexes it in sorted set joelclaw:queue:priority.
- The
restate-worker k8s deployment (packages/restate/src/index.ts) starts a deterministic queue drainer beside the channel callback listener.
- On startup, the drainer claims pending + never-delivered entries via
@joelclaw/queue#getUnacked(), reindexes replayable entries, and emits OTEL replay evidence.
- Each drain tick selects the next priority candidate from the sorted set, resolves its static registry target from
packages/queue/src/registry.ts, and POSTs a one-node DAG request to Restate /dagOrchestrator/{workflowId}/run/send.
- When backlog remains and a dispatch slot frees, the drainer self-pulses immediately instead of waiting for the next
QUEUE_DRAIN_INTERVAL_MS heartbeat. That interval is now the idle poll / retry cadence, not a mandatory 2-second tax between successful sends.
- The current Story-3 bridge re-emits the queue item to its registered Inngest event target inside that one-node DAG request. This is deliberate: the deterministic queue/drainer is proven first; per-family Restate cutovers remain Story 4 work.
- On accepted Restate dispatch, the drainer acks the queue message; on failure it leaves the message in Redis, applies retry cooldown, and emits
queue.dispatch.failed OTEL evidence.
- If backlog remains in Redis but the drainer stops making progress past
QUEUE_DRAIN_STALL_AFTER_MS, it emits queue.drainer.stalled and exits non-zero so k8s restarts deployment/restate-worker. That is the self-heal path for a wedged drainer inside an otherwise-running Bun process.
- Crash recovery comes from the Redis stream + consumer-group replay path, not from vibes: restart the
restate-worker pod, let getUnacked() reclaim the inflight entries, then drain resumes.
Workload flow: joelclaw workload run → Redis → Restate DAG → execution
joelclaw workload plan ... --stages-from <file> can load an explicit stage DAG, validate unknown deps/self-deps/duplicates/cycles, and preserve per-stage acceptance gates.
joelclaw workload run <plan-artifact> normalizes the selected stage into the canonical workload/requested runtime request.
- Queue admission writes the request into Redis, where the deterministic drainer forwards it into Restate as a
dagOrchestrator/{workflowId}/run/send request.
dagOrchestrator executes dependency waves: ready nodes in parallel, chained nodes only after every dependsOn node has terminal output.
dagWorker executes the node handler:
shell → subprocess work inside the restate-worker pod
infer → pi -p --no-session --no-extensions inside the pod, using the mounted auth + identity + skill set
microvm → Firecracker boot/restore through /dev/kvm with kernel/rootfs/snapshot files on PVC firecracker-images
- Each node emits OTEL (
dag.node.*), and the workflow emits dag.workflow.* so queue → Restate → execution remains observable.
- Current truthful limit: the microVM runtime boots and restores snapshots in-cluster, but the broader exec-in-VM workspace drive protocol is still incomplete for general coding slices.
Webhook flow
- External service posts to
/webhooks/:provider.
- Caddy routes
/webhooks/* on localhost:8443 to worker localhost:3111.
webhookApp verifies signature, normalizes payload, emits Inngest events (provider/event).
- Inngest executes subscribed functions.
"Why did this run / not run" trace recipe
- Start with an
…(truncated)
1---2name: system-architecture3description: Canonical joelclaw topology, Central/Relay vocabulary, and wiring map. Use when reasoning about architecture, Panda/Flagg Central migration, satellites, run capture, tracing event flow, debugging why something ran/didn't run, identifying which worker executes a function, checking what listens on a port, or following an event end-to-end.4---5
6# System Architecture (Canonical Topology)
7
8This skill is the **single source of truth** for joelclaw system wiring.
9
10Freshness notes:
11
12- **2026-08-23 recall cutover:** `memory_observations` and the system-log JSONL/`slog` path are retired. Production `joelclaw recall` composes lane-separated flowing reflections, flowing observations, and curated Brain pages. Every request has explicit scope and access. Chorus/Rhizomatic is parked with no briefing injection or live claims; service stop still needs steering sudo. Claude auto-memory is a pointer index, not a content store.
13- **2026-07-10 topology:** the old Panda-centric mental model is migration debt. Current responsibility lanes:
14
15- **Central**: Flagg/Mac Studio. It is authoritative for agent-mail and Run capture ingress; verify each remaining service family during migration.
16- **Relay**: Panda account-bound leftovers and explicit decommission blockers.
17- **Satellite**: Blaine/other capture clients, connectors, outboxes, and transcript backup freshness.
18
19Treat older “Panda is the whole system” wording as stale unless re-verified against live receipts.
20
21Use it for:
22
23- "why did this run / not run"
24- "which worker handles this function"
25- "what is listening on port X"
26- "how does event Y flow"
27- "where does this Run/capture/memory record go"
28- "is this Central, Relay, satellite, or shadow runtime work"
29- full-stack routing/debug across CLI → Inngest → workers → gateway → telemetry
30
31## Ground-Truth Scope + Evidence Snapshot
32
33This document is grounded in direct reads of:
34
35- `apps/docs-api/src/index.ts`
36- `packages/restate/Dockerfile`
37- `packages/restate/src/index.ts`
38- `packages/restate/src/workflows/dag-orchestrator.ts`
39- `packages/agent-execution/src/microvm.ts`
40- `packages/system-bus/src/serve.ts`
41- `packages/system-bus/src/inngest/functions/index.host.ts`
42- `packages/system-bus/src/inngest/functions/index.cluster.ts`
43- `packages/system-bus/src/inngest/client.ts`
44- `infra/worker-supervisor/src/main.rs`
45- `~/Library/LaunchAgents/com.joel*.plist`
46- `k8s/*` (all files)
47- `infra/pds/values.yaml`
48- `packages/gateway/src/daemon.ts`
49- `packages/gateway/src/channels/*.ts`
50- `~/.joelclaw/gateway/AGENTS.md`
51- `~/.joelclaw/gateway/.pi/settings.json`
52- `~/.local/caddy/Caddyfile`
53- `~/.colima/default/colima.yaml` + `colima status --json`
54- `packages/cli/src/cli.ts`, `packages/cli/src/config.ts`, `packages/cli/src/inngest.ts`
55- `packages/system-bus/src/observability/*` (key files: `emit.ts`, `otel-event.ts`, `store.ts`)
56- `packages/telemetry/src/emitter.ts`
57- `packages/system-bus/src/lib/pi-output.ts`
58- `packages/inference-router/src/tracing.ts`
59- `CONTEXT.md`
60- `docs/gateway.md`
61- `docs/inngest-functions.md`
62- `docs/runbooks/satellite-rig-setup.md`
63- `docs/runbooks/flagg-gate5-staged-migration.md`
64- `infra/central/README.md`
65- `docs/prd-rhizomatic-network-canary.md`
66- `infra/central/launchd/*.plist.template`
67- `scripts/joelclaw-capture-session.ts`
68- `scripts/joelclaw-capture-codex-session.js`
69- ADRs in `~/Vault/docs/decisions/` (required + topology-adjacent)
70- canonical OTel events plus durable Brain `.svx` receipts (the former system-log JSONL is archived and retired)
71
72### Related docs verified
73
74- `docs/architecture.md` — Restate/Firecracker runtime + workload execution flow
75- `docs/deploy.md` — Restate worker deploy + auth/identity/PVC procedures
76- `docs/cli.md` — workload command tree + runtime bridge
77- `docs/observability.md` — **not inspected in this update**
78
79### Refresh receipt: 2026-06-15
80
81This refresh folds in work from:
82
83- Central vocabulary + Project Thread docs (`6b3a1b05`, `CONTEXT.md`, `docs/gateway.md`)
84- Flagg Central scaffold and Gate 5 migration runbooks (`6e02a6cd`, `d36b52f2`, `infra/central/*`)
85- worker-hosted Run capture (`f06501a8`, `docs/inngest-functions.md`, `packages/system-bus/src/serve.ts`)
86- Inngest SDK hardening + connect-mode recovery (`d7dd7788`, `6c5d2a8e`)
87- Talon paging/debounce hardening (`c03edc5c`, `1f086cfb`, `d26351cf`)
88- satellite rig setup for Blaine/Flagg (`bc5738f3`, `9cc02f6e`)
89- historical Rhizomatic/Chorus network canary (`6bebf5b1`, `docs/prd-rhizomatic-network-canary.md`), parked by the 2026-07-17 decision
90
91---
92
93## 0) Current Operator Map
94
95The old mental model was "Panda is joelclaw." That is no longer precise enough.
96
97Use these terms:
98
99| Term | Meaning | Current truth |
100| ------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
101| Network | Users + Machines coordinated by one Central | Logical boundary, not the tailnet/k8s cluster |
102| Central | single authoritative joelclaw service for the Network | Flagg is authoritative for agent-mail and Run capture; verify remaining service families individually during migration |
103| Central host target | Machine consolidating Central responsibilities | Flagg / Mac Studio, `machine_id=mac-studio-central` |
104| Relay Machine | machine that hosts account-bound/local-hardware-bound relays while delegating state to Central | Panda becomes this after cutover; satellites stay thin |
105| Satellite Machine | thin local Pi/Codex/Claude runner with capture/search/repair hooks | Blaine and Flagg bootstrap through `scripts/setup-satellite-rig.sh` |
106| Run | one captured agent invocation | raw JSONL + metadata first, SQLite FTS is the live search index |
107| Conversation | sibling Run label for an interactive context | not the source of truth |
108| Project Thread | private `#brain-joel` operator workroom for a bounded objective | coordination only; does not authorize public replies |
109
110Current authority split (verified 2026-07-10):
111
112- **Flagg is authoritative for agent-mail and Run capture ingress.** The agent-mail daemon binds Flagg loopback; Blaine and Panda use SSH connector LaunchAgents so every `joelclaw mail` client reaches the same mailbox without exposing the service on the tailnet.
113- **Panda is migration debt plus Relay responsibilities.** Its independent agent-mail daemon and Talon are removed. A reboot-survivable SSH connector now binds Panda IPv4 loopback `127.0.0.1:3111` and forwards legacy `/api/runs` and `/webhooks` ingress to Flagg. The legacy system worker still owns the IPv6 listener until its system LaunchDaemon is booted out with sudo.
114- **Satellites stay thin**. They run Pi/Codex/Claude, local capture hooks, and connectors to Central. Do not install independent stateful Central services on a satellite without a specific reason.
115- **SQLite indexes Runs**. NAS/local Run blobs are the source of truth; `sessions.db` is the compact live FTS index. The retired Typesense `runs_dev` and `run_chunks_dev` collections must not be recreated.
116- **Flagg splits book search from the operational Typesense node.** The system LaunchDaemon on `127.0.0.1:8108` holds operational projections. The user LaunchAgent `com.joelclaw.typesense-books` on `127.0.0.1:8110` holds `docs` and `docs_chunks_v2`. `DOCS_TYPESENSE_URL` routes book readers and writers. A tailnet-only TCP forward exposes `8110` to Blaine.
117- **The book node is not a replica.** A Typesense replica would copy every collection and repeat the same memory and startup cost. The separate process gives book indexing its own failure and restart boundary.
118
119Cutover rule: avoid split-brain. Panda and Flagg must not both accept authoritative writes for the same Central service family. Gate 5 permits shadow smoke tests and migration rehearsal, but authority flips only inside an approved freeze/cutover window.
120
121---
122
123## 1) Physical Topology
124
125### Legacy Central snapshot: Panda
126
127```text
128Mac Mini "Panda" (host macOS)
129├─ launchd services (gateway, worker supervisor, caddy, talon, agent-mail, etc.)
130├─ Colima VM (driver: VZ, arch: aarch64, runtime: docker, VM IP: 192.168.64.2)
131│ └─ Talos node: joelclaw-controlplane-1 (k8s v1.35.0, internal IP 10.5.0.2)
132│ ├─ namespace: joelclaw
133│ │ ├─ inngest (StatefulSet + NodePort 8288/8289)
134│ │ ├─ redis (StatefulSet + NodePort 6379)
135│ │ ├─ typesense (StatefulSet + ClusterIP 8108)
136│ │ ├─ restate (StatefulSet + NodePort 8080/9070/9071)
137│ │ ├─ system-bus-worker (Deployment + ClusterIP 3111)
138│ │ ├─ restate-worker (Deployment + ClusterIP 9080; full agent image + Firecracker)
139│ │ ├─ dkron (StatefulSet + ClusterIP 8080)
140│ │ ├─ docs-api (Deployment + NodePort 3838)
141│ │ ├─ livekit-server (Deployment + NodePort 7880/7881)
142│ │ ├─ bluesky-pds (Deployment + NodePort 3000)
143│ │ └─ minio (StatefulSet + NodePort 30900/30901)
144│ └─ namespace: aistor
145│ ├─ aistor operator (Deployments: adminjob-operator, object-store-operator)
146│ └─ aistor-s3 object store (StatefulSet + NodePort 31000/31001)
147├─ Caddy reverse proxy (tailnet HTTPS fan-in)
148├─ Gateway daemon (embedded pi session)
149├─ Firecracker substrate (requires Colima nestedVirtualization=true for /dev/kvm; OFF by default — unstable under load)
150└─ NAS "three-body" (NFS tiers per ADR-0088)
151```
152
153### Flagg shadow / next Central target
154
155```text
156Mac Studio "Flagg" (host macOS; target Central host)
157├─ system tailscaled path required for cutover
158├─ Central Service Account: joelclaw:staff
159├─ service root: /Users/Shared/joelclaw/
160│ ├─ services/{redis,typesense,inngest,minio}/
161│ ├─ backups/central/
162│ ├─ logs/central/
163│ └─ src/joelclaw/ (service-owned checkout)
164├─ shadow Compose stack (not authoritative)
165│ ├─ Redis 7-alpine
166│ ├─ Typesense 30.1
167│ ├─ Inngest self-hosted
168│ ├─ Restate 1.6.2 (Docker named volume for data)
169│ └─ MinIO smoke surface
170├─ system LaunchDaemon templates
171│ ├─ com.joelclaw.central.colima
172│ ├─ com.joelclaw.central.compose
173│ ├─ com.joelclaw.central.health
174│ └─ com.joelclaw.central.nas-mounts
175├─ Chorus/Rhizomatic (parked historical canary)
176│ ├─ no session briefing injection and no live claims
177│ ├─ com.joelclaw.chorus-rhizomatic may remain loaded only until steering completes the sudo stop
178│ └─ old 4821/7331 endpoints are historical troubleshooting context, not active dependencies
179└─ NAS "three-body" proof path
180 ├─ /Volumes/nas-nvme -> three-body:/volume2/data
181 └─ /Volumes/three-body -> three-body:/volume1/joelclaw
182```
183
184Flagg Gate 4 is complete: shadow Central recovered after hard reboot with no GUI login. Gate 5 is not complete until Flagg owns Central state, workers, endpoints, and verification while Panda is frozen as rollback-only.
185
186### Known runtime endpoints
187
188- Colima VM IP: `192.168.64.2` (`colima status --json`)
189- Kubernetes API (stable operator tunnel): `https://127.0.0.1:16443`
190- Talos API (stable operator tunnel): `127.0.0.1:15000`
191- Tailnet hostnames seen in config:
192 - `panda.tail7af24.ts.net` (Caddy routes)
193 - `pds.panda.tail7af24.ts.net` (PDS values)
194 - `flagg.tail7af24.ts.net` (Mac Studio shadow / target Central host)
195 - `blaine.tail7af24.ts.net` (satellite)
196- Current live Run capture URL for satellites:
197 - `https://panda.tail7af24.ts.net/api/runs`
198 - served by Panda host system-bus worker on `localhost:3111`
199 - do **not** use `http://panda:3000` or `http://panda.tail7af24.ts.net:3000`; Panda has no durable Central web listener there.
200
201### Tailscale mesh state
202
203- `tailscale status --json` failed in this environment: **UNKNOWN — needs manual verification**
204
205---
206
207## 2) Process Inventory (Long-Running)
208
209### Herdr launch-domain split
210
211- `com.joelclaw.herdr-server` is the default interactive server. It runs as a per-user LaunchAgent in `gui/<uid>` so pane descendants inherit the Aqua bootstrap namespace and can reach user Keychain services.
212- `com.joelclaw.herdr-system-server` is the boot-safe automation server. It remains a system LaunchDaemon and owns only the named `system` Herdr session.
213- Never install the default server as a system LaunchDaemon. A `UserName` on a LaunchDaemon changes the Unix identity, not the launchd bootstrap namespace. Native macOS clients in those panes cannot resolve the user's `com.apple.securityd.xpc` service.
214- The installer and cutover contract lives in `infra/install-herdr-default-launchagent.sh`. The durable rationale is `.brain/resources/herdr-launch-domain-contract.svx`.
215
216## Host launchd inventory (Panda live Central snapshot)
217
218> Snapshot source: `launchctl print gui/$(id -u)/<label>` and plist inspection.
219
220| Launchd label | State | PID (snapshot) | Role | Ports / endpoints |
221| -------------------------------- | ----------------------: | ---------------------------: | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
222| `com.joel.system-bus-worker` | running | 75292 | Host worker supervisor (`worker-supervisor`) | supervises child bun on 3111 |
223| `com.joel.restate-worker` | retired / rollback-only | — | Historical host Restate wrapper (`scripts/restate/start.sh`) | superseded by `deployment/restate-worker` on 9080 |
224| `com.joel.gateway` | running | 81275 | Gateway daemon (`packages/gateway/src/daemon.ts`) | WS `:3018`, Redis bridge |
225| `com.joel.caddy` | running | 9347 | Reverse proxy | 3443, 5443, 6443, 7443, 8290, 8443, 9443 |
226| `com.joel.talon` | running | 96359 | Infra watchdog | health `127.0.0.1:9999` |
227| `com.joel.agent-secrets` | running | 98048 | Secret lease daemon | no public port |
228| `com.joel.imsg-rpc` | running | 61110 | iMessage JSON-RPC socket daemon | Unix socket `/tmp/imsg.sock` |
229| `com.joel.kube-operator-access` | running | varies | stable kubectl/talos operator tunnel | local 16443 (kube), 15000 (talos) |
230| `com.joel.voice-agent` | running | 71887 | voice agent runtime | local 8081 |
231| `com.joel.local-sandbox-janitor` | scheduled | (launchd timer) | ADR-0221 local sandbox janitor (`scripts/local-sandbox-janitor.sh` → `joelclaw workload sandboxes janitor`) | logs in `/tmp/joelclaw/local-sandbox-janitor.{log,err}` |
232| `com.joelclaw.agent-mail` | spawn scheduled | (none in launchctl snapshot) | agent-mail MCP HTTP service | observed listener `127.0.0.1:8765` (python process) |
233| `com.joel.colima` | not running | — | startup helper for Colima | n/a |
234| `com.joel.k8s-reboot-heal` | not running | — | periodic k8s heal script | n/a |
235| `com.joel.system-bus-sync` | not running | — | sync guard watcher | n/a |
236| `com.joel.gateway-tripwire` | not running | — | gateway tripwire script | n/a |
237| `com.joel.content-sync-watcher` | not running | — | fs watch -> content/updated event | n/a |
238| `com.joel.vault-log-sync` | not running | — | Vault log sync watcher | n/a |
239
240## Flagg Central launchd scaffold
241
242> Source: `infra/central/README.md`, `infra/central/launchd/*.plist.template`, and `docs/prd-rhizomatic-network-canary.md`.
243
244These labels are part of the Flagg Central shadow/cutover scaffold. They are not proof that Flagg is authoritative.
245
246| Launchd label | Domain | Role | Ports / endpoints |
247| --------------------------------- | ------------------- | ---------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
248| `com.joelclaw.central.colima` | system LaunchDaemon | starts the dedicated `joelclaw-central` Colima/Docker substrate as service infrastructure | Docker socket under `/Users/joelclaw/.colima/joelclaw-central/docker.sock` |
249| `com.joelclaw.central.compose` | system LaunchDaemon | starts the shadow Central Compose stack | Redis, Typesense, Inngest, Restate, MinIO bound to `127.0.0.1` by default |
250| `com.joelclaw.central.health` | system LaunchDaemon | bounded health + recovery state machine | `health.sh` can invoke `recover.sh --all` after repeated degraded passes |
251| `com.joelclaw.central.nas-mounts` | system LaunchDaemon | mounts/verifies Flagg NAS tiers | `/Volumes/nas-nvme`, `/Volumes/three-body` |
252| `com.joelclaw.chorus-rhizomatic` | system LaunchDaemon | **Parked historical canary**; no briefing injection or live claims; stop pending steering sudo | Old endpoint `127.0.0.1:4821/mcp`; old satellite tunnel `127.0.0.1:7331` |
253
254Flagg reboot acceptance rule: Central is not eligible for cutover until `infra/central/scripts/reboot-proof.sh` passes from another machine after hard reboot with no GUI login.
255
256### Process supervision behavior: `worker-supervisor`
257
258Source: `infra/worker-supervisor/src/main.rs`
259
260- Default config:
261 - worker dir: `~/Code/joelhooks/joelclaw/packages/system-bus`
262 - command: `bun run src/serve.ts`
263 - port: `3111`
264 - health endpoint: `/api/inngest`
265 - sync endpoint: `/api/inngest` (PUT)
266 - health interval: 30s
267 - restart after 3 consecutive health failures
268 - restart backoff: 1s → 30s max
269- Pre-start kills stale process on port 3111.
270- Runs host import preflight before spawn:
271 - `bun --eval "await import('./src/inngest/functions/index.host.ts');"`
272 - on failure, skips spawn and retries with exponential backoff
273- Loads env from `~/.config/system-bus.env` plus leased secrets.
274- Forces `WORKER_ROLE=host` for the supervised host worker.
275- Emits OTEL events via CLI on supervisor failures/restarts:
276 - `worker.supervisor.preflight.failed`
277 - `worker.supervisor.worker_exit`
278 - `worker.supervisor.health_check.restart`
279
280### Worker supervision split note
281
282- Talon is running (`com.joel.talon`), but host worker is still launched via `com.joel.system-bus-worker` -> `worker-supervisor`.
283- ADR + system-log indicate Talon can defer worker supervision during coexistence.
284
285---
286
287## Kubernetes process inventory
288
289## Node
290
291- `joelclaw-controlplane-1` (Talos v1.12.4, k8s v1.35.0, internal IP `10.5.0.2`)
292
293## Core services
294
295| Service | Workload kind | Service type | Service port(s) | NodePort(s) / exposure | Role |
296| ------------------------------- | ----------------------------------- | ---------------------------------------------------------- | ---------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- |
297| Inngest | StatefulSet `inngest` | NodePort (`inngest-svc`) | 8288, 8289 | 8288, 8289 | Event API + connect ws |
298| Redis | StatefulSet `redis` | NodePort | 6379 | 6379 | Queue/state/pubsub |
299| Typesense | StatefulSet `typesense` | NodePort | 8108 | 8108 via Colima/Lima host publish | Search + telemetry store |
300| Restate | StatefulSet `restate` | NodePort | 8080, 9070, 9071 | 8080, 9070, 9071 | Durable workflow ingress + admin + metrics |
301| system-bus-worker | Deployment | ClusterIP | 3111 | in-cluster only | Cluster-role worker (12 functions) |
302| restate-worker | Deployment | ClusterIP | 9080 | in-cluster only | `dagOrchestrator` + `dagWorker` + queue drainer in full agent image |
303| docs-api | Deployment | NodePort | 3838 | 3838 | PDF/docs API + agentic search + taxonomy graph |
304| dkron | StatefulSet | ClusterIP (`dkron-svc`) + headless peer svc (`dkron-peer`) | 8080, 8946, 6868 | in-cluster only; operator access via short-lived CLI-managed tunnel | Distributed cron scheduler for Restate pipelines |
305| livekit-server | Deployment (Helm) | NodePort | 80, 7881 | 7880 (for svc port 80), 7881 | LiveKit signaling + rtc tcp |
306| bluesky-pds | Deployment (Helm-managed) | NodePort | 3000 | 3000 | AT Proto PDS |
307| minio | StatefulSet | ClusterIP + NodePort | 9000, 9001 | 30900, 30901 | Legacy local S3-compatible runtime |
308| aistor-s3-api (`aistor` ns) | NodePort service (operator-managed) | NodePort | 443, 9000 | 31000 (+ dynamic management NodePort) | AIStor S3 API (TLS + management) |
309| aistor-s3-console (`aistor` ns) | NodePort service (operator-managed) | NodePort | 9443 | 31001 | AIStor web console |
310
311### Restate / Firecracker runtime note
312
313- `deployment/restate-worker` is the current durable execution worker. The image bundles Bun + Node + `pi` + `codex`, the full repo checkout, and 76 symlinked skills.
314- Runtime auth/identity come from `secret/pi-auth` and `configmap/agent-identity`, which recreate `/root/.pi/agent/auth.json` plus the joelclaw identity chain inside the pod.
315- Firecracker is enabled in-pod via privileged access to `/dev/kvm` on Colima VZ. The `/dev/kvm` hostPath mount uses type `""` (optional) so the pod starts without it when nestedVirtualization is off.
316- Persistent microVM assets live on PVC `firecracker-images`, mounted at `/tmp/firecracker-test` for kernel, rootfs, and snapshot files.
317- **Retry caps (2026-03-17)**: dagWorker maxAttempts=5, dagOrchestrator maxAttempts=3. Prevents Restate journal poisoning from infinite retries after code changes or infrastructure failures.
318- **Colima stability**: nestedVirtualization is OFF by default (crashes VM under Docker build load). Toggle ON only for Firecracker testing sessions, then toggle OFF. See k8s skill for recovery procedures.
319
320### Control-plane access
321
322- kube API exposed locally at `127.0.0.1:16443` via `com.joel.kube-operator-access` (`ssh -S none -o ControlPath=none -L 16443:10.5.0.2:6443`)
323- Talos API exposed locally at `127.0.0.1:15000` via the same daemon (`ssh -S none -o ControlPath=none -L 15000:10.5.0.2:50000`)
324- NodePort/runtime app ports still come from Colima/Lima forwarding; the operator daemon exists specifically because the direct host-published 6443 path was not boring after the rebuild
325
326---
327
328## 3) Worker Architecture (Role Split + Registration)
329
330Source files:
331
332- `packages/system-bus/src/serve.ts`
333- `packages/system-bus/src/inngest/functions/index.host.ts`
334- `packages/system-bus/src/inngest/functions/index.cluster.ts`
335- `packages/system-bus/src/inngest/client.ts`
336
337## Role model
338
339- `WORKER_ROLE` parsed as `host` (default) or `cluster`.
340- Registered function set is role-dependent:
341 - host uses `hostFunctionDefinitions`
342 - cluster uses `clusterFunctionDefinitions`
343
344## Ground-truth counts
345
346- Measured from the imported definition arrays on 2026-07-29:
347 - Host function set: **163**
348 - Cluster function set: **14**
349- `gateway/external-delivery-canary` is host-only. It appends synthetic
350 `message.requested` events and watches the canonical event log. It does not run
351 in the gateway driver, transport daemon, or gateway session.
352- Cluster subset functions:
353 - `agent/approval-request`, `agent/approval-resolve`
354 - `todoist-comment-notify`, `todoist-task-completed-notify`, `todoist-task-created-notify`
355 - `github-workflow-run-completed-notify`, `github-package-published-notify`
356 - `webhook-subscription-dispatch-generic`
357 - `webhook-subscription-dispatch-github-workflow-run-completed`
358 - `memory/observe-session`
359 - `queue/observer`, `queue/observer-requested`
360 - `swarm-orchestrator`, `swarm-agent-exec`
361
362## App registration isolation
363
364From `inngest/client.ts`:
365
366- app id resolves to:
367 - `system-bus-host` when role is host
368 - `system-bus-cluster` when role is cluster
369- explicit `INNGEST_APP_ID` overrides role-derived id.
370
371This prevents host and cluster workers from overwriting each other’s function graphs.
372
373## serveHost behavior
374
375From `serve.ts`:
376
377- host role default `serveHost`: `http://host.docker.internal:3111`
378- cluster role default `serveHost`: unset (connect-mode default)
379- `INNGEST_SERVE_HOST` overrides either role.
380- `INNGEST_CONNECT_MODE=1|true|yes` starts `inngest/connect` with `instanceId=system-bus-<role>-<hostname>` and `maxWorkerConcurrency=8`.
381- `/api/inngest` now explicitly allows only `GET`, `POST`, and `PUT`; `PATCH`, `OPTIONS`, and `DELETE` return `405` with `Allow: GET, POST, PUT`.
382- `/api/inngest` logs bounded request summaries for failed or ambiguous POST/PUT callbacks, including safe query/debug keys and body shape, not raw huge payloads.
383- Bun server `idleTimeout=255` because registration PUTs can exceed the default 10s while the self-hosted runtime is under cron/backlog pressure.
384
385Kubernetes cluster worker manifest sets:
386
387- `INNGEST_BASE_URL=http://inngest-svc:8288`
388- `INNGEST_SERVE_HOST=http://system-bus-worker:3111`
389- `TYPESENSE_URL=http://typesense:8108`
390- image: `ghcr.io/joelhooks/system-bus-worker:d7dd7788` (recorded after Inngest SDK advisory hardening)
391
392Panda host worker config should advertise an SDK callback URL the Inngest pod can actually reach. Current docs call out `INNGEST_SERVE_HOST=http://100.93.201.72:3111` on Panda. Do not assume `host.lima.internal` or `host.docker.internal` works from inside Talos unless a live pod-to-host probe proves it.
393
394## Registration mechanics
395
396- Worker exposes `GET|POST|PUT /api/inngest`.
397- Worker sends a delayed self-sync `PUT /api/inngest` ~5s after startup.
398- `worker-supervisor` also performs startup PUT sync.
399
400## Host is primary today
401
402From index comments + function lists:
403
404- ADR-0089 transition: host remains authoritative for broad function ownership.
405- Cluster is intentionally limited to cluster-safe subset (12 functions).
406
407---
408
409## 4) Event Flow (CLI → Inngest → Worker → Completion)
410
411## Canonical flow: `joelclaw send`
412
4131. CLI `joelclaw send <event>` calls `Inngest.send()`.
4142. `Inngest.send()` POSTs event JSON to:
415 - `${INNGEST_URL}/e/${INNGEST_EVENT_KEY}`
416 - default: `http://localhost:8288/e/<key>`
417 - 2026-06-18: Pi `memory-enforcer` observe events must follow this keyed Event API shape too; never POST to bare `/e/`. The extension resolves `INNGEST_EVENT_KEY` from env/local env files and skips observe emission when no key is available.
4183. Inngest server persists the event and resolves matching function triggers.
4194. Inngest dispatches function steps to the worker app graph that owns that function ID:
420 - host app (`system-bus-host`) for 101-host set
421 - cluster app (`system-bus-cluster`) for 12-cluster subset
4225. Worker handles callbacks via `/api/inngest` (Hono + `inngest/hono` handler).
4236. Each `step.run` result is memoized by Inngest; next step executes when prior completes.
4247. Completion/failure is queryable via GraphQL (`/v0/gql`) and CLI commands (`runs`, `run`, `event`, `events`).
425
426## Run capture flow: Machine hook → Central `/api/runs` → `memory/run.captured`
427
4281. Runtime-native hook captures only the new Run slice:
429 - Pi extension for Pi
430 - Claude Code Stop hook via `scripts/joelclaw-capture-session.ts`
431 - Codex hook/helper via `scripts/joelclaw-capture-codex-session.js`
432 - server-side runtimes can call capture inline instead of shelling through a hook.
4332. The hook posts JSON to Central:
434 - current live URL for satellites: `POST https://panda.tail7af24.ts.net/api/runs`
435 - Panda serves this through the host system-bus worker on `localhost:3111`
436 - auth: `Authorization: Bearer <~/.joelclaw/auth.json token>`
4373. `packages/system-bus/src/serve.ts` validates the bearer token against the persistent local SQLite registry `~/.joelclaw/capture-auth.db`. The worker refreshes this four-row registry from Typesense `machines_dev` in the background every five minutes. Known-token requests never wait for Typesense; an unknown token gets one bounded migration lookup.
4384. Worker writes the raw source of truth through `@joelclaw/memory#writeRunBlob`:
439 - default dev store: `~/.joelclaw/runs-dev/<user>/<yyyy-mm>/<run-id>.jsonl`
440 - companion metadata includes `user_id`, `machine_id`, `agent_runtime`, parent/conversation IDs, tags, byte count, and SHA-256.
4415. Worker emits `memory/run.captured` to Inngest.
4426. `packages/system-bus/src/inngest/functions/memory/run-captured.ts` appends Run metadata, chunks, and FTS rows to SQLite `sessions.db` in one transaction.
4437. If POST fails from a Machine, the hook writes the POST body into `~/.joelclaw/outbox/`; the Machine does not become Central just because capture is temporarily offline.
4448. If raw blobs exist but SQLite is stale, recover Inngest/worker registration first, then backfill with `scripts/backfill-session-index.ts`. Do not replay thousands of `memory/run.captured` events casually.
4459. The public Vercel app is not a Run ingress or search surface. Its `POST /api/runs` and `POST /api/runs/search` routes return `410`. Machine hooks must use the Central system-bus endpoint, and operators must use `joelclaw sessions search`.
446
447## Recall flow: scoped request → CLI composition root → lane-preserving result
448
4491. Interactive callers run in a trusted GitHub checkout or pass both `--project`
450 and `--workstream`. Automatic callers send one exact
451 `ComposedRecallRequestV1` through `joelclaw recall --request-file -` on
452 private stdin.
4532. Every request names principal, purpose, allowed privacy tiers, supersession
454 policy, and independent limits for reflections, observations, and curated
455 pages. The deprecated `--limit` flag is ignored.
4563. The CLI reads the pinned flowing-memory artifact and curated `critical.db`
457 projection, then returns three separate lanes: `flowing-reflections`,
458 `flowing-observations`, and `curated-pages`. Never merge their scores.
4594. Unavailable lanes return typed envelopes and exit 3. The gateway shows a
460 generic unavailable marker. Optional Inngest enrichment records redacted
461 telemetry and returns no context. Restate returns a generic degraded marker.
4625. Use `joelclaw sessions search "<query>" --source both --extract` for exact
463 Run/session wording, commands, and transcript evidence. Composed recall is a
464 broad memory surface, not raw transcript search.
4656. `typesense-recall` is config-selected rollback only. Status reports it as a
466 visible degraded warning without turning global health red.
467
468**Accepted cluster decision (2026-08-23):** automatic recall inside the
469system-bus and Restate cluster images is intentionally disabled/degraded after
470retiring old observation recall. `packages/recall` is present for module
471resolution, but the images do not receive a local semantic store or compiled
472`joelclaw` recall binary. Callers must take their explicit optional-enrichment
473degrade path. This is not a hidden deployment dependency. Re-enable cluster
474recall only after a separately reviewed secure remote flowing-read substrate
475exists.
476
477## Queue flow: `joelclaw queue emit` → Restate drainer → durable dispatch
478
4791. CLI `joelclaw queue emit <event>` persists a `QueueEventEnvelope` into Redis stream `joelclaw:queue:events` and indexes it in sorted set `joelclaw:queue:priority`.
4802. The `restate-worker` k8s deployment (`packages/restate/src/index.ts`) starts a deterministic queue drainer beside the channel callback listener.
4813. On startup, the drainer claims pending + never-delivered entries via `@joelclaw/queue#getUnacked()`, reindexes replayable entries, and emits OTEL replay evidence.
4824. Each drain tick selects the next priority candidate from the sorted set, resolves its static registry target from `packages/queue/src/registry.ts`, and POSTs a one-node DAG request to Restate `/dagOrchestrator/{workflowId}/run/send`.
4835. When backlog remains and a dispatch slot frees, the drainer self-pulses immediately instead of waiting for the next `QUEUE_DRAIN_INTERVAL_MS` heartbeat. That interval is now the idle poll / retry cadence, not a mandatory 2-second tax between successful sends.
4846. The current Story-3 bridge re-emits the queue item to its registered Inngest event target inside that one-node DAG request. This is deliberate: the deterministic queue/drainer is proven first; per-family Restate cutovers remain Story 4 work.
4857. On accepted Restate dispatch, the drainer acks the queue message; on failure it leaves the message in Redis, applies retry cooldown, and emits `queue.dispatch.failed` OTEL evidence.
4868. If backlog remains in Redis but the drainer stops making progress past `QUEUE_DRAIN_STALL_AFTER_MS`, it emits `queue.drainer.stalled` and exits non-zero so k8s restarts `deployment/restate-worker`. That is the self-heal path for a wedged drainer inside an otherwise-running Bun process.
4879. Crash recovery comes from the Redis stream + consumer-group replay path, not from vibes: restart the `restate-worker` pod, let `getUnacked()` reclaim the inflight entries, then drain resumes.
488
489## Workload flow: `joelclaw workload run` → Redis → Restate DAG → execution
490
4911. `joelclaw workload plan ... --stages-from <file>` can load an explicit stage DAG, validate unknown deps/self-deps/duplicates/cycles, and preserve per-stage acceptance gates.
4922. `joelclaw workload run <plan-artifact>` normalizes the selected stage into the canonical `workload/requested` runtime request.
4933. Queue admission writes the request into Redis, where the deterministic drainer forwards it into Restate as a `dagOrchestrator/{workflowId}/run/send` request.
4944. `dagOrchestrator` executes dependency waves: ready nodes in parallel, chained nodes only after every `dependsOn` node has terminal output.
4955. `dagWorker` executes the node handler:
496 - `shell` → subprocess work inside the `restate-worker` pod
497 - `infer` → `pi -p --no-session --no-extensions` inside the pod, using the mounted auth + identity + skill set
498 - `microvm` → Firecracker boot/restore through `/dev/kvm` with kernel/rootfs/snapshot files on PVC `firecracker-images`
4996. Each node emits OTEL (`dag.node.*`), and the workflow emits `dag.workflow.*` so queue → Restate → execution remains observable.
5007. Current truthful limit: the microVM runtime boots and restores snapshots in-cluster, but the broader exec-in-VM workspace drive protocol is still incomplete for general coding slices.
501
502## Webhook flow
503
5041. External service posts to `/webhooks/:provider`.
5052. Caddy routes `/webhooks/*` on `localhost:8443` to worker `localhost:3111`.
5063. `webhookApp` verifies signature, normalizes payload, emits Inngest events (`provider/event`).
5074. Inngest executes subscribed functions.
508
509## "Why did this run / not run" trace recipe
510
5111. Start with an
512
513…(truncated)