findings.report.json โ the recommendation slate. Same envelope as every phase-3 findings file ({kind, items: [{id, โฆ}]}) with kind: "report", keeping every artifact in the pipeline shaped the same way. Each item is one recommendation:
{
"kind": "report",
"items": [
{
"id": "rec-001",
"priority": "critical | high | medium | low",
"bucket": "prompting | skills | mcp",
"action": "create | modify | delete | adopt | stop",
"subject": "<the named artifact the rec is about โ e.g. `wait-for-ci` (Skill name), `agent-orchestrator` (MCP server name), `get_configs` (MCP tool name), or a short topic label for prompting recs>",
"problem": "<the failure / issue, stated as the headline โ what went wrong, in plain language>",
"recommendation": "<the proposed fix โ may be revised by review>",
"rationale": "<the leap: how the findings imply this recommendation>",
"proposed_change": {
"kind": "diff | draft",
"format": "unified-diff | skill-md | mcp-json | prompt-snippet",
"body": "<short, digestible sketch โ a unified diff for `modify` actions; a draft (Skill SKILL.md frontmatter + first sections, or mcp.json entry + proposed tool surface) for `create` actions. 10โ40 lines, intentionally not exhaustive โ the real change covers more edge cases.>"
},
"inspiring_segments": [
{
"transcript_id": "<transcript_id from segments.json>",
"segment_id": "<S0.X>",
"summary": "<one short sentence โ what happened here, plain language>",
"before_evidence": [
{ "event_id": "<id from transcript.json>", "snippet": "<short raw quote / tool name / outcome>" }
],
"after_evidence": [
{ "snippet": "<what this same moment would look like with the fix in place โ short, abbreviated>" }
]
}
]
}
]
}
The schema is deliberately tight: headline first is the problem, not the fix โ readers should see what went wrong before they see what to do about it. Plenty of recommendations get revised at the review step; problem statements rarely do. subject is the named artifact the rec is about (the Skill name, the MCP server name, the MCP tool name, or a short prompt-habit topic), so the reader knows the scope of the rec at a glance โ without it, "modify wait-for-ci" reads the same as "modify ao-router-route-request" until you've read three paragraphs.
priority is fixed semantics, not vibes:
- critical โ the underlying problem recurs across multiple instances (segments / transcripts) AND each instance was high-impact. Two High-priority recs that instantiate the same cross-transcript pattern can both be Critical.
- high โ the problem impacted overall Success vs Failure on its own โ a Failure the agent couldn't self-recover from, or a Correction the agent couldn't shake.
- medium โ a Segment hit Failure but the agent (or a subagent) self-corrected without user intervention. The system worked, even if it cost some turns.
- low โ cost / efficiency / clarity optimization. Not a capability gap. The agent succeeded; this would have made it cheaper or sharper.
inspiring_segments is the reader's bridge into the actual work behind the recommendation AND the auditable trace of where the rec came from โ usually 1โ3 entries (more for cross-transcript clusters). The previous schema's sources field (a flat list of phase-3 finding ids) has been removed: a list of fh-S0.7 / mcpg-S0.3 ids is unverifiable noise to a reader, while a Segment + an evidence chain of real events is the verification. Each entry names a real Segment in a transcript's segments.json and carries:
- a one-sentence
summary of what happened in that Segment, plain language
- a
before_evidence chain โ the 3โ6 raw events (event_id + short snippet) from transcript.json that show the problem actually playing out
- an
after_evidence chain โ the same moment, abbreviated, as it would have looked with the recommendation already in place (a hypothetical-but-grounded counterfactual: same event_ids where they still apply, new snippets elsewhere)
Both chains are kept short and concrete โ the HTML companion renders them collapsed by default. The two chains together replace what used to be prose change_contours / expected_after_state fields: a reader who wants to inspect a recommendation reads the problem, then the recommendation, then expands the before/after chains for one or two inspiring segments. No long-form description needed; the events speak.
proposed_change is the rec's concrete starting point. For a modify action: a short unified diff on the artifact's actual source (Skill SKILL.md, mcp.json, etc.). For a create action: a short draft of what the new artifact would look like (Skill frontmatter + first body sections; or an mcp.json entry + a sketched tool surface). Intentionally short โ 10 to 40 lines โ because the real change will cover more edge cases the synthesizer can't see. The point is to make the recommendation acceptable-or-rejectable in one read, not to spec the implementation.
Fields that used to exist and were removed: title (replaced by problem), effort (estimates were noise โ drop), philosophy_check (philosophy is now an under-the-hood gate, not a surfaced field โ see Sequencing checklist), sources (replaced by inspiring_segments with real evidence chains โ finding-id lists were unverifiable noise to a reader).
report.html + companion pages โ a multi-page static site (not a single SPA) written into batch_dir:
batch_dir/
report.html # landing โ recs grouped by priority, terse
recommendations/rec-<NNN>.html # one per recommendation โ detail page
sessions/<short-tag>.html # one per transcript โ segmentation overview
segments/<short-tag>--<SID>.html # one per Segment โ the deepest drilldown
context/<short-tag>.html # one per transcript โ external context (when present)
cross-transcript.html # batch-level cross-transcript patterns (when present)
Each file is a small standalone HTML page with relative-href links to the others. No CDN, no build step; opening report.html from the filesystem (or via python3 -m http.server in batch_dir if the browser blocks file:// cross-page navigation) gives the reader a navigable site. Pages cross-link freely: every rec-NNN chip on the landing page is a real <a href="recommendations/rec-NNN.html">; every (S1, e2cdbb98) or segment id is a real link to that segment's page.
The site is a read-only drilldown โ click deeper to see every decision the pipeline made. Each page exposes one more layer of the agent's intermediate work, so a reader can start at the synthesized recommendation and click all the way down to the raw events that justify it, and can wander the pipeline's other decisions that never rose to a recommendation:
report.html
โ recommendations/rec-NNN.html โโ (the synthesized leap)
โ sessions/<tag>.html โ (how this transcript was segmented: flamegraph + tree)
โโ context/<tag>.html โ (the external context the agent gathered for it)
โโ segments/<tag>--SID.html โโ (the deepest page: this Segment's decomposition
decision โ Trigger / Goal / Outcome โ plus EVERY phase-3 finding tagged to
it across all four buckets, each with its evidence events, plus the recs that
cited it and links up/across the tree)
โ cross-transcript.html (patterns no single transcript reveals, when present)
This is strictly for seeing what the pipeline decided. There is no editing, annotation, correction, or accept/reject surface anywhere in the site โ those belong nowhere in this pipeline. The reader drills in to understand the decisions; they do not change them here.
Each recommendation page renders, in this order:
- The
subject badge + a type badge next to it carrying <bucket> ยท <action> (e.g. Skill ยท modify, MCP ยท create). Two badges, side by side, both load-bearing. The reader knows at a glance: "this is about agent-orchestrator; it's an MCP modify." Bucket is rendered in a Skill / MCP / Prompting word, not the raw enum value.
- The
problem as the page title (h1), with a small priority text-label and the meta line below.
- The
recommendation paragraph.
- The
proposed_change as a syntax-highlighted code block โ a unified diff for modify actions, a draft for create actions. This is concrete starting material, not decoration: a sketch the reader lifts into a real change in the actual artifact, then amends to cover the edge cases the synthesizer couldn't see. Like every page in the site, it is display-only โ nothing is edited, accepted, or rejected in the site itself; the reader takes the sketch elsewhere to act on it.
- The
rationale.
- Inspiring transcript moments: one card per
inspiring_segments[*]. Each card has the segment summary up top โ with the segment id as a real link to that Segment's drilldown page (../segments/<tag>--<SID>.html), so the reader can leave the recommendation's curated before/after view and see everything the pipeline tagged on that Segment โ then two <details> blocks (collapsed by default) for the before_evidence and after_evidence chains.
- A pre-filled follow-up prompt at the bottom of the page with a "Copy to clipboard" button. Text template:
"I have a follow-up question about <rec_id> (<problem>) that came from <transcript_id> segment(s) <segment_id_list>. <cursor>". The helper text next to the button points the reader at the originating Claude Code session โ the session that ran synthesize-agent-transcript-analysis-report and produced this artifact (e.g. "Paste into Claude Code session <session_id> (which generated this report) to keep digging"). That session has the warm context โ the segments, the findings, the philosophy gate decisions โ so the follow-up lands somewhere that can already answer it. The synthesizer records its own session id at build time and embeds it. JS is allowed here and only here (vanilla navigator.clipboard.writeText); the rest of the site stays JS-free.
Each session page is the transcript's segmentation overview โ how the pipeline carved this transcript up. It renders the session header (with a link to the transcript's context/<tag>.html external-context page when one exists), an inline flamegraph from segments.json, the segment tree (collapsible), and event play-by-play per segment (collapsed by default). Every node in the segment tree is a real drilldown link. Each node shows, inline and terse: its Trigger (New / Correction, user / agent), its Goal (one line), its Outcome (Success / Failure), and a small count of how many phase-3 findings were tagged to it (e.g. 4 findings); the node label links to that Segment's page (../segments/<tag>--<SID>.html). The session page is the scannable map; the segment pages are where the reader goes deep.
Flamegraph layout rule. The inline flamegraph must place every Segment at its tree depth on the Y axis (root at depth 0, root's children at depth 1, grandchildren at depth 2, โฆ) and over its actual meta.event_range time window on the X axis. So a Segment like S0.5.5 sits at depth 2, horizontally inside S0.5's time window (which itself sits at depth 1, inside S0's window at depth 0). A subagent or grandchild Segment can never visually appear "under" a sibling of its parent โ its X position is governed by its real timestamps, and its Y row is governed by its tree depth. (Bugs in earlier implementations rendered descendants in sibling rows because Y was assigned by traversal order instead of by depth. Don't.)
Flamegraph width rule. Compute width so smaller Segments stay readable rather than smushed: aim for a minimum of ~2.5 px per second of session wall-clock, and let the flamegraph scroll horizontally inside a overflow-x: auto container when that pushes it past the viewport. A long session (say 38 min โ 2280 s) renders ~5700 px wide; the reader scrolls. Smushing a 38-minute session into a single 600 px panel hides the sub-second sibling structure the reader needs to see โ scroll beats smush.
Each segment page (segments/<short-tag>--<SID>.html) is the deepest drilldown โ the one place that gathers everything the pipeline decided about a single Segment. It renders, in this order:
- A breadcrumb / nav row: link up to the session page (
../sessions/<tag>.html), links across to the parent Segment and child Segments' pages, and โ when this Segment appears in any recommendation's inspiring_segments โ a "Cited by" list of links to those rec-NNN pages. (Build this reverse index once: walk findings.report.json, map each inspiring_segments[*].{transcript_id, segment_id} back to its rec id.)
- The decomposition decision โ what phase 2 decided here, as a small labeled block: the Trigger (
kind: New / Correction; source: user / agent), the Goal (verbatim), the Outcome (kind: Success / Failure, and which Goal it closes), and the meta.event_range. This is the "where/why it segmented" the reader came to see.
- Findings tagged to this Segment, grouped by bucket (Outcomes / Prompts / Skills / MCP) โ every item across
findings.{outcomes,prompts,skills,mcp}.json whose segment_id matches this Segment. Each finding renders its analyzer name, its conclusion in plain language, and a <details> block (collapsed) with its evidence โ the event_ids rendered as links into this page's own event play-by-play (or to the session page when the event falls outside this Segment's range). This is "what it tagged as the Segment's outcomes" and the rest of the per-Segment analysis, in full โ not the filtered before/after view a recommendation shows. If no findings reference this Segment, say so plainly ("No phase-3 findings were tagged to this Segment") rather than rendering empty bucket headers.
- Event play-by-play for this Segment's
meta.event_range, each event anchored at #evt-<uuid> so finding-evidence links and inbound before_evidence links land here.
Each external-context page (context/<short-tag>.html, only when that transcript has an external-context.json) renders the context the agent gathered before judging the session: the ticket it traces back to, the pull request it landed in, and the user / role / team / project background โ each external URL a real outbound link. It is linked from the session header and is the canonical source for the external links scattered through the rest of the site.
The cross-transcript page (cross-transcript.html, only when findings.cross-transcript.json is present) renders the batch-level patterns โ its five sections (Hindsight-as-foresight, Recurring prompt patterns, Cross-session Skill gaps, Cross-session MCP gaps, Time-spend patterns), one card per item, each item linking its transcripts / source Segments to the relevant segment pages. It is linked from report.html. When a section's threshold was unreachable at this batch size, render that section's stated "threshold not reachable at N=K" note rather than dropping it.
Click targets get highlighted on the destination page. When the reader clicks an evidence event link and lands at #evt-<uuid> โ on a session page or a segment page โ that event must be visibly highlighted (browser's :target pseudo-class with a clear background tint is sufficient โ no JS needed). The whole point of evidence-link drill-down is verifiability; if a click teleports the reader to a wall of text without telling them what they were just citing, the trust never builds. The new drilldown pages (segment, context, cross-transcript) follow the same JS-free rule as the rest of the site โ <details>, :target, and relative-href links only; the lone exception remains the clipboard button on the recommendation pages.
Pretty, not noisy. The visual chrome the user objected to last round (stats cards at the top, batch-report preamble, decorative priority pills, tentative chips, bucket pills, philosophy-check writeups, sources lists) stays gone. But proper styling stays: system font stack, comfortable line-height, max-width on prose, card layouts with subtle borders for distinct content blocks, monospace for event ids / code / tool names, priority shown as a small colored text label (not a chip), outcome colors confined to text on <summary> lines for the segment tree. Quiet palette in light + dark. Raw evidence over decoration is the rule; no decoration at all was the over-correction.
Round-trip rule: findings.report.json is source of truth; report.md is the canonical text artifact; the HTML pages are the rich-format reader-friendly view. All three carry the same recommendations โ if they disagree, JSON wins and the others re-render.