Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ All notable changes to vouch are documented here. Format follows
- demo image build: `hatch_build.py` (the build hook pyproject.toml
declares for console bundling) is copied into the docker build context;
the image had been unbuildable since the hook landed (#474).
- `vouch recall` excludes archived pages from the session-start digest.
claims were filtered by status but pages were not, so an archived page's
title kept reaching every new session's opening context while its claims
stayed suppressed — the same asymmetry `synthesize` already avoids. draft
and stale pages are unaffected (#490).

## [1.3.0] — 2026-07-14

Expand Down
9 changes: 7 additions & 2 deletions src/vouch/recall.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Claude session's context (memvid-style), via the SessionStart hook.

Unlike ``kb.context`` (task-scoped retrieval), this emits EVERY live approved
claim as a compact ``[id] text`` line plus every approved page title, so a
claim as a compact ``[id] text`` line plus every live page title, so a
fresh session is aware of the whole reviewed KB from the first turn. Page
bodies are fetched on demand with ``kb_read_page`` / ``kb_search``.

Expand All @@ -17,6 +17,7 @@
import yaml

from .context import _RETRACTED_CLAIM_STATUSES
from .models import PageStatus
from .storage import KBStore

DEFAULT_ENABLED = True
Expand Down Expand Up @@ -58,7 +59,11 @@ def build_digest(store: KBStore, *, max_chars: int = DEFAULT_MAX_CHARS) -> str:
c for c in store.list_claims()
if c.status not in _RETRACTED_CLAIM_STATUSES
]
pages = store.list_pages()
# archived pages are retracted knowledge, same as the archived claims
# filtered above — leaking a title back into every session's opening
# context would make the archive control decorative. DRAFT and STALE
# stay: unpolished or aging is still live.
pages = [p for p in store.list_pages() if p.status != PageStatus.ARCHIVED]
if not claims and not pages:
return ""

Expand Down
28 changes: 27 additions & 1 deletion tests/test_recall.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import pytest

from vouch import recall
from vouch.models import ClaimStatus
from vouch.models import ClaimStatus, PageStatus
from vouch.proposals import approve, propose_claim, propose_page
from vouch.storage import KBStore, _starter_config

Expand Down Expand Up @@ -47,6 +47,32 @@ def test_digest_excludes_retracted_claims(store: KBStore) -> None:
assert "archived fact" not in d


def test_digest_excludes_archived_pages(store: KBStore) -> None:
_approve_page(store, "live page")
archived = _approve_page(store, "archived page")
archived.status = PageStatus.ARCHIVED
store.update_page(archived)
d = recall.build_digest(store)
assert "live page" in d
assert "archived page" not in d
assert "1 page(s)" in d


def test_digest_keeps_stale_pages(store: KBStore) -> None:
"""Only ARCHIVED is retracted — an aging page is still live knowledge."""
stale = _approve_page(store, "stale page")
stale.status = PageStatus.STALE
store.update_page(stale)
assert "stale page" in recall.build_digest(store)


def test_digest_empty_when_only_archived_pages(store: KBStore) -> None:
archived = _approve_page(store, "archived page")
archived.status = PageStatus.ARCHIVED
store.update_page(archived)
assert recall.build_digest(store) == ""


def test_empty_kb_digest_is_empty(store: KBStore) -> None:
assert recall.build_digest(store) == ""

Expand Down