brain-mcp: capture with a tickets array fails; decide whether it graduates to its own repo (from infra#265) #91

Closed
opened 2026-08-11 21:15:54 +00:00 by mathias · 5 comments
Owner

Filed from infra#265, which is closing — brain-mcp's code lives here, not in infra.

Where this actually lives

Confirmed 2026-08-11 from koala:

kubectl get ingress -A | grep brain-mcp   → supervisor/ingestion serves brain-mcp.d-ma.be
kubectl get deploy -n supervisor ingestion → Ready 1/1, age 111d
source                                      → config/supervisor + ingestion/internal/claudewatcher

So brain-mcp is a component inside hyperguild's ingestion service, not a standalone deployable. The original issue's "can't find where this code lives" complaint was accurate — that's the finding, not a side note.

Part 1 — the bug

capture with a tickets array fails. Callers currently fall back to N individual issue_create calls: no data loss, but N round-trips instead of one.

  • Reproduce with a tickets array of length 1
  • Either fix the array path or strip the field from the schema — a schema field that always fails is worse than an absent one, because callers write code against it
  • Regression test at length 1 and length > 1

Part 2 — the repo question (decide, don't build)

Should brain-mcp graduate to mathias/brain-mcp? Zero runtime blast radius either way; this is purely about whether a service with its own public ingress and its own consumers should be discoverable at its own name. Worth deciding rather than leaving implicit — the discoverability problem is what generated the original issue.

Refs

  • infra#265 (closed)
  • infra docs/superpowers/specs/2026-08-11-backlog-verification.md
Filed from infra#265, which is closing — brain-mcp's code lives here, not in infra. ## Where this actually lives Confirmed 2026-08-11 from koala: ``` kubectl get ingress -A | grep brain-mcp → supervisor/ingestion serves brain-mcp.d-ma.be kubectl get deploy -n supervisor ingestion → Ready 1/1, age 111d source → config/supervisor + ingestion/internal/claudewatcher ``` So brain-mcp is a component *inside* hyperguild's `ingestion` service, not a standalone deployable. The original issue's "can't find where this code lives" complaint was accurate — that's the finding, not a side note. ## Part 1 — the bug `capture` with a `tickets` array fails. Callers currently fall back to N individual `issue_create` calls: no data loss, but N round-trips instead of one. - [ ] Reproduce with a `tickets` array of length 1 - [ ] Either fix the array path or strip the field from the schema — a schema field that always fails is worse than an absent one, because callers write code against it - [ ] Regression test at length 1 and length > 1 ## Part 2 — the repo question (decide, don't build) Should brain-mcp graduate to `mathias/brain-mcp`? Zero runtime blast radius either way; this is purely about whether a service with its own public ingress and its own consumers should be discoverable at its own name. Worth deciding rather than leaving implicit — the discoverability problem is what generated the original issue. ## Refs - infra#265 (closed) - infra `docs/superpowers/specs/2026-08-11-backlog-verification.md`
Author
Owner

Sprint-planning split (2026-08-18): this issue bundles a mechanical bug fix with a repo-graduation decision. For CAD execution, only part 1 is dispatchable — a headless agent should fix the tickets array bug + regression test and stop there. Part 2 ("does brain-mcp graduate to its own repo?") is a DECISION NEEDED for Mathias, not something an autonomous run should resolve — leave that half open/unaddressed rather than guessing.

Sprint-planning split (2026-08-18): this issue bundles a mechanical bug fix with a repo-graduation decision. For CAD execution, only part 1 is dispatchable — a headless agent should fix the `tickets` array bug + regression test and stop there. Part 2 ("does brain-mcp graduate to its own repo?") is a `DECISION NEEDED` for Mathias, not something an autonomous run should resolve — leave that half open/unaddressed rather than guessing.
Author
Owner

Part 2 — decided: brain-mcp stays inside ingestion

ADR in the brain: wiki/hyperguild/decisions/brain-mcp-stays-in-ingestion-fix-discoverability-by-name.

First, a flag: your comment above says part 2 is a DECISION NEEDED and an autonomous run should leave it alone. That still holds for headless CAD. This one was decided with you present and directing it, so the ADR records you as decider — but if you read the reasoning below and disagree, the decision is wrong, not the process.

The split isn't actually on offer

internal/mcp imports 17 of ingestion's other internal packages — api, audit, brain, brainstore, capture, capturehttp, classification, extract, graph, graphstore, graphsync, pipeline, reranker, search, session, textutil, vectorstore. 3 007 lines of the service's 11 171, and exactly one importer: cmd/server/main.go.

That is not a component sitting in the wrong repo. It is the MCP facade over the service's own internals — the tools are thin adapters onto the same store, index, graph and search that the HTTP API and the watcher use. So "graduate to mathias/brain-mcp" resolves to one of:

  1. move the 17 packages too — that renames ingestion, it doesn't split it;
  2. publish them as a shared library — 17 packages of version skew across a boundary that moves on nearly every feature;
  3. a thin re-export repo — a split in name, two places to keep in sync, no isolation gained.

None is worth paying for a naming problem.

Precedent cuts both ways, and it lands here

mathias/litellm-infra was split out and absorbed back within three months — a new repo has to earn itself. mathias/gitea-mcp is correctly its own repo, but it fronts a remote API and shares no store; that is what a standalone MCP server actually looks like, and brain-mcp isn't shaped like it. mcp-chassis already extracted the genuinely reusable half, so what remains in internal/mcp is brain-specific by construction.

The discoverability gap is real — I measured it

  • Repo description: "MCP supervisor for disciplined Claude Code sessions" — no brain, no brain-mcp.
  • README: says "accumulates learnings into a searchable brain", never the string brain-mcp, never the hostname.
  • brain-mcp.d-ma.be appears only in CLAUDE.md / AGENTS.md — generated agent-context files, not the human front door.

So searching the forge for "brain-mcp" finds nothing, which is precisely the experience that generated infra#265. That is fixable with a description, a README line, and a docs pointer to ingestion/internal/mcp. Cheap, and it addresses the actual complaint rather than a proxy for it.

I did not file that as a new issue yet — checking issue_list state=all first per convention, and it may just belong in this one's part 2 checklist. Say the word and I'll do it as a small PR alongside part 1.

Reversal trigger, so this isn't permanent by default

Revisit if brain-mcp needs a release cadence independent of ingestion (today they are one pod, and no consumer is asking), or if the shared surface shrinks to ≤3 stable packages. If it ever does split, the unit is the whole ingestion service renamed — not internal/mcp carved out.

The general rule

"It has its own ingress and its own consumers" is an argument about the deployment surface. "Where does the code live" is about the source surface. They're allowed to differ, and conflating them turns a naming problem into a migration.


Consequence for the rest of the queue: brain#4 (brain_read / brain_edit) lands in hyperguild/ingestion/internal/mcp, not a new repo. Item 3 is unblocked on my side.

Part 1 (the tickets array bug) is untouched and next.

## Part 2 — decided: **brain-mcp stays inside `ingestion`** ADR in the brain: `wiki/hyperguild/decisions/brain-mcp-stays-in-ingestion-fix-discoverability-by-name`. First, a flag: your comment above says part 2 is a `DECISION NEEDED` and an autonomous run should leave it alone. That still holds for headless CAD. This one was decided with you present and directing it, so the ADR records you as decider — but if you read the reasoning below and disagree, the decision is wrong, not the process. ### The split isn't actually on offer `internal/mcp` imports **17** of ingestion's other internal packages — `api`, `audit`, `brain`, `brainstore`, `capture`, `capturehttp`, `classification`, `extract`, `graph`, `graphstore`, `graphsync`, `pipeline`, `reranker`, `search`, `session`, `textutil`, `vectorstore`. 3 007 lines of the service's 11 171, and exactly one importer: `cmd/server/main.go`. That is not a component sitting in the wrong repo. It is the **MCP facade over the service's own internals** — the tools are thin adapters onto the same store, index, graph and search that the HTTP API and the watcher use. So "graduate to `mathias/brain-mcp`" resolves to one of: 1. move the 17 packages too — that renames `ingestion`, it doesn't split it; 2. publish them as a shared library — 17 packages of version skew across a boundary that moves on nearly every feature; 3. a thin re-export repo — a split in name, two places to keep in sync, no isolation gained. None is worth paying for a naming problem. ### Precedent cuts both ways, and it lands here `mathias/litellm-infra` was split out and **absorbed back within three months** — a new repo has to earn itself. `mathias/gitea-mcp` is correctly its own repo, but it fronts a remote API and shares no store; that is what a standalone MCP server actually looks like, and brain-mcp isn't shaped like it. `mcp-chassis` already extracted the genuinely reusable half, so what remains in `internal/mcp` is brain-specific by construction. ### The discoverability gap is real — I measured it - Repo **description**: *"MCP supervisor for disciplined Claude Code sessions"* — no brain, no brain-mcp. - **README**: says "accumulates learnings into a searchable brain", never the string `brain-mcp`, never the hostname. - `brain-mcp.d-ma.be` appears **only** in `CLAUDE.md` / `AGENTS.md` — generated agent-context files, not the human front door. So searching the forge for "brain-mcp" finds nothing, which is precisely the experience that generated infra#265. That is fixable with a description, a README line, and a docs pointer to `ingestion/internal/mcp`. Cheap, and it addresses the actual complaint rather than a proxy for it. I did **not** file that as a new issue yet — checking `issue_list state=all` first per convention, and it may just belong in this one's part 2 checklist. Say the word and I'll do it as a small PR alongside part 1. ### Reversal trigger, so this isn't permanent by default Revisit if brain-mcp needs a **release cadence independent** of ingestion (today they are one pod, and no consumer is asking), or if the shared surface **shrinks to ≤3 stable packages**. If it ever does split, the unit is the whole `ingestion` service renamed — not `internal/mcp` carved out. ### The general rule "It has its own ingress and its own consumers" is an argument about the **deployment** surface. "Where does the code live" is about the **source** surface. They're allowed to differ, and conflating them turns a naming problem into a migration. --- **Consequence for the rest of the queue:** brain#4 (`brain_read` / `brain_edit`) lands in `hyperguild/ingestion/internal/mcp`, not a new repo. Item 3 is unblocked on my side. Part 1 (the `tickets` array bug) is untouched and next.
Author
Owner

Reproduction probe for part 1 (capture with a tickets array, length 1, real write). Posted by the capture tool itself — if this comment exists, the array path worked from this harness.

Reproduction probe for part 1 (`capture` with a `tickets` array, length 1, real write). Posted by the capture tool itself — if this comment exists, the array path worked from this harness.
Author
Owner

Second reproduction probe: tickets array of length 2, mixed actions (create + comment), real write. Both landed in one call — so the array path works from this harness at both lengths tested.

Second reproduction probe: `tickets` array of length 2, mixed actions (`create` + `comment`), real write. Both landed in one call — so the array path works from this harness at both lengths tested.
Author
Owner

Both parts closed

Part 1 — PR #95 merged (eb9e3e6). The tickets array was never broken; the I1 refusal hid its cause, so a caller with 20 tickets could not tell which repo tripped it and reasonably concluded the array was at fault. Refusals now name the escalating target. Regression tests at length 1 and 20 pin the path that was already correct.

Part 2 — decided: brain-mcp stays inside ingestion. ADR at brain wiki/hyperguild/decisions/brain-mcp-stays-in-ingestion-fix-discoverability-by-name; reasoning in the comment above.

Also fixed, since it was the actual blocker

The refusal that started infra#265 was correct policy applied to a repo nobody had classified. classification.yaml had 10 of 47 repos tagged, so 37 — including cad, dispatch, gitea-mcp, skills, brain-ui — would each have refused the next capture. Tagged the dev stack in one reviewed sweep (109c7c6, 2568dcf), leaving eight deliberately absent with reasons in the file: schoolsoft, email-triage-agent, the three harg-* repos, journal, and two tombstones.

Done as an allowlist, not a relaxed default — the fail-safe is what protects the next client repo, and flipping it would make every future untagged repo us-nexus-eligible silently. Verified both directions against the live loader and through the running service.

Follow-through still open

#94 — brain-mcp is unfindable by name (repo description, README, topics). That is the discoverability half of the part-2 decision, and it is the thing that actually answers infra#265's complaint. Not done here.

Closing.

## Both parts closed **Part 1** — PR #95 merged (`eb9e3e6`). The `tickets` array was never broken; the I1 refusal hid its cause, so a caller with 20 tickets could not tell which repo tripped it and reasonably concluded the array was at fault. Refusals now name the escalating target. Regression tests at length 1 and 20 pin the path that was already correct. **Part 2** — decided: brain-mcp stays inside `ingestion`. ADR at brain `wiki/hyperguild/decisions/brain-mcp-stays-in-ingestion-fix-discoverability-by-name`; reasoning in the comment above. ### Also fixed, since it was the actual blocker The refusal that started infra#265 was correct policy applied to a repo nobody had classified. `classification.yaml` had **10 of 47 repos tagged**, so 37 — including `cad`, `dispatch`, `gitea-mcp`, `skills`, `brain-ui` — would each have refused the next capture. Tagged the dev stack in one reviewed sweep (`109c7c6`, `2568dcf`), leaving eight deliberately absent with reasons in the file: `schoolsoft`, `email-triage-agent`, the three `harg-*` repos, `journal`, and two tombstones. Done as an **allowlist, not a relaxed default** — the fail-safe is what protects the next client repo, and flipping it would make every future untagged repo us-nexus-eligible silently. Verified both directions against the live loader and through the running service. ### Follow-through still open **#94** — brain-mcp is unfindable by name (repo description, README, topics). That is the discoverability half of the part-2 decision, and it is the thing that actually answers infra#265's complaint. Not done here. Closing.
mathias added the status:dispatch label 2026-08-24 21:49:59 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mathias/hyperguild#91