{
  "_note": "SOURCE OF TRUTH for centra.krinc.in. Canonical record of every Centra project, phase, decision, and progress point. The Next.js hub renders this at build time. Claude and humans edit it here; push → Vercel redeploys. Progress % is computed from task `done` flags at render — never hardcode it. Keep valid JSON.",
  "meta": {
    "name": "Centra",
    "tagline": "One brain. Every project orbits it.",
    "domain": "centra.krinc.in",
    "owner": "iKrinc",
    "updated": "2026-08-18",
    "workflow": "Centra is a modular brain: a one-time core plus projects that attach as modules, added as you go. One project is active at a time; the rest wait. Centra (the core) is always live because every project runs on it. Sequencing: build the core to the Blu-enabling milestone (through S3, the event backbone), then pivot to Blu and build the first module end-to-end; return to the core (S4+) as later modules need more. New ideas join the pipeline as future modules — all coordinated through Centra.",
    "activeProject": "blu"
  },
  "projects": [
    {
      "id": "centra",
      "codename": "Centra",
      "kind": "core",
      "status": "active",
      "title": "The Brain",
      "tagline": "Local-first, event-driven core that every project plugs into.",
      "summary": "The always-on brain: local AI, a durable event bus, a self-describing device registry, and a power-cut-aware health monitor. It runs on the laptop (Intel i7 + RTX 4060) today and migrates to a dedicated NAS later — and because every service is containerized, that migration is a copy-paste rather than a rebuild. Centra is not a project you finish; it is the ground every other project stands on.",
      "accent": "purple",
      "map": {
        "angle": 0,
        "radius": 0
      },
      "repo": "/",
      "currentPhase": "S1",
      "tech": [
        "Docker Compose",
        "FastAPI",
        "Postgres + pgvector",
        "TimescaleDB",
        "NATS JetStream",
        "MQTT (Mosquitto)",
        "Redis",
        "MinIO",
        "Ollama",
        "Tailscale",
        "Caddy",
        "Authentik"
      ],
      "architecture": {
        "summary": "A modular brain: a small always-on core set up once, plus projects that attach as manifest-declared modules you add as you go. The core owns the brain-level work — coordination, memory, reasoning, anticipation, scheduling, notification — and delegates specialized capture/actuation to modules. It does most brain things, not everything.",
        "layers": [
          {
            "name": "Core — always on",
            "role": "one-time setup; never a per-project rebuild",
            "items": [
              "NATS JetStream event bus + store",
              "Postgres (pgvector + TimescaleDB)",
              "Redis cache",
              "MinIO objects",
              "Device gateway (MQTT · registry · health)"
            ]
          },
          {
            "name": "Brain — the AI",
            "role": "does most brain things; delegates the rest",
            "items": [
              "Orchestrator (local LLM: Qwen3.5-9B / Qwen3-7B)",
              "MCP tool layer (modules expose tools)",
              "Memory + RAG (pgvector, evolving profiles)",
              "Anticipation engine (scheduled + event reasoning)",
              "Notifier"
            ]
          },
          {
            "name": "Module manager",
            "role": "manifest-driven; add / upgrade / remove at runtime",
            "items": [
              "Module manifest (capabilities, events, tools, data, UI, lifecycle)",
              "Capability registry (semantic action → connector)",
              "Lifecycle hooks (install / upgrade / uninstall)"
            ]
          },
          {
            "name": "Modules — the projects",
            "role": "attach as you go; own their domain",
            "items": [
              "Blu · reminders",
              "Aura · sensing",
              "Sentry · vision",
              "Atlas · knowledge",
              "new ideas → pipeline"
            ]
          }
        ],
        "flow": [
          "A module registers via its manifest → capability registry, event subscriptions, and MCP tools are wired in; the core is unchanged.",
          "Devices and modules publish events (MQTT edge → gateway → NATS JetStream) — durable and replayable.",
          "The orchestrator reacts to events and, on a schedule, reasons over shared state (events, telemetry, memory) using module-provided MCP tools.",
          "The anticipation engine predicts likely needs and acts proactively — or asks — then notifies you.",
          "State is written back to the shared data plane so the next decision is better-informed."
        ]
      },
      "features": {
        "summary": "Centra is a limited JARVIS/FRIDAY — a support system, not an autonomous do-everything agent. It owns the brain-level work (remembering, watching, reasoning, anticipating, reminding, coordinating) and keeps you in the loop for anything consequential. It assists; it does not autonomously build, spend, or send on your behalf.",
        "groups": [
          {
            "name": "Anticipation & memory",
            "items": [
              "Persistent memory of your preferences, patterns, and decisions",
              "Scheduled reasoning that surfaces what needs you before you ask",
              "Reactive reasoning on events via the local LLM + module tools"
            ]
          },
          {
            "name": "Daily support",
            "items": [
              "Morning / evening briefings from the live state",
              "Reminders & follow-through (the Blu module)",
              "Severity- and power-cut-aware notifications, one place"
            ]
          },
          {
            "name": "Your world",
            "items": [
              "Home & ambient awareness (Aura / Sentry)",
              "Personal knowledge RAG with citations (Atlas)",
              "Local voice — wake, ask, speak, fully offline"
            ]
          },
          {
            "name": "Project & build support",
            "items": [
              "Tracks the plan from the source of truth",
              "Preps checklists, scripts, and status — it does not autonomously write or ship code"
            ]
          }
        ],
        "limits": [
          "No irreversible or outward action without your confirmation",
          "No autonomous engineering — building stays with you",
          "Local-first; nothing leaves the house without consent",
          "A small, typed, fully-logged tool surface"
        ]
      },
      "phases": [
        {
          "id": "S0",
          "title": "Foundation & runtime",
          "status": "done",
          "summary": "Containers, repo-as-infra, and verified GPU access — the bedrock.",
          "objective": "Establish a reproducible, version-controlled runtime where every service is a container defined in git, and the RTX 4060 is usable from inside containers. Everything downstream assumes this substrate.",
          "scope": [
            "Docker Engine + Compose on the host (Ubuntu Server or WSL2)",
            "NVIDIA Container Toolkit for GPU passthrough",
            "Git repository holding all compose files and configs (infra-as-code)",
            "Base bridge network, named volumes, and a static LAN hostname"
          ],
          "outOfScope": [
            "Any application logic or AI models (later phases)",
            "Remote access / public exposure (S5)"
          ],
          "approach": [
            {
              "decision": "Containerize everything from day one",
              "rationale": "The whole laptop→NAS migration reduces to copying compose files and volumes. This is the single highest-leverage decision in the project."
            },
            {
              "decision": "Ubuntu Server preferred; WSL2 + Docker Desktop as the pragmatic fallback",
              "rationale": "Native Linux gives clean GPU passthrough. If the machine stays a daily-driver Windows box, WSL2 works — but container volumes must live in the Linux filesystem."
            },
            {
              "decision": "Repo is the source of infra truth",
              "rationale": "No snowflake config. Any service can be rebuilt from the repo alone."
            }
          ],
          "stack": [
            "Docker",
            "Docker Compose",
            "NVIDIA Container Toolkit",
            "Git",
            "Portainer"
          ],
          "tasks": [
            {
              "t": "Git repo + SSH remote (github-krinc → iKrinc/Centra)",
              "done": true
            },
            {
              "t": "Containerized brain stack authored (docker-compose.yml)",
              "done": true
            },
            {
              "t": "Gateway, edge agent, and infra configs written",
              "done": true
            },
            {
              "t": "Install Docker Desktop (WSL2 backend)",
              "done": false
            },
            {
              "t": "Install NVIDIA Container Toolkit",
              "done": false
            },
            {
              "t": "Verify the RTX 4060 inside a container (nvidia-smi)",
              "done": false
            }
          ],
          "deliverables": [
            "docker-compose.yml + gpu/devices overlays",
            "Reproducible `docker compose up` that brings the brain online",
            "Portainer for at-a-glance container health"
          ],
          "acceptance": [
            "`docker run --rm --gpus all` reports the 4060",
            "`docker compose up -d` starts all core services with healthchecks green",
            "A fresh clone can recreate the stack with no manual steps"
          ],
          "risks": [
            {
              "risk": "WSL2 volume I/O on /mnt/c is extremely slow",
              "mitigation": "Keep all named volumes inside the WSL2 Linux filesystem."
            },
            {
              "risk": "GPU not visible in containers",
              "mitigation": "Match NVIDIA driver + Container Toolkit versions; test with the CUDA base image first."
            }
          ],
          "dependencies": [],
          "references": [
            {
              "title": "NVIDIA Container Toolkit — install guide",
              "url": "https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html"
            },
            {
              "title": "Docker Compose reference",
              "url": "https://docs.docker.com/compose/"
            }
          ]
        },
        {
          "id": "S1",
          "title": "Local AI stack",
          "status": "active",
          "summary": "A local LLM, STT, and TTS running on the 4060 — fully offline.",
          "objective": "Prove the core premise: capable AI on your own hardware with no internet. This is the fastest motivating win and the model backend every project will call.",
          "scope": [
            "Ollama serving a quantized 7–9B model on the GPU",
            "Open WebUI as a chat front-end",
            "whisper.cpp / faster-whisper for speech-to-text",
            "Piper for text-to-speech",
            "An embedding model for later RAG"
          ],
          "outOfScope": [
            "Agentic tool-calling and orchestration (S4)",
            "Cross-device voice pipeline (Blu's project)"
          ],
          "approach": [
            {
              "decision": "Qwen3.5-9B (or Qwen3-7B) at Q4_K_M via Ollama/GGUF",
              "rationale": "2026 sweet spot for 8 GB VRAM — ~40–55 tok/s fully in-GPU, strong reasoning/coding scores, and near-lossless under KV-cache quantization for long context."
            },
            {
              "decision": "One large model resident at a time (OLLAMA_MAX_LOADED_MODELS=1)",
              "rationale": "8 GB holds a single 7–9B Q4 model well; whisper/piper/embeddings coexist since they're small or CPU-bound."
            },
            {
              "decision": "whisper.cpp for STT",
              "rationale": "Fast, CPU/GPU flexible, no Python runtime bloat; tiny/base for realtime, larger models when accuracy matters."
            }
          ],
          "stack": [
            "Ollama",
            "Open WebUI",
            "whisper.cpp",
            "Piper",
            "nomic-embed-text"
          ],
          "tasks": [
            {
              "t": "Ollama container on the GPU",
              "done": false
            },
            {
              "t": "Pull Qwen3.5-9B / Qwen3-7B (Q4_K_M)",
              "done": false
            },
            {
              "t": "Open WebUI chat front-end",
              "done": false
            },
            {
              "t": "whisper.cpp (STT) service",
              "done": false
            },
            {
              "t": "Piper (TTS) service",
              "done": false
            },
            {
              "t": "Embedding model wired for RAG",
              "done": false
            }
          ],
          "deliverables": [
            "A browser chat against a local LLM",
            "An STT endpoint",
            "A TTS endpoint"
          ],
          "acceptance": [
            "Chat with the local LLM in the browser with Wi-Fi off",
            "Transcribe a WAV file locally in near real-time",
            "Synthesize a spoken sentence from text locally",
            "Sustained tokens/sec measured and recorded"
          ],
          "risks": [
            {
              "risk": "VRAM exhaustion / spill to CPU",
              "mitigation": "Stick to Q4_K_M; cap loaded models; watch KV-cache size and context length."
            },
            {
              "risk": "Thermal throttling on the laptop",
              "mitigation": "Elevate/cool the chassis; monitor GPU temp; keep sustained load reasonable."
            }
          ],
          "dependencies": [
            "S0"
          ],
          "references": [
            {
              "title": "Best Local LLMs for 8GB VRAM (2026)",
              "url": "https://localllm.in/blog/best-local-llms-8gb-vram-2025"
            },
            {
              "title": "Ollama VRAM requirements (2026)",
              "url": "https://localllm.in/blog/ollama-vram-requirements-for-local-llms"
            },
            {
              "title": "Ollama",
              "url": "https://ollama.com"
            },
            {
              "title": "whisper.cpp",
              "url": "https://github.com/ggerganov/whisper.cpp"
            }
          ]
        },
        {
          "id": "S2",
          "title": "Data layer",
          "status": "todo",
          "summary": "One Postgres for relational + vectors + time-series, plus cache and object store — with restores that actually work.",
          "objective": "Give the brain a single, durable source of truth for events, embeddings, and telemetry, and prove the backup can be restored — not just written.",
          "scope": [
            "Postgres with pgvector + TimescaleDB in one instance",
            "Redis for cache and sessions",
            "MinIO for blobs (audio clips, images)",
            "Automated backups (pg_dump + restic) with a tested restore"
          ],
          "outOfScope": [
            "Offsite/cloud backup (S8)",
            "Analytics dashboards (S8)"
          ],
          "approach": [
            {
              "decision": "timescaledb-ha image = Postgres + TimescaleDB + pgvector",
              "rationale": "One database engine instead of three. Telemetry is a Timescale hypertable; embeddings use pgvector; relational data is plain Postgres."
            },
            {
              "decision": "Postgres is the single source of truth",
              "rationale": "Events land here first; the bus is a fan-out convenience. If the bus is down, no event is lost."
            },
            {
              "decision": "Redis is cache/sessions only, never a bus",
              "rationale": "Avoid three overlapping message systems; keep responsibilities crisp."
            }
          ],
          "stack": [
            "Postgres 16",
            "TimescaleDB",
            "pgvector",
            "Redis",
            "MinIO",
            "restic"
          ],
          "tasks": [
            {
              "t": "Postgres + pgvector + TimescaleDB (one instance)",
              "done": true
            },
            {
              "t": "Redis cache + MinIO object store",
              "done": true
            },
            {
              "t": "Gateway auto-creates schema on startup",
              "done": true
            },
            {
              "t": "Automated pg_dump + restic to a backup drive",
              "done": false
            },
            {
              "t": "Perform and verify one full restore",
              "done": false
            }
          ],
          "deliverables": [
            "Running data services with healthchecks",
            "A scheduled backup job",
            "A documented, verified restore procedure"
          ],
          "acceptance": [
            "All data services healthy and reachable from the gateway",
            "A backup is produced on schedule",
            "A restore into a scratch database reproduces the data end-to-end"
          ],
          "risks": [
            {
              "risk": "Silent backup that never restores",
              "mitigation": "Restore drills are part of the DoD, not optional."
            },
            {
              "risk": "Consumer SSD wear / single disk",
              "mitigation": "Backups to a separate drive now; ZFS redundancy at NAS migration (S9)."
            }
          ],
          "dependencies": [
            "S0"
          ],
          "references": [
            {
              "title": "TimescaleDB",
              "url": "https://docs.timescale.com"
            },
            {
              "title": "pgvector",
              "url": "https://github.com/pgvector/pgvector"
            },
            {
              "title": "restic",
              "url": "https://restic.net"
            }
          ]
        },
        {
          "id": "S3",
          "title": "Event backbone & API",
          "status": "todo",
          "summary": "The event schema and the gateway every service and device speaks to.",
          "objective": "Define the event contract and stand up the ingest path so devices and services communicate through one durable, replayable bus.",
          "scope": [
            "NATS JetStream as internal bus + event store",
            "Mosquitto MQTT broker for the device edge",
            "FastAPI gateway: ingest, device registry, health monitor",
            "A frozen, versioned event schema"
          ],
          "outOfScope": [
            "AI decisioning (S4)",
            "Device firmware (project-specific)"
          ],
          "approach": [
            {
              "decision": "MQTT for the edge, NATS JetStream for internal",
              "rationale": "MQTT is what constrained devices speak (retain, QoS, LWT). NATS JetStream is the durable, replayable internal bus and event store. Redis stays out of messaging."
            },
            {
              "decision": "Idempotent, ordered events (event_id + seq)",
              "rationale": "Devices draining a store-and-forward outbox after reconnect must never double-count; the gateway dedupes on event_id."
            },
            {
              "decision": "Power-cut-aware health monitor",
              "rationale": "A mains device going silent during a grid outage is expected; a battery device going silent is a fault. Device power_class + grid state drive alerting."
            },
            {
              "decision": "After S3, pivot to Blu",
              "rationale": "S1–S3 give Blu everything it needs — local AI, the data plane, and the event backbone. Blu is the first module; build it end-to-end before deepening the core (S4+ returns as later modules require)."
            }
          ],
          "stack": [
            "NATS JetStream",
            "Mosquitto",
            "FastAPI",
            "asyncpg",
            "aiomqtt"
          ],
          "tasks": [
            {
              "t": "NATS JetStream bus + event store",
              "done": true
            },
            {
              "t": "Mosquitto MQTT broker with persistence + LWT",
              "done": true
            },
            {
              "t": "Gateway: ingest + registry + health monitor",
              "done": true
            },
            {
              "t": "Freeze and document the event schema",
              "done": false
            }
          ],
          "deliverables": [
            "Running gateway + brokers",
            "Documented device contract & event schema",
            "HTTP API for devices/events/alerts"
          ],
          "acceptance": [
            "publish → worker consumes → persists → triggers an action, end-to-end",
            "A duplicate event (same event_id) is deduped",
            "A silent battery device raises a fault; a mains device during grid-down does not"
          ],
          "risks": [
            {
              "risk": "Schema churn breaks every consumer",
              "mitigation": "Freeze the core schema early; version it; additive changes only."
            }
          ],
          "dependencies": [
            "S0",
            "S2"
          ],
          "references": [
            {
              "title": "NATS JetStream",
              "url": "https://docs.nats.io/nats-concepts/jetstream"
            },
            {
              "title": "Eclipse Mosquitto",
              "url": "https://mosquitto.org/documentation/"
            },
            {
              "title": "MQTT Last Will & Testament",
              "url": "https://www.hivemq.com/blog/mqtt-essentials-part-9-last-will-and-testament/"
            }
          ]
        },
        {
          "id": "S4",
          "title": "AI Orchestrator",
          "status": "todo",
          "summary": "The differentiated core: events in, typed tool-calls to the LLM, actions out.",
          "objective": "Turn raw events into intelligent actions. This is the one piece worth building custom — the glue that makes Centra more than the sum of its containers.",
          "scope": [
            "A service subscribing to centra.events.*",
            "Typed tool-calling against the local LLM",
            "RAG over your own data via pgvector",
            "Action dispatch back onto the bus / to devices"
          ],
          "outOfScope": [
            "Per-project skills (each project owns its own logic)"
          ],
          "approach": [
            {
              "decision": "Build on FastAPI + a typed agent layer (PydanticAI / LangGraph)",
              "rationale": "Structured, testable tool-calling beats free-form prompting for reliability; the tool surface stays small and typed."
            },
            {
              "decision": "Log every decision and tool call",
              "rationale": "When the model does something unexpected, you need the trace. Observability is part of the design, not an afterthought."
            }
          ],
          "stack": [
            "FastAPI",
            "PydanticAI / LangGraph",
            "Ollama",
            "pgvector"
          ],
          "tasks": [
            {
              "t": "Subscribe to centra.events.*",
              "done": false
            },
            {
              "t": "Typed tool-calling on the local LLM",
              "done": false
            },
            {
              "t": "RAG over your own data (pgvector)",
              "done": false
            },
            {
              "t": "Decision/tool-call logging",
              "done": false
            }
          ],
          "deliverables": [
            "An orchestrator service",
            "A small, typed tool registry",
            "A decision log"
          ],
          "acceptance": [
            "An event ('remind me…', a sensor trip) flows through the LLM and yields a correct action",
            "Every tool call is logged with inputs and outputs",
            "RAG returns grounded answers from your own documents"
          ],
          "risks": [
            {
              "risk": "LLM picks wrong / unsafe tools",
              "mitigation": "Small typed tool surface; confirmations for side-effectful actions; full logging."
            }
          ],
          "dependencies": [
            "S1",
            "S3"
          ],
          "references": [
            {
              "title": "PydanticAI",
              "url": "https://ai.pydantic.dev"
            },
            {
              "title": "LangGraph",
              "url": "https://langchain-ai.github.io/langgraph/"
            }
          ]
        },
        {
          "id": "S5",
          "title": "Secure remote access",
          "status": "todo",
          "summary": "Reach Centra from anywhere with zero open router ports.",
          "objective": "Make the brain reachable off-LAN securely, without exposing anything to the public internet until there's a real need.",
          "scope": [
            "Tailscale on the brain + phone/laptop",
            "Caddy reverse proxy with automatic TLS on the tailnet",
            "Authentik/Authelia forward-auth in front of web services"
          ],
          "outOfScope": [
            "Public exposure / port-forwarding (deliberately avoided)"
          ],
          "approach": [
            {
              "decision": "Tailscale-first, public-last",
              "rationale": "A tailnet gives encrypted, authenticated access with no open ports. Public exposure is only added when sharing with someone who can't join the tailnet."
            },
            {
              "decision": "Caddy over raw Nginx",
              "rationale": "Automatic HTTPS and far simpler config for the reverse-proxy + TLS need."
            },
            {
              "decision": "Authentik/Authelia over Keycloak",
              "rationale": "SSO + 2FA at a fraction of Keycloak's weight for a personal system."
            }
          ],
          "stack": [
            "Tailscale",
            "Caddy",
            "Authentik / Authelia",
            "Let's Encrypt"
          ],
          "tasks": [
            {
              "t": "Tailscale on the brain + phone/laptop",
              "done": false
            },
            {
              "t": "Caddy reverse proxy + TLS on the tailnet",
              "done": false
            },
            {
              "t": "Authentik/Authelia forward-auth",
              "done": false
            }
          ],
          "deliverables": [
            "A private tailnet",
            "TLS-terminated internal hostnames",
            "SSO in front of services"
          ],
          "acceptance": [
            "From cellular (not home Wi-Fi), reach the dashboard securely",
            "Services require login via the IdP",
            "No inbound router ports are opened"
          ],
          "risks": [
            {
              "risk": "Accidental public exposure",
              "mitigation": "No port-forwarding; audit that services bind to the tailnet/localhost only."
            }
          ],
          "dependencies": [
            "S3"
          ],
          "references": [
            {
              "title": "Tailscale",
              "url": "https://tailscale.com/kb/"
            },
            {
              "title": "Caddy automatic HTTPS",
              "url": "https://caddyserver.com/docs/automatic-https"
            },
            {
              "title": "Authentik",
              "url": "https://goauthentik.io"
            }
          ]
        },
        {
          "id": "S6",
          "title": "IoT & Home Assistant",
          "status": "todo",
          "summary": "The physical world plugs into the bus via Home Assistant + ESPHome.",
          "objective": "Adopt Home Assistant as the IoT spine rather than hand-building device management, and bridge its events into Centra's bus.",
          "scope": [
            "Home Assistant with MQTT",
            "ESP32 devices flashed via ESPHome",
            "A bridge from HA events into NATS"
          ],
          "outOfScope": [
            "Custom automation engine (HA already does this)"
          ],
          "approach": [
            {
              "decision": "Home Assistant + ESPHome as the IoT layer",
              "rationale": "1000+ integrations, device management, scenes, dashboards, local voice — building this from scratch would cost months."
            },
            {
              "decision": "Bridge HA → NATS",
              "rationale": "The orchestrator acts on physical events through the same bus as everything else."
            }
          ],
          "stack": [
            "Home Assistant",
            "ESPHome",
            "Zigbee2MQTT (optional)",
            "MQTT"
          ],
          "tasks": [
            {
              "t": "Home Assistant + MQTT",
              "done": false
            },
            {
              "t": "ESP32s via ESPHome",
              "done": false
            },
            {
              "t": "Bridge HA events into NATS",
              "done": false
            }
          ],
          "deliverables": [
            "A running HA instance",
            "At least one ESPHome device",
            "HA↔NATS bridge"
          ],
          "acceptance": [
            "A physical sensor event reaches Centra's bus",
            "The orchestrator can respond to it",
            "Devices survive an HA restart (retained state)"
          ],
          "risks": [
            {
              "risk": "USB/Zigbee coordinator instability on the brain",
              "mitigation": "Host coordinators on the Pi, physically near the devices."
            }
          ],
          "dependencies": [
            "S3"
          ],
          "references": [
            {
              "title": "Home Assistant",
              "url": "https://www.home-assistant.io"
            },
            {
              "title": "ESPHome",
              "url": "https://esphome.io"
            }
          ]
        },
        {
          "id": "S7",
          "title": "Clients",
          "status": "todo",
          "summary": "Your face on the system — a web dashboard and a phone client.",
          "objective": "Give Centra a first-class UI: live state and control from the web and your phone.",
          "scope": [
            "Next.js dashboard against the gateway API",
            "A PWA before any native app"
          ],
          "outOfScope": [
            "This public hub site (separate; it's the record, not the control panel)"
          ],
          "approach": [
            {
              "decision": "PWA first, native later",
              "rationale": "A PWA delivers ~90% of the value for ~10% of the effort; go native only if a capability truly requires it."
            }
          ],
          "stack": [
            "Next.js",
            "PWA",
            "Authentik (auth)"
          ],
          "tasks": [
            {
              "t": "Next.js dashboard against the gateway API",
              "done": false
            },
            {
              "t": "PWA packaging",
              "done": false
            }
          ],
          "deliverables": [
            "A live dashboard",
            "An installable PWA"
          ],
          "acceptance": [
            "Dashboard shows live device/event state",
            "You can issue a command from your phone over Tailscale"
          ],
          "risks": [
            {
              "risk": "Scope creep into a full native app early",
              "mitigation": "Ship the PWA; defer native."
            }
          ],
          "dependencies": [
            "S3",
            "S5"
          ],
          "references": [
            {
              "title": "Next.js",
              "url": "https://nextjs.org/docs"
            },
            {
              "title": "PWA on web.dev",
              "url": "https://web.dev/learn/pwa/"
            }
          ]
        },
        {
          "id": "S8",
          "title": "Observability, backup & hardening",
          "status": "todo",
          "summary": "The sleep-at-night layer: metrics, logs, offsite DR, and secrets.",
          "objective": "Make the brain observable and recoverable, and remove plaintext secrets — so a failure is visible and reversible.",
          "scope": [
            "Prometheus + Grafana (incl. GPU temp/util)",
            "Uptime Kuma + Loki logs",
            "Offsite backup (restic → Backblaze B2)",
            "Secrets → Docker secrets / SOPS"
          ],
          "outOfScope": [
            "NAS hardware (S9)"
          ],
          "approach": [
            {
              "decision": "restic to Backblaze B2 for offsite",
              "rationale": "Cheap (~$6/TB·mo), encrypted, S3-compatible; pairs with the local restic backups from S2."
            },
            {
              "decision": "SOPS for secrets in git",
              "rationale": "Encrypted secrets can live in the repo safely; no plaintext .env in version control."
            }
          ],
          "stack": [
            "Prometheus",
            "Grafana",
            "Loki",
            "Uptime Kuma",
            "restic",
            "Backblaze B2",
            "SOPS"
          ],
          "tasks": [
            {
              "t": "Prometheus + Grafana (incl. GPU temp)",
              "done": false
            },
            {
              "t": "Uptime Kuma + Loki logs",
              "done": false
            },
            {
              "t": "Offsite backup (restic → Backblaze B2)",
              "done": false
            },
            {
              "t": "Secrets → Docker secrets / SOPS",
              "done": false
            }
          ],
          "deliverables": [
            "Dashboards for host/GPU/services",
            "Offsite encrypted backups",
            "Encrypted secrets in the repo"
          ],
          "acceptance": [
            "Dashboards show CPU/GPU/temp and service health",
            "A simulated disk-loss recovery from offsite backup succeeds",
            "No plaintext secret exists in git history"
          ],
          "risks": [
            {
              "risk": "Backups written but never restored",
              "mitigation": "DR drill is part of the DoD."
            }
          ],
          "dependencies": [
            "S2",
            "S3"
          ],
          "references": [
            {
              "title": "Grafana",
              "url": "https://grafana.com/docs/"
            },
            {
              "title": "SOPS",
              "url": "https://github.com/getsops/sops"
            },
            {
              "title": "Backblaze B2",
              "url": "https://www.backblaze.com/cloud-storage"
            }
          ]
        },
        {
          "id": "S9",
          "title": "Expansion & NAS migration",
          "status": "todo",
          "summary": "Move the brain to dedicated hardware — the payoff for containerizing everything.",
          "objective": "Retire the laptop-as-server: stand up a NAS/mini-PC, migrate the stack, and free the laptop to be just a client.",
          "scope": [
            "Build/buy a NAS (TrueNAS SCALE or Ubuntu + ZFS)",
            "Move compose + volumes; repoint Tailscale",
            "ZFS redundancy + UPS via NUT"
          ],
          "outOfScope": [],
          "approach": [
            {
              "decision": "DIY/mini-PC NAS over turnkey Synology for a Docker + ZFS box",
              "rationale": "Better value and full control; ~$340–525 DIY 4-bay vs ~$960 Synology DS923+ (2026)."
            },
            {
              "decision": "Real UPS + NUT replaces the laptop battery",
              "rationale": "The grid-state feed moves from the laptop's AC status to a proper UPS; graceful shutdown on low battery."
            }
          ],
          "stack": [
            "TrueNAS SCALE / Ubuntu + ZFS",
            "Docker",
            "NUT (Network UPS Tools)"
          ],
          "tasks": [
            {
              "t": "Build/buy NAS (TrueNAS SCALE or Ubuntu + ZFS)",
              "done": false
            },
            {
              "t": "Move compose + volumes; repoint Tailscale",
              "done": false
            },
            {
              "t": "ZFS pool + UPS/NUT graceful shutdown",
              "done": false
            }
          ],
          "deliverables": [
            "A dedicated always-on brain",
            "ZFS-backed storage",
            "UPS-protected graceful shutdown"
          ],
          "acceptance": [
            "Centra runs on the dedicated box; the laptop is just a client",
            "A drive-loss test is survived by ZFS redundancy",
            "A power cut triggers a clean shutdown via NUT"
          ],
          "risks": [
            {
              "risk": "Migration downtime / data loss",
              "mitigation": "ZFS send + restic restore; cut over only after a verified copy exists."
            }
          ],
          "dependencies": [
            "S0",
            "S2",
            "S8"
          ],
          "references": [
            {
              "title": "TrueNAS SCALE",
              "url": "https://www.truenas.com/truenas-scale/"
            },
            {
              "title": "Network UPS Tools",
              "url": "https://networkupstools.org"
            },
            {
              "title": "DIY NAS vs Synology (2026)",
              "url": "https://homelabpicks.com/nas/diy-nas-vs-synology/"
            }
          ]
        }
      ],
      "log": [
        {
          "date": "2026-08-18",
          "entry": "Scaffolded the containerized brain (compose, gateway, edge agent, infra) and pushed to git. Added the power-cut-aware resilience architecture."
        },
        {
          "date": "2026-08-18",
          "entry": "Rebuilt the hub as a Next.js app (Vercel-ready, centra.krinc.in) with an animated project map, per-project overview pages, and engineered phase pages."
        },
        {
          "date": "2026-08-18",
          "entry": "Added CLAUDE.md operating manual + per-project folder convention so the AI works directly from this repo against the source of truth (web/content/centra.json)."
        },
        {
          "date": "2026-08-18",
          "entry": "Redesigned the home as a revolving 3D atom (crisp, no hero text; hover pauses + zooms; orbs grow with progress) and added an animated atom logo. Documented Centra's feature set as a limited support assistant (docs/centra-features.md) and shipped a one-shot S1 local-AI bring-up script."
        },
        {
          "date": "2026-08-18",
          "entry": "S0 complete: Docker Desktop running and the RTX 4060 (driver 610.88, 8 GB) verified inside a container. Started S1 — bringing up the local AI stack (Ollama + Qwen + embeddings + Open WebUI) on the GPU. Home tuned to a clean 2D orbital rotation with 3D orbs; base page has no header/footer."
        }
      ]
    },
    {
      "id": "blu",
      "codename": "Blu",
      "kind": "project",
      "status": "active",
      "title": "Pocket Voice Reminder",
      "tagline": "Speak a reminder into your pocket. It hears you offline; Centra remembers.",
      "summary": "Blu is a pocket, always-listening voice device. You mutter 'remind me to water the plants' and Blu — running fully offline — captures it, transcribes it locally, and turns it into a reminder. It never waits for the network: it responds on the spot and syncs to Centra when a link appears, where the intent is parsed, scheduled, and surfaced back to you. Blu is the first real project riding the brain, and the proving ground for the whole resilient-edge contract.",
      "accent": "blue",
      "map": {
        "angle": 205,
        "radius": 0.66
      },
      "repo": "/projects/blu",
      "currentPhase": "B0",
      "tech": [
        "EdgeAgent (store-and-forward)",
        "ESP32-S3",
        "INMP441 (I2S mic)",
        "MAX98357A (I2S amp)",
        "microWakeWord / ESP-SR",
        "whisper.cpp",
        "MQTT",
        "FastAPI"
      ],
      "architecture": {
        "summary": "Blu is the first Centra module: an ESP32-S3 pocket device captures speech locally (offline-first) and talks to the brain over MQTT via the shared EdgeAgent; a reminder module on the brain parses intent through the orchestrator, schedules, and notifies.",
        "layers": [
          {
            "name": "Device (edge)",
            "role": "runs and responds locally, offline-first",
            "items": [
              "ESP32-S3 (8MB PSRAM)",
              "INMP441 I2S mic",
              "MAX98357A I2S amp",
              "microWakeWord / ESP-SR wake word",
              "whisper.cpp tiny/base"
            ]
          },
          {
            "name": "Edge runtime",
            "role": "never waits for the brain",
            "items": [
              "EdgeAgent store-and-forward outbox",
              "Heartbeats + MQTT Last-Will",
              "Battery-aware conserve mode"
            ]
          },
          {
            "name": "Brain module",
            "role": "the reminder capability",
            "items": [
              "reminder-service (ingest → Postgres)",
              "Intent parse via orchestrator (typed)",
              "Scheduler (due-time queue)",
              "Notifier (push / TTS / phone)"
            ]
          },
          {
            "name": "Manifest",
            "role": "how Blu plugs into Centra",
            "items": [
              "events: reminder.capture (pub) · reminder.due (sub)",
              "tools: create_reminder · list_reminders · snooze",
              "data: reminders table",
              "ui: reminders card"
            ]
          }
        ],
        "flow": [
          "Wake word / button → record → local STT → local ack (works with no network).",
          "reminder.capture event → durable outbox → syncs to the brain on reconnect.",
          "Gateway → NATS → reminder-service stores it; orchestrator parses intent → due time.",
          "Scheduler fires reminder.due → notifier reaches you across channels; snooze / ack loop.",
          "A reminder that fired while you were offline resurfaces on reconnect — nothing is lost."
        ]
      },
      "phases": [
        {
          "id": "B0",
          "title": "Define & scaffold",
          "status": "active",
          "summary": "Give Blu a home, a spec, and a device runtime built on the resilient-edge contract.",
          "objective": "Establish Blu as a first-class project: a repo folder, a clear spec, a laptop-simulated device that registers with Centra, and a brain-side reminder service — all before any hardware is bought.",
          "scope": [
            "projects/blu folder + README",
            "Blu edge agent extending the shared EdgeAgent",
            "Reminder service skeleton (brain-side consumer)",
            "docker-compose.blu.yml overlay",
            "Hardware target selection"
          ],
          "outOfScope": [
            "Real firmware (B4)",
            "Wake word (B1/B5)"
          ],
          "approach": [
            {
              "decision": "Simulate on the laptop before buying hardware",
              "rationale": "Get the software loop — capture → transcript → reminder → sync → notify — correct first; hardware is cheap once the loop works."
            },
            {
              "decision": "Reuse the EdgeAgent store-and-forward runtime",
              "rationale": "Blu inherits durable offline buffering, heartbeats, Last-Will, and battery-aware conserve mode for free."
            }
          ],
          "stack": [
            "Python",
            "EdgeAgent",
            "MQTT",
            "FastAPI"
          ],
          "tasks": [
            {
              "t": "projects/blu folder + README + web sprints",
              "done": true
            },
            {
              "t": "Blu edge agent extending EdgeAgent",
              "done": true
            },
            {
              "t": "Reminder service skeleton (brain-side consumer)",
              "done": true
            },
            {
              "t": "docker-compose.blu.yml overlay",
              "done": true
            },
            {
              "t": "Pick target hardware (ESP32-S3 vs Pi Zero 2 W)",
              "done": false
            }
          ],
          "deliverables": [
            "A simulated Blu device",
            "A reminder service",
            "A compose overlay to run both"
          ],
          "acceptance": [
            "`blu` simulates on the laptop and registers with Centra",
            "Its reminder events land in the event store",
            "Pulling the network doesn't stop local responses; events sync on reconnect"
          ],
          "risks": [
            {
              "risk": "Premature hardware purchase",
              "mitigation": "Hardware selection is the LAST task of B0; the loop must work in simulation first."
            }
          ],
          "dependencies": [
            "Centra S3"
          ],
          "references": [
            {
              "title": "Blu README (repo)",
              "url": "https://github.com/iKrinc/Centra/tree/main/projects/blu"
            }
          ]
        },
        {
          "id": "B1",
          "title": "Local capture + STT",
          "status": "todo",
          "summary": "Hear, transcribe, and respond locally — offline-first.",
          "objective": "Capture speech and turn it into text on the device (or via Centra when online), buffering everything so nothing is lost offline.",
          "scope": [
            "Audio capture (push-to-talk first, VAD later)",
            "whisper.cpp on-device transcription (tiny/base)",
            "Emit reminder.capture events into the outbox",
            "Offload heavy STT to Centra when connected"
          ],
          "outOfScope": [
            "Always-on wake word (B5)",
            "Intent parsing (B2)"
          ],
          "approach": [
            {
              "decision": "whisper.cpp tiny/base on-device, larger on Centra",
              "rationale": "Tiny/base fit constrained hardware for realtime; when online, stream audio to Centra's whisper for higher accuracy — best of both."
            },
            {
              "decision": "Push-to-talk before voice-activity detection",
              "rationale": "A button is deterministic and battery-cheap; VAD and wake word add cost and come later."
            }
          ],
          "stack": [
            "whisper.cpp",
            "I2S audio",
            "EdgeAgent outbox"
          ],
          "tasks": [
            {
              "t": "Audio capture loop (push-to-talk)",
              "done": false
            },
            {
              "t": "whisper.cpp on-device transcription",
              "done": false
            },
            {
              "t": "Emit reminder.capture events to the outbox",
              "done": false
            },
            {
              "t": "Online STT offload to Centra",
              "done": false
            }
          ],
          "deliverables": [
            "A capture→transcript pipeline",
            "Buffered transcript events"
          ],
          "acceptance": [
            "Speak with Wi-Fi off → Blu transcribes and buffers locally",
            "Events sync to Centra on reconnect with no loss",
            "Online mode uses Centra's larger whisper for accuracy"
          ],
          "risks": [
            {
              "risk": "On-device STT too slow/inaccurate",
              "mitigation": "Fall back to buffered audio + Centra-side transcription when online."
            }
          ],
          "dependencies": [
            "B0",
            "Centra S1"
          ],
          "references": [
            {
              "title": "whisper.cpp",
              "url": "https://github.com/ggerganov/whisper.cpp"
            }
          ]
        },
        {
          "id": "B2",
          "title": "Reminder intent + service",
          "status": "todo",
          "summary": "Turn 'remind me to X at Y' into a structured, scheduled reminder.",
          "objective": "Parse natural-language reminders into structured records with a due time, store them, and schedule firing.",
          "scope": [
            "Intent parsing via Centra's orchestrator (local LLM)",
            "A reminders table + REST API",
            "A due-time scheduler"
          ],
          "outOfScope": [
            "Notification delivery (B3)"
          ],
          "approach": [
            {
              "decision": "Parse intent with the local LLM (typed output)",
              "rationale": "Natural-language time/task extraction is exactly what the S4 orchestrator's typed tool-calling is for; keeps Blu thin."
            },
            {
              "decision": "Confirm ambiguous times back to the user",
              "rationale": "'tomorrow evening' is fuzzy; a quick confirmation beats a silently-wrong reminder."
            }
          ],
          "stack": [
            "FastAPI",
            "Postgres",
            "Centra orchestrator",
            "APScheduler / NATS timers"
          ],
          "tasks": [
            {
              "t": "Intent parse (local LLM via orchestrator)",
              "done": false
            },
            {
              "t": "Reminders table + REST API",
              "done": false
            },
            {
              "t": "Scheduler (due-time queue)",
              "done": false
            }
          ],
          "deliverables": [
            "A reminders API",
            "A scheduler",
            "Structured reminders from speech"
          ],
          "acceptance": [
            "A spoken reminder becomes a structured record with a due time",
            "Ambiguous times trigger a confirmation",
            "The scheduler fires at the due time"
          ],
          "risks": [
            {
              "risk": "Messy natural-language time parsing",
              "mitigation": "Confirm ambiguous cases; normalize to explicit timestamps before storing."
            }
          ],
          "dependencies": [
            "B1",
            "Centra S4"
          ],
          "references": [
            {
              "title": "APScheduler",
              "url": "https://apscheduler.readthedocs.io"
            }
          ]
        },
        {
          "id": "B3",
          "title": "Notify + sync",
          "status": "todo",
          "summary": "Fire reminders and confirm them across devices, losing nothing offline.",
          "objective": "Deliver due reminders reliably, support acknowledge/snooze, and replay anything missed during an outage.",
          "scope": [
            "Notification fan-out (push / TTS / phone)",
            "Acknowledge + snooze loop",
            "Offline replay of missed reminders"
          ],
          "outOfScope": [
            "Hardware TTS playback wiring (B4)"
          ],
          "approach": [
            {
              "decision": "Reminders fire from Centra, with local echo on Blu",
              "rationale": "Centra is the reliable scheduler; Blu echoes locally (TTS/LED) so it works even if Blu was offline when it fired."
            },
            {
              "decision": "Missed-while-offline reminders resurface on reconnect",
              "rationale": "A reminder that fired while you were away must still reach you — the whole point of the resilient design."
            }
          ],
          "stack": [
            "MQTT",
            "Piper (TTS)",
            "Push (ntfy / web push)"
          ],
          "tasks": [
            {
              "t": "Notification fan-out (push / TTS / phone)",
              "done": false
            },
            {
              "t": "Acknowledge + snooze loop",
              "done": false
            },
            {
              "t": "Offline replay of missed reminders",
              "done": false
            }
          ],
          "deliverables": [
            "Multi-channel notifications",
            "Ack/snooze flow",
            "Offline replay"
          ],
          "acceptance": [
            "A due reminder reaches you across at least two channels",
            "Snooze and done both work and persist",
            "A reminder that fired during an outage surfaces on reconnect"
          ],
          "risks": [
            {
              "risk": "Duplicate notifications across channels/devices",
              "mitigation": "Idempotent notification IDs; ack collapses the fan-out."
            }
          ],
          "dependencies": [
            "B2",
            "Centra S5"
          ],
          "references": [
            {
              "title": "ntfy",
              "url": "https://docs.ntfy.sh"
            }
          ]
        },
        {
          "id": "B4",
          "title": "Real hardware",
          "status": "todo",
          "summary": "Leave the laptop simulator; become a real pocket device.",
          "objective": "Bring the working software loop onto ESP32-S3 hardware with a mic, a button, a status LED, and a battery — a device you can actually pocket.",
          "scope": [
            "ESP32-S3 + INMP441 (I2S mic) capture",
            "MAX98357A (I2S amp) for local TTS/beeps",
            "Button + status LED",
            "LiPo battery + charge management"
          ],
          "outOfScope": [
            "Wake word (B5)",
            "Enclosure polish (B5)"
          ],
          "approach": [
            {
              "decision": "ESP32-S3 (8 MB PSRAM) + INMP441 + MAX98357A",
              "rationale": "The proven 2026 DIY voice stack: I2S digital mic in, I2S amp out, enough PSRAM for audio buffers and small wake models."
            },
            {
              "decision": "Plan I2S bus ownership up front",
              "rationale": "Known gotcha: mic (input) and amp (output) contend for I2S; use separate I2S peripherals or careful half-duplex handoff to avoid playback collisions."
            },
            {
              "decision": "Battery device with store-and-forward",
              "rationale": "It must keep buffering through a power cut — that is the entire premise; the EdgeAgent's conserve mode manages low battery."
            }
          ],
          "stack": [
            "ESP32-S3",
            "INMP441",
            "MAX98357A",
            "ESP-IDF / ESPHome",
            "LiPo + TP4056/charger"
          ],
          "tasks": [
            {
              "t": "Flash firmware to ESP32-S3",
              "done": false
            },
            {
              "t": "INMP441 mic capture over I2S",
              "done": false
            },
            {
              "t": "MAX98357A playback (separate I2S / handoff)",
              "done": false
            },
            {
              "t": "Button + status LED",
              "done": false
            },
            {
              "t": "Battery + charge management",
              "done": false
            }
          ],
          "deliverables": [
            "A working hardware prototype",
            "Mic-in / amp-out",
            "Battery-powered operation"
          ],
          "acceptance": [
            "A physical Blu captures a reminder and it lands in Centra",
            "Local beep/TTS plays without colliding with capture",
            "It keeps buffering through a simulated power cut"
          ],
          "risks": [
            {
              "risk": "I2S mic/amp bus collision",
              "mitigation": "Use separate I2S peripherals or strict half-duplex handoff; validate early with a loopback test."
            },
            {
              "risk": "Battery life too short for always-on",
              "mitigation": "Push-to-talk baseline; defer always-on wake to B5 with a low-power path."
            }
          ],
          "dependencies": [
            "B1",
            "B3"
          ],
          "references": [
            {
              "title": "DIY ESP32-S3 Voice Assistant (INMP441)",
              "url": "https://www.instructables.com/DIY-ESP32-S3-Voice-Assistant-V02-Upgrade-for-Real-/"
            },
            {
              "title": "ESP32-S3 INMP441 + MAX98357A: I2S bus ownership",
              "url": "https://community.home-assistant.io/t/custom-esp32-s3-voice-assistant-inmp441-max98357a-i2s-bus-ownership-and-playback-collisions/1012315"
            },
            {
              "title": "Espressif ESP-SR (speech recognition)",
              "url": "https://github.com/espressif/esp-sr"
            }
          ]
        },
        {
          "id": "B5",
          "title": "Wake word, battery & enclosure",
          "status": "todo",
          "summary": "Hands-free 'Hey Blu', all-day battery, and a pocketable body.",
          "objective": "Make Blu genuinely wearable: on-device wake word, tuned power budget, and a printed enclosure.",
          "scope": [
            "On-device wake word ('Hey Blu') low-power path",
            "Battery-life tuning / conserve mode",
            "3D-printed enclosure"
          ],
          "outOfScope": [],
          "approach": [
            {
              "decision": "On-device wake word via microWakeWord / ESP-SR WakeNet, not server openWakeWord",
              "rationale": "Research: openWakeWord is too heavy for an MCU and forces constant audio streaming; microWakeWord/WakeNet7 run on the ESP32-S3 (~320 KB model, <2% false-positive) so Blu only wakes and streams on the phrase — critical for battery."
            },
            {
              "decision": "Wake word is the biggest battery cost — budget for it",
              "rationale": "Always-on listening dominates power draw; size the battery and duty-cycle around it, and keep push-to-talk as a fallback."
            }
          ],
          "stack": [
            "microWakeWord / ESP-SR WakeNet",
            "LiPo",
            "FreeCAD / 3D print"
          ],
          "tasks": [
            {
              "t": "Wake-word ('Hey Blu') low-power path",
              "done": false
            },
            {
              "t": "Battery-life tuning / conserve mode",
              "done": false
            },
            {
              "t": "3D-printed enclosure",
              "done": false
            }
          ],
          "deliverables": [
            "Hands-free wake",
            "A full-day battery budget",
            "A carryable enclosure"
          ],
          "acceptance": [
            "'Hey Blu' wakes the device reliably in a normal-noise room",
            "A full day on a charge with typical use",
            "Pocketable enclosure houses board, mic, battery"
          ],
          "risks": [
            {
              "risk": "Wake-word false positives/negatives",
              "mitigation": "Tune threshold; train a custom 'Hey Blu' model; validate in real ambient noise."
            },
            {
              "risk": "Battery can't sustain always-on",
              "mitigation": "Low-power wake path + conserve mode; fall back to push-to-talk."
            }
          ],
          "dependencies": [
            "B4"
          ],
          "references": [
            {
              "title": "microWakeWord (Kevin Ahrendt)",
              "url": "https://www.kevinahrendt.com/micro-wake-word"
            },
            {
              "title": "OHF-Voice / micro-wake-word",
              "url": "https://github.com/OHF-Voice/micro-wake-word"
            },
            {
              "title": "openWakeWord",
              "url": "https://github.com/dscripka/openWakeWord"
            },
            {
              "title": "Home Assistant — wake words",
              "url": "https://www.home-assistant.io/voice_control/about_wake_word/"
            }
          ]
        }
      ],
      "log": [
        {
          "date": "2026-08-18",
          "entry": "Blu is born as the first project. Folder, spec, edge agent, and reminder service scaffolded; phases B0–B5 defined with real hardware engineering."
        }
      ]
    },
    {
      "id": "aura",
      "codename": "Aura",
      "kind": "project",
      "status": "planned",
      "title": "Ambient Home Sensing",
      "tagline": "The house quietly tells Centra how it feels.",
      "summary": "Temperature, air quality, light, motion, and energy sensors on ESP32 + ESPHome, streaming into TimescaleDB and visualized in Grafana. A roadmap slot — not started. Phases will be scoped, engineer-grade, when Aura is promoted to active.",
      "accent": "green",
      "map": {
        "angle": 320,
        "radius": 0.78
      },
      "repo": "/projects/aura",
      "currentPhase": "",
      "tech": [
        "ESP32",
        "ESPHome",
        "Home Assistant",
        "TimescaleDB",
        "Grafana"
      ],
      "phases": [],
      "log": []
    },
    {
      "id": "sentry",
      "codename": "Sentry",
      "kind": "project",
      "status": "planned",
      "title": "Vision & Presence",
      "tagline": "Eyes at the edge — who's at the gate, all processed locally.",
      "summary": "Local camera vision (Frigate + YOLO, optionally a Coral TPU) turning frames into presence events on the bus. A roadmap slot — not started. Phases will be scoped when Sentry is promoted to active.",
      "accent": "purple",
      "map": {
        "angle": 90,
        "radius": 0.82
      },
      "repo": "/projects/sentry",
      "currentPhase": "",
      "tech": [
        "Frigate",
        "YOLO",
        "Coral TPU",
        "MQTT"
      ],
      "phases": [],
      "log": []
    },
    {
      "id": "atlas",
      "codename": "Atlas",
      "kind": "project",
      "status": "planned",
      "title": "Personal Knowledge",
      "tagline": "A private mind that knows your notes, docs, and life.",
      "summary": "RAG over your own documents via pgvector — a private assistant that knows your stuff and cites it. A roadmap slot — not started. Phases will be scoped when Atlas is promoted to active.",
      "accent": "blue",
      "map": {
        "angle": 25,
        "radius": 0.72
      },
      "repo": "/projects/atlas",
      "currentPhase": "",
      "tech": [
        "pgvector",
        "Ollama",
        "FastAPI",
        "MinIO"
      ],
      "phases": [],
      "log": []
    }
  ]
}
