Add brain_reject: brain/raw/ pending notes can only be promoted, never discarded #81

Open
opened 2026-07-15 09:45:18 +00:00 by mathias · 0 comments
Owner

Context

brain_pending lists notes in brain/raw/ awaiting human review; brain_promote
moves one into the structured wiki. There is no third option. Confirmed by
reading ingestion/internal/api/promote.go + ingestion/internal/mcp/handlers.go
directly (not guessing): only ListPending/PromoteNote exist, no
RejectNote, no reject.go, no reject tool descriptor.

Live consequence: brain_pending currently returns 4 items, oldest dated
2026-04-23 — three months old. They're not low-value by content (financial
sentiment analysis / hash-encoding scrape notes, an invoice-extraction design
note) — they're stuck because there's no way to say "reviewed, not worth
promoting" and have that recorded. The only way to remove one today is an
un-audited rm on the raw file directly, bypassing the tool surface entirely.

This is the same shape of gap as brain-gardener#104 (a queue that only grows
because nothing forces or records a negative decision) — found while auditing
that exact failure mode in a sibling system.

Proposal

Add RejectNote(brainDir, filename, reason string) (string, error) in
ingestion/internal/api/promote.go (or a new reject.go alongside it),
matching PromoteNote's existing conventions:

  • Same filename validation (basename-only, no traversal — reuse the exact
    check PromoteNote already does).
  • Move (not delete) brain/raw/<filename>brain/raw/rejected/<filename>,
    write-then-remove like PromoteNote's write-then-delete pattern, so a
    filesystem failure never loses the note.
  • Stamp rejected_at (+ reject_reason if given) into frontmatter before the
    move, same style as promoted_at.
  • No index rebuild / auto-tunnel needed (rejected notes never entered the wiki).

Expose as brain_reject in handlers.go's tool list, same schema shape as
brain_promote (filename required, reason optional) — human-invoked only,
matching the existing promote/pending human-in-the-loop model. Never called
automatically; this stays a human decision, same principle as
issue-status-derived-surfaced-not-closed in the brain (issue closure is a
human judgment call, never a mechanical fact) — a reject is exactly that same
shape of judgment call, just one level earlier in the pipeline.

Acceptance

  • RejectNote + test (moves file, stamps frontmatter, write-then-remove
    safety, filename validation matches PromoteNote's).
  • brain_reject MCP tool wired in, descriptor + handler + test (mirror
    brain_promote's existing test in handlers_test.go).
  • Live-run against the 4 real stale pending items once shipped — the actual
    backlog that surfaced this gap should get cleared as part of verifying it.

Related

  • brain-gardener#104/#121/#122 (same failure shape: unreviewed queue, no
    negative-decision path, discovered during a broader "how do we keep brain
    weeds from taking root" review, 2026-07-15).
  • Also noticed while reading ListPending: it explicitly skips
    tunnel-candidates-*.md files in raw/ — a second, distinct candidate queue
    (auto-detected cross-wing link suggestions?) that wasn't investigated this
    session. Worth checking whether it has the same rot risk — filed as a
    discovered-but-deferred note, not scoped here.
## Context `brain_pending` lists notes in `brain/raw/` awaiting human review; `brain_promote` moves one into the structured wiki. There is no third option. Confirmed by reading `ingestion/internal/api/promote.go` + `ingestion/internal/mcp/handlers.go` directly (not guessing): only `ListPending`/`PromoteNote` exist, no `RejectNote`, no `reject.go`, no reject tool descriptor. **Live consequence:** `brain_pending` currently returns 4 items, oldest dated 2026-04-23 — three months old. They're not low-value by content (financial sentiment analysis / hash-encoding scrape notes, an invoice-extraction design note) — they're stuck because there's no way to say "reviewed, not worth promoting" and have that recorded. The only way to remove one today is an un-audited `rm` on the raw file directly, bypassing the tool surface entirely. This is the same shape of gap as brain-gardener#104 (a queue that only grows because nothing forces or records a negative decision) — found while auditing that exact failure mode in a sibling system. ## Proposal Add `RejectNote(brainDir, filename, reason string) (string, error)` in `ingestion/internal/api/promote.go` (or a new `reject.go` alongside it), matching `PromoteNote`'s existing conventions: - Same filename validation (basename-only, no traversal — reuse the exact check `PromoteNote` already does). - Move (not delete) `brain/raw/<filename>` → `brain/raw/rejected/<filename>`, write-then-remove like `PromoteNote`'s write-then-delete pattern, so a filesystem failure never loses the note. - Stamp `rejected_at` (+ `reject_reason` if given) into frontmatter before the move, same style as `promoted_at`. - No index rebuild / auto-tunnel needed (rejected notes never entered the wiki). Expose as `brain_reject` in `handlers.go`'s tool list, same schema shape as `brain_promote` (`filename` required, `reason` optional) — human-invoked only, matching the existing promote/pending human-in-the-loop model. Never called automatically; this stays a human decision, same principle as `issue-status-derived-surfaced-not-closed` in the brain (issue closure is a human judgment call, never a mechanical fact) — a reject is exactly that same shape of judgment call, just one level earlier in the pipeline. ## Acceptance - [ ] `RejectNote` + test (moves file, stamps frontmatter, write-then-remove safety, filename validation matches `PromoteNote`'s). - [ ] `brain_reject` MCP tool wired in, descriptor + handler + test (mirror `brain_promote`'s existing test in `handlers_test.go`). - [ ] Live-run against the 4 real stale pending items once shipped — the actual backlog that surfaced this gap should get cleared as part of verifying it. ## Related - brain-gardener#104/#121/#122 (same failure shape: unreviewed queue, no negative-decision path, discovered during a broader "how do we keep brain weeds from taking root" review, 2026-07-15). - Also noticed while reading `ListPending`: it explicitly skips `tunnel-candidates-*.md` files in `raw/` — a second, distinct candidate queue (auto-detected cross-wing link suggestions?) that wasn't investigated this session. Worth checking whether it has the same rot risk — filed as a discovered-but-deferred note, not scoped here.
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mathias/hyperguild#81