Skip to content

feat(synthesize): file good answers back as page proposals - #737

Open
philluiz2323 wants to merge 2 commits into
vouchdev:testfrom
philluiz2323:feat/synthesize-file-as-page
Open

feat(synthesize): file good answers back as page proposals#737
philluiz2323 wants to merge 2 commits into
vouchdev:testfrom
philluiz2323:feat/synthesize-file-as-page

Conversation

@philluiz2323

Copy link
Copy Markdown
Contributor

What changed

kb.synthesize gains an opt-in file_as_page: bool = False parameter
(plus page_title: str | None = None). When set, the synthesized answer
is filed back as a PAGE proposal via the existing propose_page, citing
the same claim ids the answer already cites. Registered on all three
transports plus CLI: MCP kb_synthesize(..., file_as_page, page_title),
JSONL kb.synthesize params, vouch synthesize --file-as-page [--page-title T] [--agent A].

Why

ROADMAP.md's 1.4 milestone: "Good synthesized answers can be filed back
as page proposals — gated by review like every other write." kb.synthesize
already produces exactly that shape — citation-bearing prose traceable to
approved claims — but nothing wired it to propose_page; turning a good
answer into a page meant manually copying the text and re-typing the
cited claim ids into a separate call.

Closes #736

Still fully gated: propose_page files a pending proposal, the same as
every other write path in this codebase — nothing here approves anything.
Filing is skipped (not an error) when the answer is empty or cites
nothing — an uncited "answer" is the KB saying it doesn't know, not
knowledge worth filing — or when propose_page itself fails (e.g. a
title collision): the synthesis already succeeded, and losing that to a
secondary, optional step would be a worse failure than reporting why
filing didn't happen. Both outcomes surface via new page_proposal_id
(None when skipped or failed) and page_proposal_skipped_reason result
fields.

Internally, the existing no-LLM path is extracted into
_deterministic_synthesize so a new _maybe_file_page_proposal helper
can wrap either backend's result the same way — the LLM path already
produces the identical {query, answer, claims, pages, gaps, _meta}
shape, so one wrapper covers both without duplicating the filing logic.

What might break

Nothing — purely additive and opt-in. file_as_page defaults to False,
so every existing caller (MCP, JSONL, CLI) gets back the exact same
result shape as before; the new page_proposal_id/
page_proposal_skipped_reason keys are only present when file_as_page
was requested. No on-disk shape, object model, or kb.* method removal —
propose_page and the review gate itself are completely untouched.

VEP

Not applicable — no object model, on-disk layout, bundle format, or
audit-log shape change. A new opt-in parameter on an existing read method
that calls an existing, unmodified write path.

Tests

  • Local make check-equivalent: ruff clean (src + tests); mypy
    clean; tests/test_capabilities.py, tests/test_cli.py,
    tests/test_hot_memory.py, tests/test_synthesize.py all pass
    (the 3 pre-existing LLM-backend test failures are a known Windows
    python3-stub-invocation environment issue, confirmed present
    identically on test HEAD via git stash comparison, unrelated to
    this change)
  • New behaviour has tests — 8 new cases in tests/test_synthesize.py:
    files a pending proposal with the right claims/body/actor, custom
    title, skip-on-empty-answer, ValueError without proposed_by,
    default-off backward compatibility, plus JSONL and CLI
    (CliRunner) end-to-end cases
  • CHANGELOG.md updated under ## [Unreleased]

kb.synthesize already produces citation-bearing prose traceable to
approved claims, with a confidence grade - everything needed to file
a page proposal was already computed, just not wired to propose_page.
turning a good answer into a page meant manually copying the text and
re-typing the cited claim ids into a separate kb.propose_page call.

add an opt-in file_as_page parameter: when set, files the answer back
as a PAGE proposal via the existing propose_page, citing the same
claim ids the answer already cites. still fully gated - propose_page
files a pending proposal, never approves anything, identical to every
other write path in this codebase.

skip filing (not an error, not a crash) when the answer is empty or
cites nothing - an uncited "answer" is the kb saying it doesn't know,
not knowledge worth filing - or when propose_page itself fails (e.g. a
title collision): the synthesis already succeeded, and losing that to
a secondary optional step would be worse than reporting why filing
didn't happen. both outcomes surface via new page_proposal_id (None
when skipped/failed) and page_proposal_skipped_reason result fields.

extracted the existing no-llm path into _deterministic_synthesize so a
new _maybe_file_page_proposal helper can wrap either backend's result
the same way - the llm path already produces the identical
{query, answer, claims, pages, gaps, _meta} shape.

registered on mcp (kb_synthesize), jsonl (kb.synthesize), and cli
(vouch synthesize --file-as-page [--page-title T] [--agent A]).
opt-in and additive - file_as_page defaults to False, so every
existing caller's result shape is unchanged.

Closes vouchdev#736
@github-actions github-actions Bot added docs documentation, specs, examples, and repo guidance cli command line interface mcp mcp, jsonl, and http surfaces retrieval context, search, synthesis, and evaluation tests tests and fixtures size: M 200-499 changed non-doc lines labels Jul 31, 2026
diff-coverage flagged two branches in _maybe_file_page_proposal as
untested: the gaps-section body append (only reachable when
file_as_page=True AND the query is partially covered - the existing
tests either fully cover or fully miss), and the except (ProposalError,
ArtifactNotFoundError) skip path.

add a partially-covered query (some cited claims, some uncovered
terms) asserting the gaps land in the filed page body, and a
monkeypatched propose_page failure asserting it degrades to a
reported skip reason without losing the already-successful synthesis.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli command line interface docs documentation, specs, examples, and repo guidance mcp mcp, jsonl, and http surfaces retrieval context, search, synthesis, and evaluation size: M 200-499 changed non-doc lines tests tests and fixtures

Projects

None yet

Development

Successfully merging this pull request may close these issues.

kb.synthesize: file good answers back as page proposals (roadmap 1.4)

1 participant