Gate delegated discovery behind a KB lookup - #16
Open
bguidolim wants to merge 4 commits into
Open
Conversation
- Add kb-gate.sh, one dispatcher on four hook events: it warns or blocks sub-agent spawns whose prompt carries no KB findings, and briefs discovery agents so they search the KB instead of sweeping files blind - Add a KB_GATE_MODE prompt (warn/enforce/observe/off, default warn) so each project picks its own strictness at sync time - Declare the ordering dependency in the template: the KB result is an input to the sub-agent prompt, which must open with a "KB context:" block
bguidolim
commented
Jul 28, 2026
- Deny every spawn in a fan-out and keep gating after a denial; release only once a per-state or per-turn budget is spent - Add a test suite and CI, plus doctor checks for the two registrations whose absence corrupts the barrier rather than disabling it - Make `off` a true kill switch and document the modes in the README
- Trim the log to its recent tail once it passes 512 KB, checked on the turn boundary - Delete state files from sessions older than a week on the first turn of a new one - Cover both with tests that fail against an unconditional rewrite and a missing sweep
- Fix comments that no longer matched the code: logging is not mode-independent, the payload is not parsed in a single jq call, and the skip guards are not free - Assert only the boundary of each denial budget instead of every intermediate spawn, cutting the suite from 43 checks to 30 - Require `off` to write nothing at all rather than merely decline to deny
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Claude reliably searches the project knowledge base before starting work — and then spawns discovery sub-agents in the same batch, handing them bare prompts. Sub-agents can't see the parent's search results, so they rediscover the same architecture from scratch: tens of thousands of tokens per agent, and occasionally conclusions that contradict what a memory already recorded.
This makes the KB lookup a prerequisite for delegating. A spawn whose prompt carries no KB findings is flagged or blocked, and any discovery agent that starts without them is told the knowledge base exists and to spend one search on it before touching files. Strictness is chosen per project at sync time and defaults to warning only, so installing this never starts blocking work unasked.
The escape hatch
Enforce mode must never wedge a session, so denials are budgeted — but a single per-turn counter can't do that job. Sized at one denial per turn it fails in both directions: a batch of parallel spawns is denied only once and the rest run blind before Claude has read the denial, and any denial leaves the gate disabled for the remainder of the turn even after Claude complies.
Each denial is therefore stamped with the number of KB searches recorded when it was written, and the same append-only file is counted two ways: denials since the last search (resets when Claude searches, so complying re-arms the gate, and wide enough that a fan-out is denied in full) and denials this turn (never resets, so termination is unconditional). Both stay keyed by phase. Appends only, never a read-modify-write — a batch can run these hooks concurrently and only an append is atomic.
Sized by cost asymmetry rather than symmetry: a denial is one cheap round-trip, an un-primed discovery agent is tens of thousands of tokens, so a generous budget errs in the cheap direction. Worst case is a bounded number of denials in a single turn, all of them cheap, and only in a turn where Claude is already failing to comply repeatedly.
Test plan
tests/kb-gate-test.shdrives every branch with crafted JSON — deterministic, no live session, no clock — and CI runs it twice per push, since a regression to timestamp-based scoping shows up as the second run behaving differently from the first. The fan-out and gate-survives-a-denial cases fail against the pre-budget implementation, which is what makes them regression tests rather than decoration.KB context:block → allowed; a later spawn without a block in the same turn is denied again..claude/memories/, empty payloads.warnadvises without denying;observeandoffnever block.The suite asserts a non-zero count of real denials before reporting success. Without that, a fixture missing
.claude/memories/would take the skip path on every call and the whole run would pass while testing nothing.Against a real install: sync with the gate at
enforce, spawn a discovery sub-agent whose prompt has noKB context:block → expect a denial naming the missing prerequisite; search the KB, re-issue the same spawn → expect it to run. Sync with the gateoff→ expect no blocking, no reminders, and no sub-agent briefing.Decisions are recorded to
.claude/.kb-gate.login every mode; comparing its line count against the sub-agent spawns in a session transcript is the way to confirm the hooks are actually matching.