Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
joelhooks avatar

System Architecture

  • 9 installs
  • 61 repo stars
  • Updated August 4, 2026
  • joelhooks/joelclaw

Trace joelclaw event flow and topology, identify which worker runs a function, and debug why something ran or did not, from the canonical wiring map.

About

Documents the canonical joelclaw system topology and event wiring across CLI, Inngest, workers, gateway, and telemetry. A developer uses it to trace event flow, find which worker runs a function, or debug why something did or didn't run.

  • Single source of truth for joelclaw topology: CLI to Inngest to workers to gateway to telemetry
  • Grounded in direct reads of serve.ts, restate, gateway, k8s manifests, and launch agents

System Architecture by the numbers

  • 9 all-time installs (skills.sh)
  • Ranked #1,150 of 1,879 Documentation skills by installs in the Skillselion catalog
  • Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/joelhooks/joelclaw --skill system-architecture

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs9
repo stars61
Last updatedAugust 4, 2026
Repositoryjoelhooks/joelclaw

What it does

Trace joelclaw event flow and topology, identify which worker runs a function, and debug why something ran or did not, from the canonical wiring map.

Files

SKILL.mdMarkdownGitHub ↗

System Architecture (Canonical Topology)

This skill is the single source of truth for joelclaw system wiring. 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"
  • 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/langfuse.ts
  • packages/inference-router/src/tracing.ts
  • ADRs in ~/Vault/docs/decisions/ (required + topology-adjacent)
  • last 50 lines of ~/Vault/system/system-log.jsonl

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.mdnot inspected in this update

---

1) Physical Topology

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)

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)

Tailscale mesh state

  • tailscale status --json failed in this environment: UNKNOWN — needs manual verification

---

2) Process Inventory (Long-Running)

Host launchd inventory (snapshot)

Snapshot source: launchctl print gui/$(id -u)/<label> and plist inspection.
Launchd labelStatePID (snapshot)RolePorts / endpoints
com.joel.system-bus-workerrunning75292Host worker supervisor (worker-supervisor)supervises child bun on 3111
com.joel.restate-workerretired / rollback-onlyHistorical host Restate wrapper (scripts/restate/start.sh)superseded by deployment/restate-worker on 9080
com.joel.gatewayrunning81275Gateway daemon (packages/gateway/src/daemon.ts)WS :3018, Redis bridge
com.joel.caddyrunning9347Reverse proxy3443, 5443, 6443, 7443, 8290, 8443, 9443
com.joel.talonrunning96359Infra watchdoghealth 127.0.0.1:9999
com.joel.agent-secretsrunning98048Secret lease daemonno public port
com.joel.imsg-rpcrunning61110iMessage JSON-RPC socket daemonUnix socket /tmp/imsg.sock
com.joel.kube-operator-accessrunningvariesstable kubectl/talos operator tunnellocal 16443 (kube), 15000 (talos)
com.joel.voice-agentrunning71887voice agent runtimelocal 8081
com.joel.local-sandbox-janitorscheduled(launchd timer)ADR-0221 local sandbox janitor (scripts/local-sandbox-janitor.shjoelclaw workload sandboxes janitor)logs in /tmp/joelclaw/local-sandbox-janitor.{log,err}
com.joelclaw.agent-mailspawn scheduled(none in launchctl snapshot)agent-mail MCP HTTP serviceobserved listener 127.0.0.1:8765 (python process)
com.joel.colimanot runningstartup helper for Coliman/a
com.joel.k8s-reboot-healnot runningperiodic k8s heal scriptn/a
com.joel.system-bus-syncnot runningsync guard watchern/a
com.joel.gateway-tripwirenot runninggateway tripwire scriptn/a
com.joel.content-sync-watchernot runningfs watch -> content/updated eventn/a
com.joel.vault-log-syncnot runningVault log sync watchern/a

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

ServiceWorkload kindService typeService port(s)NodePort(s) / exposureRole
InngestStatefulSet inngestNodePort (inngest-svc)8288, 82898288, 8289Event API + connect ws
RedisStatefulSet redisNodePort63796379Queue/state/pubsub
TypesenseStatefulSet typesenseNodePort81088108 via Colima/Lima host publishSearch + telemetry store
RestateStatefulSet restateNodePort8080, 9070, 90718080, 9070, 9071Durable workflow ingress + admin + metrics
system-bus-workerDeploymentClusterIP3111in-cluster onlyCluster-role worker (12 functions)
restate-workerDeploymentClusterIP9080in-cluster onlydagOrchestrator + dagWorker + queue drainer in full agent image
docs-apiDeploymentNodePort38383838PDF/docs API + agentic search + taxonomy graph
dkronStatefulSetClusterIP (dkron-svc) + headless peer svc (dkron-peer)8080, 8946, 6868in-cluster only; operator access via short-lived CLI-managed tunnelDistributed cron scheduler for Restate pipelines
livekit-serverDeployment (Helm)NodePort80, 78817880 (for svc port 80), 7881LiveKit signaling + rtc tcp
bluesky-pdsDeployment (Helm-managed)NodePort30003000AT Proto PDS
minioStatefulSetClusterIP + NodePort9000, 900130900, 30901Legacy local S3-compatible runtime
aistor-s3-api (aistor ns)NodePort service (operator-managed)NodePort443, 900031000 (+ dynamic management NodePort)AIStor S3 API (TLS + management)
aistor-s3-console (aistor ns)NodePort service (operator-managed)NodePort944331001AIStor 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

  • Host function set: 101
  • Cluster function set: 12
  • Cluster subset functions:
  • approvalRequest, approvalResolve
  • todoistCommentAdded, todoistTaskCompleted, todoistTaskCreated
  • frontMessageReceived, frontMessageSent, frontAssigneeChanged
  • todoistMemoryReviewBridge
  • githubWorkflowRunCompleted, githubPackagePublished
  • webhookSubscriptionDispatchGithubWorkflowRunCompleted

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.

Kubernetes cluster worker manifest sets:

  • INNGEST_BASE_URL=http://inngest-svc:8288
  • INNGEST_SERVE_HOST=http://system-bus-worker:3111

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

1. CLI joelclaw send <event> calls Inngest.send(). 2. Inngest.send() POSTs event JSON to:

  • ${INNGEST_URL}/e/${INNGEST_EVENT_KEY}
  • default: http://localhost:8288/e/<key>

3. Inngest server persists the event and resolves matching function triggers. 4. 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

5. Worker handles callbacks via /api/inngest (Hono + inngest/hono handler). 6. Each step.run result is memoized by Inngest; next step executes when prior completes. 7. Completion/failure is queryable via GraphQL (/v0/gql) and CLI commands (runs, run, event, events).

Queue flow: joelclaw queue emit → Restate drainer → durable dispatch

1. CLI joelclaw queue emit <event> persists a QueueEventEnvelope into Redis stream joelclaw:queue:events and indexes it in sorted set joelclaw:queue:priority. 2. The restate-worker k8s deployment (packages/restate/src/index.ts) starts a deterministic queue drainer beside the channel callback listener. 3. On startup, the drainer claims pending + never-delivered entries via @joelclaw/queue#getUnacked(), reindexes replayable entries, and emits OTEL replay evidence. 4. 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. 5. 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. 6. 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. 7. 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. 8. 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. 9. 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

1. 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. 2. joelclaw workload run <plan-artifact> normalizes the selected stage into the canonical workload/requested runtime request. 3. Queue admission writes the request into Redis, where the deterministic drainer forwards it into Restate as a dagOrchestrator/{workflowId}/run/send request. 4. dagOrchestrator executes dependency waves: ready nodes in parallel, chained nodes only after every dependsOn node has terminal output. 5. dagWorker executes the node handler:

  • shell → subprocess work inside the restate-worker pod
  • inferpi -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

6. Each node emits OTEL (dag.node.*), and the workflow emits dag.workflow.* so queue → Restate → execution remains observable. 7. 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

1. External service posts to /webhooks/:provider. 2. Caddy routes /webhooks/* on localhost:8443 to worker localhost:3111. 3. webhookApp verifies signature, normalizes payload, emits Inngest events (provider/event). 4. Inngest executes subscribed functions.

"Why did this run / not run" trace recipe

1. joelclaw send <event> -d '<payload>' 2. joelclaw events --prefix <event-prefix> --hours 1 3. joelclaw event <event-id> (fan-out to function runs) 4. joelclaw run <run-id> (step trace + errors) 5. joelclaw runs --count 20 --hours 1 6. joelclaw otel search "<component/action>" --hours 1 7. Validate function ownership in index.host.ts / index.cluster.ts.

---

5) Port Map (Canonical)

Exposure sources: k8s service manifests, Caddyfile, kubectl get svc, lsof listeners.
PortListener / ownerWhat it isExposure path
3111host bun workerhost system-bus worker HTTP (/, /api/inngest, /webhooks, /observability/emit)local host; proxied via Caddy 3443 + webhook path via 8443
8080ssh forward (Colima) -> restateRestate ingress / workflow APINodePort + host forward
8288ssh forward (Colima) -> Inngest svcInngest API + dashboard backendNodePort + host forward; proxied via Caddy 9443
8289ssh forward (Colima) -> Inngest wsInngest connect websocketNodePort + host forward; proxied via Caddy 8290
6379ssh forward (Colima) -> RedisRedisNodePort + host forward
8108Typesense NodePort via Colima/Lima host publishTypesense APIstable host access for worker + CLI observability/search
9070ssh forward (Colima) -> restateRestate admin APINodePort + host forward
9071ssh forward (Colima) -> restateRestate metricsNodePort + host forward
9080k8s restate-worker serviceRestate worker HTTP (dagOrchestrator, dagWorker, queue drainer)ClusterIP only
random high local porttransient kubectl port-forward (CLI-managed) -> svc/dkron-svc:8080Dkron HTTP APIClusterIP only; short-lived operator tunnel
3838ssh forward (Colima) -> docs-apidocs-api HTTPNodePort + host forward; proxied via Caddy 5443
7880ssh forward (Colima) -> livekit-serverLiveKit signalingNodePort 7880; proxied via Caddy 7443
7881ssh forward (Colima) -> livekit-serverLiveKit RTC TCPNodePort 7881
3000k8s bluesky-pds NodePortBluesky PDS HTTPNodePort 3000
30900k8s minio-nodeportLegacy MinIO S3 API (HTTP)NodePort 30900
30901k8s minio-nodeportLegacy MinIO console (HTTP)NodePort 30901
31000k8s aistor-s3-api (aistor ns)AIStor S3 API (TLS)NodePort 31000
31001k8s aistor-s3-console (aistor ns)AIStor console (TLS)NodePort 31001
3443CaddyHTTPS reverse proxy to localhost:3111tailnet HTTPS
5443CaddyHTTPS reverse proxy to localhost:3838tailnet HTTPS
7443CaddyHTTPS reverse proxy to localhost:7880tailnet HTTPS
9443CaddyHTTPS reverse proxy to localhost:8288tailnet HTTPS
8290CaddyHTTPS reverse proxy to localhost:8289tailnet HTTPS
8443Caddy (HTTP)webhook/public ingress routerexpected Funnel target
6443Caddyreverse proxy to local 6333 (Qdrant)tailnet HTTPS
3018gateway daemongateway websocket stream portlocal
9999talonTalon health endpointlocal 127.0.0.1
8765agent-mail HTTP serviceMCP agent-mail APIlocal 127.0.0.1
15000com.joel.kube-operator-accessTalos APIstable local talosctl endpoint
16443com.joel.kube-operator-accessKubernetes APIstable local kubectl endpoint

Notes

  • Host NodePort exposure appears through an ssh listener process (Colima portForwarder=ssh).
  • Exact per-port ssh forward command line is UNKNOWN — needs manual verification (process introspection restricted in this environment).

---

6) Storage Topology

Redis

  • Runtime: k8s StatefulSet (redis:7-alpine, appendonly enabled).
  • Primary uses:
  • gateway queue/session keys (joelclaw:events:*, joelclaw:notify:*, joelclaw:gateway:sessions)
  • webhook subscriptions (joelclaw:webhook:*)
  • gateway health mute/streak keys (gateway:health:*)

Typesense

From observability code:

  • otel_events collection (canonical telemetry event store)
  • memory_observations collection (vector-aware memory index; schema validated at startup)
  • docs-api also points at http://typesense:8108 for docs search/index surfaces.

Firecracker runtime storage

  • PVC: firecracker-images
  • Mounted in deployment/restate-worker at /tmp/firecracker-test
  • Stores:
  • kernel (vmlinux)
  • rootfs (agent-rootfs.ext4)
  • snapshots (snapshots/vm.snap, snapshots/vm.mem)
  • Firecracker snapshot restore is currently operator-proven at ~9ms on the Colima VZ nested-virt path.

Inngest state

  • StatefulSet PVC mounted at /data
  • INNGEST_SQLITE_DIR=/data

docs-api surface

  • Deployment: docs-api on NodePort 3838
  • Route count: 11 endpoints including /health
  • Key routes:
  • GET /search — hybrid chunk search with concept, concepts, doc_id, expand, and assemble
  • GET /docs/search
  • GET /docs
  • GET /docs/:id
  • GET /docs/:id/toc
  • GET /docs/:id/chunks
  • GET /chunks/:id
  • GET /concepts
  • GET /concepts/:id
  • GET /concepts/:id/docs
  • Taxonomy surface: 21-concept SKOS graph (10 parents + 11 sub-concepts) with broader, narrower, and related edges.

NAS (ADR-0088 + ADR-0187)

Tiering policy:

  • Tier 1 local SSD (hot runtime state)
  • Tier 2 NAS NVMe (/Volumes/nas-nvme/volume2/data)
  • Tier 3 NAS HDD (/Volumes/three-body)

Access paths

FromNVMe tier (1.5TB)HDD tier (56TB)Method
macOS host/Volumes/nas-nvme/Volumes/three-bodyNFS mount via LaunchDaemon
k8s podsPVC nas-nvmePVC nas-hddNFS PV (192.168.1.163)
host-worker funcs/Volumes/nas-nvme/Volumes/three-bodyDirect path (runs on macOS)

k8s ↔ NAS networking

k8s pods reach the NAS via a LAN route through the Colima col0 bridge: Talos → Docker NAT → VM col0 → macOS (ip.forwarding=1) → LAN → NAS

The VZ NAT on eth0 does NOT forward LAN traffic. Route persisted in Colima provision + colima-tunnel script: ip route replace 192.168.1.0/24 via 192.168.64.1 dev col0

Always use IP 192.168.1.163, never hostname three-body — DNS doesn't resolve from k8s.

Degradation contract (ADR-0187):

  • writes must fallback local -> remote -> queued
  • queue spool default: /tmp/joelclaw/nas-queue

Vault

  • Obsidian vault at /Users/joel/Vault
  • system log file: /Users/joel/Vault/system/system-log.jsonl

---

7) Networking Topology

Caddy reverse proxy routes (from ~/.local/caddy/Caddyfile)

  • https://panda.tail7af24.ts.net:9443 -> localhost:8288 (Inngest)
  • https://panda.tail7af24.ts.net:8290 -> localhost:8289 (Inngest connect)
  • https://panda.tail7af24.ts.net:3443 -> localhost:3111 (worker)
  • https://panda.tail7af24.ts.net:5443 -> localhost:3838 (docs-api)
  • https://panda.tail7af24.ts.net:7443 -> localhost:7880 (LiveKit)
  • https://panda.tail7af24.ts.net:6443 -> localhost:6333 (Qdrant)
  • http://localhost:8443 path router:
  • /webhooks/* -> localhost:3111
  • fallback -> localhost:8288

Tailscale + Funnel

  • Config comments and ADR-0051 describe Funnel path :443 -> localhost:8443.
  • Runtime tailscale status unavailable here: UNKNOWN — needs manual verification.

External webhook ingress

Expected path: 1. Internet provider -> Tailscale Funnel :443 2. Funnel -> local :8443 3. Caddy path route /webhooks/* -> worker :3111 4. worker /webhooks/:provider verifies + emits Inngest event

---

8) CLI Wiring (Command Tree → Endpoint Surface)

Primary command tree root: packages/cli/src/cli.ts.

Endpoint map by command family

Command familyPrimary backend
sendInngest Event API POST /e/<event-key>
runs, run, functions, event, eventsInngest GraphQL POST /v0/gql
statusInngest/worker health probes + k8s checks + agent-mail liveness
gateway *Redis keys/channels + launchd/system ops
workload *workload planner + Redis queue admission + Restate dagOrchestrator / dagWorker runtime
docs *docs-api REST API (/search, /docs/*, /chunks/*, /concepts*)
restate cron *Dkron REST API via direct --base-url or short-lived kubectl port-forward to svc/dkron-svc
otel *Typesense otel_events via capability adapter
recall *Typesense recall adapter
mail *Agent-mail MCP HTTP (127.0.0.1:8765) via CLI adapter wrappers
inngest *worker launchd + Talon + k8s + Typesense diagnostics

Config source:

  • ~/.config/system-bus.env (plus env overrides)
  • defaults:
  • INNGEST_URL=http://localhost:8288
  • INNGEST_WORKER_URL=http://localhost:3111

---

9) Observability + Tracing Topology

OTEL event pipeline

  • Worker emits via emitOtelEvent() / emitMeasuredOtelEvent().
  • Gateway emits via @joelclaw/telemetry (emitGatewayOtel) to:
  • default OTEL_EMIT_URL=http://localhost:3111/observability/emit
  • Worker endpoint /observability/emit validates token (x-otel-emit-token) if configured.
  • Store path (storeOtelEvent):

1. Typesense otel_events (primary) 2. optional Convex mirror for high-severity recent window 3. optional Sentry forward for warn/error/fatal

Langfuse integration points

  • Gateway boot: packages/gateway/src/daemon.ts calls initTracing({}) from inference-router.
  • Inference router traces model-route decisions:
  • packages/inference-router/src/tracing.ts
  • used from packages/inference-router/src/router.ts
  • System-bus LLM traces:
  • packages/system-bus/src/lib/langfuse.ts (traceLlmGeneration)
  • called by packages/system-bus/src/lib/inference.ts and channel-message-classify.ts

---

10) Key ADR Topology Decisions

ADRTitleStatusTopology impact
ADR-0048Webhook gatewayshipped/webhooks/:provider normalization + signature verification + Inngest emission
ADR-0088NAS-backed storage tieringshippedDefines SSD/NAS NVMe/NAS HDD storage contract
ADR-0089Single-source worker deploymentshippedHost/cluster role split + single canonical source
ADR-0144Gateway hexagonal architectureshippedGateway as composition root; heavy logic in @joelclaw/*
ADR-0155Three-stage story pipelineshippedSimplified story function flow through Inngest durable steps
ADR-0156Graceful worker restartsupersededHistorical restart strategy; superseded by Talon ADR
ADR-0159Talon watchdog daemonshippedCompiled watchdog + infra supervision model
ADR-0038Embedded pi gateway daemonshippedAlways-on gateway session architecture
ADR-0051Tailscale Funnel ingressshippedPublic webhook ingress via Funnel/Caddy pattern
ADR-0148k8s resilience policyacceptedNodePort-first exposure, probe requirements, restart recovery checklist
ADR-0158worker-supervisor binarysupersededLegacy supervisor ADR now superseded, but binary remains in active launchd path
ADR-0182node-0 localhost resilienceshippedendpoint class fallback (localhost -> vm -> svc_dns)
ADR-0187NAS degradation fallback contractacceptedmandatory local/remote/queued write fallback
ADR-0212AIStor as local S3 runtimeacceptedmaintained local S3 runtime in aistor namespace; legacy MinIO retained for rollback

---

10.1) Sandbox Execution Contract (@joelclaw/agent-execution)

Package: packages/agent-execution/ Purpose: Canonical contract for sandboxed story execution shared between Restate workflows, system-bus Inngest functions, and k8s Job launcher.

Contract Types

Request: SandboxExecutionRequest

  • workflowId, requestId, storyId: identifiers
  • task: story prompt/task to execute
  • agent: { name, variant?, model?, program? }
  • sandbox: "workspace-write" | "danger-full-access"
  • baseSha: git SHA before execution
  • cwd?: working directory
  • timeoutSeconds?: timeout
  • verificationCommands?: post-execution verification
  • sessionId?: tracking identifier

Result: SandboxExecutionResult

  • requestId: correlation ID
  • state: "pending" | "running" | "completed" | "failed" | "cancelled"
  • startedAt, completedAt?, durationMs?: timing
  • artifacts?: execution artifacts (see below)
  • error?: error message (failed state)
  • output?: stdout/stderr output

Artifacts: ExecutionArtifacts

  • headSha: git SHA after execution
  • touchedFiles: list of modified/untracked files from git status --porcelain
  • patch?: git patch content (format-patch or diff)
  • verification?: { commands, success, output }
  • logs?: { executionLog?, verificationLog? }

Repo Materialization (Story 3)

Function: materializeRepo(targetPath, baseSha, options)

Behavior:

  • Clone repo if target path doesn't exist (requires remoteUrl)
  • Fetch + checkout if target path exists
  • SHA verification after checkout
  • Automatic unshallow if SHA not in shallow clone
  • Isolated sandbox-local workspace (host worktree untouched)

Returns: { path, sha, freshClone, durationMs }

Key options:

  • remoteUrl?: remote URL for fresh clone
  • branch?: branch/ref to fetch (default: "main")
  • depth?: shallow clone depth (default: 1)
  • includeSubmodules?: include submodules
  • timeoutSeconds?: timeout (default: 300)

Artifact Export (Story 3)

Function: generatePatchArtifact(options)

Behavior:

  • Captures touched-file inventory via getTouchedFiles()
  • Generates git patch from baseSha..headSha:
  • Uses git format-patch if commits exist in range
  • Uses git diff if only uncommitted changes
  • Optionally includes untracked files as patch content
  • Embeds verification summary and log references
  • Serializable to JSON via writeArtifactBundle()

Key options:

  • repoPath: path to git repo
  • baseSha: base SHA (start of diff range)
  • headSha?: head SHA (default: HEAD)
  • includeUntracked?: include untracked files (default: true)
  • verificationCommands?, verificationSuccess?, verificationOutput?: verification data
  • executionLogPath?, verificationLogPath?: log references
  • timeoutSeconds?: timeout (default: 60)

Returns: ExecutionArtifacts

Promotion Boundary (Phase 1)

Authoritative output is patch bundle + metadata.

Sandbox runs do not merge to main or push to remote. The runtime: 1. Materializes repo at baseSha in sandbox-local workspace 2. Executes agent task 3. Runs verification commands 4. Exports patch artifact with touched files and verification results 5. Emits SandboxExecutionResult event with ExecutionArtifacts

Promotion is a separate operator decision:

  • Restate workflow receives ExecutionArtifacts
  • Operator reviews patch + verification summary
  • Operator applies patch to host repo (or discards)
  • Operator commits and pushes (if approved)

This keeps sandbox runs isolated and reversible.

k8s Job Integration

Job spec generation: generateJobSpec(request, options)

Cold k8s Jobs for isolated story execution:

  • Deterministic Job naming keyed by requestId
  • Runtime image contract: Git, Bun, agent tooling, /workspace directory
  • Environment-driven config: WORKFLOW_ID, REQUEST_ID, STORY_ID, TASK_PROMPT_B64, BASE_SHA, etc.
  • Resource limits: 500m-2 CPU, 1-4Gi memory (configurable)
  • TTL cleanup: auto-delete after 5 minutes (default)
  • Active deadline: 1 hour max runtime (default)
  • No automatic retries (backoffLimit: 0)
  • Security: non-root (UID 1000), no privilege escalation, capabilities dropped

Runtime contract: 1. Decode TASK_PROMPT_B64 from env 2. Call materializeRepo() at BASE_SHA 3. Execute agent with task 4. Run verification commands (if VERIFICATION_COMMANDS_B64 set) 5. Call generatePatchArtifact() with results 6. Emit SandboxExecutionResult event with ExecutionArtifacts 7. Exit 0 (success) or non-zero (failure)

Cancellation: Delete Job resource (SIGTERM to container)

Job deletion: generateJobDeletion(requestId) -> { name, namespace, propagationPolicy }

See k8s/agent-runner.yaml for full runtime contract specification.

Topology Impact

  • Story 2: Added contract types and Job spec generation
  • Story 3: Added repo materialization and artifact export helpers
  • ADR-0221 phase 1: added explicit local sandbox isolation primitives — deterministic sandbox identity, deterministic local sandbox paths, per-sandbox env materialization, minimal/full mode vocabulary, and a JSON registry helper for host-worker sandboxes
  • ADR-0221 phase 2: wired those local helpers into the real host-worker system/agent-dispatch local backend so sandbox runs now allocate deterministic paths under ~/.joelclaw/sandboxes/, materialize .sandbox.env, persist registry state, and carry localSandbox metadata in inbox snapshots
  • ADR-0221 phase 3/4/5/6: phase 3 added terminal retention/cleanup policy (cleanupAfter + registry metadata), opportunistic pruning of expired local sandboxes on new-run startup, copy-first .devcontainer materialization helpers with exclusion rules for env/secret junk, live sandbox env injection so the agent process actually sees the reserved runtime identity, a hash-preserving sandbox identity fix after live dogfood exposed path collisions from long shared requestId prefixes, abbreviated-baseSha acceptance during repo materialization, truthful failed inbox snapshots when dispatch crashes before normal terminal writeback, and a repeatable operator probe at bun scripts/verify-local-sandbox-dispatch.ts; phase 4 adds sandboxMode=minimal|full through the workload front door, requested-cwd mapping inside the cloned checkout, compose-backed full local mode startup, the reality that stale Restate workers can reject workload/requested until restarted and reloaded, a recursion guard because sandboxed stage runs were able to call scripts/verify-workload-full-mode.ts / joelclaw workload run from inside the sandbox and spawn nested canaries instead of terminating honestly, and a guarded workflow-rig proof run (WR_20260310_013158) that completes terminally with healthy compose startup plus clean teardown; phase 5 adds the operator-facing CLI surface joelclaw workload sandboxes list|cleanup|janitor so retained sandboxes can be inspected and janitored on demand instead of only during startup opportunistic pruning, and the operator surfaces now reconcile registry entries against per-sandbox metadata before reporting or deleting so old partial writeback residue stops lying about terminal state; phase 6 makes janitoring scheduled instead of purely manual via repo-managed launchd service com.joel.local-sandbox-janitor, which runs scripts/local-sandbox-janitor.shjoelclaw workload sandboxes janitor at load and every 30 minutes
  • Future: Runtime image build, hot-image CronJob, warm-pool scheduler, Restate integration

Current state: the host-worker local sandbox path is now using the local-isolation helpers in production code, the package has a concurrent proof that two local sandboxes keep distinct compose identity plus copied devcontainer state, guarded full-mode workflow-rig dogfood closes terminally, and cleanup now has both on-demand CLI surfaces and scheduled launchd janitoring. Follow-on work is now about deeper runtime ergonomics and debugging any remaining non-terminal stale residues, not missing basic cleanup automation.

---

11) Verification Commands (Health + Wiring)

Core topology

# Colima + VM IP
colima status --json

# Kubernetes control plane + node
kubectl cluster-info
kubectl get nodes -o wide

# Core workloads
kubectl get pods -n joelclaw -o wide
kubectl get svc -n joelclaw -o wide

Host supervision

# Worker supervisor launchd state
launchctl print gui/$(id -u)/com.joel.system-bus-worker | rg "state =|pid =|last exit code"

# Gateway / Caddy / Talon
launchctl print gui/$(id -u)/com.joel.gateway | rg "state =|pid ="
launchctl print gui/$(id -u)/com.joel.caddy | rg "state =|pid ="
launchctl print gui/$(id -u)/com.joel.talon | rg "state =|pid ="

# Talon health
curl -s http://127.0.0.1:9999/health

Worker role split

# Parse role counts directly from source lists
python - <<'PY'
import re
from pathlib import Path
for f,name in [('packages/system-bus/src/inngest/functions/index.host.ts','host'),('packages/system-bus/src/inngest/functions/index.cluster.ts','cluster')]:
    txt=Path(f).read_text()
    body=re.search(rf'export const {name}FunctionDefinitions = \[(.*?)\];', txt, re.S).group(1)
    count=sum(1 for line in body.splitlines() if line.strip() and not line.strip().startswith('//'))
    print(name, count)
PY

# Inngest app ID derivation logic
rg -n "INNGEST_APP_ID|system-bus-host|system-bus-cluster|WORKER_ROLE" packages/system-bus/src/inngest/client.ts

Event flow trace

# Send event
joelclaw send <event> -d '<json>'

# Trace event and resulting runs
joelclaw events --prefix <event-prefix> --hours 1 --count 20
joelclaw event <event-id>
joelclaw runs --hours 1 --count 20
joelclaw run <run-id>

# Telemetry correlation
joelclaw otel search "<component_or_action>" --hours 1

Networking

# Caddy route config
caddy validate --config ~/.local/caddy/Caddyfile

# Listening ports snapshot
/usr/sbin/lsof -iTCP -sTCP:LISTEN -n -P

# Tailscale runtime (if daemon available)
tailscale status --json

---

12) Known Unknowns (Do Not Guess)

  • Tailscale daemon state is not readable in this environment.
  • tailscale status --json -> failed to connect.
  • UNKNOWN — needs manual verification
  • docs/architecture.md, docs/deploy.md, docs/observability.md are absent in-repo.
  • UNKNOWN — needs manual verification
  • Exact command-line ownership of all Colima ssh forwarding ports (64784, 64785, 9627, etc.)
  • UNKNOWN — needs manual verification
  • Ingress controller runtime status for k8s/docs-api-ingress.yaml
  • UNKNOWN — needs manual verification

---

13) Mandatory Update Policy (Non-Optional)

Update this skill in the same change whenever any of these change:

1. Worker runtime wiring

  • serve.ts, client.ts, index.host.ts, index.cluster.ts
  • WORKER_ROLE, app IDs, serveHost behavior, registration path

2. Supervision/process topology

  • any ~/Library/LaunchAgents/com.joel*.plist
  • infra/worker-supervisor/*, Talon behavior, gateway launch script/label

3. Kubernetes topology

  • any file under k8s/
  • Helm values affecting core services (livekit, pds, etc.)
  • Service type/port changes (NodePort/ClusterIP)

4. Networking/ingress

  • Caddyfile route/port changes
  • Tailscale/Funnel hostnames or ingress path changes
  • Colima/VM networking model changes

5. Storage topology

  • Redis keyspace contracts for gateway/webhook routing
  • Typesense telemetry collection/schema changes
  • NAS mount/fallback/queue contract changes

6. Observability/tracing

  • OTEL emit endpoint/token behavior
  • telemetry storage path changes (Typesense/Convex/Sentry)
  • Langfuse integration points

7. CLI control-plane routing

  • command families moved to different endpoints/services

8. ADR status changes affecting topology

  • especially ADR-0048, 0088, 0089, 0144, 0155, 0156, 0159, 0182, 0187

If any item above changed and this skill was not updated, this skill is stale and non-canonical.

Related skills

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.