feat(hub): dedicated agent kbs with credentials bound to one kb - #742
Open
minion1227 wants to merge 1 commit into
Open
feat(hub): dedicated agent kbs with credentials bound to one kb#742minion1227 wants to merge 1 commit into
minion1227 wants to merge 1 commit into
Conversation
a scheduled agent — a pr triager in ci, an incident summariser — produces a lot of narrow, machine-shaped memory, and mixing it into the project kb buries the knowledge a human curated. `vouch kb create <name> --for-agent <agent>` provisions an isolated kb outside any project tree, registers it with an owner and the agent it serves, and issues a credential bound to that kb and only that kb. the binding is the part that makes this worth more than `vouch init` in a subdirectory. which kb an agent hits is otherwise a function of its working directory, because discovery walks upward — so a leaked ci secret reaches the project kb from the wrong cwd. binding fixes that at the existing transport chokepoint (`agents.subject_is_active`), which already funnels every token transport through one gate. it lives machine-local in ~/.config/vouch/credentials.yaml, 0600, keyed on the 16-hex subject `trust.py` already derives — never the secret, which is printed once and never persisted. a binding stored inside the kb it names would be circular: the project kb's copy has nothing to say about a token issued for another kb, so presenting that token there would still fall open. the question is machine-scoped, so the answer has to be too. unbound subjects keep working everywhere, so this is opt-in rather than a migration — the same back-compat rule `agents.is_active` follows. a bound one fails closed everywhere but its own kb, including against a kb whose `kb.id` was stripped, so removing an identity is not an escape hatch. a missing or corrupt binding file denies nobody: an unreadable file must not lock a fleet out of a running server. the registry gains owner and agent. both are sticky across a plain re-register, so `vouch hub register` cannot launder an agent kb into a human-owned one, and both are omitted when empty so existing rows read back unchanged. ambient capture refuses to land in an agent kb reached by upward discovery — the same guard the personal catch-all already gets. `vouch kb issue` is the rotation path, `vouch kb list` shows what exists, `vouch agents revoke` still retires a credential, and `vouch adopt` still promotes anything worth keeping into the project kb. Co-authored-by: Cursor <cursoragent@cursor.com>
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.
closes #609.
What changed
vouch kb create <name> --for-agent <agent>provisions an isolated KB outside any project tree, registers it in the machine registry with an owner and the agent it serves, and issues a credential bound to that KB and only that KB — printed once, never persisted.vouch kb issueis the rotation path;vouch kb listshows what exists.The binding is the half that makes this more than
vouch initin a subdirectory. It lives in a new module,kb_binding.py, and is enforced at the existing transport chokepoint (agents.subject_is_active) so every token transport inherits it without a second implementation.Why
A scheduled agent — a PR triager in CI, an incident summariser — produces a lot of narrow, machine-shaped memory. Mixing it into the project KB buries the knowledge a human curated. The issue's framing is exact: you want it to remember things across runs, but you don't want a thousand machine-written memories mixed into your personal knowledge graph.
Isolation of storage alone is not enough. Which KB an agent hits is otherwise a function of its working directory, because discovery walks upward — so a leaked CI secret reaches the project KB from the wrong cwd. Binding fixes that: a bound subject authenticates against its own KB and fails closed everywhere else, regardless of working directory.
Three things follow, and they are the design:
The binding is keyed on the subject, never the secret. Registering a binding does not require storing, echoing, or even seeing the credential again — the same split
agents.pyalready draws and the reason the agent registry can live in committed YAML. The secret is printed once; what lands on disk is the 16-hex subjecttrust.pyalready derives.It lives machine-local, not inside a KB. A binding stored in the KB it names is circular: the project KB's copy has nothing to say about a token issued for another KB, so presenting that token there would still fall open. The question "which KB may this subject reach" is machine-scoped, so the answer has to be too —
~/.config/vouch/credentials.yaml, 0600, a sibling of the hub registry. Same trust level as the bearer accept-list it guards: anyone who can rewrite this file can already rewriteserve.bearer_tokens.Unbound subjects are unaffected. A token with no binding authenticates exactly as before, against whatever KB it is presented to. Binding is opt-in, not a migration — the same back-compat rule
agents.is_activefollows. A missing or corrupt binding file denies nobody: an unreadable file must not lock a fleet out of a running server. A bound subject fails closed against a KB whosekb.idwas stripped, so removing an identity is not an escape hatch.Ownership on the registry row is routing metadata, and only that. It records whose KB this is so
vouch kb listcan say so and so ambient capture knows a machine-owned KB when it sees one. What a credential may actually reach is the separate, enforced question above. Bothownerandagentare sticky across a plainvouch hub register, so a re-register cannot launder an agent KB into a human-owned one, and both are omitted when empty so existing rows read back unchanged. Ambient capture refuses to land in an agent KB reached by upward discovery — the same guard the personal catch-all already gets.What might break
Honest list:
VOUCH_KB_PATHthat points at a KB whosekb.idwas stripped refuses every bound subject. Removing the identity of a KB does not become an escape hatch from the binding; unbound subjects still authenticate against it as before.VOUCH_KB_PATHwhen you mean it deliberately — same remedy as for the personal catch-all.How it was tested
42 tests in
tests/test_kb_binding.py, plus the existing hub / agents / trust / capabilities suites still green. Diff coverage is 100% of the 251 changed Python lines undersrc/vouch/. The isolation property is pinned end-to-end: a bound subject authenticates against its own KB from its own cwd, is refused against the project KB from the project cwd, and an unbound subject is untouched by any of it. The "token never reaches disk" assertion fromtest_agentsis reused for the binding file and for the KB's ownagents.yaml. CLI domain errors render asError: …, never a traceback.make check(ruff, mypy, pytest) green locally.VEP
Not filed. This adds no new
kb.*method and changes no existing method signature — provisioning a KB is a human/operator act, CLI-only, following theagents/hubprecedent. The binding is enforced at an existing internal chokepoint rather than exposed as a surface. Happy to file one if a maintainer wants the binding shape locked as a protocol decision.Screenshots
N/A — CLI + transport gate, no UI.