Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
28 KiB
joelclaw Architecture
Overview
joelclaw is a personal AI infrastructure monorepo built around event-driven workflows, always-on gateway services, and sandboxed agent execution. The system follows hexagonal architecture principles (ADR-0144), with heavy logic in standalone packages behind interfaces.
Core Architecture Principles
-
Hexagonal Architecture (ADR-0144): Heavy logic lives in standalone
@joelclaw/*packages behind interfaces. Consumers (gateway, CLI) are thin composition roots that wire adapters together. -
Event-Driven: Durable work now runs through a dual-runtime phase: Inngest remains the legacy/event-native backbone while Restate owns new DAG-style execution and queue-drainer pilots. Events remain the primary integration point between subsystems.
-
CLI-First: The
joelclawCLI is the primary operator interface. All commands return HATEOAS JSON envelopes for agent consumption. -
Single Source of Truth: Never copy files across boundaries. Symlink. Skills live canonically in
skills/and are symlinked to agent home directories. -
Observable by Default: Every pipeline step emits structured telemetry via OTEL. Silent failures are bugs.
System Components
Infrastructure Layer
┌─ Mac Mini "Panda" (M4 Pro, 64GB, always-on) ──────────────────────┐
│ │
│ Colima VM (VZ framework, aarch64, grpc port-forwarder, 24 GiB) │
│ └─ Talos v1.12.4 container (18 GiB cap) → k8s v1.35.0 (single node) │
│ └─ namespace: joelclaw │
│ ├─ inngest-0 (StatefulSet, ports 8288/8289) │
│ ├─ redis-0 (StatefulSet, port 6379) │
│ ├─ typesense-0 (StatefulSet, port 8108) │
│ ├─ restate-0 (StatefulSet, ports 8080/9070/9071) │
│ ├─ system-bus-worker (Deployment, port 3111) │
│ ├─ restate-worker (Deployment, port 9080) │
│ ├─ dkron-0 (StatefulSet, port 8080) │
│ ├─ minio-0 (StatefulSet, ports 9000/9001) │
│ ├─ docs-api (Deployment, port 3838) │
│ ├─ livekit-server (Deployment, ports 7880/7881) │
│ └─ bluesky-pds (Deployment, port 3000) │
│ │
│ Operator access: direct Colima ports 6443 (kube) / 50000 (Talos) │
│ Gateway daemon (pi session, always-on, Redis event bridge) │
│ NAS "three-body" (ASUSTOR, 10GbE NFS, 64TB RAID5 + 1.9TB NVMe) │
│ Tailscale mesh (all devices) │
└─────────────────────────────────────────────────────────────────────┘
Package Architecture
The monorepo follows pnpm workspaces with strict package boundaries:
Core Packages
@joelclaw/cli
- Primary operator interface
- Built with
@effect/cli - Compiled to binary at
~/.bun/bin/joelclaw - Returns HATEOAS JSON envelopes
- Must never crash (lazy-load heavy dependencies)
@joelclaw/sdk
- Programmatic wrapper for CLI contracts
- Type-safe interface to joelclaw operations
- Used by other packages to invoke CLI commands
@joelclaw/system-bus
- 110+ Inngest durable functions
- Webhook gateway for external integrations
- Deployed to k8s as
system-bus-worker - Event processing backbone
@joelclaw/gateway
- Chat SDK adapter layer for Telegram, Slack, and Discord behind joelclaw contract-v2 policy; iMessage remains hand-rolled
- One embedded Chat SDK runtime and one platform-listener owner; shadow normalization stays in-process
- Always-on pi session with Redis event bridge
- Durable command queue remains the only inbound execution path
- Contract-v2 outbound receipts and wired reaction correlation use
flowId; reply correlation remains schema-only until its acting publisher lands - Sleep mode awareness
@joelclaw/message-contract
- Versioned outbound kinds (
memory,alert,digest,ask,receipt) and one kind-to-route table - HATEOAS delivery receipts with platform IDs mapped back to
flowId - Bus schemas for
message/reaction.receivedandmessage/reply.received; reaction publication is wired, reply publication is not yet acting
@joelclaw/restate
- Restate worker package for durable DAG/workflow execution
- Hosts deploy gate,
dagOrchestrator,dagWorker, and the deterministic queue drainer - Owns the execution-adjacent queue → Restate
/sendbridge for ADR-0217 Story 3 - Restate server runs in k8s as
statefulset/restateand is exposed on NodePort8080/9070/9071 deployment/restate-workeris the current durable runtime on port9080dagWorkerhandlers now covershell,infer, andmicrovm- The drainer now self-pulses immediately when backlog remains and a dispatch slot frees, so
QUEUE_DRAIN_INTERVAL_MSis an idle poll heartbeat instead of a fixed per-message tax - If Redis backlog remains but the drainer stops making progress for
QUEUE_DRAIN_STALL_AFTER_MS, it emitsqueue.drainer.stalledand exits non-zero so k8s can restart the pod and replay the backlog instead of silently wedging pilot traffic behind a still-listening Bun process - The worker image is a full agent environment: Bun + Node +
pi+codex, the full repo checkout, 76 skills, mounted pi auth, and the joelclaw identity chain - Provides the current operator-facing sandbox orchestration surface
- The repo-managed launchd service
com.joel.local-sandbox-janitornow runsjoelclaw workload sandboxes janitorat load and every 30 minutes so ADR-0221 retained local sandbox cleanup is scheduled instead of purely manual - Hosts the pi-mono research/indexing sync that materializes GitHub docs/issues/PRs/comments/commits/releases into Typesense collection
pi_mono_artifactsvia a Restate DAG + host runner
Workload Runtime (Restate + Firecracker)
joelclaw workload is now the durable runtime front door for staged work:
joelclaw workload plan --stages-from stages.json
↓ writes plan artifact
joelclaw workload run <plan.json>
↓
Redis queue admission
↓
Restate dagOrchestrator
↓
dagWorker wave execution
├─ shell → subprocess work in the restate-worker pod
├─ infer → pi inference in the pod
└─ microvm → Firecracker boot/restore in the pod
↓
Artifacts + inbox truth + OTEL
Firecracker is the isolation substrate for the new microVM path:
- nested virtualization rides the Colima VZ stack and
/dev/kvm - the
restate-workerpod mounts PVCfirecracker-images - kernel, rootfs, and snapshot files live on that PVC
- snapshot restore is currently operator-proven at ~9ms
Restate agent image composition
The restate-worker image contains:
- Bun + Node.js
- global
piCLI - global
codexCLI - the monorepo
packages/andapps/checkout - 76 canonical skills symlinked into pi's discovery path
- Firecracker binary
- mounted pi auth secret and mounted joelclaw identity files at runtime
Contract Packages
@joelclaw/agent-execution (NEW)
- Canonical sandbox execution contract
- Shared request/response types for sandboxed story execution
- Lifecycle states:
pending,running,completed,failed,cancelled - Agent identity tracking: name, variant, model, program
- Sandbox profiles:
workspace-write,danger-full-access - Artifact manifests: baseSha, headSha, touched files, verification results
- Runtime validation schemas and type guards
- Repo materialization:
materializeRepo()for clean checkout at exact SHA - Artifact export:
generatePatchArtifact()for auditable patch bundles - Touched-file inventory:
getTouchedFiles()captures modified/untracked files - Verification helpers:
verifyRepoState()for SHA validation - Local sandbox primitives: deterministic sandbox identity, path resolution, per-sandbox env materialization, terminal retention/cleanup policy, copy-first devcontainer materialization helpers, minimal/full mode contract, and JSON registry helpers for local host-worker isolation
- Firecracker microVM primitives:
microvm.tsnow owns Firecracker socket-path resolution, cold-boot vs snapshot-restore request builders, lifecycle helpers (boot,restore,pause,snapshot,destroy), and the initial workspace command-file protocol for Lambda-style single-use guest execution - Consumed by: Restate workflows, system-bus functions, k8s Job launcher, runtime images
This package eliminates ad-hoc type duplication between Restate and system-bus. All sandboxed story execution must use these types to ensure contract stability. Story 3 added repo materialization and artifact export helpers for isolated sandbox runs. ADR-0221 phase 1 added the first explicit local-isolation primitives, and phase 2 now feeds those helpers into the real host-worker local sandbox path. ADR-0230 step 7 added the package-level Firecracker runner surface, and step 8 wires Restate DAG nodes to dispatch handler: "microvm" executions through that package.
Sandboxed Story Execution
Current live path: local sandbox runner on the host worker
executionMode: "sandbox"is live insystem/agent-dispatch- Default
sandboxBackendis still"local" - Each local run now resolves a deterministic sandbox identity/path under
~/.joelclaw/sandboxes/, preserves a request-derived hash in bothsandboxIdandCOMPOSE_PROJECT_NAMEso long shared request prefixes still diverge cleanly, materializes a per-sandbox.sandbox.env, injects that sandbox identity into the live agent process environment, writes sandbox state to the JSON registry, and persists artifact bundles into the sandbox directory - Terminal local sandboxes now carry a retention decision (
cleanupAfter) and the local backend opportunistically prunes expired retained sandboxes before starting a new run @joelclaw/agent-executionnow includes copy-first.devcontainermaterialization helpers with exclusion rules for env/secret junk, plus a concurrent package-level proof that two local sandboxes keep distinctCOMPOSE_PROJECT_NAMEvalues and isolated copied devcontainer state- Repo materialization now treats abbreviated
baseShavalues as valid refs when they resolve to the checked-out commit, which matches how the live worker receives request payloads during dogfood runs InboxResultsnapshots for local sandbox runs now carrylocalSandboxmetadata so cancellation and operator follow-up can see the sandbox identity/path/env/registry surface, andsystem/agent-dispatchnow overwrites those snapshots with a terminalfailedstate if execution crashes before the normalwrite-inboxstep- Firecracker microVM helpers now exist in
@joelclaw/agent-execution, and Restate DAG nodes can now dispatchhandler: "microvm"runs through that package via dynamic import; broader workload-dispatch defaults still remain on the proven local sandbox path until later ADR-0230 steps land - host-worker agent subprocess capture now uses exit-driven temp files instead of waiting on pipe EOF, because codex/claude/bash descendants can inherit stdout/stderr and otherwise block terminal inbox writeback long after the real parent process has already exited
- the installed CLI now has an ADR-0221 operator surface:
joelclaw workload sandboxes list|cleanup|janitorreads the registry, reconciles it against each sandbox’ssandbox.jsonmetadata before reporting or deleting, reports retention/filesystem truth, supports bounded manual cleanup, and runs the dedicated expired-sandbox janitor path instead of waiting for the next sandbox startup - Narrow operator proof now lives at
bun scripts/verify-local-sandbox-dispatch.ts; it dispatches one happy-path local sandbox run plus one intentional bad-SHA run and waits for truthful terminal inbox state - The host-worker path now also has a deterministic non-LLM verification lane:
tool: "canary"insystem/agent-dispatchruns fixed subprocess scenarios through the same timeout/capture/writeback path,bun scripts/verify-agent-dispatch-timeout.tsforces the outer timeout path live without depending on an LLM to cooperate,joelclaw status --agent-dispatch-canaryis the canonical on-demand health surface for that proof, the defaultjoelclaw statusenvelope now shows the latest persisted canary truth, and the existingcheck/system-healthsignals schedule can optionally run the same proof when the live worker setsHEALTH_AGENT_DISPATCH_CANARY_SCHEDULE=signals - The host-worker path now also has an opt-in full local mode: it maps the requested sandbox
cwdinto the cloned checkout, discovers compose files relative to that workdir, and can bring up a compose-backed runtime with the sandbox-specificCOMPOSE_PROJECT_NAMEbefore agent execution starts - Workflow-rig dogfood proved the new front door can carry
sandboxMode=fullthroughjoelclaw workload run→workload/requested→system/agent.requested, and it exposed one real upstream ops bug along the way: a stale long-running Restate worker can rejectworkload/requestedas unregistered until it is restarted and reloads the queue registry - Dogfood also exposed a second, more embarrassing failure mode: stage-2 agents could recurse by running
scripts/verify-workload-full-mode.tsfrom inside the sandbox, and that verifier itself launches anotherjoelclaw workload run. The runtime now blocks nested workflow-rig execution inside sandboxed stage runs unless explicitly overridden for debugging, and the sandbox task contract tells the agent to use direct local proof commands instead - Guarded workflow-rig dogfood is now earned end-to-end for stage-2:
bun scripts/verify-workload-full-mode.tsproducedWR_20260310_013158, the stage completed terminally, the compose-backed runtime came up healthy, emitted the requiredfull-mode-ok|full|...proof line inside the summary output, and tore itself back down with no remaining containers - Gate A (non-coding) and Gate B (minimal coding) are proven, and a real ADR-0217 acceptance run completed on this path without dirtying the operator checkout
- This remains the current working isolation surface for autonomous story execution
Opt-in next gate now landed in code: cold k8s Jobs
sandboxBackend: "k8s"is now a real control-plane path in repo, but it is still meant for supervised rollout rather than broad default enablement- Deterministic Job naming keyed by
requestId - Runtime image contract: Git, Bun, agent tooling,
/workspacedirectory - Environment-driven config:
WORKFLOW_ID,REQUEST_ID,STORY_ID,BASE_SHA,REPO_URL,REPO_BRANCH,TASK_PROMPT_B64, optional verification commands, and callback settings - Result callback path: runner posts
SandboxExecutionResultto/internal/agent-result; the worker preservesInboxResult.sandboxBackendand optional Job metadata for operator visibility - Log fallback path: Job logs include result markers so the host worker can still recover terminal truth if callback delivery fails
- 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
- Cancellation: delete Job resource (SIGTERM to container) plus host-worker cancellation cleanup
- Job spec generation via
@joelclaw/agent-execution/job-spec, Job lifecycle helpers via@joelclaw/agent-execution/k8s, and runtime entrypoint inpackages/agent-execution/src/job-runner.ts
Runtime Image Contract:
Required tools:
- Git (checkout, diff, commit)
- Bun runtime
- Runner-installed agent programs (currently `claude` and/or other installed CLIs; `pi` remains local-backend only for now)
Expected paths:
- `/workspace` (working directory)
- `/app/packages/agent-execution/src/job-runner.ts` (default runtime entrypoint in the current image contract)
Expected behavior:
1. Decode `TASK_PROMPT_B64` from env
2. Materialize repo at `BASE_SHA` from `REPO_URL`/`REPO_BRANCH` in sandbox-local workspace
3. Execute agent with task
4. Run verification commands (if `VERIFICATION_COMMANDS_B64` set)
5. Export patch artifact with touched files and verification results
6. Print terminal `SandboxExecutionResult` markers to stdout and POST the same result to `/internal/agent-result`
7. Exit `0` (success) or non-zero (failure)
Cancellation handling:
- Gracefully handle SIGTERM
- Emit a cancelled terminal result when possible
- Exit promptly
See k8s/agent-runner.yaml for the full runtime contract specification.
Repo Materialization (Story 3):
materializeRepo(): Clone or checkout repo at exact SHA in sandbox-local workspace- Fresh clone if target path doesn't exist, fetch + checkout otherwise
- SHA verification after checkout with automatic unshallow if needed
- Isolated from host worktree — no mutation of operator checkout
- Returns:
{ path, sha, freshClone, durationMs }
Artifact Export (Story 3):
generatePatchArtifact(): Export auditable patch from baseSha..headSha- Captures touched-file inventory via
git status --porcelain - Generates git patch (format-patch for commits, diff for uncommitted)
- Includes verification summary: commands, success, output
- Includes log references: executionLog, verificationLog
- Serializable to JSON via
writeArtifactBundle() - Promotion boundary: Patch artifact is authoritative output; merge/push is operator decision
Phase 1 Trade-Off: Sandbox runs produce patch bundles, not direct commits to main. This keeps runs isolated and reversible while we validate the story execution quality. Future phases may introduce automatic merge with approval gates.
@joelclaw/inference-router
- Model selection catalog
- Provider fallback chains
- Single source of truth for model→provider mappings
- Pipeline policy is OpenAI Codex-first: GPT-5.6 Sol for default/summary/digest/reasoning/complex work, GPT-5.4 Mini for cheap simple/classification/json/rewrite work, GPT-5.4 as stable secondary fallback. Anthropic catalog entries remain for compatibility and vision fallback, not pipeline defaults.
@joelclaw/model-fallback
- Provider fallback implementation
- Retry logic for failed inference calls
Infrastructure Packages
@joelclaw/message-store
- Redis message queue
- Priority-based draining
- Gateway message persistence
@joelclaw/queue
- Redis stream + sorted-set queue primitives
- Shared queue event envelope and static registry
- Replay, lease, ack, and operator inspection surfaces
- Envelope may carry optional trace (
correlationId,causationId) and triage metadata, but the package remains model-free for correctness - Used by CLI queue commands and the Restate queue drainer
Queue admission triage boundary (ADR-0217 Phase 2)
- bounded queue-admission triage lives in
packages/system-bus/src/lib/queue-triage.ts - canonical server-side admission helper lives in
packages/system-bus/src/lib/queue.ts - edge clients hit
POST /internal/queue/enqueue; worker-local ingress paths call the helper directly - canonical model is Haiku via the shared
infer()path - the model may only shape priority, dedup suggestion, and route confirmation/mismatch signal
QUEUE_TRIAGE_MODEsets the base triage mode;QUEUE_TRIAGE_ENFORCE_FAMILIESis the narrow Story 4 override for the two earned families- static registry routing remains authoritative in this phase; no dynamic handler invention
- canonical OTEL vocabulary for this layer is
queue.triage.started|completed|failed|fallback - canonical operator view for this layer is the
triageblock insidejoelclaw queue stats
Queue observation contract boundary (ADR-0217 Phase 3 Story 1-4)
- bounded Sonnet observation now lives in
packages/system-bus/src/lib/queue-observe.ts - canonical queue-observation contracts (
QueueObservationSnapshot,QueueObservationDecision,QueueObserverAction) plus deterministic queue-control state (QueueFamilyPauseState,QueueControlMode) live in@joelclaw/queue - the observer consumes a deterministic server-built snapshot and may only return bounded action suggestions from the shared enum
- the snapshot now carries active deterministic pauses so resume suggestions are grounded in real control state, overlong Sonnet summaries are trimmed instead of causing bogus schema fallbacks during live probes, legacy
escalate.reasonoutput is normalized into the canonical{ severity, message }report shape, and settled observer-held backlog is normalized back to healthy so the observer can deterministicallyresume_familyinstead of poisoning its own downstream health signal - canonical model is Sonnet via the shared
infer()path - canonical OTEL vocabulary for this layer is
queue.observe.started|completed|failed|fallbackplusqueue.control.applied|expired|rejected - Story 1 stops at contract/snapshot/fallback vocabulary; Story 2 adds the dry-run CLI surface
joelclaw queue observe; Story 3 adds the deterministic pause/resume control plane and the dedicated CLI surfacejoelclaw queue control status; Story 4 adds the durable host-workerqueue/observerruntime with cron + manual trigger support - the Restate queue drainer now respects active deterministic pauses and only resumes dispatch after manual resume or TTL expiry
- queue operator commands resolve Redis from the canonical CLI/system-bus config (
~/.config/system-bus.env) so manual control and live queue observation hit the same queue the worker drains - current live posture is intentionally conservative: supervised enforce canaries have now earned one full observer-applied
pause_family→resume_familycycle oncontent/updated, but the worker still sits back inQUEUE_OBSERVER_MODE=dry-runoutside those canary windows until broader soak evidence says otherwise
@joelclaw/vault-reader
- Obsidian Vault context injection
- ADR reading
- PARA method navigation
@joelclaw/telemetry
- OTEL emission interface
- Typesense storage
- Langfuse integration for LLM tracking
@joelclaw/markdown-formatter
- Per-platform formatting (Telegram, Slack, Discord)
- Code block handling
- Link transformations
Extensions and Integrations
@joelclaw/pi-extensions
- Pi agent extensions
- Session lifecycle hooks
- Langfuse cost tracking
- Async runtime jobs monitor widget/report-back extension (transitional Restate + queue + Dkron + Inngest view)
@joelclaw/email
- Email processing
- Front webhook handling
- VIP email workflows
@joelclaw/mdx-pipeline
- MDX content processing
- Blog post transformations
@joelclaw/lexicons
- AT Protocol lexicons
- PDS record schemas
@joelclaw/discord-ui
- Discord components
- Interaction handlers
@joelclaw/things-cloud
- Things task integration
- Task sync workflows
Deployment Model
Local Development
- All packages run locally via
bun - Inngest dev server for function development
- Local k8s cluster via Colima + Talos
Production
Web App (apps/web/)
- Next.js 16 with App Router, RSC, PPR
- Deployed to Vercel
- Post-push verification required (ADR-0144)
System Bus Worker (packages/system-bus/)
- Docker image built for ARM64
- Pushed to GitHub Container Registry
- Deployed to k8s via
k8s/publish-system-bus-worker.sh - Verifies rollout completion
CLI Binary (packages/cli/)
- Compiled to standalone binary
- Installed at
~/.bun/bin/joelclaw - Rebuild after any CLI changes
Data Flow
Event Flow
External Event
↓
Webhook Gateway (system-bus)
↓
Inngest Event Emission
↓
Function Execution (system-bus-worker)
↓
OTEL Telemetry → Typesense
↓
Gateway Notification (if needed)
↓
contract v2 kind → centralized route/policy
↓
Chat SDK adapter delivery (Telegram/Slack/Discord) or legacy iMessage
↓
receipt + journal mapping (platform message ID ↔ flowId)
Workload Execution Flow
workload plan artifact
↓
joelclaw workload run
↓
Redis queue (`workload/requested`)
↓
Restate `dagOrchestrator`
↓
dependency-aware waves
↓
`dagWorker` handler (`shell` | `infer` | `microvm`)
↓
terminal outputs + OTEL + inbox truth
Sandboxed Story Execution Flow
PRD Plan (Restate workflow)
↓
SandboxExecutionRequest (@joelclaw/agent-execution)
↓
system-bus function OR k8s Job
↓
Agent execution (codex, claude, pi)
↓
SandboxExecutionResult (@joelclaw/agent-execution)
↓
Restate workflow continues
The canonical contract in @joelclaw/agent-execution ensures that all participants in sandboxed execution (Restate, system-bus, k8s Jobs) use the same type shapes for requests, results, and lifecycle states.
Memory Flow
Agent Observation
↓
Memory Write Gate (quality filter)
↓
Vector Embedding
↓
Typesense Storage
↓
Semantic Recall (`joelclaw recall`)
Observability
All subsystems emit structured telemetry:
- OTEL Events → Typesense
otel_eventscollection - Langfuse Traces → LLM call tracking, cost attribution
- Inngest Runs → Legacy/event-native durable function execution history
- Restate Runs → DAG/workflow execution history and queue-drainer evidence
- Gateway Logs → Pi session transcripts, Redis queue depth
- pi-mono Corpus → Typesense
pi_mono_artifactscollection (repo docs, issues, PRs, comments, commits, releases, materialized maintainer profile)
ADR-0224 adds the first repo-managed ClickHouse substrate at k8s/clickhouse.yaml. Current runtime truth is still Typesense + slog, but the migration target is explicit now: ClickHouse will become the canonical observability store on a local-path PVC with NAS reserved for explicit backup/export, not live pod mounts.
Access via:
joelclaw otel list --hours 24
joelclaw otel search "error" --hours 24
joelclaw otel stats --hours 24
joelclaw runs --count 50
joelclaw run <run-id>
Package Import Rules (ADR-0144)
✅ Correct:
import { persist } from "@joelclaw/message-store";
import type { TelemetryEmitter } from "@joelclaw/telemetry";
import { SandboxExecutionRequest } from "@joelclaw/agent-execution";
❌ Wrong:
import { persist } from "../../message-store/src/store";
- Import via
@joelclaw/*, never via relative paths across package boundaries - DI via interfaces in library packages
- Only composition roots do concrete wiring
- New heavy logic → new package if >100 lines and reusable
Validation
After any code change:
bunx tsc --noEmit
pnpm biome check packages/ apps/
bun test <package>
After git push affecting apps/web/:
# Wait 60-90s, then:
vercel ls --yes 2>&1 | head -10
# If ● Error → STOP and fix before pushing more
# If ● Ready → Continue
Related Documentation
- CLI Commands — Full command reference
- Inngest Functions — Function catalog and patterns
- Gateway — Multi-channel routing architecture
- Deployment — Deploy procedures and verification
- Webhooks — External integration patterns
- Skills — Canonical agent skills directory
Architecture Decision Records
Key ADRs:
- ADR-0144: Hexagonal architecture and package boundaries
- ADR-0088: NAS-backed storage tiering (Phase 2.5: k8s ↔ NAS NFS access via col0 LAN route)
- ADR-0127: Feed subscriptions and resource monitoring
- ADR-0140: Inference router
- ADR-0155: Three-stage story pipeline (PRD → stories → execution)
- ADR-0156: Graceful worker restart
Full ADR index: ~/Vault/docs/decisions/