buildNotifier returns a nil interface when BRAIN_NTFY_URL is unset, and every consumer skips a nil notifier silently. That is right for an unconfigured homelab and wrong for an alert whose entire value is that someone finds out: "configured and working" and "never configured" were byte-identical in behaviour — no error, no warning, only a message that never arrives, which nobody notices because nobody is waiting for an alert they have never seen. Two alerts were dark in production for exactly that reason (#100): the governance-rewrite alert from #97, and the audit-degradation alert that reports the audit trail is down. Both were found by firing a real event and watching nothing happen, not by reading anything. WARN rather than fatal, deliberately. The brain rule `empty-credential-must-refuse-to-start-not-switch-modes` says an empty credential must be a startup error rather than a silent mode switch — but that is about a control being removed. Here observability degrades, no gate is bypassed, and refusing to boot would break the unconfigured-homelab case the nil notifier exists to support. What that rule demands and this provides is that the mode switch stops being silent. buildAuditSink already announces its own fallback ("capture audit: slog sink (BRAIN_LOKI_URL unset)"), so only the notifier needed this. alertingWarnings is pure and env-injected: the reason this gap survived is that nothing about it was observable, so the fix is tested rather than asserted. Refs #107, #100, #97.
hyperguild
An MCP server that acts as a disciplined AI supervisor for Claude Code sessions. Instead of letting Claude Code do whatever it wants, hyperguild enforces structured workflows (TDD red/green/refactor), logs every session, and accumulates learnings into a searchable brain.
This repo is also where brain-mcp lives — the MCP server at
brain-mcp.d-ma.be that brain_query, brain_write, brain_read, brain_edit
and capture are served from. It is not a separate deployable; see
Where brain-mcp lives.
Hypothesis
We believe routing skill tasks through local models, backed by brain context, produces measurably better outcomes than raw Claude Code alone — measurable by per-skill pass rate over rolling 30-day windows (available at
GET /pass-rate?skill=<name>&window=30don the brain pod).
This is the falsifiable claim the routing pod and pass-rate infrastructure exist to test. If per-skill pass rates don't improve over baseline (all-cloud) after 30 days of real usage, the fast-model routing path should be reconsidered.
Harness
hyperguild = Claude Code + MCP. This is a supervisor for Claude Code sessions specifically. For multi-agent orchestration (OpenCode + LiteLLM, executor/reviewer pipelines), see agentsquad — a separate harness for a different orchestration model. Skills (mathias/skills) are shared between both.
How it works
Your Claude Code session (in any project)
│
│ MCP over HTTP (Tailscale)
├──▶ routing :3210 (NodePort 30310 on koala) — review, debug, retrospective, trainer
└──▶ brain :3300 (NodePort 30330 on koala) — brain_query, brain_write, brain_ingest, session_log
│
└─ also serves the legacy REST endpoints (/query, /write, /ingest, …)
│
▼
brain/
├── sessions/ — JSONL log, one file per session_id
├── wiki/ — searchable knowledge (wing/hall layout)
│ ├── homelab/
│ ├── claude-sessions/
│ └── ...
└── knowledge/ — legacy flat notes (migration pending: hyperguild#22)
Where brain-mcp lives
brain-mcp is served from ingestion/internal/mcp in this repo, at
brain-mcp.d-ma.be. It is a component inside the ingestion service, not
a standalone deployable — one pod in the supervisor namespace serves the MCP
endpoint, the REST API, and the claudewatcher together.
| You are looking for | It is here |
|---|---|
The MCP tool surface (brain_query, brain_write, brain_read, brain_edit, capture, …) |
ingestion/internal/mcp |
The HTTP/REST API (/query, /write, /ingest) |
ingestion/internal/api |
| Session ingestion from Claude Code transcripts | ingestion/internal/claudewatcher |
| The brain content (notes, wings, halls) | mathias/brain — a separate repo |
| Deployment manifests | config/supervisor |
Why it is not its own repo
Asked and decided in #91
(part 2). internal/mcp imports 17 of ingestion's other internal packages and
has exactly one importer — it is the MCP facade over the service's own
internals, so "move it out" resolves to renaming ingestion, publishing 17
packages as a shared library, or maintaining a shim. The discoverability problem
that prompted the question is answered by naming it here instead.
Full reasoning, including the reversal trigger: brain
wiki/hyperguild/decisions/brain-mcp-stays-in-ingestion-fix-discoverability-by-name.
Phase 1 tools (available now)
| Tool | What it does |
|---|---|
session_log |
Appends a structured entry to the session JSONL log |
retrospective |
Reads the session log, identifies novel learnings, writes to brain |
review |
Structured code review via local model, brain-context injected |
debug |
Hypothesis-driven debugging via local model |
brain_query |
Full-text search over brain/wiki/ |
brain_write |
Writes a note to brain (with wing/hall routing) |
brain_answer |
BM25 + LLM synthesis — Q&A over brain corpus |
tier |
Returns the current connectivity tier (1=cloud, 2=LAN, 3=offline) |
Note:
tdd_red/green/refactorandspecwere retired in Plan 7 (2026-05-12). They are now SKILL.md files in mathias/skills.
Connect a project
Create .mcp.json in your project root:
{
"mcpServers": {
"routing": {
"type": "http",
"url": "http://koala:30310/mcp"
},
"brain": {
"type": "http",
"url": "http://koala:30330/mcp"
}
}
}
Two MCP servers are exposed, both reachable over Tailscale:
routingatkoala:30310— skill workers (review,debug,retrospective,trainer). Routes each call to fast local model or thinking model based on per-skill pass rate.brainatkoala:30330— knowledge access (brain_query,brain_write,brain_ingest,brain_ingest_raw,brain_answer,brain_classify) andsession_log.
No local binary or stdio shim is required — Claude Code talks to both via HTTP.
Open Claude Code in your project — run /mcp to confirm both servers are listed.
A typical session
1. Call review → brain context injected + local model review → findings
2. Call session_log → log each phase result
3. Call retrospective → extracts learnings → brain
4. Future sessions: call brain_query / brain_answer to retrieve relevant context
Tier detection
The routing pod probes connectivity at call time:
| Tier | Label | Condition |
|---|---|---|
| 1 | full-online | Can reach api.anthropic.com |
| 2 | lan-only | Can reach LiteLLM but not Anthropic |
| 3 | airplane | No external connectivity |
Model routing
The routing pod selects models per skill call based on historical pass rate:
| Pass rate | Decision |
|---|---|
| ≥ 0.90 (FLOOR) | Fast model (HYPERGUILD_FAST_MODEL) |
| ≤ 0.70 (CEIL) | Thinking model (HYPERGUILD_THINKING_MODEL) |
| between CEIL and FLOOR | Sample band — probabilistic routing |
| nil (no history yet) | Defaults to thinking model |
Bootstrap note: With no session history, all calls route to the thinking model. The fast-model path activates only after real pass-rate data accumulates at
/pass-rate. Seed with real usage — don't try to pre-populate.
Key env vars
| Variable | Default | Purpose |
|---|---|---|
INGEST_BRAIN_DIR |
../brain |
Brain directory for ingestion server |
INGEST_PORT |
3300 |
Ingestion server port |
INGEST_BASE_URL |
http://localhost:3300 |
Routing pod → brain |
LITELLM_BASE_URL |
— | LiteLLM proxy for Tier 2 model routing |
ROUTING_PORT |
3210 |
Routing pod's listen port |
ROUTING_MCP_TOKEN |
— | Optional bearer token; when empty, no auth enforced |
BRAIN_URL |
http://ingestion.supervisor:3300 |
Routing pod → brain (in-cluster) |
HYPERGUILD_FAST_MODEL |
koala/qwen35-9b-fast |
Fast model for high-pass-rate skill calls |
HYPERGUILD_THINKING_MODEL |
iguana/gemma4-26b |
Thinking model for low-pass-rate skill calls |
HYPERGUILD_ROUTE_LOCAL_FLOOR |
0.90 |
Fast model threshold |
HYPERGUILD_ROUTE_LOCAL_CEIL |
0.70 |
Thinking model threshold |
HYPERGUILD_PASS_RATE_TTL_SECONDS |
60 |
Per-skill pass-rate cache TTL |
Operator note: LiteLLM at
LITELLM_BASE_URLmust register bothHYPERGUILD_FAST_MODELandHYPERGUILD_THINKING_MODEL. If a model is missing, the fail-open retry also fails and the only signal isfinal_status: "fail"on_routingentries in the brain.
Open issues
See issues — key open items:
- #25 — skills platform overhaul (audit first, then lazy loading + brain feedback loop)
- #24 — reduce context burn from skill listing
- #22 — migrate legacy brain notes to wing/hall layout (one-shot script, low risk)
- #31 — connect routing-mcp to claude.ai as custom connector