You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
an operator returning to a kb after a day (or a week) has to run several commands to reconstruct "what needs my attention": vouch list-pending for the review backlog, vouch metrics for approval rate and stale ratio, and a grep through .vouch/decided/ to see what got decided while they were away. a single read-only briefing folds those into one glance — "n proposals awaiting review (oldest first), what was approved/rejected since your last look, which claims have aged past the freshness threshold, and whether citation coverage moved" — so the human at the review gate knows where to spend attention before opening the queue.
this is a pure viewport. it reads the audit log, the decided records, and the artifact files; it writes nothing and touches no proposal. it composes the existing metrics.compute and store reads rather than duplicating any of them.
proposed surface
a new read-only cli command plus a matching kb.* read method:
--since reuses metrics.parse_since verbatim — a duration (30d, 12h, 2w), an iso date (2026-01-01), or all. default window is a short recent span (e.g. 7d), since a digest is inherently "since last look" (unlike vouch metrics, which defaults to all-history).
--stale-days mirrors metrics.DEFAULT_STALE_DAYS (and vouch lint --stale-days) so the three surfaces agree on what "stale" means.
--limit caps the oldest-first pending list and the recent-decisions list (default e.g. 10 each) so the briefing stays a briefing.
--format selects human text (default), the stable json contract, or a markdown block suitable for pasting into a notification hook or a standup note.
the briefing composes four sections, all derived from sources that already exist on disk:
pending awaiting review — store.list_proposals(ProposalStatus.PENDING) (the same set behind kb.list_pending), sorted oldest proposed_at first, each row showing kind, proposer, age, and id. surfaces the review-gate backlog metrics.pending_now already counts, but as an actionable list.
recent decisions — approvals and rejections in the window, read from .vouch/audit.log.jsonl via audit.read_events (the authoritative, append-only stream), cross-referenced to the .vouch/decided/*.yaml summary for the payload title. reuses the _APPROVE_RE / _REJECT_RE verb matching in metrics.py.
stale items — active claims whose freshness anchor (last_confirmed_at or updated_at or created_at) is older than --stale-days, the exact predicate in metrics._fill_corpus_metrics, listed oldest first rather than merely counted.
citation-coverage delta — metrics.compute gives current citation_coverage; the digest also computes it as of the --since cutoff and reports the movement, so a drop is visible.
a Digest dataclass with a to_dict() stable schema lives in a new src/vouch/digest.py, composing metrics.compute and store reads. no new business logic in storage.py, which stays pure i/o.
review gate & scope
the digest is read-only by construction: it calls list_* / read_events and formats. it never calls propose_*, approve, reject, or any write path, so the review gate is untouched — nothing here can create or edit knowledge, and there is nothing to approve. the four sections are all viewports over .vouch/ files that are already committed; no new on-disk state, no schema migration, fully local-first.
it stays distinct from the nearby aggregate surfaces. #192 / #101 ship vouch metrics — aggregate gauges (rates, ratios, percentiles) for graphing and alerting; digest is the human-facing counterpart of named, oldest-first lists of what to act on now, and consumes metrics.compute rather than recomputing it (it renders the citation-coverage delta over a window rather than a single point value). #164 (vouch stats / kb.stats) answers "is the kb alive?" with artifact counts; digest answers "what changed and what needs me?" over a window. #135 (vouch expire) acts on stale pending proposals through the gate; digest only reports the same backlog read-only — the "see it" that precedes running #135's "clear it".
if kb.digest is exposed over mcp, it must touch the four registration sites — @mcp.tool() in src/vouch/server.py, _h_digest + HANDLERS["kb.digest"] in src/vouch/jsonl_server.py, METHODS in src/vouch/capabilities.py, and the vouch digest command in src/vouch/cli.py — with a test at tests/test_digest.py, and should decide whether to attach the _meta.vouch_salience sidebar the way other reads do.
acceptance criteria
vouch digest runs read-only against a .vouch/ and writes nothing (no new files, no audit events, no proposals).
pending section lists proposals oldest proposed_at first, capped by --limit, from the same set as kb.list_pending.
recent-decisions section reads approvals/rejections from audit.log.jsonl within the --since window, titled via the decided/ record.
stale section uses the same predicate and default as metrics._fill_corpus_metrics / vouch lint --stale-days.
citation-coverage delta reports movement between the --since cutoff and now.
--format text|json|markdown all supported; the json shape is a documented, stable to_dict() schema.
--since accepts the same specs as vouch metrics (duration / iso / all) via metrics.parse_since.
if exposed over mcp, kb.digest is registered in all four sites and passes test_capabilities.
tests/test_digest.py covers oldest-first ordering, window filtering, empty-kb, and the read-only invariant.
an operator returning to a kb after a day (or a week) has to run several commands to reconstruct "what needs my attention":
vouch list-pendingfor the review backlog,vouch metricsfor approval rate and stale ratio, and a grep through.vouch/decided/to see what got decided while they were away. a single read-only briefing folds those into one glance — "n proposals awaiting review (oldest first), what was approved/rejected since your last look, which claims have aged past the freshness threshold, and whether citation coverage moved" — so the human at the review gate knows where to spend attention before opening the queue.this is a pure viewport. it reads the audit log, the decided records, and the artifact files; it writes nothing and touches no proposal. it composes the existing
metrics.computeandstorereads rather than duplicating any of them.proposed surface
a new read-only cli command plus a matching
kb.*read method:--sincereusesmetrics.parse_sinceverbatim — a duration (30d,12h,2w), an iso date (2026-01-01), orall. default window is a short recent span (e.g.7d), since a digest is inherently "since last look" (unlikevouch metrics, which defaults to all-history).--stale-daysmirrorsmetrics.DEFAULT_STALE_DAYS(andvouch lint --stale-days) so the three surfaces agree on what "stale" means.--limitcaps the oldest-first pending list and the recent-decisions list (default e.g. 10 each) so the briefing stays a briefing.--formatselects human text (default), the stablejsoncontract, or amarkdownblock suitable for pasting into a notification hook or a standup note.the briefing composes four sections, all derived from sources that already exist on disk:
store.list_proposals(ProposalStatus.PENDING)(the same set behindkb.list_pending), sorted oldestproposed_atfirst, each row showing kind, proposer, age, and id. surfaces the review-gate backlogmetrics.pending_nowalready counts, but as an actionable list..vouch/audit.log.jsonlviaaudit.read_events(the authoritative, append-only stream), cross-referenced to the.vouch/decided/*.yamlsummary for the payload title. reuses the_APPROVE_RE/_REJECT_REverb matching inmetrics.py.last_confirmed_at or updated_at or created_at) is older than--stale-days, the exact predicate inmetrics._fill_corpus_metrics, listed oldest first rather than merely counted.metrics.computegives currentcitation_coverage; the digest also computes it as of the--sincecutoff and reports the movement, so a drop is visible.a
Digestdataclass with ato_dict()stable schema lives in a newsrc/vouch/digest.py, composingmetrics.computeandstorereads. no new business logic instorage.py, which stays pure i/o.review gate & scope
the digest is read-only by construction: it calls
list_*/read_eventsand formats. it never callspropose_*,approve,reject, or any write path, so the review gate is untouched — nothing here can create or edit knowledge, and there is nothing to approve. the four sections are all viewports over.vouch/files that are already committed; no new on-disk state, no schema migration, fully local-first.it stays distinct from the nearby aggregate surfaces. #192 / #101 ship
vouch metrics— aggregate gauges (rates, ratios, percentiles) for graphing and alerting; digest is the human-facing counterpart of named, oldest-first lists of what to act on now, and consumesmetrics.computerather than recomputing it (it renders the citation-coverage delta over a window rather than a single point value). #164 (vouch stats/kb.stats) answers "is the kb alive?" with artifact counts; digest answers "what changed and what needs me?" over a window. #135 (vouch expire) acts on stale pending proposals through the gate; digest only reports the same backlog read-only — the "see it" that precedes running #135's "clear it".if
kb.digestis exposed over mcp, it must touch the four registration sites —@mcp.tool()insrc/vouch/server.py,_h_digest+HANDLERS["kb.digest"]insrc/vouch/jsonl_server.py,METHODSinsrc/vouch/capabilities.py, and thevouch digestcommand insrc/vouch/cli.py— with a test attests/test_digest.py, and should decide whether to attach the_meta.vouch_saliencesidebar the way other reads do.acceptance criteria
vouch digestruns read-only against a.vouch/and writes nothing (no new files, no audit events, no proposals).proposed_atfirst, capped by--limit, from the same set askb.list_pending.audit.log.jsonlwithin the--sincewindow, titled via thedecided/record.metrics._fill_corpus_metrics/vouch lint --stale-days.--sincecutoff and now.--format text|json|markdownall supported; thejsonshape is a documented, stableto_dict()schema.--sinceaccepts the same specs asvouch metrics(duration / iso /all) viametrics.parse_since.kb.digestis registered in all four sites and passestest_capabilities.tests/test_digest.pycovers oldest-first ordering, window filtering, empty-kb, and the read-only invariant.make checkgreen (pytest, mypy, ruff).