reflect · git:20261026.a9f60bf · 2026-10-26 · sha256 44afd2abc5be7b46

reflect git:20261026.a9f60bfA

Immutable. This exact content never changes and is served at /api/v1/blob/44afd2abc5be7b46.

---
name: reflect
description: "Review the active transcript or a finished program with three parallel reviewers, record the learnings in the lessons ledger, and route each to an edit on an existing skill. Use for reflect, \"스킬에 반영해줘\", \"스킬이 왜 안 떴어\", \"이 세션 돌아보고 스킬 개선해줘\", \"개선 이력 보여줘\"; at orchestrate's Close; and each flow-improver or /loop round."
---

# Reflect

Mine the work for durable learnings, record each in the lessons ledger, then route them into skill edits.

## When to invoke

| Mode | Invoked by | Input | Who approves edits |
|---|---|---|---|
| session | the user ("reflect", "/reflect"), or `end-session` when the human corrected the work | this conversation's transcript | the user, in this session |
| program | `orchestrate` Close, unattended | the program's evidence pack (step 1) | the user, later, through one draft PR |
| standing | the flow improver each round (`orchestrate` `references/roles.md`), the daily Orca automation whose precheck wakes it only on something new (below), or `/loop` in a session when neither runs, unattended | what is new since the last round (step 1) | the user, later, through one draft PR |

Skip when the work is trivial, off-topic, or already covered by an existing skill the parent followed correctly. One-offs are not learnings: a finding changes a skill only once it has two independent occurrences (step 4).

"개선 이력 보여줘" and similar only read the ledger: show its open candidates and the last applied lessons with their outcome, and stop. The dashboard's Skill changes page has the commits, open PRs and usage signals beside them.

## The lessons ledger

Use `reflect.ledger` in the skill configuration or an explicit path supplied for this task.
Keep private evidence outside public repositories. If no destination is configured, report
findings in the current task; do not invent a shared note location. Existing ledgers stay at
their current path and retain their IDs and history. A new ledger uses this table and a
`## History` section:

| ID | First seen | Source | Kind | Evidence | Occurrences | Learning | Target | Status | Change | Verification | Outcome |
|---|---|---|---|---|---|---|---|---|---|---|---|

- **ID** `L-<n>`, never reused. **Source** the program slug or session. **Kind** a signal kind (`human_correction`, `brief_gap`, `stall`, `tooling`), `land_failed`, `main_red`, `verdict_fail`, `review` for a finding the reviewers drew from the whole record rather than one signal, or `usage` for a skill the usage collector flagged (Source `skill-usage`, Learning `<skill>: <flag>`). **Evidence** pointers only (message ID, PR comment URL, transcript path and line), never copied text; a program-ledger row with no `evidence` field is pointed to as `<slug>:<ev>:<ts>:<pr or sha>`, in every mode, so the same row is never counted twice.
- **Status** is `candidate`, then `proposed` (a draft PR exists), or in standing mode `waiting (standing PR)` for an Accepted row until step 5 opens its PR, then `applied` (merged) or `rejected (<reason>)`; `backlog (<ticket>)` for a finding filed to the tracker for the human; `retired` once its target is gone or it stopped being true.
- **Verification** holds only what a script, CI or `measure-delivery` printed (`claude plugin eval` scores before and after, test names). A reviewer's opinion is not verification.
- **Outcome** is filled by later programs: `recurred (<source>)`, or `held through <n> programs (<slug>, ...)` listing each program counted, so no program counts twice.
- A change to a row edits that row in place and appends one dated line to `## History` (`2026-09-25 L-4 candidate to proposed: <PR URL>`), so the history survives.

## Process

### 1. Gather the input

**Program mode.** Build an evidence pack at `~/.claude/programs/<slug>/reflect/pack.md`:
- From the ledger: every `signal` row, every `land_failed`, `main_red` and failed `verdict`, each with its ticket, PR and evidence pointer. A `main_red` row carries only a sha: its PRs are every `landed` row with that sha (a stack lands several at once), and each PR's ticket is on its `ready` row or, when the ledger has none, in the PR's title, branch or body (`gh pr view`); write "no ticket" when none names one. When the ledger has no `verdict` rows at all, write "verdicts not recorded", not 0.
- Rows with note `backfill` were recorded by `orch backfill` for merges before the program was registered. They carry no process evidence: give their count on one line, and leave them out of everything below, the stop rule included.
- The decision trail (`decisions.tsv`, or the `decision` rows in `dashboard/notes.jsonl`), the program note's digest and standing orders as they ended, and the `measure-delivery` output from this Close, saved as `reflect/delivery.md` beside the pack. Write "missing" for any that do not exist.
- The transcripts behind the signals: the orchestrator's or lead's own (reflect runs in that session, so find it as in session mode below), and a worker's when a signal names it (its worktree path encoded the same way).
- Every `applied` ledger row whose Outcome is not `recurred`. Update its Outcome now, judged against the lesson's own Learning, not its Kind: `recurred (<slug>)` when this program repeats the failure that lesson targets; add this slug to its `held through` list when the program ran the step the lesson changed and the failure did not return. A program that never ran that step leaves the row unchanged.

Check each signal against the skills at current HEAD of the explicitly selected source checkout and mark in the pack the ones HEAD already fixes (with the file and line that fixes it), so the reviewers and the synthesizer do not propose them again. `git -C <source checkout> log --oneline <skills_commit>..HEAD -- <skill paths>`, with `skills_commit` from `program.json` minus any `+local` suffix, lists what changed since the program started. Use paths within that checkout; if no source checkout is configured, report that verification gap.

With no signal and no failure left open after that, stop after the Outcome update. Otherwise pass the pack path to the reviewers in place of the transcript path.

**Standing mode.** Rounds do not overlap: `mkdir -p ~/.claude/programs/_standing/reflect`, then take `mkdir ~/.claude/programs/_standing/reflect/lock` and write the start time into it. Skip the round while the lock exists, unless it started more than 4 hours ago (stale: remove it). Remove it when the round ends.

Then settle the last standing PR, the newest in the configured source repository whose branch starts `reflect/standing-` (`gh pr list -R <repo> --state all --limit 100 --json number,state,headRefName,createdAt`): merged, its rows go `applied`; closed unmerged, `rejected (PR closed)`.

New means a `signal`, `land_failed`, `main_red` or failed `verdict` row after the cursor (a ledger the cursor does not list yet: at or after its newest row), whose pointer the lessons ledger does not hold yet, in any `~/.claude/programs/*/ledger.jsonl`, the usage collector's `_skill-usage/ledger.jsonl` included (its `skill_usage` signals carry evidence `skill-usage:<skill>:<flag>@<window>`). The cursor, `~/.claude/programs/_standing/reflect/cursor.json`, maps each ledger's directory name (`<slug>`, `_skill-usage`) to the last `ts` that ledger gave a finished round, written with its UTC offset as the ledger writes it.

Standing mode writes an Accepted row as `waiting (standing PR)` in step 4, and step 5 takes every waiting row once no standing PR is open: the HEAD check first (a row HEAD already fixes goes `retired`), then the edits, the PR, and `proposed`. While a standing PR is open, step 5 still files Backlog tickets and opens no PR, and writes that PR's URL to `~/.claude/programs/_standing/reflect/waiting.json` (`{"pr": "<url>"}`): the waiting rows live in the lessons ledger, which the precheck cannot read, so the file is what wakes a round once that PR closes. Step 5 deletes the file when it opens the PR that takes the waiting rows, or when none are left.

With nothing new and no waiting row for step 5, move the cursor to the newest row of every ledger read, then stop and send nothing; a cursor left behind wakes the automation again on the same rows. With only waiting rows, go straight to step 5. Otherwise build the pack at `~/.claude/programs/_standing/reflect/pack-<UTC timestamp>.md` over the new rows only: program mode's ledger and backfill bullets, the transcripts each row's evidence points to, and the HEAD check, which also catches a lesson the mail path already committed. The decision trail, `measure-delivery` and the Outcome update belong to Close. Continue from step 2, and move the cursor once step 4 has written the lessons ledger, so a round that dies early is read again.

The daily automation: `python3 <checkout>/skills/orchestrate/scripts/skill_usage.py schedule` prints the `orca automations create` command (`--write` runs it; `--workspace path:<runner>` runs it in a runner worktree whose settings pick the model, [docs/automation.md](../../docs/automation.md)). Its precheck, `skill_usage.py precheck`, collects usage, then exits 0 only when no round holds the lock and either some ledger has a finding row after the cursor or `waiting.json` names a standing PR that is no longer open (or whose state `gh` cannot read), with no model call; Orca records any other exit as a skipped run. The precheck cannot read the lessons ledger, so a woken round may still find nothing new. A round it wakes runs from this step to step 5, opens at most the one draft PR, and merges nothing.

**Session mode.** The parent finds its own transcript file before fanning out. Claude Code writes it under `~/.claude/projects/<cwd with every / and . replaced by ->/`, for this session's working directory. Use that path. Do not glob across `~/.claude/projects/*/`. That crosses workspace boundaries and reads private chats from unrelated projects.

```bash
ls -t <project-dir>/*.jsonl <project-dir>/*/subagents/*.jsonl 2>/dev/null | head -10
```

Two transcript layouts: session (`<session-id>.jsonl`) and subagent (`<session-id>/subagents/agent-<id>.jsonl`).

For each candidate, find the first JSONL line with `"type":"user"` and check that its `message.content` contains the conversation's opening user prompt. Take the matching path. If no path resolves, write a tight digest of the session and pass that instead.

### 2. Spawn three reviewers in parallel

One message, three reviewers launched together. Each reviewer is an `Agent` call with `subagent_type: "general-purpose"`, not the read-only `Explore`/`Plan` agents: reviewers need MCP access for context lookups (tickets, chat threads, observability traces referenced in the transcript), and the read-only agents lack it.

| Lens | Runner | Prompt template |
|---|---|---|
| Judgment | Independent reviewer; the template forbids edits | `references/judgment-reviewer.md` |
| Tooling | Independent reviewer; the template forbids edits | `references/tooling-reviewer.md` |
| Divergent | Independent reviewer; the template forbids edits | `references/divergent-reviewer.md` |

Pass each template verbatim, substituting the transcript path or digest where marked. Reviewers return findings in their final response.

### 3. Synthesize

One independent synthesizer using the host's available delegation tool. The synthesizer's quality check includes spot-verifying citations, which can require MCP access. The read-only agents lack it. Use `references/synthesizer.md` verbatim, with each reviewer's full output inlined where marked. The synthesizer returns a structured Accepted / Rejected / Backlog list.

### 4. Record, count, and check structure

Write every Accepted, Backlog and Rejected finding to the lessons ledger. A finding that matches an existing row adds its evidence and raises Occurrences instead of making a new row; evidence the row already holds raises nothing.

- An Accepted finding stays Accepted only with two or more independent occurrences (different tickets, workers or sessions), or when it is a reproduced security or data defect. A `usage` row is a measurement: a later window of the same skill and flag raises Occurrences on its row, its two occurrences are two windows that do not overlap, and a window on a `rejected` row adds its evidence and leaves it rejected. Count occurrences from the ledger's rows and from the `signal` rows in other programs' ledgers in the store, since a program that ran before the ledger existed left no rows. Otherwise it stays `candidate` in the ledger and waits for a second occurrence.
- For any item that would be enforced more reliably by a lint rule, script, metadata flag, or runtime check, move it from Accepted to Backlog. See `principles` → Encode Lessons in Structure.
- Never propose changes to `hooks/guard.py`, the `orch land` gate in `skills/orchestrate/scripts/prog.py`, tests or eval graders, or this skill. A finding about one of them goes to Backlog for the human.

### 5. Apply

**Program and standing mode** run with nobody to approve, so they propose instead of applying:
1. Make the Accepted edits in one worktree branch of the configured source checkout (routing below). An edit that changes a command, flag or term greps all of `skills/` and changes every copy in the same branch.
2. Verify each against the change it makes, and record the printed result in Verification.
   - A description change: `claude plugin eval <checkout> --case <skill> --ablation none --no-publish --trust-plugin` on the old and the new checkout (3 runs each by default), with a case under `evals/<skill>/` whose prompt should fire the skill and a grader for a neighbouring skill that should not; add the case if it is missing, in both checkouts.
   - A body or script change: `claude plugin validate <checkout>` and a check of the changed path itself: a test under the skill that fails without the change, or the incident replayed with the tools it needs in a scratch copy of the repo, with the asserted result. The trigger cases grant only read tools and grade only which skills fired, so they verify triggering, never behavior.
   - With no such check, write `Verification: none` in the row and the PR body.
3. Open at most one draft PR (`gh pr create --draft`) titled with the lesson IDs, body per `write-plainly`. Never merge it. Standing mode opens it from a branch `reflect/standing-<UTC timestamp>`.
4. Mark the rows `proposed`, and put one line per lesson plus the PR link in the program digest for approval (standing mode: in the round's report).

A `usage` row about a skill that other skills invoke (the development flow) or that runs only at a rare moment (Close, setup or onboarding, incident response, a legacy alias) is rejected with that reason. For the rest, judge why the skill goes unused; the flag picks the route and the collector's `suggest` is only a hint. `misses` and `slash_only` go to `tune description`. Zero uses alone is no finding: the collector sends no signal for `unused_30d`, and an `unused_30d` row an older collector wrote is rejected with that reason. `retire_candidate` means unused for three 30-day windows, with misses or an overlapping skill as its basis (in the note), and not on the rare list; with an overlap it goes to a merge into that skill (a substantive edit), with misses only to `tune description` first, and a retirement is only ever a Backlog ticket for the human. Never delete a skill.

With no Accepted finding, skip the PR and write one digest line saying why (standing mode: nothing; a round reports only a PR or a ticket it filed).

**Session mode.** Before applying any Accepted edit, present the synthesizer's full Accepted/Rejected/Backlog output to the user and wait for explicit approval. The user picks which subset to apply and may redirect routings. Skill changes reach every session that loads this plugin, so nothing is applied without that approval.

File Backlog items only when the user's task or configured workflow authorizes tracker writes; otherwise keep them as proposed follow-ups in the report.

Choose the source checkout explicitly with `reflect.skill_repository` or the user's task.
Edit only that authorized repository, on a worktree branch, never an installed plugin cache.
If the source repository is unknown, report the proposed edit without changing an installed
copy. Use the target platform's validation and isolated loading mechanism for testing.

For each approved Accepted item, follow the Routing field exactly:

- Trivial existing-skill edit (a one-line bullet, a tightened sentence, a stale fact corrected): parent does directly.
- Substantive existing-skill edit (a new section, a new pattern table, more than ~10 lines): hand to the `skill-creator:skill-creator` skill and run its draft / test / iterate loop.
- `tune description: <skill path>` (the skill exists but didn't trigger when it should have): hand to `skill-creator:skill-creator` and run its description-optimization loop.
- `new skill via skill-creator: <kebab-name>`: hand creation to `skill-creator:skill-creator`. Do not invent the shape ad hoc.

Before declaring done, run the `write-skill` steps on the change (prompt audit, internal-name grep, tests, `claude plugin validate`) and fix what they report. Put the lesson IDs in the commit message (`L-4`), and mark the rows `applied` once the change reaches main.

### 6. Summarize for the user

Short list, no preamble:

- Edits applied or proposed: `<skill path>` (`L-<n>`). What changed, one line each, with the PR in program and standing mode.
- Candidates waiting for a second occurrence: `L-<n>`, one line each.
- New skills created: `<skill path>`. One line each (rare).
- Backlog filed to the tracker: `<issue title>` (`<tags>`). One line each.
- Dropped: one line per rejected finding + reason from the synthesizer.