---
description: Extract insights from plan-associated sessions
argument-hint: "[plan-number]"
---

# /erk:learn

Create a documentation plan from Claude Code sessions associated with a plan implementation. The verb "learn" means: analyze what happened, extract insights, and create an actionable plan to document those learnings.

## Usage

```
/erk:learn                              # Infers plan from current branch
/erk:learn 4655                          # Explicit plan number
```

## Purpose

All documentation produced by this command follows the content quality standards in the `learned-docs` skill. Load that skill for the full rules on audience (AI agents), the cornerstone principle (cross-cutting insight), and what belongs in learned docs vs code comments.

## Agent Instructions

**Prerequisite:** Load the `learned-docs` skill for content quality standards before proceeding.

Tell the user (choose the appropriate pipeline display based on whether `.erk/impl-context/` exists):

**When `.erk/impl-context/` exists (CI/async mode):**

```
Learn pipeline for plan #<plan-number> (using preprocessed materials):
  1. Read preprocessed materials from .erk/impl-context/
  2. Launch analysis agents (session, diff, docs check, PR comments)
  3. Synthesize findings into a documentation plan
  4. Save plan as a draft PR
```

**When no `.erk/impl-context/`:**

```
Learn pipeline for plan #<plan-number>:
  1. Discover and preprocess session logs
  2. Launch analysis agents (session, diff, docs check, PR comments)
  3. Synthesize findings into a documentation plan
  4. Save plan as a draft PR
```

### Step 1: Validate Plan Type

First, check if the plan is a learn plan. **Learn plans cannot generate additional learn plans** - this would create documentation cycles.

```bash
erk exec get-issue-body <plan-number>
```

Parse the JSON output and check the `labels` array. If `erk-learn` is present, stop with this error:

```
Error: Plan #<plan-number> is a learn plan (has erk-learn label).
Cannot learn from a learn plan - this would create documentation cycles.
```

If the plan is NOT a learn plan, proceed to Step 2.

### Step 2: Check for Preprocessed Materials

Check if `.erk/impl-context/` exists and contains files (this is the case when running in CI after the upload-impl-session pipeline committed materials to the learn branch):

```bash
ls .erk/impl-context/ 2>/dev/null
```

**If `.erk/impl-context/` contains files:**

1. Create the learn directory:

   ```bash
   mkdir -p .erk/scratch/sessions/${CLAUDE_SESSION_ID}/learn
   ```

2. Copy files from impl-context to the learn directory:

   ```bash
   cp .erk/impl-context/* .erk/scratch/sessions/${CLAUDE_SESSION_ID}/learn/
   ```

3. Tell the user:

   ```
   Using preprocessed materials from .erk/impl-context/:
     - Copied N file(s) to .erk/scratch/sessions/${CLAUDE_SESSION_ID}/learn/
     - Skipping session discovery and preprocessing
   ```

4. **Skip to Step 4** (Gather and Analyze Sessions). The preprocessed sessions and PR comments are already in the learn directory.

**If `.erk/impl-context/` does not exist or is empty:**

Proceed to Step 3 for standard session discovery and preprocessing.

### Step 3: Get Session Information

Run the exec script to get session details:

```bash
erk exec get-learn-sessions <plan-number>
```

Parse the JSON output to get:

- `session_sources`: List of session source objects, each containing:
  - `source_type`: Either "local" (from ~/.claude) or "remote" (from GitHub Actions)
  - `session_id`: The Claude Code session ID
  - `run_id`: GitHub Actions run ID (legacy, may be null)
  - `path`: File path to the session (for local sessions only)
  - `gist_url`: Raw gist URL for downloading session (for remote sessions)
- `planning_session_id`: Session ID that created the plan
- `implementation_session_ids`: Session IDs that executed the plan
- `local_session_ids`: Fallback sessions found locally (when no tracked sessions exist)
- `last_remote_impl_at`: Timestamp if implemented via GitHub Actions (remote)
- `last_remote_impl_run_id`: GitHub Actions run ID for remote implementation
- `last_remote_impl_session_id`: Claude Code session ID from remote implementation

If no sessions are found, inform the user and stop.

**Note on remote sessions:** Remote sessions appear in `session_sources` with `source_type: "remote"` and `path: null`. These sessions must be downloaded before processing (see Step 4).

Tell the user:

```
Found N session(s) for plan #<plan-number>:
  - N local session(s) from ~/.claude/projects/
  - N remote session(s) from GitHub Actions
```

### Step 4: Gather and Analyze Sessions

Before gathering sessions, get the PR information for this plan (needed by analysis agents):

```bash
erk exec get-pr-for-plan <plan-number>
```

This returns JSON with PR details (`number`, `title`, `state`, `url`, `head_ref_name`, `base_ref_name`) or an error if no PR exists. Save the PR number for the parallel agents below.

**Session discovery priority:**

1. If you copied preprocessed materials from `.erk/impl-context/` in Step 2, skip the "Preprocess Sessions" and "Save PR Comments" subsections. The files are already in `.erk/scratch/sessions/${CLAUDE_SESSION_ID}/learn/`. Proceed to "Launch Parallel Analysis Agents".

2. **NEW: If `get-learn-sessions` returned `preprocessed_manifest` (non-null):** Download the preprocessed XMLs directly from the async-learn branch — skip raw session preprocessing:

```bash
erk exec fetch-sessions --plan-id <plan-number> \
    --output-dir .erk/scratch/sessions/${CLAUDE_SESSION_ID}/learn
```

This downloads all accumulated preprocessed XML files (planning, impl, address stages) to the learn directory. The manifest contains metadata about which stages and sessions are included. Proceed to "Launch Parallel Analysis Agents".

3. Else — discover local sessions, preprocess, then analyze (existing local path below).

#### Check Existing Documentation

**Note:** This manual check provides a quick overview. The **existing-docs-checker agent** (launched in parallel below) performs a thorough search across all documentation directories.

Quick scan for existing documentation:

```bash
ls -la docs/learned/ 2>/dev/null || echo "No docs/learned/ directory"
ls -la .claude/skills/ 2>/dev/null || echo "No .claude/skills/ directory"
```

#### Analyze Current Conversation

Before preprocessing session files, analyze the current conversation context:

**You have direct access to this session.** No preprocessing needed - examine what happened:

1. **User corrections**: Did the user correct any assumptions or approaches?
2. **External lookups**: What did you WebFetch or WebSearch? Why wasn't it already documented?
3. **Unexpected discoveries**: What surprised you during implementation?
4. **Repeated patterns**: Did you do something multiple times that could be streamlined?

**Key question**: "What would have made this session faster if I'd known it beforehand?"

These insights are often the most valuable because they represent real friction encountered during implementation.

#### Preprocess Sessions

For each session source from Step 3, preprocess to compressed XML format:

**IMPORTANT:** Check `source_type` before processing:

- If `source_type == "local"` and `path` is set: Process the session using the path
- If `source_type == "remote"`: Download the session first, then process:
  1. Check if `gist_url` is set (not null). If null, the session cannot be downloaded (legacy artifact-based session).
  2. Run: `erk exec download-remote-session --gist-url "<gist_url>" --session-id "<session_id>"`
  3. Parse the JSON output to get the `path` field
  4. If `success: true`, use the returned `path` for preprocessing
  5. If `success: false` (gist not accessible, etc.), inform the user and skip this session

```bash
mkdir -p .erk/scratch/sessions/${CLAUDE_SESSION_ID}/learn

# For each local session source with a valid path:
# Compare source.session_id to planning_session_id to determine prefix

# For the planning session (source.session_id == planning_session_id):
erk exec preprocess-session "<source.path>" \
    --max-tokens 20000 \
    --output-dir .erk/scratch/sessions/${CLAUDE_SESSION_ID}/learn \
    --prefix planning

# For implementation sessions (source.session_id in implementation_session_ids):
erk exec preprocess-session "<source.path>" \
    --max-tokens 20000 \
    --output-dir .erk/scratch/sessions/${CLAUDE_SESSION_ID}/learn \
    --prefix impl
```

Note: The preprocessor applies deduplication, truncation, and pruning optimizations automatically. Files are auto-chunked if they exceed the token limit (20000 tokens safely under Claude's 25000 read limit). Output files include session IDs in filenames (e.g., `planning-{session-id}.xml` or `impl-{session-id}-part{N}.xml` for chunked files).

Tell the user:

```
Preprocessing N session(s): compressing JSONL → XML, deduplicating, truncating to 20k tokens each...
```

#### Save PR Comments

If a PR exists for this plan, save PR comments for analysis:

```bash
erk exec get-pr-review-comments --pr <pr-number> --include-resolved \
    > .erk/scratch/sessions/${CLAUDE_SESSION_ID}/learn/pr-review-comments.json

erk exec get-pr-discussion-comments --pr <pr-number> \
    > .erk/scratch/sessions/${CLAUDE_SESSION_ID}/learn/pr-discussion-comments.json
```

#### Launch Parallel Analysis Agents

After preprocessing, launch analysis agents in parallel to extract insights concurrently.

First, create the output directory for agent results:

```bash
mkdir -p .erk/scratch/sessions/${CLAUDE_SESSION_ID}/learn-agents/
```

Tell the user:

```
Launching analysis agents in parallel:
  - Session analyzer (1 per session file)
  - Code diff analyzer (PR #<number>)
  - Existing documentation checker
  - PR comment analyzer (PR #<number>)
```

**Agent 1: Session Analysis** (for each preprocessed session)

For each XML file in `.erk/scratch/sessions/${CLAUDE_SESSION_ID}/learn/`:

<!-- Model: sonnet - Session analysis benefits from nuanced reasoning about patterns and decisions -->

```
Task(
  subagent_type: "general-purpose",
  model: "sonnet",
  run_in_background: true,
  description: "Analyze session <session-id>",
  prompt: |
    Load and follow the agent instructions in `.claude/agents/learn/session-analyzer.md`

    Input:
    - session_xml_path: .erk/scratch/sessions/${CLAUDE_SESSION_ID}/learn/<filename>.xml
    - context: <brief description from plan title>
    - output_path: .erk/scratch/sessions/${CLAUDE_SESSION_ID}/learn-agents/session-<session-id>.md

    ## Output Routing
    CRITICAL: Write your complete output to the output_path using the Write tool.
    Your final message MUST be only: "Output written to <output_path>"
    Do NOT return the analysis content in your final message.
)
```

**Agent 2: Code Diff Analysis** (if PR exists)

<!-- Model: sonnet - Diff analysis benefits from understanding architectural significance of changes -->

```
Task(
  subagent_type: "general-purpose",
  model: "sonnet",
  run_in_background: true,
  description: "Analyze PR diff",
  prompt: |
    Load and follow the agent instructions in `.claude/agents/learn/code-diff-analyzer.md`

    Input:
    - pr_number: <pr-number>
    - plan_number: <plan-number>
    - output_path: .erk/scratch/sessions/${CLAUDE_SESSION_ID}/learn-agents/diff-analysis.md

    ## Output Routing
    CRITICAL: Write your complete output to the output_path using the Write tool.
    Your final message MUST be only: "Output written to <output_path>"
    Do NOT return the analysis content in your final message.
)
```

**Agent 3: Existing Documentation Check**

Proactively search for existing documentation to prevent duplicates and detect contradictions:

<!-- Model: sonnet - Phantom detection and contradiction analysis require reasoning about code references -->

```
Task(
  subagent_type: "general-purpose",
  model: "sonnet",
  run_in_background: true,
  description: "Check existing docs",
  prompt: |
    Load and follow the agent instructions in `.claude/agents/learn/existing-docs-checker.md`

    Input:
    - plan_title: <title from the plan>
    - pr_title: <PR title if available, or empty string>
    - search_hints: <key terms extracted from plan title, comma-separated>
    - output_path: .erk/scratch/sessions/${CLAUDE_SESSION_ID}/learn-agents/existing-docs-check.md

    ## Output Routing
    CRITICAL: Write your complete output to the output_path using the Write tool.
    Your final message MUST be only: "Output written to <output_path>"
    Do NOT return the analysis content in your final message.
)
```

Extract search hints by:

1. Taking significant nouns/concepts from the plan title
2. Removing common words (the, a, an, to, for, with, add, update, fix, etc.)
3. Example: "Add parallel agent orchestration" → "parallel, agent, orchestration"

**Agent 4: PR Comment Analysis** (if PR exists)

Analyze PR review comments and discussion comments to identify documentation opportunities:

<!-- Model: sonnet - Comment classification benefits from understanding nuance in reviewer intent -->

```
Task(
  subagent_type: "general-purpose",
  model: "sonnet",
  run_in_background: true,
  description: "Analyze PR comments for docs",
  prompt: |
    Analyze PR review comments to identify documentation opportunities.

    ## Steps
    1. Run: `erk exec get-pr-review-comments --pr <pr-number> --include-resolved`
    2. Run: `erk exec get-pr-discussion-comments --pr <pr-number>`
    3. Classify and summarize the comments

    ## Classification
    For each comment, identify documentation opportunities:
    - **False positives**: Reviewer misunderstood something → document to prevent future confusion
    - **Clarification requests**: "Why does this..." → document the reasoning
    - **Suggested alternatives**: Discussed but rejected → document the decision
    - **Edge case questions**: "What happens if..." → document the behavior

    ## Output

    **CRITICAL:** Write your complete output to the output_path using the Write tool.
    Your final message to the caller MUST be only: "Output written to <output_path>"

    output_path: .erk/scratch/sessions/${CLAUDE_SESSION_ID}/learn-agents/pr-comments-analysis.md

    Use this format for the content written to the file:

    ### PR Comment Analysis Summary
    PR #NNNN: N review threads, M discussion comments analyzed.

    ### Documentation Opportunities from PR Review
    | # | Source | Insight | Documentation Suggestion |
    |---|--------|---------|--------------------------|
    | 1 | Thread at abc.py:42 | Reviewer asked about LBYL pattern | Document when LBYL is required |
    | 2 | Discussion | Clarified retry behavior | Add to API quirks doc |

    ### Key Insights
    [Bullet list of the most important documentation opportunities]

    If no comments or no documentation opportunities found, write:
    "No documentation opportunities identified from PR review comments."
)
```

#### Wait for Parallel Agents and Verify Output Files

Wait for each background agent to complete:

```
TaskOutput(task_id: <agent-task-id>, block: true)
```

Each agent writes its own output file and returns only a short confirmation. You do NOT need to relay or write the agent output — just confirm the tasks completed.

After all agents finish, verify their output files exist:

```bash
ls -la .erk/scratch/sessions/${CLAUDE_SESSION_ID}/learn-agents/
```

Confirm you see the expected files (session-\*.md, diff-analysis.md, existing-docs-check.md, pr-comments-analysis.md) before proceeding. If any files are missing, the agent failed to write its output and must be re-launched.

Tell the user:

```
Parallel analysis complete. Running sequential synthesis:
  - Identifying documentation gaps
  - Synthesizing learn plan
  - Extracting tripwire candidates
```

#### Synthesize Agent Findings (Agent 5)

Launch the DocumentationGapIdentifier agent to synthesize outputs from the parallel agents:

<!-- Model: sonnet - Gap identification with adversarial verification requires reasoning about phantom references -->

```
Task(
  subagent_type: "general-purpose",
  model: "sonnet",
  description: "Identify documentation gaps",
  prompt: |
    Load and follow the agent instructions in `.claude/agents/learn/documentation-gap-identifier.md`

    Input:
    - session_analysis_paths: [".erk/scratch/sessions/${CLAUDE_SESSION_ID}/learn-agents/session-<id>.md", ...]
    - diff_analysis_path: ".erk/scratch/sessions/${CLAUDE_SESSION_ID}/learn-agents/diff-analysis.md" (or null if no PR)
    - existing_docs_path: ".erk/scratch/sessions/${CLAUDE_SESSION_ID}/learn-agents/existing-docs-check.md"
    - pr_comments_analysis_path: ".erk/scratch/sessions/${CLAUDE_SESSION_ID}/learn-agents/pr-comments-analysis.md" (or null if no PR)
    - plan_title: <title from the plan>
    - output_path: .erk/scratch/sessions/${CLAUDE_SESSION_ID}/learn-agents/gap-analysis.md

    ## Output Routing
    CRITICAL: Write your complete output to the output_path using the Write tool.
    Your final message MUST be only: "Output written to <output_path>"
    Do NOT return the analysis content in your final message.
)
```

**Note:** This agent runs AFTER the parallel agents complete (sequential dependency). It writes its own output file and returns only a short confirmation.

The DocumentationGapIdentifier agent will:

- Collect all candidates from session-analyzer, code-diff-analyzer, existing-docs-checker, and PR comment analyzer
- Deduplicate against existing documentation (ALREADY_DOCUMENTED, PARTIAL_OVERLAP, NEW_TOPIC)
- Cross-reference against the diff inventory to ensure completeness
- Classify items: NEW_DOC | UPDATE_EXISTING | TRIPWIRE | SKIP
- Prioritize by impact: HIGH (gateway methods, contradictions) > MEDIUM (patterns) > LOW (helpers)
- Produce the MANDATORY enumerated table required by Step 5

#### Synthesize Learn Plan (Agent 6)

Launch the PlanSynthesizer agent to transform the gap analysis into a complete learn plan:

<!-- Model: opus - Creative authoring of narrative context and draft content; quality-critical final output -->

```
Task(
  subagent_type: "general-purpose",
  model: "opus",
  description: "Synthesize learn plan",
  prompt: |
    Load and follow the agent instructions in `.claude/agents/learn/plan-synthesizer.md`

    Input:
    - gap_analysis_path: ".erk/scratch/sessions/${CLAUDE_SESSION_ID}/learn-agents/gap-analysis.md"
    - session_analysis_paths: [".erk/scratch/sessions/${CLAUDE_SESSION_ID}/learn-agents/session-<id>.md", ...]
    - diff_analysis_path: ".erk/scratch/sessions/${CLAUDE_SESSION_ID}/learn-agents/diff-analysis.md" (or null if no PR)
    - plan_title: <title from the plan>
    - pr_number: <PR number if available, else null>
    - output_path: .erk/scratch/sessions/${CLAUDE_SESSION_ID}/learn-agents/learn-plan.md

    ## Output Routing
    CRITICAL: Write your complete output to the output_path using the Write tool.
    Your final message MUST be only: "Output written to <output_path>"
    Do NOT return the analysis content in your final message.
)
```

**Note:** This agent runs AFTER DocumentationGapIdentifier completes (sequential dependency). It writes its own output file and returns only a short confirmation.

#### Extract Tripwire Candidates (Agent 7)

Launch the TripwireExtractor agent to pull structured tripwire data from the plan:

<!-- Model: sonnet - Tripwire extraction benefits from understanding the significance of patterns -->

```
Task(
  subagent_type: "general-purpose",
  model: "sonnet",
  description: "Extract tripwire candidates",
  prompt: |
    Load and follow the agent instructions in `.claude/agents/learn/tripwire-extractor.md`

    Input:
    - learn_plan_path: ".erk/scratch/sessions/${CLAUDE_SESSION_ID}/learn-agents/learn-plan.md"
    - gap_analysis_path: ".erk/scratch/sessions/${CLAUDE_SESSION_ID}/learn-agents/gap-analysis.md"
    - output_path: .erk/scratch/sessions/${CLAUDE_SESSION_ID}/learn-agents/tripwire-candidates.json

    ## Output Routing
    CRITICAL: Write your complete output to the output_path using the Write tool.
    Your final message MUST be only: "Output written to <output_path>"
    Do NOT return the analysis content in your final message.
)
```

**Note:** This agent runs AFTER PlanSynthesizer completes (sequential dependency). It writes its own output file and returns only a short confirmation.

#### Agent Dependency Graph

```
Parallel Tier (can run simultaneously):
  ├─ SessionAnalyzer (per session XML)
  ├─ CodeDiffAnalyzer (if PR exists)
  ├─ ExistingDocsChecker
  └─ PRCommentAnalyzer (if PR exists)

Sequential Tier 1 (depends on Parallel Tier):
  └─ DocumentationGapIdentifier

Sequential Tier 2 (depends on Sequential Tier 1):
  └─ PlanSynthesizer

Sequential Tier 3 (depends on Sequential Tier 2):
  └─ TripwireExtractor
```

#### Deep Analysis (Manual Fallback)

If agents were not launched or failed, fall back to manual analysis.

Read all preprocessed session files in the learn directory:

```bash
# Use Glob to find all XML files (they include session IDs and may be chunked)
ls .erk/scratch/sessions/${CLAUDE_SESSION_ID}/learn/*.xml
```

Files are named: `{prefix}-{session_id}.xml` or `{prefix}-{session_id}-part{N}.xml` for chunked files.

Read each file and mine them thoroughly.

**Compaction Awareness:** Long sessions may have been "compacted" (earlier messages summarized), but pre-compaction content still contains valuable research.

**Subagent Mining:**

1. Identify all Task tool invocations (`<invoke name="Task">`)
2. Read subagent outputs - each returns a detailed report
3. Mine Explore agents for codebase findings
4. Mine Plan agents for design decisions
5. Extract specific insights, not just summaries

**What to capture:**

- Files read and what was learned from them
- Patterns discovered in the codebase
- Design decisions and reasoning
- External documentation fetched (WebFetch, WebSearch)
- Error messages and how they were resolved

### Step 5: Review Synthesized Plan

Read the PlanSynthesizer output:

```bash
cat .erk/scratch/sessions/${CLAUDE_SESSION_ID}/learn-agents/learn-plan.md
```

The PlanSynthesizer has already:

- Collected all candidates from the parallel agents via DocumentationGapIdentifier
- Created a narrative context explaining what was built
- Generated documentation items with draft content starters
- Described tripwire insights naturally in prose

The TripwireExtractor has:

- Extracted structured tripwire candidate data from the plan into `tripwire-candidates.json`

#### Validate the Synthesized Plan

Review the synthesized plan:

1. **Context section**: Does it accurately describe what was built?
2. **Contradiction resolutions**: Do they make sense?
3. **HIGH priority items**: Are they appropriate?
4. **Draft content starters**: Are they actionable (not just "document this")?
5. **Skip reasons**: Are they valid (not "self-documenting code")?

#### PR Comment Analysis

PR comment insights are already integrated via the PRCommentAnalyzer agent (Agent 4) → DocumentationGapIdentifier pipeline. Review the gap analysis output to verify PR comment documentation opportunities were captured correctly. If you spot additional insights not covered by the agent, add them manually to the plan.

#### Reference: Common Documentation Locations

| What was built            | Documentation needed                                       |
| ------------------------- | ---------------------------------------------------------- |
| New CLI command           | Document in `docs/learned/cli/` - usage, flags, examples   |
| New gateway method        | Add tripwire about ABC implementation (5 places to update) |
| New capability            | Update capability system docs, add to glossary             |
| New config option         | Add to `docs/learned/glossary.md`                          |
| New exec script           | Document purpose, inputs, outputs                          |
| New architectural pattern | Create architecture doc or add tripwire                    |
| External API integration  | Document quirks, rate limits, auth patterns discovered     |

#### Validation Checkpoint

**⚠️ CHECKPOINT: Before proceeding to Step 6**

Verify the PlanSynthesizer output:

- [ ] Context section accurately describes what was built
- [ ] Documentation items have actionable draft content (not just "document this")
- [ ] Every SKIP has an explicit, valid reason (not "self-documenting")
- [ ] HIGH priority contradictions have resolution plans
- [ ] All PR comment insights are captured

**If no documentation needed:**

If the synthesized plan shows NO documentation items:

1. Re-read the agent's skip reasons
2. Ask: "Would a future agent benefit from this?"
3. If still no documentation needed, state: "After explicit review of N inventory items, no documentation is needed because [specific reasons for top 3 items]"

Only proceed to Step 11 (skipping Steps 6-10) after this explicit justification.

#### Outdated Documentation Check (MANDATORY)

**Removals and behavior changes require doc audits.** When the PR removes features or changes behavior, existing documentation may become incorrect.

**Search for documentation that references changed features:**

```bash
# Search docs for terms related to removed/changed features
grep -r "<removed-feature>" docs/learned/ .claude/commands/ .claude/skills/
```

**Categorize findings:**

| Finding                           | File | Status        | Action Needed          |
| --------------------------------- | ---- | ------------- | ---------------------- |
| Reference to removed feature      | ...  | Outdated      | Remove/update section  |
| Describes old behavior            | ...  | Incorrect     | Update to new behavior |
| Conflicts with new implementation | ...  | Contradictory | Reconcile              |

**Common patterns to check:**

- **Removed CLI flags**: Search for `--flag-name` in docs
- **Removed files/modules**: Search for import paths, file references
- **Changed behavior**: Search for behavioral descriptions that no longer apply
- **Removed modes**: Search for "three modes", "fallback", etc.

**Include outdated doc updates in the documentation plan** alongside new documentation needs.

### Step 6: Present Findings

Present the synthesized plan to the user. The PlanSynthesizer output already includes:

1. **Context section** - What was built and why docs matter
2. **Summary statistics** - Documentation items, contradictions, tripwires
3. **Documentation items** - Prioritized with draft content starters and source attribution ([Plan], [Impl], [PR #N])

**CI Detection**: Check if running in CI/streaming mode by running:

```bash
[ -n "$CI" ] || [ -n "$GITHUB_ACTIONS" ] && echo "CI_MODE" || echo "INTERACTIVE"
```

**If CI mode (CI_MODE)**: Skip user confirmation. Auto-proceed to Step 7 to save the learn plan. This is expected behavior - CI runs should complete without user interaction.

**If interactive mode (INTERACTIVE)**: Confirm with the user before saving the learn plan. If the user decides to skip (no valuable insights), proceed to Step 11.

### Step 7: Validate and Save Learn Plan to GitHub Issue

First validate the synthesized plan has actionable content:

```bash
cat .erk/scratch/sessions/${CLAUDE_SESSION_ID}/learn-agents/learn-plan.md | erk exec validate-plan-content
```

Parse the JSON output:

- If `valid: false` → Skip saving, proceed to Step 9 with `completed_no_plan`
- If `valid: true` → Continue with save below

**If plan is valid**, save it as a draft PR:

```bash
# Build command with optional workflow run URL for backlink
CMD="erk exec plan-save \
    --plan-type learn \
    --plan-file .erk/scratch/sessions/${CLAUDE_SESSION_ID}/learn-agents/learn-plan.md \
    --session-id=\"${CLAUDE_SESSION_ID}\" \
    --learned-from-issue <parent-issue-number> \
    --session-xml-dir .erk/scratch/sessions/${CLAUDE_SESSION_ID}/learn \
    --format json"

# Add workflow run URL if set (enables backlink to GitHub Actions run)
if [ -n "$WORKFLOW_RUN_URL" ]; then
    CMD="$CMD --created-from-workflow-run-url \"$WORKFLOW_RUN_URL\""
fi

eval "$CMD"
```

Parse the JSON output to get `plan_number`, `plan_backend`, `title`, and `plan_url` (the new learn plan number).

Display the result:

```
Learn plan saved as draft PR #<plan_number>: <title>
URL: <plan_url>
```

### Step 8: Store Tripwire Candidates on Learn Plan PR

**If plan was valid and saved**, store tripwire candidates as a metadata comment:

```bash
erk exec store-tripwire-candidates \
    --issue <new-learn-plan-issue-number> \
    --candidates-file .erk/scratch/sessions/${CLAUDE_SESSION_ID}/learn-agents/tripwire-candidates.json
```

This stores the tripwire candidates as a machine-readable metadata block comment on the learn plan, enabling `erk land` to read them directly without regex parsing. The store command automatically normalizes common schema drift (wrong root keys, field aliases, extra fields) before validation.

Parse the JSON output. If `count` is 0, no comment was added (no candidates found by the extractor). This is normal and not an error.

### Step 9: Track Learn Result on Parent Plan

**If plan was valid and saved**, update the parent plan's status to link the two issues:

```bash
erk exec track-learn-result \
    --issue <parent-issue-number> \
    --status completed_with_plan \
    --learn-plan <learn-plan-number>
```

This sets `learn_status: completed_with_plan` and `learn_plan_issue: <N>` on the parent plan,
enabling the TUI to show the linked learn plan.

**If plan validation failed (no actionable documentation):**

```bash
erk exec track-learn-result \
    --issue <parent-issue-number> \
    --status completed_no_plan
```

### Step 10: Post-Learn Decision Menu

Present a decision menu to the user for next actions.

**CI Detection**: Reuse the CI check from Step 6:

- If CI_MODE: Auto-select "Done" and proceed to Step 11
- If not interactive: Auto-select "Done" and proceed to Step 11

**Check for other open learn plans** (for consolidation option):

```bash
OTHER_LEARN_COUNT=$(erk exec list-plans --label erk-learn --state open --format json 2>/dev/null | jq '.plans | length')
```

If the count is > 1 (current plan + at least one other), include the consolidation option.

**Interactive mode**: Present the menu using direct output and wait for user selection.

If other learn plans exist (count > 1):

```
Post-learn actions:
  1. Submit for implementation (Recommended) — Queue for remote implementation
  2. Review in browser — Open draft PR in web browser for review/editing
  3. Consolidate with other learn plans — Merge overlapping learn plans
  4. Done — Finish learn workflow
```

If no other learn plans (count <= 1):

```
Post-learn actions:
  1. Submit for implementation (Recommended) — Queue for remote implementation
  2. Review in browser — Open draft PR in web browser for review/editing
  3. Done — Finish learn workflow
```

**Execute the selected action:**

- **Submit**: Run `/erk:pr-dispatch`
- **Review**: Display the `plan_url` from the JSON output for the user to click. Then inform the user they can run `/erk:pr-dispatch` when ready.
- **Consolidate**: Run `/local:replan-learn-plans`
- **Done**: Proceed directly to Step 11

### Step 11: Track Evaluation

**CRITICAL: Always run this step**, regardless of which option was selected in Step 10.

This ensures `erk land` won't warn about unlearned plans:

```bash
erk exec track-learn-evaluation <issue-number> --session-id="${CLAUDE_SESSION_ID}"
```

### Tips

- Preprocessed sessions use XML: `<user>`, `<assistant>`, `<tool_use>`, `<tool_result>`
- `<tool_result>` elements with errors often reveal the most useful insights
- The more context you include in the issue, the faster the implementing agent can work
