feat(agent): Phase 1 — chat-window AI assistant via Claude Code subprocess
Adds an in-dashboard AI assistant that answers questions about live game
state. Designed reactively (no background loops) — every user message in
the chat window or via /api/agent/ask runs one `claude -p` invocation.
Architecture:
- New host-side FastAPI service (agent/) on 127.0.0.1:8767, OUTSIDE the
dereth-tracker Docker container because `claude` and ~/.claude
credentials live on the host.
- nginx routes /api/agent/* to the host service.
- The same browser session cookie the tracker issues authenticates
agent requests (shared SECRET_KEY).
- The agent shells out to `claude -p --session-id <uuid>` with
cwd=/home/erik/MosswartOverlord. Sessions persist as JSONL on disk
via Claude Code's built-in machinery.
- An MCP stdio server (agent/mcp_overlord.py) exposes tools to Claude:
get_live_players, get_recent_rares, query_telemetry_db (read-only,
parsed by sqlglot to reject DML/DDL), get_player_state, get_inventory,
get_inventory_search, get_combat_stats, get_equipment_cantrips,
get_quest_status, get_server_health, suitbuilder_search.
- Read-only PG role (overlord_agent_ro) is the second line of defense
on the SQL tool — even a parser bypass can't mutate.
Frontend:
- AgentWindow.tsx — draggable chat window with localStorage-pinned
session UUID, "New Chat" button, on-mount rehydration from
/agent/sessions/{id}/history (parses Claude Code's JSONL).
- Wired into WindowRenderer + Sidebar (🤖 Assistant button).
Operational:
- systemd unit (overlord-agent.service) + install.sh.
- agent/README.md documents env vars, deploy flow, smoke tests.
- nginx/overlord.conf gets a new /api/agent/ location with 180s timeout.
- CLAUDE.md gets an "Overlord Assistant Mode" section briefing the
agent on which tools to use and how to behave.
NOT YET DEPLOYED — server still needs:
1. Apply agent/sql/0001_overlord_agent_ro.sql + ALTER ROLE password
2. Add AGENT_DB_DSN to /home/erik/MosswartOverlord/.env
3. bash agent/install.sh (creates venv, installs unit, starts service)
4. sudo cp /home/erik/MosswartOverlord/nginx/overlord.conf to nginx + reload
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
aeddaf9925
commit
79cf88d3f7
35 changed files with 1763 additions and 25 deletions
146
agent/README.md
Normal file
146
agent/README.md
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
# Overlord Agent
|
||||
|
||||
A small host-side Python service that gives Claude Code (running in
|
||||
headless mode) access to live Overlord data so it can answer questions
|
||||
from the dashboard chat window.
|
||||
|
||||
## Why a separate service?
|
||||
|
||||
`dereth-tracker` runs in Docker. The `claude` CLI binary at
|
||||
`/home/erik/.local/bin/claude` depends on `~/.claude` credentials owned
|
||||
by user `erik` on the host. The tracker container can't invoke it.
|
||||
|
||||
So this service runs **outside** Docker, listens on `127.0.0.1:8767`,
|
||||
and nginx routes `/api/agent/*` to it. It validates the same browser
|
||||
session cookie the tracker issues (shared `SECRET_KEY`) and shells out
|
||||
to `claude -p` with `cwd=/home/erik/MosswartOverlord`.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Browser ──nginx──┬─► /api/* ──► dereth-tracker (Docker, 8765)
|
||||
│
|
||||
└─► /api/agent/* ──► overlord-agent (host, 8767)
|
||||
│
|
||||
├─► subprocess: claude -p ...
|
||||
│ │
|
||||
│ └─► MCP stdio ──► mcp_overlord.py
|
||||
│ │
|
||||
│ └─► HTTP loopback to tracker
|
||||
│ └─► asyncpg to dereth-db
|
||||
│
|
||||
└─► validates "session" cookie
|
||||
```
|
||||
|
||||
## Files
|
||||
|
||||
| File | What |
|
||||
|------|------|
|
||||
| `service.py` | FastAPI app (`/agent/health`, `/agent/sessions/new`, `/agent/ask`, `/agent/sessions/{id}/history`) |
|
||||
| `auth.py` | Session-cookie validation (mirrors `main.py:1013-1019`) |
|
||||
| `claude_wrapper.py` | `asyncio.create_subprocess_exec("claude", "-p", ...)` |
|
||||
| `tools.py` | Pure tool implementations (HTTP loopback + read-only DB) |
|
||||
| `mcp_overlord.py` | MCP stdio server registering tools for Claude Code |
|
||||
| `sql/0001_overlord_agent_ro.sql` | Read-only PG role for the SQL tool |
|
||||
| `overlord-agent.service` | systemd unit |
|
||||
| `install.sh` | One-shot installer (venv + pip install + systemd) |
|
||||
|
||||
## Required env vars (in repo-root `.env`)
|
||||
|
||||
```
|
||||
SECRET_KEY=<same value the tracker uses to sign cookies>
|
||||
AGENT_DB_DSN=postgresql://overlord_agent_ro:<password>@127.0.0.1:5432/dereth
|
||||
TRACKER_URL=http://127.0.0.1:8765 # optional, this is the default
|
||||
CLAUDE_BIN=/home/erik/.local/bin/claude # optional, this is the default
|
||||
CLAUDE_CWD=/home/erik/MosswartOverlord # optional, this is the default
|
||||
CLAUDE_TIMEOUT_S=120 # optional
|
||||
```
|
||||
|
||||
## First-time setup on the server
|
||||
|
||||
1. **Create the read-only DB role** (one-time):
|
||||
```bash
|
||||
docker exec -i dereth-db psql -U postgres -d dereth \
|
||||
< /home/erik/MosswartOverlord/agent/sql/0001_overlord_agent_ro.sql
|
||||
docker exec -it dereth-db psql -U postgres -d dereth \
|
||||
-c "ALTER ROLE overlord_agent_ro PASSWORD '<random-password>';"
|
||||
```
|
||||
2. **Add `AGENT_DB_DSN`** to `/home/erik/MosswartOverlord/.env` with the
|
||||
password you just set.
|
||||
3. **Run the installer**:
|
||||
```bash
|
||||
cd /home/erik/MosswartOverlord
|
||||
bash agent/install.sh
|
||||
```
|
||||
4. **Update nginx**: edit `/etc/nginx/sites-enabled/overlord` to add the
|
||||
`/api/agent/` location (already in `nginx/overlord.conf` in the repo —
|
||||
just `sudo cp` and reload).
|
||||
|
||||
## Day-to-day deploy
|
||||
|
||||
After editing any agent file:
|
||||
|
||||
```bash
|
||||
# On dev:
|
||||
git push
|
||||
|
||||
# On server:
|
||||
ssh erik@overlord.snakedesert.se
|
||||
cd /home/erik/MosswartOverlord
|
||||
git pull
|
||||
sudo systemctl restart overlord-agent
|
||||
journalctl -u overlord-agent -f # tail logs
|
||||
```
|
||||
|
||||
For Python dependency changes:
|
||||
|
||||
```bash
|
||||
agent/.venv/bin/pip install -r agent/requirements.txt
|
||||
sudo systemctl restart overlord-agent
|
||||
```
|
||||
|
||||
## Smoke tests
|
||||
|
||||
```bash
|
||||
# 1. Service alive?
|
||||
curl http://127.0.0.1:8767/agent/health
|
||||
|
||||
# 2. Cookie required?
|
||||
curl -X POST http://127.0.0.1:8767/agent/ask \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"session_id":"x","message":"hi"}'
|
||||
# ⇒ 401
|
||||
|
||||
# 3. Direct claude invocation works?
|
||||
echo "hello" | /home/erik/.local/bin/claude -p \
|
||||
--session-id 11111111-1111-1111-1111-111111111111 \
|
||||
--output-format json
|
||||
|
||||
# 4. End-to-end via nginx (with cookie):
|
||||
curl -X POST https://overlord.snakedesert.se/api/agent/ask \
|
||||
-b 'session=<your-session-cookie>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"session_id":"<uuid>","message":"How many characters are online?"}'
|
||||
```
|
||||
|
||||
## Cost / rate-limit notes
|
||||
|
||||
- Each `/agent/ask` shells out to `claude -p` once.
|
||||
- We use the user's Claude subscription (no API key) — flat-rate, no
|
||||
per-call billing, but subscription-tier rate limits still apply.
|
||||
- **Reactive only**: there are no background loops or periodic ticks.
|
||||
Each user message = one Claude turn (which may chain several tool
|
||||
calls internally before producing a final answer).
|
||||
- The SQL tool is hard-capped at 10s and 200 rows.
|
||||
- `suitbuilder_search` is the only tool that can take minutes; nginx
|
||||
read timeout is 180s for `/api/agent/`.
|
||||
|
||||
## Adding a new MCP tool
|
||||
|
||||
1. Implement `async def my_tool(...) -> dict` in `tools.py`.
|
||||
2. Register it in `mcp_overlord.py` under `TOOL_DEFS`:
|
||||
- description (the agent reads this to decide when to call)
|
||||
- JSON schema for arguments
|
||||
- lambda dispatching to `T.my_tool(...)`
|
||||
3. `sudo systemctl restart overlord-agent`. Claude Code re-discovers the
|
||||
tool list on each invocation.
|
||||
Loading…
Add table
Add a link
Reference in a new issue