| 1 | # Zion PM v2 |
| 2 | |
| 3 | A single self-hosted Project Manager app for Zion's homelab. The PM brain is **GLM** (via the Z.ai Coding Plan subscription); it takes any task, decomposes it, delegates work to the worker fleet (Claude, Codex, local Ollama), supervises, and gates every outward action behind an approval outbox. |
| 4 | |
| 5 | One surface, three panes: |
| 6 | - **Chat** - channels with a default model; `@mention` any model mid-conversation. |
| 7 | - **Board** - kanban tasks (todo / doing / done / blocked). |
| 8 | - **Fleet** - worker status, cap gauges, ping, and the approval outbox. |
| 9 | |
| 10 | Built per `SEED.md` (the authoritative charter). Runs on **CT120 (REDACTED-IP)**, fronted at **https://REDACTED-HOST** behind Authentik SSO. |
| 11 | |
| 12 | ## Architecture |
| 13 | |
| 14 | ``` |
| 15 | REDACTED-HOST (Traefik + Authentik forwardAuth, CT244) |
| 16 | │ |
| 17 | ▼ http://REDACTED-IP:4100 |
| 18 | Next.js app (App Router, port 4100) |
| 19 | │ REST API routes |
| 20 | ▼ |
| 21 | pmConductor (Mastra Agent, GLM via Z.ai Anthropic-compat) |
| 22 | │ tools: delegate / createTask / updateTask / draftApproval |
| 23 | ▼ |
| 24 | Worker adapters ──► claude (SSH root@CT215, claude -p) |
| 25 | ──► codex (SSH zion@CT241, codex exec) |
| 26 | ──► ollama (HTTP 3060 /api/chat) |
| 27 | shared memory ──► queryMemory (homelab-rag MCP :9876) |
| 28 | ──► queryGraph (cbm-homelab MCP :9877) |
| 29 | store ───────────► better-sqlite3 at data/zionpm.db |
| 30 | ``` |
| 31 | |
| 32 | GLM is the **conductor**, not a worker. Worker tier = claude / codex / ollama, with strong→weak fallback (claude → codex → ollama) when a model is capped. |
| 33 | |
| 34 | ## Layout |
| 35 | |
| 36 | | Path | Purpose | |
| 37 | |---|---| |
| 38 | | `lib/glm.ts` | GLM model via Z.ai Anthropic-compat (`@ai-sdk/anthropic`, custom baseURL) | |
| 39 | | `lib/mastra.ts` | `pmConductor` Mastra agent + tool registry + `runConductor()` | |
| 40 | | `lib/memory.ts` | `queryMemory` / `queryGraph` over the MCP SDK (SSE) | |
| 41 | | `lib/router.ts` | channel-default + `@mention` dispatch (§7) | |
| 42 | | `lib/db.ts` | SQLite schema + seed + accessors (§5) | |
| 43 | | `lib/workers/{delegate,claude,codex,ollama,ssh}.ts` | worker adapters + tier fallback (§6) | |
| 44 | | `app/api/**` | REST routes (channels, messages, tasks, outbox, fleet) | |
| 45 | | `app/page.tsx` | Chat / Board / Fleet UI | |
| 46 | |
| 47 | ## Configuration (`.env.local`, never committed) |
| 48 | |
| 49 | | Var | Default | Notes | |
| 50 | |---|---|---| |
| 51 | | `ZAI_API_KEY` | - | Z.ai Coding Plan subscription key (from `.env.secrets`, CRLF-stripped) | |
| 52 | | `PM_BRAIN_MODEL` | `glm-4.6` | conductor model (also `glm-4.5`, `glm-5.2`) | |
| 53 | | `ZAI_BASE_URL` | `https://api.z.ai/api/anthropic/v1` | Anthropic-compatible endpoint | |
| 54 | | `ZPM_OLLAMA_URL` | `http://REDACTED-IP:11434` | 3060 Ollama (CT205 GPU node was offline at build) | |
| 55 | | `ZPM_CLAUDE_USER` | `root` | root@CT215 has the working claude login | |
| 56 | | `ZPM_SSH_KEY` | `/root/.ssh/id_ed25519` | CT120 key, authorized on CT215 + CT241 | |
| 57 | | `ZPM_RAG_SSE` / `ZPM_CBM_SSE` | `:9876` / `:9877` | MCP SSE endpoints | |
| 58 | |
| 59 | Secrets live only in `.env.local` on CT120 and `/shared/projects/.env.secrets`. Never in code or git. |
| 60 | |
| 61 | ## Run / deploy |
| 62 | |
| 63 | The canonical repo lives on the share at `/shared/projects/zion-pm` (committed + pushed from CT215; remote `github.com/zionboggan/zion-pm`, private). The app **runs from CT120 local fs** `/opt/zion-pm` (noexec CIFS rule - never execute from the share). |
| 64 | |
| 65 | CT120 has no direct egress to github.com, so deploys are **CT215-mediated** (CT215 has GitHub access and syncs source to CT120 over SSH): |
| 66 | |
| 67 | ```bash |
| 68 | # on CT215: pull latest canonical, sync source to CT120, build + restart there |
| 69 | cd /shared/projects/zion-pm && git pull |
| 70 | tar czf - app lib public package.json pnpm-lock.yaml tsconfig.json next.config.ts \ |
| 71 | postcss.config.mjs | ssh root@REDACTED-IP 'tar xzf - -C /opt/zion-pm' |
| 72 | ssh root@REDACTED-IP 'cd /opt/zion-pm && pnpm install && pnpm build && systemctl restart zion-pm' |
| 73 | ``` |
| 74 | |
| 75 | Service: `zion-pm.service` (systemd, onboot). Logs: `journalctl -u zion-pm -f`. |
| 76 | |
| 77 | ## Acceptance (SEED §10) - all 8 verified |
| 78 | |
| 79 | 1. Boot: CT120 clean, app on :4100, pm.lab behind Authentik login. |
| 80 | 2. Channels `#pm`(glm) / `#claude` / `#codex` / `#ollama` seeded with model badges. |
| 81 | 3. GLM conductor: 5-bullet cbm-homelab summary grounded via queryMemory/queryGraph. |
| 82 | 4. `@ollama:mistral-nemo:12b` override → Ollama reply authored `ollama`. |
| 83 | 5. `@claude` → SSH CT215 → `claude -p` creates a file; path returned. |
| 84 | 6. Board: task created, drag todo→done persists. |
| 85 | 7. Fleet: glm + claude + codex + ollama green; Ping returns latency for each. |
| 86 | 8. Outbox: conductor/UI drafts an outward action → Approve/Reject; nothing auto-sends. |
| 87 | |
| 88 | ## Build notes / deviations from SEED |
| 89 | |
| 90 | - **Node 22 LTS** (not 20): `create-mastra`/Mastra require Node ≥22.13. |
| 91 | - **Scaffold**: `create-next-app` + `@mastra/core` (the `--template nextjs` flag was not usable non-interactively). Honors the locked "Mastra/TypeScript" decision. |
| 92 | - **Ollama host**: CT205 / the pve-gpu node were **offline** at build time; the worker uses the documented overflow 3060 at `REDACTED-IP` (has `mistral-nemo:12b`). |
| 93 | - **Claude worker** runs as `root@CT215` with `IS_SANDBOX=1` (zion's claude login returned 401; root's is valid; root needs the sandbox flag for `--dangerously-skip-permissions`). |
| 94 | - **Live updates**: polling (MVP) rather than websockets (SEED permits). |
| 95 | - **Authentik**: a proxy provider + `zion-pm` application were added to the embedded outpost for `pm.lab` (mirrors the dashboard/notes apps). |
| 96 | |
| 97 | See `OPS_RUNBOOK.md` for operations and `SEED.md` for the full charter. |