/ce-compound
Coordinate multiple subagents working in parallel to document a recently solved problem.
Purpose
Captures problem solutions while context is fresh, creating structured documentation in <root>/solutions/ with YAML frontmatter for searchability and future reference. Uses parallel subagents.
Why "compound"? Each documented solution compounds your team's knowledge. The first time you solve a problem takes research. Document it, and the next occurrence takes minutes. Knowledge compounds.
Usage
/ce-compound # Document the most recent fix
/ce-compound [brief context] # Provide additional context hint
/ce-compound mode:headless # Non-interactive run for automations
/ce-compound mode:headless [context] # Non-interactive run with context hint
/ce-compound mode:headless depth:lightweight [context] # Lower-overhead non-interactive run
/ce-compound mode:headless depth:full [context] # Full non-interactive run
One learning per run. The workflow's grounding, overlap detection, and cross-referencing all assume a single solved problem. When a session produced multiple distinct learnings, run the skill once per learning, sequentially — each run grounds fresh against the tree. Do not batch several learnings through one run and stitch cross-references between the drafts afterward; drafting-context numbering ("Learning 3") leaking into written docs is the failure this rule prevents.
CONCEPTS.md bootstrap requests
If invoked specifically to create or bootstrap CONCEPTS.md from scratch rather than to document a solved problem, do not run the normal phases — ce-compound populates CONCEPTS.md only as a side effect of documenting a real learning (it seeds the learning's area, not the whole repo; see Phase 2.4). Repo-wide concept-map creation is ce-compound-refresh's job. Redirect a standalone bootstrap request to ce-compound-refresh (which asks whether to build the concept map or run a refresh cycle), then exit.
Mode Detection
Enter headless mode when either holds: the arguments you were invoked with contain the mode:headless token, or the invocation makes non-interactive intent unmistakable — a caller or standing instruction asking to run ce-compound "headless", "non-interactively", "unattended", or "without prompts/questions". The token is the explicit form; a clear natural-language request for a non-interactive run is equivalent. Bare "automatically" or "auto-run" is not on its own a headless signal — it speaks to invoking the skill, not to suppressing its prompts — so an ambiguous or absent signal defaults to interactive. Tokens starting with mode: or depth: are flags, not context — strip them before treating the remainder as the brief context hint.
Depth is an explicit headless-only selector. In headless mode, accept at most one depth token: depth:lightweight routes directly to Lightweight Mode, while depth:full routes to Full Mode with its automatic session-history probe. mode:headless without a depth: token remains backward compatible and runs Full Mode. Headless lightweight asks no blocking questions and launches no subagents. If the invocation contains an unknown depth: token, multiple depth: tokens, or a depth: token without headless intent, do not guess; emit the headless failure report with the reason and end with Documentation skipped.
| Mode | When | Behavior |
|---|---|---|
| Interactive (default) | No headless token or clear non-interactive intent | Auto-pick Full vs Lightweight and report the choice; run session history as an automatic probe (Full only); prompt for Discoverability Check consent; end with a plain summary (no "What's next?" menu) |
| Headless | mode:headless token present, or the invocation makes non-interactive intent unmistakable |
No blocking questions. Run the explicitly requested depth, defaulting to Full mode with the automatic session-history probe. If the Discoverability Check finds a gap, report it without editing instruction files. Skip Phase 3 specialized reviews. End with a structured terminal report — no "What's next?" menu. |
Headless mode is intended for automations and skill-to-skill invocation where no human is present to answer questions. Once detected, headless mode applies for the entire run.
Session context
Resolve two values at runtime with the shell tool before Phase 1 session-history filtering. Run each as its own command and read its exit status — a non-zero exit is a normal state here, not an error to route around:
- Git branch — run
git rev-parse --abbrev-ref HEAD. Use the branch name to filter session history in Phase 1. If it returnsHEAD(detached) or exits non-zero (not a git repo), skip branch filtering. - Repo root — run
git rev-parse --show-toplevel. Use it as the session-history repo filter in Phase 1. If it exits non-zero (not a git repo), fall back to the working directory.
Support Files
These files are the durable contract for the workflow. Read them on-demand at the step that needs them — do not bulk-load at skill start.
references/schema.yaml— canonical frontmatter fields and enum values (read when validating YAML)references/yaml-schema.md— category mapping from problem_type to directory (read when classifying)references/concepts-vocabulary.md— CONCEPTS.md format and inclusion rules (read in Phase 2.4 when domain terms surface)references/agents/session-historian.md— skill-local synthesis prompt for optional session-history compounding context (read only when the user opts into session history)references/grounding-validation.md— grounding-validation protocol: flag adjudication rules and the semantic validator prompt (read in Phase 2.45)assets/resolution-template.md— section structure for new docs (read when assembling)scripts/session-history/— session discovery and extraction scripts bundled into this skill so session-history support is fully self-containedscripts/validate-frontmatter.py— frontmatter parser-safety validator (run in Phase 2 step 8 through the existence guard documented there; setSKILL_DIRto this skill's directory, with a manual-checklist fallback if the script is missing)scripts/validate-doc-claims.py— mechanical claims validator: cited paths, commit SHAs, relative links, dangling drafting scaffold (run in Phase 2.45 via theSKILL_DIRanchor)
When spawning subagents, pass the relevant file contents into the task prompt so they have the contract without needing cross-skill paths.
Artifact Root
This skill writes and reads learnings under <root>/solutions/. Resolve <root> when you first compose a <root>/solutions/ path (per the block below); when passing search or write scope to a subagent, pass the resolved <root>/solutions/ path, not the config.
Resolve the CE artifact root <root> before composing any artifact path.
- Read
docs_rootfrom<repo-root>/.compound-engineering/config.local.yaml, thenconfig.yaml; first non-empty value wins (<repo-root>=git rev-parse --show-toplevel). Unset -><root>isdocs, exactly as before. - Validate a set value: a repo-relative directory whose real, symlink-resolved path stays inside the repo and is neither the repo root nor under
.git/. Otherwise stop with an error namingdocs_rootand the value -- never fall back todocs. - Use
<root>as the sole artifact location: create it if absent, compose each path as<root>/<subdir>with this skill's own subdirectory, and never also readdocs.
Execution Strategy
ce-compound does not ask the user which mode to run or whether to search session history. Both are decisions the agent is better positioned to make: mode depends on context budget the agent can observe, and session-history value is unknowable a priori to either party (the payoff is an unrelated earlier session the current agent was never in), so it is resolved by a cheap probe rather than a question. The only interactive prompt in the whole workflow is the Discoverability Check consent, because that one edits a tracked instruction file.
Mode selection (Full vs Lightweight) — decide it, don't ask it.
- Default to Full: the complete workflow (research, cross-referencing, overlap detection, grounding validation). This is the right choice for essentially every documented learning — its token cost is small next to the engineering work that produced the learning and is dwarfed by the value of a doc that compounds.
- Choose Lightweight (single-pass, no subagents — see Lightweight Mode) ONLY under real context pressure: the session is near its context limit, or the fix is trivial enough that cross-referencing would add nothing. These are conditions the agent can observe and the user cannot, which is exactly why this is not a question.
- State the chosen mode and a one-line reason as the first line of the completion output (e.g., "Ran Full mode." / "Ran Lightweight mode — session context was tight."). If Lightweight was the wrong call for the user's taste, re-running is a rare, cheap correction — cheaper than taxing every run with a prompt.
In headless mode, skip automatic mode selection. Run the depth selected during Mode Detection: depth:lightweight enters Lightweight Mode; depth:full or no depth token enters Full Mode, including the automatic session-history probe (Phase 1 step 4).
Session history — an automatic probe in Full mode, never a question. The point of searching prior sessions is that an unrelated earlier session may hold related problem-solving; neither the agent nor the user can know that a priori, so asking is pointless. Instead, Full mode always runs the cheap discovery+metadata probe (Phase 1 step 4) — it runs in parallel with the research subagents, so it is near-free on wall-clock — and escalates to the expensive extraction+synthesis only when the probe surfaces genuinely relevant candidate sessions. Lightweight mode skips session history entirely; headless Full runs the same automatic probe, since it prompts for nothing and so keeps headless non-interactive. This support exists only inside the compounding workflow; there is no standalone session-history product surface.
Full Mode
Phase 1 subagents write their full structured output to a per-run scratch artifact under <run-dir>/ and return only a compact confirmation containing the artifact path. The orchestrator Reads those artifacts back in Phase 2 assembly. This is scratch space, identical in spirit to ce-code-review's per-reviewer run artifacts; it does not make the scratch files additional deliverables. Only the orchestrator writes product files — the final solution doc and the maintenance side effects below. Subagents must not touch docs/, project instruction files, or any tracked path. Beyond the Phase 2 solution doc, the orchestrator may also write maintenance side effects — not additional deliverables, and creating one when absent is expected, not a violation of this rule. There are three write-target classes; only the first two are unconditional:
<root>/solutions/...— the primary deliverable (always).CONCEPTS.md— create or update in Phase 2.4 (Vocabulary Capture) when a qualifying domain term surfaces (Full mode; every mode that reaches vocabulary capture may refine an existing file).- A project instruction file (AGENTS.md or CLAUDE.md) — a small edit when the Discoverability Check finds a gap, only in interactive Full mode after consent. Headless and lightweight never apply this edit (they report or tip instead). Callers that hand off headless after an approval gate must not see an unreviewed change to the repo's operating contract.
CONCEPTS.md and the instruction-file edit ensure future agents can discover and ground in the knowledge store; neither makes the documentation any less the single deliverable.
Why the scratch artifact (issue #956): a subagent asked to return a long prose body as its inline response intermittently returns an executive summary instead ("Doc body complete — six sections filled. Returning above."), and the original prose is then unrecoverable from the orchestrator side. Writing to disk first means the full output always survives; the inline confirmation is just a pointer, and the orchestrator falls back to whatever the subagent did return inline only when the artifact is missing.
Phase 0.5: Auto Memory Scan
Before launching Phase 1 subagents, check the auto-memory block injected into your system prompt for notes relevant to the problem being documented.
- Look for a block labeled "user's auto-memory" (Claude Code only) already present in your system prompt context — MEMORY.md's entries are inlined there
- If the block is absent, empty, or this is a non-Claude-Code platform, skip this step and proceed to Phase 1 unchanged
- Scan the entries for anything related to the problem being documented -- use semantic judgment, not keyword matching
- If relevant entries are found, prepare a labeled excerpt block:
## Supplementary notes from auto memory
Treat as additional context, not primary evidence. Conversation history
and codebase findings take priority over these notes.
[relevant entries here]
- Pass this block as additional context to the Context Analyzer and Solution Extractor task prompts in Phase 1. If any memory notes end up in the final documentation (e.g., as part of the investigation steps or root cause analysis), tag them with "(auto memory [claude])" so their origin is clear to future readers.
If no relevant entries are found, proceed to Phase 1 without passing memory context.
Phase 1: Research
Launch research subagents. Each writes its full output to a per-run scratch artifact and returns only the artifact path to the orchestrator.
Run ID and run dir (before dispatching any subagent): generate a unique run identifier and create the run directory. This scopes every Phase 1 artifact file to the same directory so the orchestrator can Read them back in Phase 2.
SCRATCH_ROOT="/tmp/compound-engineering-$(id -u)";
if [ -L "$SCRATCH_ROOT" ]; then echo "unsafe scratch root symlink: $SCRATCH_ROOT" >&2; exit 1; fi;
install -d -m 700 "$SCRATCH_ROOT" || exit 1;
if [ -L "$SCRATCH_ROOT" ] || [ ! -O "$SCRATCH_ROOT" ]; then echo "scratch root is not owned by the current user: $SCRATCH_ROOT" >&2; exit 1; fi;
chmod 700 "$SCRATCH_ROOT" || exit 1;
RUN_ID=$(date +%Y%m%d-%H%M%S)-$(head -c4 /dev/urandom | od -An -tx1 | tr -d ' ');
RUN_DIR="$SCRATCH_ROOT/ce-compound/$RUN_ID";
(umask 077; mkdir -p "$RUN_DIR") || exit 1; chmod 700 "$RUN_DIR" || exit 1;
echo "$RUN_DIR";
Resolve current vocabulary and conventions before dispatching subagents. Use the project's active instructions and conventions already in your context. If CONCEPTS.md exists, read its relevant terms and pass them to the Context Analyzer.
CRITICAL — glob <root>/solutions/ fresh every run. The current vocabulary and conventions above do not substitute for the live-tree search in step 3.
Pass {run_id} and the resolved absolute {run_dir} into every Phase 1 subagent prompt. Each subagent writes its full structured output to its own file under {run_dir}/, confirms the write succeeded (the file exists and is non-empty), and then returns only a one-line confirmation containing the artifact path — not the prose body inline. Artifact filenames by subagent:
- Context Analyzer →
{run_dir}/context.json(frontmatter skeleton, category path, filename, track) - Solution Extractor →
{run_dir}/solution.md(the full doc-body prose sections) - Related Docs Finder →
{run_dir}/related.json(links, refresh candidates, overlap assessment) - Session History synthesis subagent (when run) →
{run_dir}/session-history.md(prose findings)
Return the full output inline whenever the artifact write did not succeed. This covers both cases where the orchestrator's Phase 2 inline fallback would otherwise have nothing to read: (a) {run_id} is empty or did not resolve (non-Claude-Code platforms where the pre-resolution failed), so there is no path to write to; and (b) {run_id} resolved but the write itself failed — tool permission denied, absolute-path writes unavailable, disk error, or the post-write existence check came back empty. In either case the subagent must return its complete structured output inline instead of a path, because the path would point at a file that does not exist. Return only the bare path when — and only when — the write is confirmed on disk. The artifact pattern is a reliability improvement, not a hard requirement; the orchestrator handles a missing artifact in Phase 2 by using the inline return.
Dispatch order:
- Launch
Context Analyzer,Solution Extractor, andRelated Docs Finderin parallel (background) - Then run the internal session-history discovery/extraction/synthesis flow (see step 4 below) in Full mode, including headless — skipped only in lightweight. Its cheap discovery+metadata probe always runs; it escalates to extraction+synthesis only on a relevance hit (see step 4's Escalation gate). This flow is synchronous from this orchestrator's main-context turn, but the already-dispatched background subagents continue running in parallel underneath, so the wall-clock benefit is preserved (
max(session-history, slowest background subagent), not their sum). Running session history before the parallel block would serialize it in front of the research subagents and regress wall-clock time.
1. Context Analyzer
- Extracts conversation history
- Reads
references/schema.yamlfor enum validation and track classification - Determines the track (bug or knowledge) from the problem_type
- Identifies problem type, component, and track-appropriate fields:
- Bug track: symptoms, root_cause, resolution_type
- Knowledge track: applies_when (symptoms/root_cause/resolution_type optional)
- Incorporates auto memory excerpts (if provided by the orchestrator) as supplementary evidence
- Reads
references/yaml-schema.mdfor category mapping into<root>/solutions/ - Suggests a filename using the pattern
[sanitized-problem-slug].md— no date suffix, even if existing files in the target directory have one; thedate:frontmatter field is the canonical creation date - Writes to
context.json: YAML frontmatter skeleton (must includecategory:field mapped from problem_type), category directory path, suggested filename, and which track applies. Returns only the artifact path. - Does not invent enum values, categories, or frontmatter fields from memory; reads the schema and mapping files above
- Does not force bug-track fields onto knowledge-track learnings or vice versa
2. Solution Extractor
- Reads
references/schema.yamlfor track classification (bug vs knowledge) - Adapts output structure based on the problem_type track
- Writes the full doc-body prose (all track-appropriate sections below) to
solution.mdand returns only the artifact path. This is the subagent most prone to the issue #956 summary-collapse, so its prose must land on disk rather than only in the inline return. - Incorporates auto memory excerpts (if provided by the orchestrator) as supplementary evidence -- conversation history and the verified fix take priority; if memory notes contradict the conversation, note the contradiction as cautionary context
- Grounds code-behavior claims in source, not conversation memory. Before asserting how code behaves (enum values, status semantics, limits, defaults), Read the defining line at the current tree and cite
file:linealongside the claim. A claim that cannot be verified against the tree is softened or attributed ("per this session's conclusion…"), never stated as fact - Writes merge-state claims for time. Cite PR numbers rather than bare commit SHAs — SHAs are rewritten by rebase/squash merges and may not exist on other checkouts. A "fixed in X" claim requires the fix to be reachable from the current tree; otherwise phrase it as pending ("fix opened in #1608, unmerged as of this writing")
Bug track output sections:
- Problem: 1-2 sentence description of the issue
- Symptoms: Observable symptoms (error messages, behavior)
- What Didn't Work: Failed investigation attempts and why they failed
- Solution: The actual fix with code examples (before/after when applicable)
- Why This Works: Root cause explanation and why the solution addresses it
- Prevention: Strategies to avoid recurrence, best practices, and test cases. Include concrete code examples where applicable (e.g., gem configurations, test assertions, linting rules)
Knowledge track output sections:
- Context: What situation, gap, or friction prompted this guidance
- Guidance: The practice, pattern, or recommendation with code examples when useful
- Why This Matters: Rationale and impact of following or not following this guidance
- When to Apply: Conditions or situations where this applies
- Examples: Concrete before/after or usage examples showing the practice in action
3. Related Docs Finder
- Searches
<root>/solutions/for related documentation - Identifies cross-references and links
- Finds related GitHub issues
- Flags any related learning or pattern docs that may now be stale, contradicted, or overly broad
- Assesses overlap with the new doc being created across five dimensions: problem statement, root cause, solution approach, referenced files, and prevention rules. Score as:
- High: 4-5 dimensions match — essentially the same problem solved again
- Moderate: 2-3 dimensions match — same area but different angle or solution
- Low: 0-1 dimensions match — related but distinct
- Writes to
related.json: Links, relationships, refresh candidates, and overlap assessment (score + which dimensions matched). Returns only the artifact path.
Search strategy (grep-first filtering for efficiency):
- Extract keywords from the problem context: module names, technical terms, error messages, component types
- If the problem category is clear, narrow search to the matching
<root>/solutions/<category>/directory - Use the native content-search tool (e.g., Grep in Claude Code) to pre-filter candidate files BEFORE reading any content. Run multiple searches in parallel, case-insensitive, targeting frontmatter fields. These are template patterns -- substitute actual keywords:
title:.*<keyword>tags:.*(<keyword1>|<keyword2>)module:.*<module name>component:.*<component>
- If search returns >25 candidates, re-run with more specific patterns. If <3, broaden to full content search
- Read only frontmatter (first 30 lines) of candidate files to score relevance
- Fully read only strong/moderate matches
- Return distilled links and relationships, not raw file contents
GitHub issue search:
Prefer the gh CLI for searching related issues: gh issue list --search "<keywords>" --state all --limit 5. If gh is not installed, fall back to the GitHub MCP tools (e.g., unblocked data_retrieval) if available. If neither is available, skip GitHub issue search and note it was skipped in the output.
4. Session History (internal flow after launching the parallel block — automatic in Full mode, including headless)
- Skip entirely in lightweight mode. In Full mode (including headless) it always runs as a two-stage probe: the cheap discovery+metadata pass (below) always executes, and the expensive extraction+synthesis executes only when the probe clears the relevance gate (see Escalation gate below).
- Run session discovery, branch/keyword filtering, scan-window selection, deep-dive selection, and per-session extraction directly inside this skill using
scripts/session-history/. - Read the skill-local synthesis prompt at
references/agents/session-historian.md, then dispatch a generic subagent using that prompt content. Do not dispatch a standalone agent by type/name.
Session-history payload — keep tight. A long, keyword-rich payload licenses widening. Use this shape:
Session context (only if the values resolved cleanly above; otherwise omit): repo name, current git branch.
Time window: explicit
7 daysunless the documented problem clearly spans a longer arc.Problem topic: one sentence naming the concrete issue — error message, module name, what broke and how it was fixed. Not a paragraph; not a bullet list of related topics.
Filter rule (one line): "Only surface findings directly relevant to this specific problem. Ignore unrelated work from the same sessions or branches."
Output schema:
Structure your response with these sections (omit any with no findings): - What was tried before - What didn't work - Key decisions - Related context
Do not append additional context blocks, exclusion lists, or topic-keyword bullets — verbose payloads give the session-history flow license to keep widening the search and rapidly compound wall time. If keyword search is needed, the internal flow owns that decision based on the topic.
- Returns: structured digest of findings from prior sessions, or "no relevant prior sessions" if none found.
- Session history is the final Phase 1 input, not a workflow stop. When it returns, proceed directly to Phase 2 with its output as the last input — do not emit a summary and do not pause for the user. A "no relevant prior sessions" return is still a valid input; the documentation gets written without session context.
Script resolution. Set SKILL_DIR to the absolute path of the directory containing the SKILL.md you just read, and run the bundled scripts from "$SKILL_DIR/scripts/session-history/". Set SKILL_DIR inline in each bash block below (shell state does not persist between commands). If the bundled scripts are genuinely not present on disk under "$SKILL_DIR/scripts/session-history/", skip session history visibly with: "Session history bundled scripts were not found in this skill's directory; skipping the session-history probe for this run." Continue Phase 2 without session context.
Discovery pipeline. Infer the scan window from the problem topic, starting with 7 days. Run discovery and metadata extraction:
SKILL_DIR="<absolute path of the directory containing the SKILL.md you just read>";
if [ -f "$SKILL_DIR/scripts/session-history/discover-sessions.sh" ] && [ -f "$SKILL_DIR/scripts/session-history/extract-metadata.py" ]; then
PY="$(for c in python3 python py; do command -v "$c" >/dev/null 2>&1 && "$c" -c '' >/dev/null 2>&1 && { echo "$c"; break; }; done)"; [ -n "$PY" ] || { echo "no working Python 3 interpreter on PATH" >&2; exit 1; };
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd); REPO_NAME=$(basename "$REPO_ROOT"); SCAN_DAYS="7"; bash "$SKILL_DIR/scripts/session-history/discover-sessions.sh" "$REPO_NAME" "$SCAN_DAYS" --cwd "$REPO_ROOT" | tr '\n' '\0' | xargs -0 "$PY" "$SKILL_DIR/scripts/session-history/extract-metadata.py" --cwd-filter "$REPO_ROOT";
else echo "Session history bundled scripts were not found in this skill's directory; skipping the session-history probe for this run."; fi
Pi sessions are included when present under ~/.pi/agent/sessions/; they carry cwd like Codex but no git branch. If _meta.files_processed is 0, return no relevant prior sessions. If the first pass finds no relevant branch matches, or if processing Codex or Pi sessions, derive 2-4 keywords from the topic and re-run metadata extraction with --keyword K1,K2,.... Keep at most 5 sessions across Claude Code, Codex, Cursor, and Pi, ranked by branch match, keyword match count, file size over 30KB, and recency. Exclude the current session.
Escalation gate. The discovery+metadata pass above is the cheap probe and always runs in Full mode. Escalate to the extraction and synthesis stages below only when at least one retained candidate clears the relevance bar: a current-branch match, or ≥2 topic-keyword matches. If no candidate clears the bar (including the _meta.files_processed is 0 case), stop here, record no relevant prior sessions as the session-history input, and skip extraction and synthesis. This gate is what keeps the always-on probe cheap — the expensive synthesis is paid for only when a prior session is genuinely relevant.
Extraction pipeline. Create SCRATCH=$(mktemp -d -t ce-compound-sessions-XXXXXX). For each selected session, write extracted content to scratch files:
SKILL_DIR="<absolute path of the directory containing the SKILL.md you just read>";
if [ -f "$SKILL_DIR/scripts/session-history/extract-skeleton.py" ]; then
PY="$(for c in python3 python py; do command -v "$c" >/dev/null 2>&1 && "$c" -c '' >/dev/null 2>&1 && { echo "$c"; break; }; done)"; [ -n "$PY" ] || { echo "no working Python 3 interpreter on PATH" >&2; exit 1; };
"$PY" "$SKILL_DIR/scripts/session-history/extract-skeleton.py" --output "$SCRATCH/<session-id>.skeleton.txt" < <session-file>;
else echo "Session history bundled scripts were not found in this skill's directory; skipping the session-history probe for this run."; fi
Use extract-errors.py selectively when dead ends or recurring errors are likely useful. Pass only the scratch file paths and metadata to the synthesis subagent.
Synthesis dispatch. Build a generic subagent prompt containing:
- the full content of
references/agents/session-historian.md problem_topicscratch_dir- a
sessionsarray with extracted file paths and metadata - the output schema above
- the filter rule above
The subagent reads only the scratch paths, writes its prose findings to {run_dir}/session-history.md, and returns only that artifact path once the write is confirmed (same #956 reliability rationale — session-history findings are long-form prose prone to summary-collapse). If {run_id} did not resolve or the artifact write failed, it returns the prose inline instead (per the inline-fallback rule above). If synthesis fails, note the failure and continue without session context.
Phase 2: Assembly & Write
WAIT for all Phase 1 inputs to complete before proceeding — the three parallel subagents and, in Full mode, the internal session-history flow (which may have stopped at the probe with no relevant prior sessions). Session history is a Phase 1 input even though it runs in the orchestrator rather than as a public skill.
The orchestrating agent (main conversation) performs these steps:
Collect Phase 1 results from the run artifacts. For each Phase 1 subagent,
Readits artifact file under{run_dir}/(context.json,solution.md,related.json, andsession-history.mdwhen session history ran). The artifact holds the subagent's full output. Fall back to the subagent's inline return only when its artifact file is absent or empty (e.g.,{run_id}did not resolve, or the subagent failed to write). The artifact is authoritative when present — this is what makes the workflow resilient to the issue #956 summary-collapse, where the inline return is only an executive summary.Check the overlap assessment from the Related Docs Finder before deciding what to write:
Overlap Action High — existing doc covers the same problem, root cause, and solution Update the existing doc with fresher context (new code examples, updated references, additional prevention tips) rather than creating a duplicate. The existing doc's path and structure stay the same. Moderate — same problem area but different angle, root cause, or solution Create the new doc normally. Flag the overlap for Phase 2.5 to recommend consolidation review. Low or none Create the new doc normally. The reason to update rather than create: two docs describing the same problem and solution will inevitably drift apart. The newer context is fresher and more trustworthy, so fold it into the existing doc rather than creating a second one that immediately needs consolidation.
When updating an existing doc, preserve its file path and frontmatter structure. Update the solution, code examples, prevention tips, and any stale references. Add a
last_updated: YYYY-MM-DDfield to the frontmatter. Do not change the title unless the problem framing has materially shifted.Incorporate session history findings (if available). When the internal session-history flow returned relevant prior-session context:
- Fold investigation dead ends and failed approaches into the What Didn't Work section (bug track) or Context section (knowledge track)
- Use cross-session patterns to enrich the Prevention or Why This Matters sections
- Tag session-sourced content with "(session history)" so its origin is clear to future readers
- If findings are thin or "no relevant prior sessions," proceed without session context
Assemble complete markdown file from the collected pieces, reading
assets/resolution-template.mdfor the section structure of new docsValidate YAML frontmatter against
references/schema.yaml, including the YAML-safety quoting rule for array items (seereferences/yaml-schema.md> YAML Safety Rules)Create directory if needed:
mkdir -p <root>/solutions/[category]/Write the file: either the updated existing doc or the new
<root>/solutions/[category]/[filename].mdValidate parser-safety of the written frontmatter to catch silent-corruption issues the prose rules miss: malformed
---delimiter lines, unquoted#in scalar values (silent comment truncation), and unquoted:in scalar values (silent mapping confusion). The bundled validator ships inside the skill bundle; setSKILL_DIRto the absolute path of the directory containing this SKILL.md and run it through an existence guard so platforms that cannot locate the script fall back to a manual check instead of silently skipping the protection:SKILL_DIR="<absolute path of the directory containing the SKILL.md you just read>"; if [ -f "$SKILL_DIR/scripts/validate-frontmatter.py" ]; then PY="$(for c in python3 python py; do command -v "$c" >/dev/null 2>&1 && "$c" -c '' >/dev/null 2>&1 && { echo "$c"; break; }; done)"; [ -n "$PY" ] || { echo "no working Python 3 interpreter on PATH" >&2; exit 1; }; "$PY" "$SKILL_DIR/scripts/validate-frontmatter.py" <output-path>; else echo "Bundled validate-frontmatter.py not resolvable on this platform; applying the parser-safety checklist manually."; fi- If the script ran: exit 0 means parser-safe; exit 1 means stderr names the offending field(s) — quote the value(s), re-write the doc, and re-run until exit 0. Do not declare success while validation fails.
- If the script did not run (else branch): apply the validator's checks by hand, matching its exact scope — checking more broadly risks edits the validator would not require. Fix any violation by quoting the whole value before continuing:
- The opening and closing frontmatter delimiters are each a line whose content is
---(trailing whitespace is fine;----or---extrais not a valid delimiter). - For each top-level mapping entry (
key: value, no leading indentation) whose value is not already quoted or structured (does not start with",',[,{,|, or>): the value must contain no unquoted#(space-then-hash — YAML treats it as a comment and silently truncates) and no unquoted:(colon-then-space — strict YAML may read it as a nested mapping). Quote the whole value if either appears. Nested values, array items, and already-quoted values are out of scope here (array-item quoting is handled by the schema/YAML-safety step above). Then state in the completion output that the bundled script validator was unavailable on this platform and the checks were applied manually.
- The opening and closing frontmatter delimiters are each a line whose content is
The validator does not enforce schema rules and does not flag YAML reserved-indicator characters (those produce loud parser errors downstream rather than silent corruption — out of scope). Uses Python 3 stdlib only (no PyYAML or other deps).
When creating a new doc, preserve the section order from assets/resolution-template.md unless the user explicitly asks for a different structure.
Phase 2.4: Vocabulary Capture
First, read references/concepts-vocabulary.md. This is unconditional. Do not pre-judge from memory that nothing qualifies — the reference's criteria are non-obvious and qualifying terms often live in the surrounding conversation rather than the new doc itself. Reading the reference is what makes the rest of the phase possible.
Then, applying those criteria, scan the new doc and the surrounding conversation for qualifying domain terms. If CONCEPTS.md exists at repo root, add missing qualifying terms and refine existing entries when new precision surfaced. If it does not exist and at least one qualifying term surfaced, create it.
Verify behavior assertions against source before writing them. When an entry asserts how code behaves (states, transitions, limits, semantics), Read the defining source at the current tree first — an entry drafted from a session-level summary is exactly how wrong semantics enter the glossary. Phase 2.45 re-checks these entries, but the cheap fix is to not write the error.
Seed the learning's area at creation — don't write a lone term. When CONCEPTS.md does not yet exist, alongside the surfaced term also seed the core domain nouns of the area this learning touched, following the Seed goal and Scope of a seed rules in references/concepts-vocabulary.md. The seed is scoped to the learning's area (the modules and domain the fix touched) and defines only terms investigated here — it does not reach for repo-wide nouns. This anchors the surfaced term so it does not dangle against undefined siblings. A repo-wide concept map is ce-compound-refresh's bootstrap path, not this one.
At creation, hold the qualifying bar conservatively for borderline terms. A borderline term, or a class/table/file name dressed up as an entity, defers to a later run — clear core nouns are seeded, borderline ones wait. The conservatism is about quality, not count; updates to an existing file follow the normal criteria.
When bootstrapping the file, start with this preamble under the # Concepts heading, then add the qualifying entries below it:
Shared domain vocabulary for this project — entities, named processes, and status concepts with project-specific meaning. Seeded with core domain vocabulary, then accretes as ce-compound and ce-compound-refresh process learnings; direct edits are fine. Glossary only, not a spec or catch-all.
Refresh the coherence neighborhood of any entry you touch. When adding or editing an entry, also inspect its coherence neighborhood — its cluster siblings and the terms it cross-references or that reference it. Within that neighborhood, do two things: fix glossary violations (implementation specifics — file paths, class names, function signatures, curr
…(truncated)