Skip to content

fix: reject incomplete research reports before publication - #177

Merged
iamjr15 merged 1 commit into
mainfrom
fix/research-report-integrity
Aug 5, 2026
Merged

fix: reject incomplete research reports before publication#177
iamjr15 merged 1 commit into
mainfrom
fix/research-report-integrity

Conversation

@iamjr15

@iamjr15 iamjr15 commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Rejects length-truncated or structurally invalid research Markdown before any PDF is published.
  • Restricts every citation to provider-collected HTTP(S) evidence and requires the final Sources list to match inline citations exactly.
  • Doubles synthesis output headroom so reasoning-capable models can finish a focused report without cutting off the references.

Architecture

The synthesis model still produces the canonical Markdown displayed in chat and rendered into the PDF. A deterministic validation boundary now runs before ResearchReportSchema succeeds and before the artifact renderer or generated-output store is invoked. Invalid output uses the existing bounded in-memory retry; a second invalid result fails safely instead of publishing a broken report.

Decisions Made

Decision Choice Alternatives considered Reasoning
Failure handling Reject and regenerate Repair malformed Markdown after generation Repair would invent or alter source attribution and make chat/PDF diverge from the canonical report.
Citation trust Exact canonical URL membership Accept any syntactically valid link Research reports must cite only evidence returned by Exa or Firecrawl.
Sources contract Exact set parity with inline citations Allow partial or extra references Prevents missing, unused, duplicate, and truncated source entries.
Output budget 8,192 tokens with a 20,000-character report bound Keep the 4,096-token ceiling Provides headroom for reasoning and long URLs while retaining the existing byte bound and 1,200-word prompt.

Edge Cases Handled

Scenario Handling
Provider stops at output limit Reject by finish reason and retry.
Markdown link is cut off Reject the unparsed link token.
Model cites an uncollected URL Reject before artifact generation.
Sources list omits or adds a citation Reject unless source and body URL sets are equal.
Duplicate or non-link source entry Reject the report.
Wrong heading/list shape Require one H1, one final H2 Sources section, and an unordered link-only list.

How to Review

  1. Start with research-markdown.ts for the deterministic publication contract.
  2. Review deep-research-workflow.ts for finish-reason wiring, output headroom, and synthesis instructions.
  3. Confirm packages/agent-core/README.md matches the runtime boundary.

Verification

  • pnpm lint
  • pnpm typecheck
  • pnpm turbo build --force
  • pnpm deadcode
  • pnpm architecture:check
  • pnpm turbo skills:build
  • Direct contract exercise: one valid report accepted unchanged; length cutoff, malformed source, uncollected URL, missing source entry, body/source mismatch, and ordered Sources list all rejected.
  • Production baseline reproduced before this change: natural-language research completed, PDF downloaded as a valid 4-page A4 document, and / listed it; visual inspection exposed the truncated final Markdown link this PR prevents.

Reject truncated or malformed research Markdown before publication.

Validate citation provenance and Sources parity, and increase synthesis headroom.
@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
cheatcode Skipped Skipped Aug 5, 2026 12:56pm

@iamjr15
iamjr15 merged commit be85331 into main Aug 5, 2026
6 checks passed
@iamjr15
iamjr15 deleted the fix/research-report-integrity branch August 5, 2026 12:58
iamjr15 added a commit that referenced this pull request Aug 5, 2026
## Summary

- Publishes a successful deep-research tool's validated Markdown
directly as assistant text in the same durable Workflow step that
publishes its artifact.
- Treats successful `research_deep` and `research_fanout` calls as
terminal response producers, so no second model turn can summarize or
rewrite the report.
- Suppresses model pre-tool narration for terminal research calls,
making the visible report body and PDF input identical by construction.

## Why

Production QA for PR #177 proved that the PDF was complete and valid,
but the outer agent ignored its prompt and posted a 1,386-character
summary instead of the canonical report. Prompt-only enforcement cannot
guarantee content identity. This moves ownership to the durable runtime
boundary.

## Architecture

The research tool continues to return `{ artifact, report }`, with
`report` already validated against collected citation provenance. The
Workflow projects that strict output into both destinations:

1. `artifact` becomes the durable deliverable part.
2. `report` becomes the assistant text part.
3. The run completes immediately after the successful tool turn.

Both parts share the deterministic `tool:<model-step>:<tool-index>`
publication receipt, so Workflow replay cannot duplicate either one.

## Decisions Made

| Decision | Choice | Alternatives considered | Reasoning |
|---|---|---|---|
| Response ownership | Durable Workflow publishes canonical report | Ask
the outer model to copy tool output | The production model summarized
despite explicit instructions. |
| Completion | Successful research tool is terminal | Run another model
turn | A second turn can rewrite, truncate, or append content. |
| Progress prose | Suppress research-call model text | Keep preamble
before the report | Ensures visible report content and PDF source are
the same Markdown. |
| Failure path | Continue agent loop on tool error | Treat every
research attempt as terminal | The model still needs to explain provider
or validation failures. |
| Replay behavior | Report and artifact share tool publication |
Separate non-durable append | Existing atomic receipts already guarantee
exactly-once transcript publication. |

## Edge Cases Handled

| Scenario | Handling |
|---|---|
| Research tool fails | No canonical response is projected; the next
model turn explains the error. |
| Non-terminal search tool runs | Existing model loop is unchanged. |
| Research output shape is invalid | Strict schema parsing fails the run
rather than publishing divergent content. |
| Worker/DO replays publication | Deterministic event receipt prevents
duplicate report or artifact parts. |
| Model emits a preamble with the tool call | It stays in model context
but is not published to the user. |

## How to Review

1. Review `agent-run-research-response-support.ts` for terminal-tool
classification and strict output projection.
2. Review `agent-run-workflow.ts` for suppression, atomic report
publication, and terminal completion.
3. Confirm the agent-worker README and system prompt document the same
ownership boundary.

## Verification

- `pnpm lint`
- `pnpm typecheck`
- `pnpm turbo build --force`
- `pnpm deadcode`
- `pnpm architecture:check`
- `pnpm turbo skills:build`
- Direct projection contract: successful report preserved byte-for-byte;
failed, non-terminal, and invalid outputs handled as designed.
- Production evidence before this fix: reload recovery completed one run
with one tool and one artifact, but persisted assistant text was a short
model summary rather than the PDF's report.
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.

1 participant