executive-summary-writer
The skill that produces 1-page executive summaries from research notes. Tunes length, voice, format, and section emphasis per named audience by reading a Markdown preferences doc the user maintains in Obsidian. First skill in the toolkit to do per-audience personalization — every exec read of a research finding can be calibrated to that exec's actual taste.
When to use
Only on explicit user request. Triggers include:
"Summarize that research for the CISO."
"Give me an exec summary of the latest Copilot research."
"Produce a 1-page version of <note> for VP Eng."
"I need CISO + VP Eng + Default versions of this for sharing."
Research notes do NOT auto-convert into exec summaries when they're written. The Category 1 researchers (copilot-deep-dive, sdlc-best-practice, etc.) finish at the research-note write — they do NOT chain into this skill. The user decides which research findings warrant an exec pass.
When NOT to use
- Auto-triggered after a
vault-writer.write_research succeeds. Even if the research seems exec-worthy, do not auto-invoke. Wait for the user's explicit ask.
- Source content is a digest (
vault/digests/{cadence}/...), not research — digests are already summarized; this skill doesn't second-summarize. If you really want an exec take on a digest, copy the digest body to a research/ note first.
- Decision memos, ADRs, RFCs — those have their own writer skills (
decision-memo-writer, adr-writer, rfc-writer) with different structural conventions. The exec summary spine doesn't fit a formal decision request.
- Non-research input (raw meeting notes, fragments) — produce a
research/ note first; this skill assumes the source has TL;DR + Findings already shaped by the Category 1 researcher.
- Stakeholder-update-style multi-tier batched updates (CIO/CISO + VP Eng + IC in one document) — that's
stakeholder-update-writer's job. This skill is single-audience, single-source, on-demand.
Prerequisites
vault-conventions cached this session (for the insight.yml schema).
~/Obsidian/Research-Brain/_config/exec-preferences.md exists with at least a ## Default section. Missing → stop-and-report. Copy exec-preferences.example.md to that path on first use.
The 8-section structure
The spine of every summary. Three mandatory, five toggleable per audience. Total target: ~200 words (override via prefs).
| # |
Section |
Mandatory? |
Purpose |
| 1 |
Bottom Line Up Front (BLUF) |
✅ |
1-2 sentences. The actual answer. If they only read this, they know what changed and why it matters. |
| 2 |
Why This Matters Now |
optional |
What triggered surfacing this? (regulator move, peer move, deadline, incident.) Skip for evergreen research. |
| 3 |
Key Findings |
✅ |
3-5 bullets. Each with a source-tier marker [T1]/[T2]/[T3] and a confidence indicator if non-obvious. |
| 4 |
Implications for Us |
optional |
"So what" — concrete impact on the org's posture, programs, or pending decisions. |
| 5 |
Recommended Action |
✅ |
1-3 concrete next steps. Each with owner + rough timeline if applicable. "I recommend we..." framing. |
| 6 |
Risks & What We're Watching |
optional |
Uncertainty, dissent in sources, what to monitor before next checkpoint. |
| 7 |
Next Decision Point |
optional |
When does this come back to them? What would they need to decide? "By DATE we should..." |
| 8 |
Sources (footer) |
✅ |
Tier-1 citations with URLs + vault path to the deeper research note for follow-up. Minimal. |
The 3 mandatory sections never disappear — that's the discipline that keeps the document a 1-pager. Prefs can toggle the other 5 on/off, override length, tune voice, etc.
Helpers
summarize_for(source_note_path, audience="default", subject_override=None)
The primary action. Steps:
- Resolve
source_note_path to an absolute path; read the file (Markdown + YAML frontmatter). Missing → stop-and-report #1.
- Load
~/Obsidian/Research-Brain/_config/exec-preferences.md; parse audiences (see Parsing). Missing file → stop-and-report #2. Missing ## Default → #3.
- Look up
audiences[audience]. Unknown handle → stop-and-report #4 listing available handles. For missing fields on a named audience, fall back to the corresponding ## Default field (field-by-field merge).
- Build the writing prompt:
- Include the source note's TL;DR + Findings + Sources as "research input."
- Apply the audience's
length, format, voice, emphasize, avoid, special_interests.
- Toggle optional sections per
sections field (e.g. +next-decision-point, -risks).
- If
special_interests overlaps with the research topic, surface those points explicitly in BLUF or Key Findings.
- Apply
regulated-finance-framer as a prompt fragment for the "Implications for Us" section so framing stays consistent with other Category 1 outputs.
- Pass the audience's free-form prose (everything under the bold-key fields that isn't a field itself) verbatim as "additional context about this reader."
- Generate the 8-section summary respecting mandatory/optional toggles.
- Compose the slug from the source note's title (lowercase-kebab, truncate at 50 chars).
- Call
vault-writer.write_insight(...) with:
- Path:
vault/insights/YYYY-MM-DD-exec-summary-{audience}-{slug}.md
- Frontmatter per
insight.yml plus audience: {handle} and source_research: {vault-relative-path-to-source}.
- Body: the 8-section structure.
- Compose with
email-sender.prompt_then_send(written_path) — asks [y/n] to deliver via the existing distribution list.
Return:
{
"path": "vault/insights/2026-06-23-exec-summary-ciso-agentic-features.md",
"audience": "ciso",
"source_research": "research/copilot/2026-06-22-agentic-features.md",
"word_count": 148,
"sections_included": ["bluf", "key-findings", "recommended-action", "next-decision-point", "sources"],
"sections_omitted": ["why-this-matters-now", "implications", "risks"],
"email_action": "sent" | "skipped" | "prompted"
}
list_audiences()
User-facing diagnostic. Loads and parses exec-preferences.md; prints each audience with its length, voice, emphasize, and which optional sections are toggled on. No mail traffic, no file writes. Useful before invoking summarize_for with a new audience name to confirm the prefs file parses cleanly.
Parsing — exec-preferences.md
Markdown with YAML frontmatter (frontmatter ignored by the parser; consumed by Obsidian for display).
- Strip the YAML frontmatter (
---\n...\n---\n block at top, if present).
- Strip HTML comments (
<!-- ... -->) — anything inside is ignored, so commenting out a field or whole audience pauses it.
- Scan for H2 (
##) headings. Each H2 is one audience. The H2 title becomes the audience handle (lowercased, spaces → hyphens) unless an explicit **handle**: field under that audience overrides it.
- The first H2 whose title (case-insensitive) is
Default is required — it's the fallback for unspecified audiences.
- Under each H2, recognize these bold-key fields (Markdown
- **key**: value lines):
handle — kebab-case identifier (optional override)
role — descriptive (informational; copied into the summary's frontmatter as audience context)
length — integer (target word count; trailing "words"/"w" stripped)
format — bullets | prose | mixed
voice — free-text descriptor
emphasize — comma-separated list
avoid — comma-separated list
special_interests — comma-separated list
sections — comma-separated +name/-name toggles (e.g. +next-decision-point, -risks); recognized section names: bluf, why-this-matters-now, key-findings, implications, recommended-action, risks, next-decision-point, sources. Mandatory sections cannot be turned off; the parser silently drops -bluf etc. with a note in the return.
- Everything else under each H2 (paragraphs, sub-bullets, prose) is collected as
additional_context for that audience — passed verbatim to the writing prompt.
- Sections OTHER than the audience H2 list (e.g.
## How it's parsed documentation in the file) are scanned for diagnostic only; their content does not become an audience.
Defaults applied when a named audience omits a field:
- Pull the corresponding value from
## Default field-by-field.
- If
## Default also omits the field, apply built-in defaults: length=200, format=bullets, voice=declarative, dense, forward-looking, emphasize=[], avoid=[], special_interests=[], sections=all.
Stop and report — enumerated cases
Each surfaces a structured error to the caller:
- Source research note missing →
"Source note not found at <path>. Pass an absolute path or a vault-relative path starting at 'research/'."
- exec-preferences.md missing →
"~/Obsidian/Research-Brain/_config/exec-preferences.md not found. Copy .claude/skills/executive-summary-writer/exec-preferences.example.md to that path and edit."
## Default section missing → "exec-preferences.md is missing the required '## Default' section. Add it as the fallback for unspecified audiences."
- Named audience not found →
"Audience '<name>' not in exec-preferences.md. Available: [default, ciso, vp-eng]." (Available list parsed at run time so it reflects whatever the user actually has.)
- Source note empty / no body →
"Source note at <path> has no body content to summarize. Confirm the research write completed before invoking exec-summary."
- Insight write collision (same date + audience + slug) → per
vault-writer.write_insight's idempotency rule, the slug gets a -2, -3 suffix; no error to the caller.
Email-side failures (missing GMAIL_APP_PASSWORD, missing distribution list, SMTP error) flow through email-sender's own stop-and-report cases — the summary remains written; only delivery fails.
Output shape
Frontmatter follows _meta/schema/insight.yml plus two skill-specific extras:
---
title: "Exec summary — agentic Copilot features ({audience} take)"
created: 2026-06-23
updated: 2026-06-23
tags: [insight, executive-summary, copilot]
source_skill: executive-summary-writer
confidence: 2
audience: ciso # the handle that drove the prefs lookup
source_research: research/copilot/2026-06-22-agentic-features.md # vault-relative
links: []
---
Body: the 8-section structure with section omission/length/voice tuned per audience prefs. Headings are H2 (##); BLUF is H2 even though it's brief, so the document scans top-to-bottom in Obsidian.
Composition
vault-conventions — schemas + tag vocabulary (cached).
vault-querier — fetch source research note + any backlink context (related facts, prior digests on the same topic). The backlink context isn't pasted into the summary; it's input to the writing prompt so the summary can say things like "this is consistent with our 2026-Q1 research."
vault-writer.write_insight — write the summary to vault/insights/.
email-sender.prompt_then_send — post-write, ask [y/n] to deliver via the existing distribution list. Matches the pattern Category 1 researchers already use.
regulated-finance-framer — applied as a prompt fragment for the "Implications for Us" section.
Acceptance test (live round-trip)
- Default audience, full sections: Pick a recent research note at
vault/research/copilot/.... Run summarize_for(<path>) (audience defaults to default). Confirm: output at vault/insights/YYYY-MM-DD-exec-summary-default-{slug}.md, all 8 sections present, ~200 words, prompts to email after write.
- Named audience override: Run
summarize_for(<same path>, audience="ciso"). Confirm: output is shorter (~150 words), risk-forward voice, "Next Decision Point" present (per CISO sections toggle), file path includes -exec-summary-ciso-.
list_audiences(): Returns the 3 audiences from the template (default + ciso + vp-eng) with their length + voice + emphasize fields. No mail traffic, no file writes.
- Unknown audience: Run with
audience="cfo" when no CFO section exists. Confirm: stop-and-report #4 listing available audiences.
- Missing prefs: Temporarily rename
_config/exec-preferences.md. Confirm: stop-and-report #2 with the exact remediation path.
- Missing Default: Edit a copy of the template to remove the
## Default H2. Confirm: stop-and-report #3.
- Paused audience: Wrap an audience's H2 + body in
<!-- ... -->. Confirm list_audiences() no longer lists it; summarize_for(..., audience=that-handle) stop-and-reports #4.
1---2name: executive-summary-writer3description: Turn an existing research note into a 1-page executive summary personalized to a named audience (e.g. `ciso`, `vp-eng`, `ceo`, or `default`). **Invoke ONLY when the user explicitly asks for an exec summary** — never as a post-write step in a Category 1 researcher flow. Loads audience preferences — length, voice, format, emphasize/avoid lists, special interests, section toggles — from `~/Obsidian/Research-Brain/_config/exec-preferences.md`. Writes the summary to `vault/insights/YYYY-MM-DD-exec-summary-{audience}-{slug}.md` via `vault-writer.write_insight`, then composes with `email-sender.prompt_then_send` to optionally deliver via Gmail. The 8-section structure (BLUF, Why This Matters Now, Key Findings, Implications, Recommended Action, Risks, Next Decision Point, Sources) is the spine — three sections are mandatory; the other five are toggleable per audience. Stop-and-reports on missing source note, missing prefs file, missing `## Default` section, or unknown audience. Use when the user asks to summarize a r4---56# executive-summary-writer78The skill that produces 1-page executive summaries from research notes. Tunes length, voice, format, and section emphasis per named audience by reading a Markdown preferences doc the user maintains in Obsidian. First skill in the toolkit to do per-audience personalization — every exec read of a research finding can be calibrated to that exec's actual taste.910## When to use1112**Only on explicit user request.** Triggers include:1314- `"Summarize that research for the CISO."`15- `"Give me an exec summary of the latest Copilot research."`16- `"Produce a 1-page version of <note> for VP Eng."`17- `"I need CISO + VP Eng + Default versions of this for sharing."`1819Research notes do NOT auto-convert into exec summaries when they're written. The Category 1 researchers (`copilot-deep-dive`, `sdlc-best-practice`, etc.) finish at the research-note write — they do NOT chain into this skill. The user decides which research findings warrant an exec pass.2021## When NOT to use2223- **Auto-triggered after a `vault-writer.write_research` succeeds.** Even if the research seems exec-worthy, do not auto-invoke. Wait for the user's explicit ask.24- Source content is a digest (`vault/digests/{cadence}/...`), not research — digests are already summarized; this skill doesn't second-summarize. If you really want an exec take on a digest, copy the digest body to a `research/` note first.25- Decision memos, ADRs, RFCs — those have their own writer skills (`decision-memo-writer`, `adr-writer`, `rfc-writer`) with different structural conventions. The exec summary spine doesn't fit a formal decision request.26- Non-research input (raw meeting notes, fragments) — produce a `research/` note first; this skill assumes the source has TL;DR + Findings already shaped by the Category 1 researcher.27- Stakeholder-update-style multi-tier batched updates (CIO/CISO + VP Eng + IC in one document) — that's `stakeholder-update-writer`'s job. This skill is single-audience, single-source, on-demand.2829## Prerequisites3031- `vault-conventions` cached this session (for the `insight.yml` schema).32- `~/Obsidian/Research-Brain/_config/exec-preferences.md` exists with at least a `## Default` section. Missing → stop-and-report. Copy [`exec-preferences.example.md`](./exec-preferences.example.md) to that path on first use.3334## The 8-section structure3536The spine of every summary. Three mandatory, five toggleable per audience. Total target: ~200 words (override via prefs).3738| # | Section | Mandatory? | Purpose |39|---|---------|------------|---------|40| 1 | **Bottom Line Up Front (BLUF)** | ✅ | 1-2 sentences. The actual answer. If they only read this, they know what changed and why it matters. |41| 2 | **Why This Matters Now** | optional | What triggered surfacing this? (regulator move, peer move, deadline, incident.) Skip for evergreen research. |42| 3 | **Key Findings** | ✅ | 3-5 bullets. Each with a source-tier marker `[T1]`/`[T2]`/`[T3]` and a confidence indicator if non-obvious. |43| 4 | **Implications for Us** | optional | "So what" — concrete impact on the org's posture, programs, or pending decisions. |44| 5 | **Recommended Action** | ✅ | 1-3 concrete next steps. Each with owner + rough timeline if applicable. "I recommend we..." framing. |45| 6 | **Risks & What We're Watching** | optional | Uncertainty, dissent in sources, what to monitor before next checkpoint. |46| 7 | **Next Decision Point** | optional | When does this come back to them? What would they need to decide? "By DATE we should..." |47| 8 | **Sources** (footer) | ✅ | Tier-1 citations with URLs + vault path to the deeper research note for follow-up. Minimal. |4849The 3 mandatory sections never disappear — that's the discipline that keeps the document a 1-pager. Prefs can toggle the other 5 on/off, override length, tune voice, etc.5051## Helpers5253### `summarize_for(source_note_path, audience="default", subject_override=None)`5455The primary action. Steps:56571. Resolve `source_note_path` to an absolute path; read the file (Markdown + YAML frontmatter). Missing → stop-and-report #1.582. Load `~/Obsidian/Research-Brain/_config/exec-preferences.md`; parse audiences (see [Parsing](#parsing)). Missing file → stop-and-report #2. Missing `## Default` → #3.593. Look up `audiences[audience]`. Unknown handle → stop-and-report #4 listing available handles. For missing fields on a named audience, fall back to the corresponding `## Default` field (field-by-field merge).604. Build the writing prompt:61 - Include the source note's TL;DR + Findings + Sources as "research input."62 - Apply the audience's `length`, `format`, `voice`, `emphasize`, `avoid`, `special_interests`.63 - Toggle optional sections per `sections` field (e.g. `+next-decision-point, -risks`).64 - If `special_interests` overlaps with the research topic, surface those points explicitly in BLUF or Key Findings.65 - Apply `regulated-finance-framer` as a prompt fragment for the "Implications for Us" section so framing stays consistent with other Category 1 outputs.66 - Pass the audience's free-form prose (everything under the bold-key fields that isn't a field itself) verbatim as "additional context about this reader."675. Generate the 8-section summary respecting mandatory/optional toggles.686. Compose the slug from the source note's title (lowercase-kebab, truncate at 50 chars).697. Call `vault-writer.write_insight(...)` with:70 - Path: `vault/insights/YYYY-MM-DD-exec-summary-{audience}-{slug}.md`71 - Frontmatter per `insight.yml` plus `audience: {handle}` and `source_research: {vault-relative-path-to-source}`.72 - Body: the 8-section structure.738. Compose with `email-sender.prompt_then_send(written_path)` — asks `[y/n]` to deliver via the existing distribution list.7475Return:7677```python78{79 "path": "vault/insights/2026-06-23-exec-summary-ciso-agentic-features.md",80 "audience": "ciso",81 "source_research": "research/copilot/2026-06-22-agentic-features.md",82 "word_count": 148,83 "sections_included": ["bluf", "key-findings", "recommended-action", "next-decision-point", "sources"],84 "sections_omitted": ["why-this-matters-now", "implications", "risks"],85 "email_action": "sent" | "skipped" | "prompted"86}87```8889### `list_audiences()`9091User-facing diagnostic. Loads and parses `exec-preferences.md`; prints each audience with its length, voice, emphasize, and which optional sections are toggled on. No mail traffic, no file writes. Useful before invoking `summarize_for` with a new audience name to confirm the prefs file parses cleanly.9293## Parsing — exec-preferences.md9495Markdown with YAML frontmatter (frontmatter ignored by the parser; consumed by Obsidian for display).96971. Strip the YAML frontmatter (`---\n...\n---\n` block at top, if present).982. Strip HTML comments (`<!-- ... -->`) — anything inside is ignored, so commenting out a field or whole audience pauses it.993. Scan for H2 (`##`) headings. Each H2 is one audience. The H2 title becomes the audience `handle` (lowercased, spaces → hyphens) unless an explicit `**handle**:` field under that audience overrides it.1004. The first H2 whose title (case-insensitive) is `Default` is required — it's the fallback for unspecified audiences.1015. Under each H2, recognize these **bold-key fields** (Markdown `- **key**: value` lines):102 - `handle` — kebab-case identifier (optional override)103 - `role` — descriptive (informational; copied into the summary's frontmatter as audience context)104 - `length` — integer (target word count; trailing "words"/"w" stripped)105 - `format` — `bullets` | `prose` | `mixed`106 - `voice` — free-text descriptor107 - `emphasize` — comma-separated list108 - `avoid` — comma-separated list109 - `special_interests` — comma-separated list110 - `sections` — comma-separated `+name`/`-name` toggles (e.g. `+next-decision-point, -risks`); recognized section names: `bluf`, `why-this-matters-now`, `key-findings`, `implications`, `recommended-action`, `risks`, `next-decision-point`, `sources`. Mandatory sections cannot be turned off; the parser silently drops `-bluf` etc. with a note in the return.1116. Everything else under each H2 (paragraphs, sub-bullets, prose) is collected as `additional_context` for that audience — passed verbatim to the writing prompt.1127. Sections OTHER than the audience H2 list (e.g. `## How it's parsed` documentation in the file) are scanned for diagnostic only; their content does not become an audience.113114Defaults applied when a named audience omits a field:115- Pull the corresponding value from `## Default` field-by-field.116- If `## Default` also omits the field, apply built-in defaults: `length=200`, `format=bullets`, `voice=declarative, dense, forward-looking`, `emphasize=[]`, `avoid=[]`, `special_interests=[]`, `sections=all`.117118## Stop and report — enumerated cases119120Each surfaces a structured error to the caller:1211221. **Source research note missing** → `"Source note not found at <path>. Pass an absolute path or a vault-relative path starting at 'research/'."`1232. **exec-preferences.md missing** → `"~/Obsidian/Research-Brain/_config/exec-preferences.md not found. Copy .claude/skills/executive-summary-writer/exec-preferences.example.md to that path and edit."`1243. **`## Default` section missing** → `"exec-preferences.md is missing the required '## Default' section. Add it as the fallback for unspecified audiences."`1254. **Named audience not found** → `"Audience '<name>' not in exec-preferences.md. Available: [default, ciso, vp-eng]."` (Available list parsed at run time so it reflects whatever the user actually has.)1265. **Source note empty / no body** → `"Source note at <path> has no body content to summarize. Confirm the research write completed before invoking exec-summary."`1276. **Insight write collision** (same date + audience + slug) → per `vault-writer.write_insight`'s idempotency rule, the slug gets a `-2`, `-3` suffix; no error to the caller.128129Email-side failures (missing `GMAIL_APP_PASSWORD`, missing distribution list, SMTP error) flow through `email-sender`'s own stop-and-report cases — the summary remains written; only delivery fails.130131## Output shape132133Frontmatter follows `_meta/schema/insight.yml` plus two skill-specific extras:134135```yaml136---137title: "Exec summary — agentic Copilot features ({audience} take)"138created: 2026-06-23139updated: 2026-06-23140tags: [insight, executive-summary, copilot]141source_skill: executive-summary-writer142confidence: 2143audience: ciso # the handle that drove the prefs lookup144source_research: research/copilot/2026-06-22-agentic-features.md # vault-relative145links: []146---147```148149Body: the 8-section structure with section omission/length/voice tuned per audience prefs. Headings are H2 (`##`); BLUF is H2 even though it's brief, so the document scans top-to-bottom in Obsidian.150151## Composition152153- [`vault-conventions`](../vault-conventions/SKILL.md) — schemas + tag vocabulary (cached).154- [`vault-querier`](../vault-querier/SKILL.md) — fetch source research note + any backlink context (related facts, prior digests on the same topic). The backlink context isn't pasted into the summary; it's input to the writing prompt so the summary can say things like "this is consistent with our 2026-Q1 research."155- [`vault-writer.write_insight`](../vault-writer/SKILL.md) — write the summary to `vault/insights/`.156- [`email-sender.prompt_then_send`](../email-sender/SKILL.md) — post-write, ask `[y/n]` to deliver via the existing distribution list. Matches the pattern Category 1 researchers already use.157- [`regulated-finance-framer`](../regulated-finance-framer/SKILL.md) — applied as a prompt fragment for the "Implications for Us" section.158159## Acceptance test (live round-trip)1601611. **Default audience, full sections**: Pick a recent research note at `vault/research/copilot/...`. Run `summarize_for(<path>)` (audience defaults to `default`). Confirm: output at `vault/insights/YYYY-MM-DD-exec-summary-default-{slug}.md`, all 8 sections present, ~200 words, prompts to email after write.1622. **Named audience override**: Run `summarize_for(<same path>, audience="ciso")`. Confirm: output is shorter (~150 words), risk-forward voice, "Next Decision Point" present (per CISO `sections` toggle), file path includes `-exec-summary-ciso-`.1633. **`list_audiences()`**: Returns the 3 audiences from the template (default + ciso + vp-eng) with their length + voice + emphasize fields. No mail traffic, no file writes.1644. **Unknown audience**: Run with `audience="cfo"` when no CFO section exists. Confirm: stop-and-report #4 listing available audiences.1655. **Missing prefs**: Temporarily rename `_config/exec-preferences.md`. Confirm: stop-and-report #2 with the exact remediation path.1666. **Missing Default**: Edit a copy of the template to remove the `## Default` H2. Confirm: stop-and-report #3.1677. **Paused audience**: Wrap an audience's H2 + body in `<!-- ... -->`. Confirm `list_audiences()` no longer lists it; `summarize_for(..., audience=that-handle)` stop-and-reports #4.