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
fix: render research PDFs from canonical Markdown (#164)
## Summary
- Render the validated research report Markdown directly into the PDF
instead of flattening it into a separate section/paragraph model.
- Preserve the same headings, prose, lists, task items, tables, code,
links, citations, and ordering shown in chat.
- Keep structured claim provenance internal while returning a smaller
user-facing research tool result.
## Architecture
The research synthesis remains the single content source. The Worker
parses that bounded Markdown with a pinned GFM parser, stages the
Markdown and its semantic tokens in the existing hidden artifact-input
path, and the sandbox's existing React PDF runtime renders those tokens
into a durable workspace/R2 PDF.
No database, storage, sandbox snapshot, or deployment-topology change is
required.
## Decisions Made
| Decision | Choice | Alternatives considered | Reasoning |
|---|---|---|---|
| Content source | Use `report.report` verbatim | Rebuild a document
from claims and sources | Prevents chat/PDF drift and removes synthetic
appendices the user did not see in chat. |
| Markdown parsing | Pin `marked` in `agent-core` | Regex parsing; add
runtime dependencies | Gives standards-based GFM tokens without
requiring a new Daytona snapshot. |
| PDF runtime | Reuse React PDF and baked Liberation fonts | Browser
print-to-PDF; new PDF vendor | Keeps rendering deterministic,
sandbox-local, and compatible with the current document boundary. |
| Tool result | Return report + artifact only | Return all findings,
claims, and sources | Provenance is validated internally; the smaller
result makes verbatim chat presentation reliable. |
## Edge Cases Handled
| Scenario | Handling |
|---|---|
| Long multi-page reports | A4 wrapping, widow/orphan controls, and
heading presence rules prevent clipping and orphaned headings. |
| Nested, ordered, and task lists | Semantic list tokens retain nesting,
numbering, and task state. |
| Comparison tables | Header, alignment, cell borders, and row-safe
pagination are preserved. |
| Citations | Safe HTTP(S)/mailto links remain visible and clickable PDF
annotations. |
| Inline HTML or unsafe URLs | HTML is rendered as inert text and
unsupported URL schemes are not made clickable. |
## How to Review
1. Start with `research-report-document-support.ts` and
`research-tools.ts` for the one-source data flow.
2. Review `execute.ts` and `scripts.ts` for parsing, staging, and
semantic PDF rendering.
3. Review the synthesis/system prompts and README for the chat/PDF
contract.
## Verification
- [x] `pnpm lint`
- [x] `pnpm typecheck`
- [x] `pnpm turbo build --force`
- [x] `pnpm deadcode`
- [x] `pnpm architecture:check`
- [x] `pnpm turbo skills:build`
- [x] Rendered a five-page hostile GFM fixture and visually inspected
every page.
- [x] Verified extracted text, A4 metadata, and clickable link
annotations with Poppler.
Copy file name to clipboardExpand all lines: packages/agent-core/src/mastra/system-prompt.ts
+1-1Lines changed: 1 addition & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -233,7 +233,7 @@ Load the generate-media skill before creating or editing an image or generating
233
233
234
234
constRESEARCH_MODULE=`## Research
235
235
236
-
Gather sources with search_web / search_web_advanced / search_company / search_web_content, then use search_scrape or search_extract for source retrieval. For an explicit deep-research request, cited report, market analysis, due diligence, or comprehensive investigation of one topic, use research_deep with 3 queries for a concise/narrow report, 4 by default, and 5-6 only when the user explicitly asks for deeper coverage. Use research_fanout when the request compares many entities or independent angles. Call the selected research workflow once per user request; if it fails, explain the failure instead of immediately rerunning it. Those workflows produce the complete cited PDF deliverable automatically, so do not regenerate it with a document tool. Treat search snippets as leads, not sources — open the real pages and cross-check. Cite as you go: attribute each claim to its source inline with the page title and its URL, and make sure every citation resolves. End a research answer with a short Sources list of the URLs you actually used.`;
236
+
Gather sources with search_web / search_web_advanced / search_company / search_web_content, then use search_scrape or search_extract for source retrieval. For an explicit deep-research request, cited report, market analysis, due diligence, or comprehensive investigation of one topic, use research_deep with 3 queries for a concise/narrow report, 4 by default, and 5-6 only when the user explicitly asks for deeper coverage. Use research_fanout when the request compares many entities or independent angles. Call the selected research workflow once per user request; if it fails, explain the failure instead of immediately rerunning it. Those workflows produce the complete cited PDF deliverable automatically, so do not regenerate it with a document tool. After a research workflow succeeds, present its returned report Markdown as the response body without summarizing, restructuring, or appending hidden claim-map data; the PDF is rendered from that same Markdown. Treat search snippets as leads, not sources — open the real pages and cross-check. Cite as you go: attribute each claim to its source inline with the page title and its URL, and make sure every citation resolves. End a research answer with a short Sources list of the URLs you actually used.`;
237
237
238
238
/** Compact all-domains pointer for an ambiguous general request — keeps the model aware without the full modules. */
Copy file name to clipboardExpand all lines: packages/agent-core/src/mastra/workflows/deep-research-workflow.ts
+2Lines changed: 2 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -305,6 +305,8 @@ function researchSynthesisPrompt(
305
305
config.synthesisPrompt(findings),
306
306
"Consolidate overlapping evidence into at most 16 distinct claims with no more than 4 source IDs per claim.",
307
307
"Keep the report focused and complete within 2,000 words while retaining actionable findings and citations.",
308
+
"Write report as polished GitHub-flavored Markdown for direct display and PDF rendering. Preserve a clear heading hierarchy, lists, and comparison tables where useful.",
309
+
"Cite factual claims with descriptive Markdown links to the exact source URLs in the findings, and finish with a Sources heading containing only sources used in the report.",
0 commit comments