Q-01 Migration Plan — Pi 4 Surface Overhaul (Bun-First)
Status: SPECIFICATION (pending execution) Agent: opencode/ext-agent (sandshrew) Timestamp UTC: 2026-05-12T03:15:00Z Session: MjF directives for clean Pi 4 surface — Bun everywhere, single container, zero duplication
Directives (from MjF)
- Wiki LLM gateway → Bun (currently Python Hermes — migrate)
- Clean reinstall targeting Bun. Back up EVERYTHING first (wiki, Forgejo repos, configs, data).
- Proper mount conventions — wiki, repos, dirs — with unambiguous protocols for APIs, creds, work org.
- Deps on host/global. Containers hold instances (agents, graphs), not duplicated runtimes.
- Single Docker container for all agents — NOT per-agent containers.
- OAuth via Nous Portal link — MjF handles from Mac.
- Fallback: Qwen primary, Kimi/MiniMax backup. MjF provides keys.
- Everything that can run on Bun, runs on Bun.
Phase 1: Backup (Before Anything Moves)
# 1. Wiki depot (CRITICAL)
docker cp d3-tui-pi-teams-proto:/workcell/llm-wiki /home/mehdifarah/archive/llm-wiki-$(date +%Y%m%d)
tar czf /home/mehdifarah/archive/llm-wiki-$(date +%Y%m%d).tar.gz /home/mehdifarah/archive/llm-wiki-$(date +%Y%m%d)/
# 2. Forgejo repos (on host — already safe, but double-check)
cp -r /home/mehdifarah/git /home/mehdifarah/archive/git-$(date +%Y%m%d)
# 3. Hermes Python gateway config
cp -r /mnt/kitchen/private/hermes /home/mehdifarah/archive/hermes-gateway-$(date +%Y%m%d)
# 4. Docker images (if needed for rollback)
docker save d3-tui-pi-teams-proto:local | gzip > /home/mehdifarah/archive/d3-tui-image-$(date +%Y%m%d).tar.gz
docker save codeberg.org/forgejo/forgejo:14.0.3 | gzip > /home/mehdifarah/archive/forgejo-image-$(date +%Y%m%d).tar.gz
# 5. API keys (from d3-tui container env — document only, don't lose)
docker exec d3-tui-pi-teams-proto env | grep -E 'KIMI|MINIMAX|MISTRAL' > /home/mehdifarah/archive/api-keys-$(date +%Y%m%d).txt
chmod 600 /home/mehdifarah/archive/api-keys-$(date +%Y%m%d).txt
Verification gate: Before any destructive action, confirm all archives exist and are non-empty.
Phase 2: Cleanup (Remove What's Being Replaced)
# 1. Kill probe server
pkill -f probe_server.py
# 2. Kill unknown Python HTTP on port 8080
kill $(lsof -ti:8080) 2>/dev/null
# 3. Stop Python Hermes gateway (80MB freed)
systemctl stop hermes-gateway 2>/dev/null || pkill -f hermes_cli
# 4. Archive and remove game-surface-venv (75MB disk freed — no longer needed)
tar czf /home/mehdifarah/archive/game-surface-venv-$(date +%Y%m%d).tar.gz /home/mehdifarah/game-surface-venv/
rm -rf /home/mehdifarah/game-surface-venv
# 5. Prune Docker images (~2.5GB disk freed)
docker image prune -a --force
# 6. Retire d3-tui container (wiki already extracted in Phase 1)
docker stop d3-tui-pi-teams-proto
docker rm d3-tui-pi-teams-proto
Post-cleanup baseline: ~400MB RAM, ~20GB disk free.
Phase 3: Install Bun + Dependencies (Host Level)
# 1. Install Bun
curl -fsSL https://bun.sh/install | bash
mkdir -p /opt/pearl/bin
ln -sf ~/.bun/bin/bun /opt/pearl/bin/bun
# 2. Create game-surface Bun project
mkdir -p /home/mehdifarah/game-surface
cd /home/mehdifarah/game-surface
bun init
# 3. Install all deps
bun add @langchain/langgraph @langchain/core
bun add @langchain/langgraph-checkpoint
bun add hermes-agent # Hermes on Bun (exact package TBD)
bun add hono # HTTP server
bun add zod # State schema validation
All deps in one place. No pip. No npm. Just bun add.
Phase 4: Container Shape
Decision: Host Process (Not Docker)
Given that Bun is installed on host and all deps are in one Bun project, the game backend can run directly on the host — no Docker container needed. This is simpler, faster, and eliminates another image to manage.
If Docker is preferred for isolation, use oven/bun as base:
FROM oven/bun:1
WORKDIR /app
COPY package.json bun.lock ./
RUN bun install
COPY src/ ./src/
CMD ["bun", "run", "src/http.ts"]
Mounts:
-v /home/mehdifarah/game-surface/wiki:/wiki # wiki files
-v /home/mehdifarah/git:/git # Forgejo repos (read-only)
-v /opt/pearl/config:/config # secrets, env
But for the prototype, host process is simpler and recommended. One tmux pane: bun run src/http.ts. Done.
What Stays in Docker
| Container | Purpose |
|---|---|
| from-forgejo | Git forge — already working, leave as-is |
What Runs on Host
| Process | Command |
|---|---|
| LangGraph + HTTP + Hermes | bun run src/http.ts |
| Wiki gateway (Bun) | bun run src/wiki-server.ts (separate process or same — TBD) |
Phase 5: Directory Conventions (Unambiguous)
/home/mehdifarah/game-surface/ ← Bun project root
├── src/
│ ├── http.ts ← Hono HTTP server (port 8000)
│ ├── graph.ts ← LangGraph graph definition
│ ├── state.ts ← StateSchema (omni config)
│ ├── hermes.ts ← Hermes agent harness (Bun)
│ ├── wiki-server.ts ← Wiki gateway (Bun, serves markdown)
│ └── checkpointer.ts ← SqliteSaver setup
├── wiki/ ← Wiki depot (extracted from d3-tui)
│ ├── architecture/
│ ├── concepts/
│ ├── decisions/
│ ├── research/
│ └── runtime/ ← Runtime wiki pages (per-run)
├── config/
│ ├── models.ts ← Model registry (Qwen, Kimi, MiniMax)
│ ├── fallback.ts ← Fallback chain logic
│ └── nodes.ts ← Node definitions (36 hexes, phase tags)
├── package.json
└── bun.lock
/mnt/kitchen/private/hermes/ ← Old Python Hermes (archived, not used)
/opt/pearl/
├── bin/bun ← Bun symlink
├── config/
│ ├── .env ← API keys (never committed)
│ └── oauth/ ← Hermes Portal OAuth token
└── secrets/ ← 600 perms, never committed
Mount/Path Protocol (Non-Negotiable)
| Path | Purpose | Permissions | Backed by |
|---|---|---|---|
/home/mehdifarah/game-surface/wiki/ |
Wiki depot | read/write | Phase 1 archive |
/home/mehdifarah/git/ |
Forgejo repos | read for research, write for git ops | Host filesystem |
/opt/pearl/config/.env |
API keys | read-only, 600 perms | Manual creation |
/opt/pearl/config/oauth/ |
Hermes Portal token | read-only, 600 perms | MjF provides |
Phase 6: OAuth & Fallback Configuration
OAuth (Nous Portal)
- MjF receives OAuth link from Hermes Portal
- Token stored at
/opt/pearl/config/oauth/hermes-portal.json - Hermes on Bun reads token at startup
- If token expires, Hermes prompts for re-auth (flag to MjF)
Fallback Chain
// config/fallback.ts
export const MODEL_CHAIN = [
{
id: "qwen-3.6-plus",
provider: "hermes-portal",
auth: { type: "oauth", path: "/opt/pearl/config/oauth/hermes-portal.json" },
priority: 1,
},
{
id: "kimi-k2.6",
provider: "kimi",
auth: { type: "api_key", env: "KIMI_API_KEY" },
priority: 2,
},
{
id: "minimax",
provider: "minimax",
auth: { type: "api_key", env: "MINIMAX_API_KEY" },
priority: 3,
},
];
export async function getActiveModel(): Promise<ModelConfig> {
for (const model of MODEL_CHAIN.sort((a, b) => a.priority - b.priority)) {
if (await isModelAvailable(model)) {
return model;
}
}
throw new Error("No models available");
}
The RG displays which model is active per unit:
Rif: Qwen 3.6+ | Echo: Qwen 3.6+ | Sherpa: Kimi K2.6 (fallback)
Phase 7: Verify
# 1. Bun version
bun --version
# 2. LangGraph import
bun -e "import { StateGraph } from '@langchain/langgraph'; console.log('OK')"
# 3. HTTP server starts
bun run src/http.ts &
sleep 2
curl http://localhost:8000/health
# 4. Hermes auth
bun run src/hermes.ts --check-auth
# 5. Wiki gateway serves pages
curl http://localhost:8000/wiki/index.md
# 6. Forgejo reachable
curl http://localhost:3001/
# 7. Memory after startup
free -h
# 8. Disk after cleanup
df -h /
Dependency Summary
| What | Where | Install Method |
|---|---|---|
| Bun | Host (/opt/pearl/bin/bun) |
curl -fsSL https://bun.sh/install \| bash |
| LangGraph | Bun project | bun add @langchain/langgraph |
| Hermes | Bun project | bun add hermes-agent |
| Hono | Bun project | bun add hono |
| Zod | Bun project | bun add zod |
| SqliteSaver | Bun project | bun add @langchain/langgraph-checkpoint |
| Forgejo | Docker | Existing — no change |
| Wiki files | Host filesystem | Extracted from d3-tui in Phase 1 |
Zero pip installs. Zero npm installs. Everything is bun add or already exists.