Compress a single CLI session into an HTML report saved in the current directory that can be opened offline. The report has three tabs:
- Narrative — what was done this time (timeline + key feedback moments + narrative summary written by the Agent)
- Health — token / cache / tool usage diagnostics
- Compound — playground: candidate entry list on the left (checkable + inline editing), live markdown composition on the right, one-click copy of the "prompt" at the bottom, paste it back into Claude / Codex to have the agent persist it according to the rules
When to use
- The user asks to "复盘 / 总结 / 沉淀 / wrap up" the current session
- The user explicitly invokes
/session-compoundor a similar command - The user wants to extract AGENTS.md /
docs/rules/additions, reusable ready-made skills, or abstractable skill gaps from this session
Do notuse for cross-session analysis—that is the scope of the session-report plugin (last 7 days × all projects).
Workflow
Step 1: Run the analyzer
First determine the CLI identity (read the environment variable CLAUDE_CODE_SESSION_ID or CODEX_THREAD_ID, whichever matches is the side), then execute the corresponding branch.
Claude Code branch
node <skill-dir>/analyzers/claude-code.mjs > /tmp/session-compound.json
<skill-dir> is the absolute path of the directory containing this SKILL.md.
The script will automatically infer CLAUDE_CODE_SESSION_ID via the ~/.claude/projects/<cwd-slug>/<session-id>.jsonlenvironment variable + the current cwd. Optional override:
--session-id <uuid>— specify the session id--project-slug <slug>— specify the cwd slug (overrides automatic inference)--file <abs-path>— directly specify the JSONL file path
Runtime requires Node 18+ (the script uses node:fs / readline namespace import).
Codex branch
node <skill-dir>/analyzers/codex.mjs > /tmp/session-compound.json
The script will read the thread id from the CODEX_THREAD_ID environment variable, then locate the file under ~/.codex/sessions/**/rollout-*-<thread-id>.jsonl(and the ~/.codex/archived_sessions/**/ fallback). Optional override:
--thread-id <uuid>— specify the thread id (--session-idis its alias)--file <abs-path>— directly specify the rollout JSONL file path
General constraints
If the analyzer exits non-zero, read stderr, fix the corresponding issue (wrong path, missing session, file not generated, etc.), and rerun.Do not continue to subsequent steps without obtaining the JSON。
Step 2: Read the JSON summary
Read /tmp/session-compound.json. Focus on scanning these fields:
session— id / cwd / duration / model / gitnarrative.human_turns— summary of each user turn + token + triggered toolsnarrative.feedback_moments— detected user correction/feedback momentshealth.tokens/health.cache_hit_rate/health.context_window_used_pcthealth.tools/health.subagents/health.skills(on the Claude side there are usually both subagents + skills; on the Codex side skills is always empty, and subagents containsspawn_agentinvocations)health.expensive_turns— the turn with the highest token consumptionhealth.waste_signals— waste signals such as repeatedly reading the same file, low cache hit rate, etc.raw_for_compound— the raw material used to write candidate entries (includingagent_invocations)
Thecore fields(session.{id,cwd,duration_ms,model,git} / narrative.{human_turns,feedback_moments} / health.{tokens,cache_hit_rate,tools,subagents,skills,expensive_turns,waste_signals} / raw_for_compound.{feedback_moments,repeated_reads,agent_invocations}output by the two analyzers) are consistent, and the template renders according to these fields. In addition, the two sides each have CLI-specific extension fields; Codex has additional health.{compaction_count, turn_aborted_count, patch_apply, mcp_tool_call_count, custom_tool_call_count, web_search_count, tool_search_count, image_generation_count, context_window, reasoning_output_ratio} and narrative.{task_title, task_conclusion, task_completed, task_duration_ms, time_to_first_token_ms}; Claude has additional health.{api_calls, cache_breaks}. The template already handles these differences by CLI branch.
Step 3: Copy the template to the output file
cp <skill-dir>/template.html ./session-compound-$(date +%Y%m%d-%H%M).html
Step 4: Pre-check ecosystem skills based on the data
Before writing candidates,firstidentify 3–5 possible skill gap patterns from this session's data (repeatedly reading the same document, recurring tool combinations + failures, fixed multi-step process keywords appearing in long turns), generate a search query for each pattern, then run:
npx skills find "<query>" 2>&1 | head -30
For each query, extract the top-3 results (name / install_count / source URL).
Based on the returned results, make a verdict:
recommend-install— found one or more ready-made skills with high install counts, just reuse them (no need to writeskill-gapcandidate)partial-match— there are related skills but the semantics do not fully match (inskill-gapcandidate's find-skills check field, reference these results to avoid downstream agents re-running it)no-match— nothing reusable at all, writeskill-gapcandidate, create your own
The output of this stepgoes directly into the candidates array of step 5d next, but it needs a special type existing-skill— any recommend-install verdict's results should be added to the array as a existing-skill candidate (the user checks it in the browser → after copying the prompt, the downstream agent will automatically run npx skills add install).
Step 5: Inject data + write the Agent fill-in sections (use Edit, not Write— the template's JS/CSS must be preserved)
4 edits are needed:
5a. Replace <script id="report-data"> 's content
Replace this block's content with the complete JSON produced in step 1:
<script id="report-data" type="application/json">
{ "cli": "claude-code", ... }
</script>
The template's JS will automatically render the hero, all tables, bars, and timeline from this JSON.
5b. Fill in the <!-- AGENT: narrative-summary --> block
Replace this div:
<div id="narrative-summary" class="empty-hint">No summary yet — ...</div>
with:
<div id="narrative-summary">这里写 ≤3 句话的会话叙事摘要</div>
Summary requirements:Factual. Reference real turn content, real decisions, real tool patterns—no boilerplate.
5c. Fill in the <!-- AGENT: anomalies --> block
Replace the placeholder hints inside <div class="takes" id="anomalies">...</div> with 3–5 take cards. Express values as "% of total tokens" where possible. Exact markup:
<div class="take bad"><div class="fig">62%</div><div class="txt">Turn <b>#4</b> 一个 prompt 消耗了 62% 的总 token</div></div>
class meanings:
.take.bad— waste / red.take.good— healthy signal / green.take.warn— warning / yellow.take.info— neutral fact / blue
.fig is a short number (%, count, or 12× multiple)..txt is a plain-language sentence, with the subject wrapped in <b> .
Angles worth exploring:
- A single turn takes a disproportionate share
- Cache hit < 85% (Claude) or reasoning accounts for > 50% of output (Codex)
- Repeatedly reading the same file
- Sub-agent calls have no output format constraints
- Context window near its limit (Codex)
5d. Fill in the <script id="candidates"> array (the core value of this skill)
Replace that script tag's [] with an array of candidate entries. Each candidate belongs to one of the following three categories—only these three:
existing-skill— a ready-made ecosystem skill hit by the step 4 pre-check, installable with onenpx skills addcommandagent-md— write into the AGENTS.md system (root AGENTS.md /docs/rules/<topic>.md+ index / subdirectory AGENTS.md, choose one of the three targets)skill-gap— multi-step repeatable pattern, no ready-made reusable one in the ecosystem, worth abstracting into a new skill
Schema:
[
{
"name": "kebab-case-name",
"type": "existing-skill | agent-md | skill-gap",
"body": "条目正文 markdown——直接落库的文本(或对 existing-skill 来说,含安装命令)",
"default_selected": true
}
]
type semantics
-
existing-skill— a ready-made ecosystem skill found during the pre-check in step 4, with verdictrecommend-install. Body template:**来源**: <owner/repo@skill> · <NK installs> · <skills.sh URL> **解决的本会话问题**: <为什么这个 skill 适合本会话出现的某个模式> **安装命令**: `npx -y skills add <owner/repo@skill> -a codex claude-code -y`Install command breakdown:
npx -y—-ycomes first, letting npx auto-upgrade / pull the package and skip the "need to install? (y/N)" prompt-a codex claude-code—skills add's-acollects all subsequent non-flag arguments, sospace-separatedmultiple agents. This repo targets only these two agents- at the end
-y—skills add's own "yes to confirmation prompts" After checking it, it enters the synthesized prompt, and the downstream agent will automatically execute thenpx skills addinstall
-
agent-md— persisted into the AGENTS.md system.Choose the target based on the scope of the experience, and declare the target on the first line of the body so the downstream agent can land it directly:Experience scope target When to choose Across the entire repo / across languages / workflow-level Root AGENTS.md(ifCLAUDE.mdis a symlink, write it in only one place)Short rules that all future sessions should see Across the entire repo butlong content(>10 lines / has tables / has example code) docs/rules/<topic>.mdCreate a new one, then in the rootAGENTS.mdadd an index lineWriting it in the root AGENTS.md would bloat that file Only for a specific independent subdirectory (one plugin, one package, one service) That subdirectory's AGENTS.md(create it if it does not exist)The experience only takes effect in that subdirectory's context; writing it to the root AGENTS.md would pollute the global scope Body template:
**target**: <path>(如 `AGENTS.md` / `docs/rules/<topic>.md` + 索引到根 AGENTS.md / `<subdir>/AGENTS.md`) **要写入的内容**: > <逐字给出要追加 / 修改的段落,下游 agent 复制粘贴即可> **索引行**(仅当 target 是 `docs/rules/<topic>.md` 时填): > - [<title>](docs/rules/<topic>.md) — <一句 hook> -
skill-gap— in this session there appeared amulti-step repeatable pattern, worth abstracting into a new skill. The body must contain: trigger phrase / find-skills check result / imperative 3–5 step process / bundled resources / verification method (see template below)
Decision table
| Experience form | Persistence path |
|---|---|
| The ecosystem already has a matching skill | existing-skill(cheapest) |
| Cross-session workflow constraints / process rules (short text) | agent-md → root AGENTS.md |
| Cross-session long-form conventions (>10 lines / tables / code) | agent-md → docs/rules/<topic>.md + root AGENTS.md index |
| Conventions that affect only one subdirectory | agent-md → <subdir>/AGENTS.md |
| Multi-step, repeatable, has trigger conditions, needs script assistance | skill-gap(new skill) |
| One-off / upstream tool issue / outside the user's control | Do not write a candidate |
skill-gap A candidate must prove for itself that it is "worth making into a skill"
Refer to the skill-development skill's judgment framework; only write it if it satisfies all 5 of the following hard gates skill-gap:
- Multi-step: can be broken into ≥3 steps, with order / dependencies between steps
- Repeatable: likely to happen again in future sessions (one-off items are not written as candidates)
- Can write third person + specific trigger phrases—this is the hard spec for the skill description, and also an implicit sanity check: if you can't write "This skill should be used when the user asks to \"X\", \"Y\"", it means the trigger scenarios are unclear and the skill isn't worth making
- Bindable resources: requires
scripts/(deterministic scripts) /references/(documents loaded on demand) /assets/(output templates). Pure text rules go inagent-mdwhich is lighter - No existing skill in the ecosystem can be reused—checked with
npx skills find <query>before producing the candidate (this is the core action of thefind-skillsskill)
Counterexamples (these should not be written as skill-gap):
- "User prefers to write the spec before implementing" → single rule → write it as
agent-md(one line in the root AGENTS.md) - "In the lingolens project, question-type components are named *Question not *Player" → subdirectory-scoped convention →
agent-md(<subdir>/AGENTS.md)
Positive examples (these can be made into skills):
- "Before e2e verification, first check the dev server / database / dependency state machine" → multi-step checklist + bindable script →
skill-gap - "When adding a new question-type component: first create
*Question.tsx, then add unit tests, then register with the router, then write the mobile preview" → fixed 4-step process →skill-gap
skill-gap body template (the agent fills this in when writing a candidate, covering all 5 hard gates):
**触发短语**(第三人称 + 具体短语,自证 gate #3):
> This skill should be used when the user asks to "<具体短语 1>", "<具体短语 2>", or mentions <场景>.
**find-skills 检查**(自证 gate #5,用 `npx skills find <query>`):
- 已搜:<keyword>
- 结果:无现成可复用 / 找到 `<owner/repo@skill>` 但 <理由不合适>
**3–5 步流程**(imperative,verb-first;遵循 skill-development 写作规范):
1. Verb …
2. Verb …
3. Verb …
**Bundled resources**(自证 gate #4):
- `scripts/<name>.sh` — 做 <X>
- `references/<name>.md` — 提供 <Y> 的细节
- `assets/<name>/` — <Z> 模板(可选)
**验证**:跑 `<command>` 应该看到 <expected>;失败时检查 <fallback>。
**为什么不是 `agent-md`**:本条满足 5 条 hard gate(多步骤 + 可重复 + 触发清晰 + 资源可绑定 + ecosystem 没现成的);写成 AGENTS.md 段落无法承载脚本和多步骤逻辑。
After the downstream agent receives this kind of skill-gap candidate, it should follow the complete skill-creator process (capture-intent → interview → draft → eval → iterate),and not write SKILL.md directly on the spot——skill-creator will ensure that skill-development standards such as description trigger rate, imperative style, and progressive disclosure (SKILL.md ≤2k words, details split into references/) are all followed.
Raw material
From the JSON's raw_for_compound: feedback moments, repeated file reads, sub-agent calls, turn timeline.narrative.feedback_moments user feedback snippets (≤200 character summaries) is usually the starting point for agent-md candidates—if a piece of feedback appears repeatedly, it is a workflow-level rule.human_turns Repeated tool combinations + failure modes are clues for skill-gap .
Quality standards
Better fewer than too many.3–8 high-value candidates beats 20 mediocre candidates. Don't write the obvious ones—keep only whatsome future sessionwill actually use.skill-gap has the highest standard; producing 0–2 per run is enough.
Step 6: Report the output path
Report the saved absolute path to the user.Do notopen it,do notpre-render. The user opens it themselves, checks the Compound tab, edits the wording inline, clicks Copy, and pastes the generated prompt back into Claude / Codex—that one sentence completes the persistence.
Notes
- Write
skill-gap/agent-mdbeforefind-skillsIt is strongly recommended to first run an ecosystem search withnpx skills find <query>, candidate gate #5); when creating a new skill, write it according to the skill-development spec (third-person description, imperative body, progressive disclosure: SKILL.md ≤2k words + references/ + scripts/ + assets/, validation checklist),skill-gapeach field of the body template corresponds to a requirement of that spec - The template JS only reads two script blocks:
<script id="report-data">(analyzer output) and<script id="candidates">(the candidate you wrote). All other rendering is driven by these two blobs.Do not change the HTML structure。 - The Compound tab is the core value that distinguishes this skill from an ordinary session report—it turns "AI extracts candidates → human reviews → lands in AGENTS.md / installs skill / creates new skill" into a frictionless closed loop.
- Codex has native sub-agents (
spawn_agent/wait_agent/close_agenttool calls), and the analyzer aggregatesagent_typeintohealth.subagents. Codex has no concept of skills, so the template hides the corresponding table. - Codex's
healthsection additionally contains:compaction_count(number of automatic compactions),patch_apply.{success, failure}(code modification success/failure ratio),mcp_tool_call_count/custom_tool_call_count/web_search_count/tool_search_count/image_generation_countand other specialized tool counts, as well ascontext_window(model window size). - If
raw_for_compoundis very sparse (short session, no feedback moments), prefer producing 1–3 high-qualityskill-gaprather than forcing 5. - If the JSON exceeds 2MB, truncate
narrative.human_turnsandhealth.expensive_turnsto the first 50 entries before embedding (the analyzer usually already controls this, but check).