Skip to content

Feat/volunteer context#240

Merged
plind-junior merged 2 commits into
vouchdev:testfrom
claytonlin1110:feat/volunteer-context
Jun 17, 2026
Merged

Feat/volunteer context#240
plind-junior merged 2 commits into
vouchdev:testfrom
claytonlin1110:feat/volunteer-context

Conversation

@claytonlin1110

@claytonlin1110 claytonlin1110 commented Jun 17, 2026

Copy link
Copy Markdown

What changed

Adds kb.volunteer_context — a confidence-gated push-context channel for active sessions. kb.session_start(task=…) registers hot memory, runs an immediate relevance pass, and starts a background watch. When an approved claim's normalized relevance exceeds the configured threshold (default 0.85), vouch queues {claim_id, relevance, why} and emits an MCP notification. JSONL and CLI clients poll via kb.volunteer_context / vouch session volunteer. New modules: hot_memory.py, volunteer_context.py. Optional volunteer: config block in config.template.yaml.

Why

Today vouch is pull-only: the agent asks, vouch answers. This adds a push channel so vouch can surface "you have an approved claim about this; consider citing it" mid-session when the task is highly relevant — matching the gbrain push-context model described in #236.

Fixes #236.

What might break

No breaking changes for existing .vouch/ directories.

  • No on-disk layout, bundle format, or audit-log shape changes.
  • kb.session_start / kb.session_end behaviour is unchanged from the caller's perspective (same return shapes); they now start/stop an in-memory background watch when task is set and volunteer.enabled is true (default).
  • New method kb.volunteer_context added to MCP, JSONL, and capabilities — additive only.
  • MCP kb_session_start is now async (FastMCP supports this; return shape unchanged).

VEP

Not required — additive kb.* method; no on-disk or object-model changes. Issue #236 scoped the feature.

Tests

  • make check passes locally (lint + mypy + pytest)
  • New / changed behaviour has a test (tests/test_volunteer_context.py)
  • CHANGELOG.md updated under ## [Unreleased]

Summary by CodeRabbit

  • New Features

    • HTTP transport for exposing KB over network
    • Browser-based review console with real-time updates
    • IDE/editor integrations (Claude Code, Desktop, Cline, Cursor, Windsurf, Zed)
    • Artifact scoping by project and agent
    • Provenance graph tracking claim dependencies
    • Schema migration system
    • Stale proposal expiration
    • KB sync and reconciliation
    • Obsidian vault bidirectional sync
    • Similarity-based duplicate detection
    • Metrics and observability
    • Structured JSON logging
    • Batch approval
    • Deep integrity checking (fsck)
  • Bug Fixes

    • Bundle import validation hardening
    • Improved error messaging
    • Better claim retraction handling
  • Documentation

    • New adapter guides
    • Migration documentation
    • Metrics reference
    • Design proposals (VEPs)

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 83b68c1a-3d52-4c6b-897b-7e49a35a1351

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds HTTP and web review surfaces, scoped retrieval and volunteered context, migrations and sync tooling, metrics and logging, diff and lifecycle commands, adapter installers and host configs, onboarding templates, bundle and storage validation, benchmarks, and extensive product, contributor, and agent documentation.

Changes

Platform expansion

Layer / File(s) Summary
Contracts, packaging, and product docs
pyproject.toml, src/vouch/models.py, src/vouch/capabilities.py, schemas/*, templates/config.template.yaml, README.md, docs/*, spec/transports.md, proposals/VEP-*, CHANGELOG.md
Updates package metadata, scoping contracts, advertised capabilities, config examples, and the main documentation set for retrieval, HTTP, review UI, metrics, provenance, and related features.
Scoped retrieval and volunteered context
src/vouch/scoping.py, src/vouch/context.py, src/vouch/embeddings/similarity.py, src/vouch/hot_memory.py, src/vouch/volunteer_context.py, src/vouch/sessions.py, tests/test_context.py, tests/test_jsonl_server.py
Adds viewer-based filtering, backend selection, context output viewer metadata, similarity warnings, in-memory session salience tracking, and session-driven volunteered claim offers.
Lifecycle, integrity, stats, logging, and diff
src/vouch/proposals.py, src/vouch/health.py, src/vouch/stats.py, src/vouch/metrics.py, src/vouch/logging_config.py, src/vouch/diff.py, src/vouch/cli.py, src/vouch/jsonl_server.py, src/vouch/server.py, tests/test_cli.py, tests/test_expire.py, tests/test_health.py, tests/test_diff.py, tests/test_logging*.py
Expands approval and expiration flows, health checking, observability outputs, structured logging, artifact diffing, and the related CLI, JSONL, and MCP surfaces with regression coverage.
Storage, bundle hardening, migrations, sync, and onboarding
src/vouch/storage.py, src/vouch/bundle.py, src/vouch/migrations/*, src/vouch/sync.py, src/vouch/vault_sync.py, src/vouch/onboarding.py, docs/gittensor.md, migrations/README.md, tests/test_bundle.py, tests/fixtures/migrations/*
Strengthens durable-artifact validation, adds KB format and schema migration runners, introduces sync and vault mirroring flows, and extends starter onboarding with page and template seeding.
Provenance graph and queries
src/vouch/provenance/*, src/vouch/index_db.py, src/vouch/cli.py, src/vouch/jsonl_server.py, src/vouch/server.py, docs/provenance.md
Adds cached provenance edges, graph construction, why/impact/trace and export queries, and exposes them through CLI and server methods.
HTTP transport and runtime serving
src/vouch/http_server.py, src/vouch/cli.py, src/vouch/server.py, spec/transports.md, adapters/http-tunnel/*, tests/test_http_server*.py
Introduces HTTP serving with /rpc, /mcp, health and capabilities routes, bearer-token config and bind enforcement, deployment scaffolding, and transport parity tests.
Browser review UI
src/vouch/web/*, src/vouch/cli.py, docs/review-ui.md, pyproject.toml, .github/workflows/ci.yml
Adds the FastAPI review console with paginated queue and detail pages, approve/reject and contradict actions, websocket refresh, optional auth, and the web extra in CI.
Adapter installer, host assets, and agent docs
src/vouch/install_adapter.py, adapters/*, openclaw.plugin.json, AGENTS.md, CLAUDE.md, CONTRIBUTING.md, llms.txt, tests/test_install_adapter.py
Adds tiered adapter installation logic, host-specific MCP config assets and command docs, the OpenClaw plugin manifest, and updated contributor and agent guidance.
Benchmarks and support materials
benchmarks/*, ROADMAP.md, proposals/README.md, skills/pr-precheck/SKILL.md
Adds benchmark fixtures and benchmark cases for bundle, search, propose, and index rebuild paths, plus supporting repo materials.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant HTTP as HTTP Server
  participant JSONL as JSONL Dispatcher
  participant KB as KBStore
  Client->>HTTP: POST /rpc or /mcp
  HTTP->>HTTP: validate bearer token
  HTTP->>JSONL: dispatch kb.* request
  JSONL->>KB: read/write KB data
  KB-->>JSONL: result
  JSONL-->>HTTP: response
  HTTP-->>Client: JSON payload
Loading
sequenceDiagram
  participant Session
  participant Volunteer as volunteer_context
  participant Retrieval
  participant MCP
  Session->>Volunteer: start session watch
  Volunteer->>Retrieval: evaluate scoped claim relevance
  Retrieval-->>Volunteer: ranked approved claims
  Volunteer-->>MCP: push volunteered offer
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related issues

Possibly related PRs

  • vouchdev/vouch#198 — Both add the review-ui web surface, related CLI wiring, and web assets.
  • vouchdev/vouch#193 — Both add the metrics feature across src/vouch/metrics.py, CLI exposure, and docs.
  • vouchdev/vouch#151 — Both center on the adapter installer and the Claude Code host integration assets and tests.

Suggested reviewers

  • plind-junior

Poem

🐇 I thumped through routes both web and plain,
with scopes and graphs across the lane.
I packed some bundles, checked each byte,
and lit review pages warm and bright.
A token tucked behind my ear—
more trails for every agent here.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

@claytonlin1110 claytonlin1110 changed the base branch from main to test June 17, 2026 02:53

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/vouch/jsonl_server.py (1)

573-625: ⚠️ Potential issue | 🔴 Critical

All 51 methods are missing from server.py and 50 of 51 are missing from cli.py—registration is incomplete.

The four-surface registration requirement is not met. Cross-check shows:

  • jsonl_server.py: 51 methods registered ✓
  • capabilities.py: 51 methods registered ✓
  • server.py: 0 of 51 methods registered
  • cli.py: 50 of 51 methods missing (only kb.status present) ✗

Per coding guidelines, every kb.* method must be registered in all four files before merge. The test_capabilities test should catch this drift.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vouch/jsonl_server.py` around lines 573 - 625, All 51 methods defined in
the HANDLERS dictionary in jsonl_server.py must be registered in both server.py
and cli.py to meet the four-surface registration requirement. Add all 51 kb.*
method handlers to server.py (currently 0 registered) and add the 50 missing
method handlers to cli.py (which currently only has kb.status). Use the HANDLERS
dictionary in jsonl_server.py as the source of truth for which methods need to
be registered, ensuring consistency across all four registration points.

Sources: Coding guidelines, Learnings

src/vouch/bundle.py (1)

488-543: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

avoid caching every bundle file body during import_check.

Line 529 stores all tar member bytes in incoming, including potentially huge sources/*/content files. this can spike memory and fail imports on large bundles.

proposed fix
-            incoming[member.name] = body
+            # keep full bytes only for artifacts parsed in _check_graph_integrity;
+            # for other paths, path-derived ids are enough.
+            if member.name.startswith(("claims/", "pages/", "relations/")):
+                incoming[member.name] = body
+            else:
+                incoming[member.name] = b""
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vouch/bundle.py` around lines 488 - 543, The current implementation
stores every tar member body in the incoming dictionary at line 529, which can
cause significant memory spikes when processing large bundle files. Instead of
caching all file bodies in the incoming dictionary before calling
_check_graph_integrity, restructure the code to either pass file bodies directly
to _check_graph_integrity as they are validated, or modify
_check_graph_integrity to accept a callback or generator that yields file data
on demand rather than requiring all file contents to be pre-loaded into memory.
This allows large content files to be processed and discarded rather than
accumulated in the incoming dictionary throughout the entire bundle import check
process.
🟠 Major comments (28)
src/vouch/server.py-540-548 (1)

540-548: ⚠️ Potential issue | 🟠 Major

Import get_context from fastmcp.server.dependencies and call it directly.

mcp.get_context() will fail at runtime with AttributeError. The get_context() function is a standalone utility in fastmcp.server.dependencies, not a method on the FastMCP instance. Add the import and call get_context() directly instead of mcp.get_context().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vouch/server.py` around lines 540 - 548, The kb_session_start function
incorrectly calls get_context() as a method on the mcp instance, which will fail
at runtime with AttributeError. Fix this by importing get_context directly from
fastmcp.server.dependencies at the top of the file, then replace the
mcp.get_context() call with a direct call to get_context() in the
kb_session_start function.
src/vouch/onboarding.py-123-145 (1)

123-145: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

route onboarding writes through the review gate

these paths write durable artifacts directly (put_page/put_entity/put_claim) and mark them approved without going through proposal approval flow. that creates a parallel write path and weakens lifecycle/audit invariants for seeded content. please stage these through the same review-gated approval pipeline used by other kb writes.

Based on learnings: all writes to the knowledge base must go through the proposals.approve() review gate; never create parallel data paths that bypass this invariant.

Also applies to: 236-268

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vouch/onboarding.py` around lines 123 - 145, The _starter_page function
is directly writing to the knowledge base by calling store.put_page(page)
without routing through the review approval gate. This creates a parallel write
path that bypasses the proposals.approve() review pipeline used by all other
knowledge base writes. Replace the direct store.put_page(page) call with the
proper review-gated approval flow by creating a proposal for the page artifact
and passing it through the proposals.approve() method, ensuring the starter page
write follows the same lifecycle and audit invariants as all other knowledge
base writes.

Source: Learnings

src/vouch/migrations/runner.py-194-233 (1)

194-233: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

block re-apply when a step may be partially applied

apply() recalculates diffs from current bytes and journals only what still differs. after a mid-step crash, a second apply can succeed with a partial journal; then rollback() may revert only that subset and leave already-transformed files behind while schema version moves backward. add a preflight guard to detect unfinished migration state (for example, latest journal/header not aligned with current schema) and force explicit recovery before continuing.

Also applies to: 259-293

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vouch/migrations/runner.py` around lines 194 - 233, The apply() function
needs a preflight guard at its beginning to detect unfinished migration states
where the latest journal and header are not aligned with the current schema
version. Add a check before the existing _pending_proposals_guard(store) call
that validates the migration state is consistent, and raise an exception (or
return an error state) if the journal entries or manifest headers indicate a
partial application that was interrupted, forcing the user to perform explicit
recovery before allowing the migration to proceed.
src/vouch/vault_sync.py-299-303 (1)

299-303: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

preserve edited page id when filing proposals

Line 299 parses page_id, but the proposal at Line 335-345 does not pass it through. propose_page will derive a new id from title, so a title edit can turn into a new page proposal instead of an edit proposal for the existing page.

suggested patch
@@
         page_id = _page_id_from_frontmatter(text)
         if not page_id:
             result.pages_skipped_unknown_id.append(rel)
             continue
+        try:
+            store.get_page(page_id)
+        except ArtifactNotFoundError:
+            result.pages_skipped_unknown_id.append(rel)
+            continue
@@
         propose_page(
             store,
             title=edited.title,
             body=edited.body,
             page_type=str(edited.type.value if hasattr(edited.type, "value") else edited.type),
             claim_ids=list(edited.claims),
             entity_ids=list(edited.entities),
             source_ids=list({*edited.sources, source.id}),
+            slug_hint=page_id,
             proposed_by=actor,
             tags=list(edited.tags),
         )

Also applies to: 335-346

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vouch/vault_sync.py` around lines 299 - 303, The page_id extracted by
_page_id_from_frontmatter at line 299 is not being passed to the propose_page
function call in the proposal creation block (around lines 335-345). This causes
propose_page to derive a new id from the title instead of using the existing
page_id, which can incorrectly turn title edits into new page proposals. Pass
the extracted page_id variable to the propose_page function call to preserve the
edited page identity.
src/vouch/vault_sync.py-225-227 (1)

225-227: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

backward sync should prune orphaned mirrored markdown files

The sync treats <vault>/vouch/* as vouch-managed, but this pass only overwrites known ids and never removes old files. Removed/archived artifacts can remain indefinitely and drift the mirror from KB truth.

suggested patch
@@
     # Pages
+    expected_pages: set[str] = set()
     for page in _approved_pages(store):
         text = _serialize_page(page)
         dst = mirror / f"{page.id}.md"
         dst.write_text(text, encoding="utf-8")
         result.pages_mirrored.append(page.id)
+        expected_pages.add(f"{page.id}.md")
@@
     # Claim stubs
+    expected_claims: set[str] = set()
     for claim in _approved_claims(store):
@@
         dst = claims_out / f"{claim.id}.md"
         dst.write_text(body, encoding="utf-8")
         result.claims_mirrored.append(claim.id)
+        expected_claims.add(f"{claim.id}.md")
+
+    for f in mirror.glob("*.md"):
+        if f.name not in expected_pages:
+            f.unlink()
+    for f in claims_out.glob("*.md"):
+        if f.name not in expected_claims:
+            f.unlink()

Also applies to: 235-253, 257-263

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vouch/vault_sync.py` around lines 225 - 227, The backward sync operation
creates or overwrites files in the mirror and claims_out directories for known
artifact IDs, but does not remove orphaned markdown files that correspond to
archived or deleted artifacts. After the mkdir calls on mirror and claims_out
directories and after all known artifact IDs have been synced/written to their
respective directories, implement a cleanup step that iterates through existing
files in both the mirror and claims_out directories, compares them against the
set of known/current artifact IDs that were just processed, and removes any
files that do not have a corresponding known ID. This ensures the mirror stays
in sync with the knowledge base by pruning files for removed or archived
artifacts.
src/vouch/sessions.py-36-37 (1)

36-37: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

make volunteer hooks best-effort and always run end teardown on idempotent calls

Line 36 and Line 56 execute optional hooks inline after durable writes. If a hook raises, the api returns an error even though state already changed. Also, Line 43’s early return can skip on_session_end on retries, which can leave background watch state dangling.

suggested patch
@@
-    volunteer_context.on_session_start(store, sess)
+    try:
+        volunteer_context.on_session_start(store, sess)
+    except Exception:
+        logger.exception("session_start: volunteer hook failed for %s", sess.id)
@@
-    if sess.ended_at is not None:
-        return sess  # idempotent
+    if sess.ended_at is not None:
+        try:
+            volunteer_context.on_session_end(session_id)
+        except Exception:
+            logger.exception("session_end: volunteer hook failed for %s", session_id)
+        return sess  # idempotent
@@
-    volunteer_context.on_session_end(session_id)
+    try:
+        volunteer_context.on_session_end(session_id)
+    except Exception:
+        logger.exception("session_end: volunteer hook failed for %s", session_id)

Also applies to: 42-44, 56-57

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vouch/sessions.py` around lines 36 - 37, The volunteer hooks
on_session_start at line 36 and on_session_end at line 56 are executed inline
after durable writes, causing API errors when hooks fail even though state has
persisted, and the early return at line 43 can skip on_session_end during
retries leaving background state dangling. Refactor these hooks to be
best-effort by either wrapping them in try-except blocks that log but don't
propagate exceptions, or by moving them to async execution. Additionally, ensure
the early return at line 43 does not bypass the on_session_end call by
restructuring the control flow so that on_session_end teardown always executes
regardless of previous operations.
src/vouch/web/server.py-343-346 (1)

343-346: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

set auth cookie as secure on https requests

The bootstrap token cookie is HttpOnly and SameSite=Strict, but it is not marked Secure. On https deployments this weakens transport guarantees for the auth credential.

suggested patch
@@
         resp.set_cookie(
             _TOKEN_COOKIE, exc.token,
-            httponly=True, samesite="strict", path="/",
+            httponly=True,
+            samesite="strict",
+            path="/",
+            secure=(request.url.scheme == "https"),
         )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vouch/web/server.py` around lines 343 - 346, The set_cookie call for the
_TOKEN_COOKIE is missing the secure flag, which weakens transport security on
HTTPS deployments since the cookie can then be sent over unencrypted
connections. Add the secure=True parameter to the set_cookie method call in the
bootstrap token cookie section to ensure the cookie is only transmitted over
HTTPS connections.
src/vouch/web/server.py-318-327 (1)

318-327: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

restrict query-token auth to bootstrap navigation only

Line 327 currently authenticates non-GET requests using ?token=.... That contradicts the one-time bootstrap model and allows mutation requests with secrets in URLs (logs/history/referrers).

suggested patch
@@
         q = _query_token(request)
         if q is not None and auth.matches(q):
             if request.method in ("GET", "HEAD"):
                 stripped = request.url.remove_query_params("token")
                 location = stripped.path + (f"?{stripped.query}" if stripped.query else "")
                 raise _BootstrapRedirect(location=location or "/", token=q)
-            return
+            raise HTTPException(
+                status_code=401,
+                detail="query token is only allowed for GET/HEAD bootstrap",
+                headers={"WWW-Authenticate": "Bearer"},
+            )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vouch/web/server.py` around lines 318 - 327, The unindented return
statement at line 327 currently authenticates non-GET/HEAD requests using the
query token, which violates the one-time bootstrap model and exposes secrets in
URLs for mutation requests. Remove the return statement that follows the if
block checking for GET/HEAD methods in the _query_token authentication logic.
This ensures that only GET/HEAD requests are authenticated via the query token
through the bootstrap redirect mechanism, while other HTTP methods are not
authenticated by the query token at all.
.github/workflows/ci.yml-27-27 (1)

27-27: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

sync local setup docs with the ci extras set.

Line 27 installs .[dev,web], but onboarding commands in AGENTS.md (Line 43) and CLAUDE.md (Line 72) still install .[dev]. this creates a ci/local env mismatch for contributors and can cause avoidable red checks.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml at line 27, The pip installation extras in the CI
workflow (ci.yml line 27) specifies `.[dev,web]` but the onboarding
documentation in AGENTS.md (line 43) and CLAUDE.md (line 72) still reference
`.[dev]` only. Update both documentation files to include the web extra in their
pip install commands, changing `.[dev]` to `.[dev,web]` in both locations to
ensure local development environments match the CI configuration and prevent
contributors from encountering environment mismatch issues.
adapters/zed/install.yaml-9-11 (1)

9-11: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

declare full t1-t4 tiers for new install-mcp hosts.

Line 9-11 defines only T1, but new host manifests are required to declare T1-T4 and ship the corresponding host structure/files. this currently under-specifies the adapter contract.

as per coding guidelines: “when adding a new install-mcp host, create adapters/<host>/ with install.yaml (T1-T4 tier declarations), .mcp.json, CLAUDE.md.snippet, .claude/commands/*, and .claude/settings.json.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapters/zed/install.yaml` around lines 9 - 11, Extend the tiers section in
the install.yaml file to include complete T1 through T4 tier declarations.
Currently only T1 is defined with the settings.json mapping. Add T2, T3, and T4
tier entries following the same structure pattern as T1, ensuring each tier
includes appropriate source and destination file mappings that represent the
complete host structure required by the adapter contract specification.

Source: Coding guidelines

src/vouch/jsonl_server.py-149-156 (1)

149-156: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

avoid breaking kb.search response shape for existing clients.

lines 149-156 now return an object (backend/viewer/hits) instead of the prior top-level hit list. clients expecting result as a list will break immediately.

backward-compatible pattern
-    return {
-        "backend": used,
-        "viewer": {"project": viewer.project, "agent": viewer.agent},
-        "hits": [
-            {"kind": k, "id": i, "snippet": sn, "score": sc, "backend": used}
-            for k, i, sn, sc in scoped
-        ],
-    }
+    hits_out = [
+        {"kind": k, "id": i, "snippet": sn, "score": sc, "backend": used}
+        for k, i, sn, sc in scoped
+    ]
+    if bool(p.get("include_meta", False)):
+        return {
+            "backend": used,
+            "viewer": {"project": viewer.project, "agent": viewer.agent},
+            "hits": hits_out,
+        }
+    return hits_out
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vouch/jsonl_server.py` around lines 149 - 156, The current return
statement in the kb.search response (lines 149-156) changes the response
structure from a list of hits to an object containing backend, viewer, and hits
fields, which breaks existing clients expecting a list. Modify the return
statement to maintain backward compatibility by returning the list of hits as
the primary response structure while preserving any necessary metadata in a way
that doesn't break existing clients, or wrap the new object structure to include
the original list format as the top-level response.
schemas/claim.schema.json-145-145 (1)

145-145: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

keep Claim.scope backward-compatible during the transition.

line 145 switches scope to object-only. older artifacts using scalar scope values will fail validation/import, which is a breaking persisted-contract change.

possible compatibility bridge
-    "scope": {
-      "$ref": "`#/`$defs/ArtifactScope"
-    },
+    "scope": {
+      "oneOf": [
+        { "$ref": "`#/`$defs/ArtifactScope" },
+        { "$ref": "`#/`$defs/Visibility" }
+      ]
+    },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schemas/claim.schema.json` at line 145, The Claim.scope field at line 145
currently only references the ArtifactScope object definition, which breaks
compatibility with older artifacts that use scalar scope values. Modify the
scope property definition to accept both the ArtifactScope object reference and
the previously supported scalar value types by using a union construct (such as
anyOf or oneOf) that includes both the $ref to ArtifactScope and the allowed
scalar type(s) that were previously valid.
adapters/claude-code/.claude/settings.json-3-19 (1)

3-19: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

allow the new kb_volunteer_context method in the adapter allowlist.

Lines 4-19 omit mcp__vouch__kb_volunteer_context, so claude-code sessions can miss the new pull surface for volunteered context even though the feature is introduced in this PR.

proposed fix
       "mcp__vouch__kb_search",
       "mcp__vouch__kb_context",
+      "mcp__vouch__kb_volunteer_context",
       "mcp__vouch__kb_read_claim",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapters/claude-code/.claude/settings.json` around lines 3 - 19, The "allow"
array in the settings.json file is missing the new
mcp__vouch__kb_volunteer_context method, which prevents Claude from accessing
this feature. Add mcp__vouch__kb_volunteer_context to the "allow" array
alongside the other existing mcp__vouch__kb_* methods to enable the volunteer
context surface for claude-code sessions.
src/vouch/__init__.py-3-3 (1)

3-3: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

avoid regressing package version metadata

setting __version__ to 0.0.1 appears to roll back versioning for a feature-adding PR, which can break upgrade detection and cross-manifest consistency. keep this value aligned with the repository’s canonical release version.

Based on learnings: "Bump version in pyproject.toml and sync it with the version field in openclaw.plugin.json whenever you release."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vouch/__init__.py` at line 3, The `__version__` variable in the vouch
package's __init__.py file is hardcoded to "0.0.1" which does not reflect the
actual package version. Update the `__version__` value to match the version
defined in pyproject.toml (the canonical release version), and ensure this same
version is also consistent with the version field in openclaw.plugin.json if
that file exists in the repository.

Source: Learnings

adapters/claude-desktop/install.yaml-16-20 (1)

16-20: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

add the required t3/t4 adapter tiers

this manifest currently declares only T1 and T2. the adapter contract for new adapters/*/install.yaml hosts requires T1T4; leaving out T3/T4 creates install-surface drift.

proposed manifest shape update
 tiers:
   T1:
     - { src: claude_desktop_config.json, dst: claude-desktop/claude_desktop_config.json }
   T2:
     - { src: README.snippet.md, dst: claude-desktop/README.md, fenced_append: true }
+  T3:
+    - { src: .claude/commands/vouch-status.md, dst: claude-desktop/.claude/commands/vouch-status.md }
+  T4:
+    - { src: .claude/settings.json, dst: claude-desktop/.claude/settings.json }

As per coding guidelines adapters/*/install.yaml: "When adding a new install-mcp host ... containing ... T1-T4 tier declarations ..."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapters/claude-desktop/install.yaml` around lines 16 - 20, The install.yaml
manifest for the claude-desktop adapter is incomplete as it only declares T1 and
T2 tiers. According to the adapter contract requirements and coding guidelines,
the tiers section must include all four tiers (T1-T4) to prevent install-surface
drift. Add T3 and T4 tier entries to the tiers section in the same structure as
the existing T1 and T2 entries, ensuring each tier contains the appropriate file
mappings with their source and destination paths along with any required
configuration options like fenced_append.

Source: Coding guidelines

adapters/continue/install.yaml-10-12 (1)

10-12: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

declare the full t1-t4 tiers for this host manifest.

only T1 is present, so this host does not satisfy the required install-mcp tier contract.

As per coding guidelines, “when adding a new install-mcp host, create adapters/<host>/ containing install.yaml (T1-T4), .mcp.json, CLAUDE.md.snippet, .claude/commands/*, and .claude/settings.json.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapters/continue/install.yaml` around lines 10 - 12, The install.yaml file
for the continue adapter only declares the T1 tier and is missing the required
T2, T3, and T4 tiers. Add the three missing tier declarations (T2, T3, and T4)
to the tiers section in the install.yaml file, each with appropriate
configuration entries that follow the same structure as the existing T1 tier
definition, to satisfy the install-mcp host manifest contract.

Source: Coding guidelines

adapters/cline/install.yaml-9-15 (1)

9-15: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

add the required t3/t4 tiers and canonical host scaffold.

this new host manifest only declares T1/T2, so it does not meet the required install-mcp host contract and will drift from the expected adapter surface.

As per coding guidelines, “when adding a new install-mcp host, create adapters/<host>/ containing install.yaml (T1-T4), .mcp.json, CLAUDE.md.snippet, .claude/commands/*, and .claude/settings.json.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapters/cline/install.yaml` around lines 9 - 15, The install.yaml file for
the cline host adapter is incomplete and only defines T1 and T2 tiers, but the
install-mcp host contract requires T1-T4 tiers to be defined. Add T3 and T4 tier
sections below the existing T2 tier in the cline install.yaml file, following
the same structure pattern with appropriate src and dst file mappings for each
tier. This ensures the adapter meets the required host manifest contract and
provides the complete canonical scaffold.

Source: Coding guidelines

adapters/codex/install.yaml-8-10 (1)

8-10: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

complete the install tiers to t1-t4 for this new host.

this manifest currently defines only T1, which breaks the required adapter-host install contract and leaves the host scaffold incomplete.

As per coding guidelines, “when adding a new install-mcp host, create adapters/<host>/ containing install.yaml (T1-T4), .mcp.json, CLAUDE.md.snippet, .claude/commands/*, and .claude/settings.json.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapters/codex/install.yaml` around lines 8 - 10, The install.yaml manifest
in the codex adapter only defines the T1 tier, which violates the adapter-host
install contract. Complete the manifest by adding the missing T2, T3, and T4
tiers below the existing T1 tier definition. Each tier should contain
appropriate source-to-destination file mappings that follow the same pattern as
T1 (using src and dst keys). Ensure all four tiers (T1 through T4) are properly
defined with their respective file configurations to satisfy the required
scaffold structure for new install-mcp hosts.

Source: Coding guidelines

adapters/cursor/install.yaml-13-17 (1)

13-17: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

finish this adapter manifest with the required t3/t4 layers.

T1/T2 are present, but the new-host install contract requires all four tiers plus the canonical companion files.

As per coding guidelines, “when adding a new install-mcp host, create adapters/<host>/ containing install.yaml (T1-T4), .mcp.json, CLAUDE.md.snippet, .claude/commands/*, and .claude/settings.json.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapters/cursor/install.yaml` around lines 13 - 17, The install.yaml manifest
in the cursor adapter is incomplete with only T1 and T2 tiers defined. Add the
missing T3 and T4 tiers to the tiers section in install.yaml with appropriate
file mappings for the remaining installation layers. Additionally, ensure all
required canonical companion files exist in the adapters/cursor/ directory:
.mcp.json, CLAUDE.md.snippet, .claude/commands/*, and .claude/settings.json,
following the new-host install contract requirements.

Source: Coding guidelines

src/vouch/http_server.py-182-193 (1)

182-193: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

enforce max body size while streaming the request body.

the current limit is enforced only from Content-Length; await request.body() can still buffer an oversized chunked/misreported payload into memory.

suggested fix
-    raw = await request.body()
+    size = 0
+    chunks: list[bytes] = []
+    async for chunk in request.stream():
+        size += len(chunk)
+        if size > MAX_BODY_BYTES:
+            return _json(413, _error_payload("invalid_request", "request body too large"))
+        chunks.append(chunk)
+    raw = b"".join(chunks)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vouch/http_server.py` around lines 182 - 193, The current implementation
only validates the Content-Length header against MAX_BODY_BYTES but does not
enforce the limit while actually reading the request body with await
request.body(). This allows clients to send chunked or misreported payloads that
exceed the maximum size. Modify the code to read the request body in chunks,
accumulating the total bytes read and checking against MAX_BODY_BYTES during the
streaming process. Return a 413 error if the actual payload exceeds the limit,
regardless of what the Content-Length header claims.
src/vouch/http_server.py-436-444 (1)

436-444: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

normalize accepted tokens before applying the non-loopback safety gate.

the public-bind check uses raw tokens truthiness, but auth middleware drops empty entries. a value like tokens=[""] can satisfy the bind gate while effectively disabling auth.

suggested fix
-    accepted = list(tokens) + ([token] if token else [])
+    accepted = [t.strip() for t in tokens if isinstance(t, str) and t.strip()]
+    if token is not None and token.strip():
+        accepted.append(token.strip())
     if host not in _LOOPBACK_HOSTS and not (allow_public and accepted):
         raise RuntimeError(
             f"refusing to bind non-loopback host {host!r} without both "
             "--allow-public and at least one bearer token "
             "(set VOUCH_HTTP_TOKEN / pass --token / use config.yaml serve.bearer_tokens)"
         )
-    app = make_app(token=token, tokens=tokens)
+    app = make_app(tokens=accepted)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vouch/http_server.py` around lines 436 - 444, The non-loopback safety
gate check uses the raw `accepted` list directly in the condition, but this list
may contain empty string tokens that are truthy as a list container but are
dropped by the auth middleware, allowing the security check to pass when it
shouldn't. Before the safety gate validation that checks `not (allow_public and
accepted)`, normalize the `accepted` list by filtering out any empty or falsy
token values. This ensures the check correctly validates that at least one
meaningful bearer token exists when binding to non-loopback hosts, preventing
cases where `tokens=[""]` would incorrectly satisfy the security requirement.
adapters/windsurf/install.yaml-8-10 (1)

8-10: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

add T2-T4 tier declarations for this new host manifest.

this manifest defines only T1, but the new-host adapter contract requires tier declarations through T4.

based on learnings: adapters/*/install.yaml: "when adding a new install-mcp host, create the directory structure adapters/<host>/ containing: install.yaml (T1-T4 tier declarations), .mcp.json (T1 MCP wire), CLAUDE.md.snippet (T2 fenced append), .claude/commands/* (T3 slash commands), and .claude/settings.json (T4 hooks). use strict YAML manifest validation."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapters/windsurf/install.yaml` around lines 8 - 10, The tiers section in the
install.yaml manifest is incomplete and only declares T1, but the new-host
adapter contract requires tier declarations through T4. Add tier declarations
for T2, T3, and T4 to the tiers section following the same structure as the
existing T1 entry, ensuring each tier properly maps the appropriate
configuration files according to the adapter contract requirements (T2 for
fenced append snippets, T3 for slash commands, and T4 for hooks).

Source: Coding guidelines

adapters/http-tunnel/fly.toml-4-6 (1)

4-6: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

bootstrap token name mismatch can block first fly deploy.

the quickstart sets VOUCH_TOKEN, but this template’s documented fallback is VOUCH_HTTP_TOKEN. with --allow-public and a fresh /data volume, startup can fail before any config-backed token exists.

Also applies to: 21-25

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapters/http-tunnel/fly.toml` around lines 4 - 6, The bootstrap token
environment variable name in the fly.toml file has a mismatch between the
quickstart command documentation and the template's expected fallback variable.
The fly secrets set command is currently setting VOUCH_TOKEN, but the template's
documented fallback expects VOUCH_HTTP_TOKEN instead. Change all occurrences of
VOUCH_TOKEN to VOUCH_HTTP_TOKEN in the fly.toml file (including the fly secrets
set command and other locations mentioned at lines 21-25) to ensure the
bootstrap token is properly recognized on first deploy, preventing startup
failures before any config-backed token can be established.
src/vouch/health.py-496-553 (1)

496-553: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

wire on_progress into rebuild phases; it is currently dead code.

_tick() is defined but never called, so the new on_progress API never emits updates.

proposed patch
 def rebuild_index(store: KBStore, *, on_progress: Callable[[str], None] | None = None) -> dict:
@@
     index_db.reset(store.kb_dir)
     with index_db.open_db(store.kb_dir) as conn:
+        _tick("claims")
         for c in store.list_claims():
             index_db.index_claim(
@@
+        _tick("pages")
         for p in store.list_pages():
             index_db.index_page(
@@
+        _tick("entities")
         for e in store.list_entities():
             index_db.index_entity(
@@
+    _tick("embeddings")
     _rebuild_embeddings(store)
     return index_db.stats(store.kb_dir)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vouch/health.py` around lines 496 - 553, The _tick() helper function
defined in rebuild_index is never invoked, making the on_progress callback dead
code. Wire the _tick() function into each phase of the rebuild process by
calling _tick() with the appropriate phase label before each processing section:
call _tick("claims") before the loop that iterates over store.list_claims(),
call _tick("pages") before the loop that iterates over store.list_pages(), call
_tick("entities") before the loop that iterates over store.list_entities(), and
call _tick("embeddings") before the _rebuild_embeddings() function call. This
ensures progress updates are emitted as documented in the function's docstring.
schemas/source.schema.json-3-37 (1)

3-37: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

preserve legacy scope string compatibility during the transition

Source.scope now accepts only the object form. that conflicts with the stated back-compat behavior for existing artifacts that still use scope: project, and creates contract drift for schema-driven validators/importers.

proposed schema adjustment
   "scope": {
-    "$ref": "`#/`$defs/ArtifactScope"
+    "oneOf": [
+      { "$ref": "`#/`$defs/ArtifactScope" },
+      { "$ref": "`#/`$defs/Visibility" }
+    ],
+    "default": "project"
   },

Also applies to: 119-121

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schemas/source.schema.json` around lines 3 - 37, The ArtifactScope schema
definition currently only accepts the object form with visibility, project, and
agent properties, but this breaks backward compatibility with existing artifacts
that use the legacy string form (e.g., scope: "project"). Modify the
ArtifactScope schema to support both forms by using anyOf at the root level: one
option for the current object schema with the visibility, project, and agent
properties, and another option for accepting a string type to maintain legacy
string compatibility.
src/vouch/install_adapter.py-122-133 (1)

122-133: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

validate manifest paths to prevent escaping adapter/target roots

src/dst are only checked as non-empty strings. absolute paths or .. segments can escape src_root/target, which allows unintended file reads/writes outside the install boundary.

proposed hardening
+            src_rel = Path(src)
+            dst_rel = Path(dst)
+            if src_rel.is_absolute() or ".." in src_rel.parts:
+                raise AdapterError(
+                    f"{host}: install.yaml tier {tier_name}: `src` must be a relative path"
+                )
+            if dst_rel.is_absolute() or ".." in dst_rel.parts:
+                raise AdapterError(
+                    f"{host}: install.yaml tier {tier_name}: `dst` must stay within target"
+                )
             fenced = bool(raw.get("fenced_append", False))
             parsed_entries.append(_FileEntry(src=src, dst=dst, fenced_append=fenced))
@@
-            src = src_root / entry.src
-            dst = target / entry.dst
+            src = (src_root / entry.src).resolve()
+            dst = (target / entry.dst).resolve()
+            if not src.is_relative_to(src_root.resolve()):
+                raise AdapterError(f"{adapter}: src {entry.src!r} escapes adapter directory")
+            if not dst.is_relative_to(target):
+                raise AdapterError(f"{adapter}: dst {entry.dst!r} escapes target directory")

Also applies to: 198-220

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vouch/install_adapter.py` around lines 122 - 133, The src and dst path
validation in the function only checks for non-empty strings but does not
prevent absolute paths or path traversal sequences like .., which could allow
escaping the adapter and target root directories. Add validation after the
existing non-empty string checks to ensure both src and dst are relative paths
(not absolute) and do not contain path traversal components. This validation
should occur before creating the _FileEntry object to prevent files from being
read or written outside the intended installation boundaries.
skills/pr-precheck/SKILL.md-38-38 (1)

38-38: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

fix the install command to match the package metadata

Line 38 says pip install vouch-kb, but the project metadata in pyproject.toml is now name = "vouch". this will send users to the wrong install target.

proposed doc fix
-- **`vouch` CLI** on `PATH` (`pip install vouch-kb` ≥ the version that ships `pr-cache`).
+- **`vouch` CLI** on `PATH` (`pip install vouch` ≥ the version that ships `pr-cache`).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@skills/pr-precheck/SKILL.md` at line 38, The documentation in SKILL.md line
38 instructs users to install the package using `pip install vouch-kb`, but the
actual package name defined in pyproject.toml is `vouch`. Update the
installation command in SKILL.md from `pip install vouch-kb` to `pip install
vouch` to match the current package metadata and ensure users install the
correct package.
src/vouch/hot_memory.py-54-56 (1)

54-56: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

deactivate prior session state when replacing the same session_id

register() overwrites _registry[session_id] but leaves any previously returned HotMemory object with active=True. if a watcher still holds that object, it can keep running and emit duplicate pushes.

proposed fix
     with _lock:
+        prev = _registry.get(session_id)
+        if prev is not None:
+            prev.active = False
         _registry[session_id] = mem
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vouch/hot_memory.py` around lines 54 - 56, The register() function
overwrites the _registry[session_id] entry without deactivating the previously
stored HotMemory object, allowing old watchers to continue running and emitting
duplicate pushes. Before assigning the new mem object to _registry[session_id],
check if a prior HotMemory instance already exists for that session_id and
deactivate it by setting its active attribute to False before the overwrite
occurs.
🧹 Nitpick comments (3)
src/vouch/sync.py (1)

148-154: 💤 Low value

Same extractfile() None risk in _load_bundle_source.

Line 154 has the same pattern where extractfile(member) could return None if manifest.json were somehow a non-regular file (unlikely but defensive coding would catch malformed bundles).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vouch/sync.py` around lines 148 - 154, The `_load_bundle_source` function
has a defensive coding issue where `tar.extractfile(member)` could return `None`
if the manifest.json file is not a regular file within the tar archive. Add a
null check after calling `tar.extractfile(member)` to verify the result is not
`None` before attempting to call `.read()` on it. If `extractfile()` returns
`None`, raise a descriptive RuntimeError similar to the existing manifest
validation error to handle malformed bundles gracefully.
src/vouch/stats.py (1)

16-17: 💤 Low value

Importing private functions creates coupling to internal implementations.

_yaml_load from storage and _load_claims_for_lint from health (line 88) are private functions. If these internals change, this module will break silently. Consider either making these public APIs or duplicating the minimal logic needed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vouch/stats.py` around lines 16 - 17, The import of `_yaml_load` from
storage module creates coupling to private implementation details (functions
prefixed with underscore are internal APIs). The comment also notes
`_load_claims_for_lint` from health has the same issue. To fix this, either
refactor these private functions to be public by removing the underscore prefix
and treating them as public APIs, or alternatively duplicate the minimal logic
needed from these private functions directly in the stats module to eliminate
the dependency on internal implementations. Choose the approach based on whether
the logic is general-purpose enough to be a public API or specific to stats
module requirements.
src/vouch/health.py (1)

359-372: ⚡ Quick win

guard proposal-kind lookup to keep fsck from aborting on unmapped kinds.

presence[pr.kind] assumes only four kinds. if an approved proposal kind falls outside that set, fsck raises KeyError and stops reporting.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vouch/health.py` around lines 359 - 372, The code directly accesses
presence[pr.kind] without checking if pr.kind exists in the presence dictionary.
If an approved proposal has a kind that is not one of the expected kinds, this
raises a KeyError and stops the fsck process. Guard the lookup by checking if
pr.kind exists in presence before accessing it, or handle the case where pr.kind
is not found by adding an appropriate error finding similar to how the missing
artifact_id case is handled.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b8fa4735-1f87-4638-bd9e-bc46c70cd977

📥 Commits

Reviewing files that changed from the base of the PR and between 3beb821 and 489c6e2.

📒 Files selected for processing (144)
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
  • AGENTS.md
  • CHANGELOG.md
  • CLAUDE.md
  • CONTRIBUTING.md
  • README.md
  • ROADMAP.md
  • adapters/claude-code/.claude/commands/vouch-propose-from-pr.md
  • adapters/claude-code/.claude/commands/vouch-recall.md
  • adapters/claude-code/.claude/commands/vouch-resolve-issue.md
  • adapters/claude-code/.claude/commands/vouch-status.md
  • adapters/claude-code/.claude/settings.json
  • adapters/claude-code/install.yaml
  • adapters/claude-desktop/README.md
  • adapters/claude-desktop/README.snippet.md
  • adapters/claude-desktop/claude_desktop_config.json
  • adapters/claude-desktop/install.yaml
  • adapters/cline/README.md
  • adapters/cline/cline_mcp_servers.json
  • adapters/cline/install.yaml
  • adapters/cline/vscode_settings.json
  • adapters/codex/install.yaml
  • adapters/continue/install.yaml
  • adapters/cursor/AGENTS.md.snippet
  • adapters/cursor/install.yaml
  • adapters/http-tunnel/Dockerfile
  • adapters/http-tunnel/README.md
  • adapters/http-tunnel/cloudflare-tunnel/compose.yml
  • adapters/http-tunnel/fly.toml
  • adapters/windsurf/README.md
  • adapters/windsurf/install.yaml
  • adapters/windsurf/mcp_config.json
  • adapters/zed/README.md
  • adapters/zed/install.yaml
  • adapters/zed/settings.json
  • benchmarks/bench_bundle.py
  • benchmarks/bench_index_rebuild.py
  • benchmarks/bench_propose.py
  • benchmarks/bench_search.py
  • benchmarks/conftest.py
  • docs/README.md
  • docs/gittensor.md
  • docs/metrics.md
  • docs/migrations.md
  • docs/multi-agent.md
  • docs/provenance.md
  • docs/review-ui.md
  • docs/superpowers/plans/2026-05-27-claude-code-adapter.md
  • docs/superpowers/specs/2026-05-25-vouch-diff-design.md
  • llms.txt
  • migrations/README.md
  • openclaw.plugin.json
  • proposals/README.md
  • proposals/VEP-0004-http-transport.md
  • proposals/VEP-0005-richer-scopes.md
  • pyproject.toml
  • schemas/capabilities.schema.json
  • schemas/claim.schema.json
  • schemas/source.schema.json
  • skills/pr-precheck/SKILL.md
  • spec/transports.md
  • src/vouch/__init__.py
  • src/vouch/bundle.py
  • src/vouch/capabilities.py
  • src/vouch/cli.py
  • src/vouch/context.py
  • src/vouch/diff.py
  • src/vouch/embeddings/similarity.py
  • src/vouch/health.py
  • src/vouch/hot_memory.py
  • src/vouch/http_server.py
  • src/vouch/index_db.py
  • src/vouch/install_adapter.py
  • src/vouch/jsonl_server.py
  • src/vouch/logging_config.py
  • src/vouch/metrics.py
  • src/vouch/migrations/__init__.py
  • src/vouch/migrations/_legacy.py
  • src/vouch/migrations/journal.py
  • src/vouch/migrations/manifest.py
  • src/vouch/migrations/rewriter.py
  • src/vouch/migrations/runner.py
  • src/vouch/migrations/schema.py
  • src/vouch/migrations/semver.py
  • src/vouch/models.py
  • src/vouch/onboarding.py
  • src/vouch/pr_cache.py
  • src/vouch/proposals.py
  • src/vouch/provenance/__init__.py
  • src/vouch/provenance/cache.py
  • src/vouch/provenance/graph.py
  • src/vouch/provenance/model.py
  • src/vouch/provenance/query.py
  • src/vouch/scoping.py
  • src/vouch/server.py
  • src/vouch/sessions.py
  • src/vouch/stats.py
  • src/vouch/storage.py
  • src/vouch/sync.py
  • src/vouch/vault_sync.py
  • src/vouch/volunteer_context.py
  • src/vouch/web/__init__.py
  • src/vouch/web/server.py
  • src/vouch/web/static/app.css
  • src/vouch/web/static/app.js
  • src/vouch/web/templates/audit.html
  • src/vouch/web/templates/base.html
  • src/vouch/web/templates/claim.html
  • src/vouch/web/templates/queue.html
  • src/vouch/web/templates/session.html
  • src/vouch/web/templates/source.html
  • templates/config.template.yaml
  • tests/embeddings/test_search.py
  • tests/fixtures/migrations/0099-test-rename.yaml
  • tests/test_bundle.py
  • tests/test_cli.py
  • tests/test_context.py
  • tests/test_diff.py
  • tests/test_expire.py
  • tests/test_health.py
  • tests/test_http_server.py
  • tests/test_http_server_mcp.py
  • tests/test_install_adapter.py
  • tests/test_jsonl_server.py
  • tests/test_logging.py
  • tests/test_logging_config.py
  • tests/test_metrics.py
  • tests/test_migrations.py
  • tests/test_pr_cache.py
  • tests/test_propose_similarity.py
  • tests/test_provenance.py
  • tests/test_retrieval_backend.py
  • tests/test_schema_migrations.py
  • tests/test_scoping.py
  • tests/test_sessions.py
  • tests/test_stats.py
  • tests/test_storage.py
  • tests/test_sync.py
  • tests/test_vault_sync.py
  • tests/test_volunteer_context.py
  • tests/test_web.py
  • tests/test_web_e2e.py
  • web/index.html
💤 Files with no reviewable changes (1)
  • .github/workflows/release.yml

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

🛑 Comments failed to post (3)
adapters/claude-code/.claude/commands/vouch-recall.md (1)

13-13: ⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# verify parameter names for kb.context across surfaces and tests
rg -n -C3 'def _h_context|p\["task"\]|kb\.context|kb_context|query=|task=' \
  src/vouch/jsonl_server.py src/vouch/server.py adapters/claude-code/.claude/commands/vouch-recall.md tests

Repository: vouchdev/vouch

Length of output: 22380


Fix parameter name in kb_context call: use task instead of query.

Line 13 calls kb_context with query: "$ARGUMENTS", but both the MCP tool definition (src/vouch/server.py, line 174) and the JSONL handler (src/vouch/jsonl_server.py, line 163) expect the parameter to be named task. The command will fail with a missing parameter error.

Change line 13 to:

1. Call `kb_context` with `task: "$ARGUMENTS"`.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapters/claude-code/.claude/commands/vouch-recall.md` at line 13, The
kb_context call is using an incorrect parameter name. The parameter should be
named `task` instead of `query` to match the expected interface defined in both
the MCP tool definition and the JSONL handler. Update the kb_context call to use
`task: "$ARGUMENTS"` instead of `query: "$ARGUMENTS"` to ensure the command
passes the correct parameter name.
src/vouch/server.py (1)

80-88: ⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify new kb.* methods are registered in all four places

methods=(
  "kb_stats"
  "kb_expire"
  "kb_volunteer_context"
  "kb_why"
  "kb_trace"
  "kb_impact"
  "kb_graph_export"
  "kb_provenance_rebuild"
)

echo "=== Checking registrations ==="
for m in "${methods[@]}"; do
  echo ""
  echo "--- $m ---"
  echo "server.py:"
  rg -n "def $m" src/vouch/server.py | head -1
  echo "jsonl_server.py:"
  rg -n "$m" src/vouch/jsonl_server.py | head -2
  echo "capabilities.py:"
  rg -n "$m" src/vouch/capabilities.py | head -1
  echo "cli.py:"
  rg -n "$m" src/vouch/cli.py | head -1
done

Repository: vouchdev/vouch

Length of output: 1209


All eight new kb.* methods require registration in jsonl_server.py, capabilities.py, and cli.py.

The following methods are defined in server.py but missing from the other three required registration points:

  • kb_stats (line 81)
  • kb_expire (line 460)
  • kb_volunteer_context (line 552)
  • kb_why (line 725)
  • kb_trace (line 735)
  • kb_impact (line 742)
  • kb_graph_export (line 751)
  • kb_provenance_rebuild (line 759)

Per the coding guidelines, each new kb.* method must be registered in exactly four places: server.py (✓), jsonl_server.py (as handler with HANDLERS map entry), capabilities.py (appended to METHODS), and cli.py (as CLI mirror). The test_capabilities test validates this requirement.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vouch/server.py` around lines 80 - 88, The eight kb.* methods (kb_stats,
kb_expire, kb_volunteer_context, kb_why, kb_trace, kb_impact, kb_graph_export,
and kb_provenance_rebuild) are defined in server.py but are missing required
registrations in three other files. Add each method to jsonl_server.py by
creating handler functions and adding entries to the HANDLERS map, append each
method to the METHODS list in capabilities.py, and add CLI mirror functions for
each method in cli.py. The test_capabilities test will validate that all eight
methods are properly registered in all four locations.

Source: Coding guidelines

src/vouch/volunteer_context.py (1)

229-245: ⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

MCP python server send_notification ServerNotification custom method

💡 Result:

In the Model Context Protocol (MCP) Python SDK, the send_notification method is used to emit one-way messages from a server to a client that do not require a response [1][2][3]. The standard way to send notifications is through high-level helper methods provided by the Context object or ServerSession, such as ctx.session.send_resource_list_changed() or ctx.session.send_log_message() [4][5][2]. These helpers ensure that notifications conform to the protocol's expected types and schemas [2][3]. Regarding the use of send_notification for custom methods: 1. Protocol Validation: The send_notification method in the base session relies on Pydantic models (specifically ServerNotification or ClientNotification) to validate outgoing messages [6][7][1]. These are defined as union types within the SDK [6]. 2. Limitations: The SDK enforces strict validation on notification methods and parameters [7]. Attempting to pass a custom ServerNotification that does not match the predefined types (e.g., notifications/tools/list_changed, notifications/resources/updated) will typically result in validation errors [7]. 3. Recommended Approach: If you need to send information that does not fit into the standard notification types, it is generally recommended to use established channels such as logging messages (if for monitoring) or to extend the standard types if custom behavior is required [5][2]. Directly using send_notification with custom, non-standard types is not officially supported by the SDK's current architecture, as the underlying Pydantic models are restrictive [6][7]. If you encounter a need for a custom notification, it is often a sign that you should either use an existing, standardized notification type or that the desired communication pattern might be better suited for a request-response interaction [2].

Citations:


Custom notification methods are not supported by the MCP SDK.

The MCP Python SDK enforces strict validation on send_notification() using Pydantic models that restrict notification methods to predefined types (e.g., notifications/tools/list_changed, notifications/resources/updated). Using a custom method name like kb.volunteer_context will fail validation at runtime and is not part of the official MCP API architecture. Consider using a standard notification type or an alternative communication pattern that fits within the protocol's constraints.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vouch/volunteer_context.py` around lines 229 - 245, The _send() function
is attempting to send a notification with a custom method name
"kb.volunteer_context", but the MCP Python SDK only validates and supports
predefined notification types through its Pydantic models. Replace the custom
notification approach in the Notification object with either a standard
predefined MCP notification type (such as notifications/tools/list_changed or
notifications/resources/updated) that aligns with your use case, or implement an
alternative communication pattern that conforms to the MCP protocol constraints
rather than using custom method names.

@plind-junior plind-junior merged commit 230b380 into vouchdev:test Jun 17, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: confidence-gated push-context channel — kb.volunteer_context

2 participants