# Session summary (post-conference reconstruction) `session-summary` is an agent skill that turns a finished recording into the files you actually share. It is the post-conference stage of the pipeline: the daemon and the assistant produce evidence during the meeting, this skill reconstructs it afterwards. It is **not** a daemon process, a console script, or a socket client. It runs inside an agent session (OpenCode), driven by [`.opencode/skills/session-summary/SKILL.md`](../.opencode/skills/session-summary/SKILL.md). ## Inputs Everything comes from one session directory under `transcripts/` (gitignored): | Input | Role | |---|---| | `transcripts.jsonl` | **The evidence.** Read-only. `seg_id`, `ts`, `lang`, `lang_prob`, `text`, `speaker`, `speaker_id`, `speaker_status`, `speaker_score` | | `audio/*.wav` | Optional corroboration (only written when `[output].save_audio = true`) | | `brief.md` | **Low-trust input.** A list of possible leads, never a source of facts | | `speakers/speakers.json` | Name mapping only (`speakers..name`); not an attendee list | | `questions.json` | Not read by this skill | Rules that shape every run: - A run touches exactly **one** session directory. It must not glob, list, or compare other sessions: cross-session knowledge only reaches it through the glossary. - `transcripts.jsonl` and `audio/` are never modified. - Every claim in the output must be traceable to a `seg_id`. If it cannot be, it is not written. - Paraphrase, never invent. An unresolvable token is reported as unresolved, not guessed. ## Outputs Every run writes these files into the same session directory, and nothing elsewhere: | File | What it is | |---|---| | `session-summary.svg` | One-page visual reconstruction, English | | `session-summary.png` | Shareable export of the SVG (1680 px, 64-color, ~0.27 MB) | | `session-summary.content.json` | The content the English SVG was rendered from | | `session-summary.es.svg` | Spanish reconstruction — same topics, same order, same claims | | `session-summary.es.png` | Shareable export of the Spanish SVG | | `session-summary.es.content.json` | Spanish content JSON | | `brief.md` | Rewritten in place, keeping the `Summary:` … `Prior Details:` layout | The run is **not complete** until both SVGs and both PNGs exist and have passed verification. A PNG around 1 MB means the quantization step did not run. The page layout is fixed by the renderer: header with computed stats, a main-point band, a sidebar (session focus, participants and roles, transcription quality), any number of topic cards, an Opportunities / Blockers-Risks / Open-questions block, optional next steps, and a footer of method notes. ## How to run it Invoke the skill from an agent session and name the session directory, for example "summarize `transcripts/2026-10-30-13-07-19`" or "fix the brief of that session". The skill drives the whole flow: load the evidence, reconstruct, write the content JSONs, render, verify, correct the brief, then update the glossary. Rendering is a separate script; the skill calls it, and you can call it directly for a re-render after editing a content JSON: ```bash S=transcripts/ R=.opencode/skills/session-summary/scripts/render.py uv run python $R $S $S/session-summary.content.json $S/session-summary.svg uv run python $R $S $S/session-summary.es.content.json $S/session-summary.es.svg ``` | Flag | Meaning | |---|---| | `` | Directory holding `transcripts.jsonl`; stats are computed from it | | `` | Content produced by the run | | `` | Destination; the matching `.png` is written beside it | | `--no-png` | Skip the PNG. Draft re-renders only; a final render must not use it | `render.py` writes the SVG, validates that it parses as XML, then exports the PNG with **Inkscape** and quantizes it with **ImageMagick** (`magick`). Both must be on `PATH`; a missing binary makes the run fail loudly instead of leaving a silent fallback. It prints `wrote ` and `wrote ( KB)`, and warns when the content JSON lacks an `opportunities` or `blockers` key. ## The brief is corrected, not trusted `brief.md` is written live by a small, fast model, so the skill treats it as unreliable and rewrites it after reconstructing from the JSONL. The error classes it checks come from real failures and map directly onto known live-mode behaviour: - sections and sentences cut off mid-word (`…Open Ques`) — the same failure the character cap used to cause before the brief was made non-destructive; - the same fact repeated in slightly different wording; - `Unknown participant` for lines the JSONL attributes to a named speaker; - ASR mishearings copied literally; - mis-detected languages reported as statements ("said in Cyrillic"); - hedged statements promoted to "approved" decisions or "confirmed" opportunities; - reversed direction (buyer vs seller, who pays whom); - worked examples, on-screen steps and numbers dropped; - acknowledgements logged as facts. Name and term corrections are applied through the glossary (for example "Good One" → HUD-1). ## The glossary is the only shared state `.opencode/skills/session-summary/references/glossary.md` is the sole file outside the session directory a run may modify. It holds domain terms and known ASR mishearings, and every new row cites its evidence as `(, seg )`. That is deliberate: it is how knowledge survives across sessions without a run ever reading another session's evidence. It is **gitignored private state**, like `questions.json`: it accumulates client vocabulary from real recordings, so it never enters the repository. A fresh clone has no glossary, and the first run creates one from what it observes. Losing it costs accumulated corrections, not data. ## Version control `.opencode/` is gitignored (it holds `node_modules` and local agent state). The skill directory is the exception — it is tracked repo tooling, minus its glossary. The rule lives in `.gitignore` as a progressive un-ignore, with the glossary re-ignored last: ```gitignore .opencode/* !.opencode/skills/ .opencode/skills/* !.opencode/skills/session-summary/ .opencode/skills/session-summary/references/glossary.md ``` Two things stay untracked: the glossary (private client vocabulary, see above) and everything the skill writes. The outputs — the session directory and its SVG/PNG/JSON — are gitignored with `transcripts/`, and the corrected `brief.md` lives inside that directory. Nothing this skill writes is ever committed. ## Troubleshooting | Symptom | Cause | |---|---| | `error: inkscape not found` / `magick` missing | Install both; the PNG step has no fallback | | `PNG is ~1 MB` | Quantization skipped; re-run without `--no-png` | | `no 'opportunities' key` warning | The content JSON is missing a required block; add it and re-render | | SVG opens clipped or overlapping | Do not hand-write the SVG; fix the content JSON and re-render | | Run reports unresolved tokens | Expected when the audio is genuinely ambiguous; they belong in the glossary | See also [OUTPUT.md](OUTPUT.md) for the JSONL fields the reconstruction depends on, and [TRIAGE.md](TRIAGE.md) for how `brief.md` is produced live.