split claude.md into docs/ — per-topic, human-readable
claude.md was eating 400 lines of subsystem detail that's useful
when you're working on that subsystem and noise the rest of the
time. split into:
- docs/conventions.md naming, identity, async forms, commit style
- docs/gotchas.md nspawn / nixos-container quirks
- docs/web-ui.md dashboard + per-agent layouts and endpoints
- docs/turn-loop.md claude invocation, wake prompt, mcp surface
- docs/approvals.md approval flow, manager policy, helper events
- docs/persistence.md sqlite dbs, retention, state dir layout
claude.md is now the entry point — file map, reading paths
("pick the doc that matches your task"), quick reminders that
fit on one screen, and a small scratchpad section for in-flight
context. references the docs; the docs don't reference claude.md.
no content was lost — the docs/ files cover everything the old
claude.md did, plus things i wrote up better while extracting.
This commit is contained in:
parent
c27111ac32
commit
8b10731aa4
7 changed files with 708 additions and 396 deletions
118
docs/turn-loop.md
Normal file
118
docs/turn-loop.md
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
# Turn loop + MCP
|
||||
|
||||
How the harness wakes up, what it asks claude to do, and what tools
|
||||
claude has access to in return.
|
||||
|
||||
## The loop
|
||||
|
||||
Each agent harness (`hive-ag3nt serve` or `hive-m1nd serve`) runs:
|
||||
|
||||
1. Long-poll `Recv` on its socket. The host-side broker
|
||||
(`broker.rs::recv_blocking`) returns immediately if there's a
|
||||
pending message, otherwise waits up to 30 s for a broker `Sent`
|
||||
event for this recipient.
|
||||
2. Pop one message. Peek the remaining inbox depth with `Status`.
|
||||
3. Emit `LiveEvent::TurnStart { from, body, unread }` onto the SSE
|
||||
bus.
|
||||
4. Spawn claude (one process per turn) and pipe the wake prompt
|
||||
over stdin.
|
||||
5. Stream stdout (JSON lines) into the bus as
|
||||
`LiveEvent::Stream(value)`. Pump stderr as `Note`.
|
||||
6. Wait for claude to exit. On `Prompt is too long`, run `/compact`
|
||||
on the session once and retry the turn.
|
||||
7. Emit `LiveEvent::TurnEnd { ok, note }`. Sleep `poll_ms` to avoid
|
||||
tight loops on transient failures.
|
||||
|
||||
## The claude invocation
|
||||
|
||||
```
|
||||
claude --print --verbose --output-format stream-json --model haiku \
|
||||
--continue --settings /run/hive/claude-settings.json \
|
||||
--system-prompt-file /run/hive/claude-system-prompt.md \
|
||||
--mcp-config /run/hive/claude-mcp-config.json --strict-mcp-config \
|
||||
--tools <builtins> --allowedTools <builtins+mcp>
|
||||
# wake prompt piped over stdin
|
||||
```
|
||||
|
||||
`--continue` keeps a persistent session per agent (claude stores
|
||||
sessions in `~/.claude/projects/`, which is bind-mounted
|
||||
persistently). Auto-compact and auto-memory are disabled via
|
||||
`--settings` because hyperhive owns compaction (`/compact` on
|
||||
overflow, retry once; operator can also force one via `/api/compact`).
|
||||
|
||||
The wake prompt is intentionally minimal: just the popped message's
|
||||
`from`/`body`, plus an inline `({unread} more pending — drain via
|
||||
…)` hint when `unread > 0`. Claude drives any further `recv`/`send`
|
||||
itself via the embedded MCP server.
|
||||
|
||||
### On-boot files
|
||||
|
||||
`hive_ag3nt::turn::write_*` writes three files next to the per-agent
|
||||
socket at `/run/hive/` once at startup:
|
||||
|
||||
- `claude-mcp-config.json` — re-invokes the running binary as `mcp`
|
||||
child (so the same binary serves as harness + as claude's MCP
|
||||
child process).
|
||||
- `claude-settings.json` — the `--settings` blob (auto-compact and
|
||||
auto-memory off, effortLevel medium).
|
||||
- `claude-system-prompt.md` — rendered from
|
||||
`hive-ag3nt/prompts/{agent,manager}.md` with `{label}`
|
||||
substituted. Passed via `--system-prompt-file`.
|
||||
|
||||
The shared per-turn plumbing lives in `hive_ag3nt::turn::{write_mcp_config,
|
||||
write_settings, write_system_prompt, run_turn, drive_turn,
|
||||
emit_turn_end, wait_for_login, compact_session}` so the two binaries
|
||||
can't drift.
|
||||
|
||||
## MCP surface
|
||||
|
||||
The harness ships an embedded MCP server (rmcp 1.7). Claude launches
|
||||
it as a stdio child via `--mcp-config`. The hyperhive socket name is
|
||||
`hyperhive`, so the tools land in claude as `mcp__hyperhive__<tool>`.
|
||||
|
||||
### Sub-agent tools
|
||||
|
||||
- `send(to, body)` — message a peer (logical agent name), another
|
||||
agent, or the operator (recipient `operator`, surfaces in the
|
||||
dashboard inbox).
|
||||
- `recv()` — drain one inbox message.
|
||||
|
||||
### Manager tools (in addition to send/recv)
|
||||
|
||||
- `request_spawn(name)` — queue a Spawn approval for a brand-new
|
||||
sub-agent (≤9 char name). Operator approves on the dashboard.
|
||||
- `kill(name)` — graceful stop. No approval required.
|
||||
- `start(name)` — start a stopped sub-agent. No approval.
|
||||
- `restart(name)` — stop + start. No approval.
|
||||
- `request_apply_commit(agent, commit_ref)` — submit a config
|
||||
change for any agent (`hm1nd` for the manager's own config) for
|
||||
operator approval.
|
||||
- `ask_operator(question, options?, multi?)` — surface a question
|
||||
on the dashboard. Non-blocking — returns the queued question id;
|
||||
the operator's answer arrives later as
|
||||
`HelperEvent::OperatorAnswered` in the manager inbox. Options
|
||||
always render alongside a free-text fallback; `multi=true`
|
||||
renders options as checkboxes.
|
||||
|
||||
The boundary: lifecycle ops on *existing* sub-agents
|
||||
(`kill`/`start`/`restart`) are at the manager's discretion — no
|
||||
operator approval. Creating a new agent (`request_spawn`) and
|
||||
changing any agent's config (`request_apply_commit`) still go
|
||||
through the approval queue.
|
||||
|
||||
### Tool envelope
|
||||
|
||||
`mcp::run_tool_envelope`: every MCP tool handler logs the request,
|
||||
runs the body, logs the result. Pre-/post-log only — the inbox
|
||||
status hint moved to the wake prompt + UI header.
|
||||
|
||||
### Tool whitelist (`mcp::ALLOWED_BUILTIN_TOOLS`)
|
||||
|
||||
- Allowed built-ins: `Bash`, `Edit`, `Glob`, `Grep`, `Read`,
|
||||
`TodoWrite`, `Write`.
|
||||
- Denied by omission: `WebFetch`, `WebSearch`, `Task`,
|
||||
`NotebookEdit`.
|
||||
- Allowed MCP tools: as listed above per flavor.
|
||||
|
||||
`Bash` is on the allow-list pending a finer-grained pattern allow-list
|
||||
(`Bash(git *)`-style) — see [TODO](../TODO.md).
|
||||
Loading…
Add table
Add a link
Reference in a new issue