Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Use this skill when the user wants to reconcile learnings into rules, run /reconcile, propose rules from learnings, turn learnings into .claude/rules/ entries, or review what rules would be generated from current session learnings. On-demand version of session-end Phase 3.6.8.
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-14 | ✗→✓ | ▲ Improved | 295% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 140% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 177% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 127% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 160% | 0% |
> Platform Note: State files use the platform's native directory: .claude/ (Claude Code), .codex/ (Codex CLI), or .cursor/ (Cursor IDE). Shared metrics live in .orchestrator/metrics/. See skills/_shared/platform-tools.md.
On-demand version of the session-end Phase 3.6.8 reconciliation flow. Turns eligible learnings from .orchestrator/metrics/learnings.jsonl into proposed .claude/rules/<slug>.md entries, presenting each batch of 4 to the coordinator via AUQ multiSelect for operator approval before any file is written. Advisory-only — rules are NEVER auto-applied.
The AUQ multiSelect is the mandatory gate; there is no bypass.
emitter.mjs) throwson any eligible learning that would produce an alwaysApply: true rule — the engine structurally cannot emit always-on rules. This invariant is enforced upstream, not by this skill.
.claude/rules/. runReconcile computes proposals and recordsthem in the idempotency sidecar only. The only module that writes .claude/rules/ is writer.mjs, and only AFTER the operator approves proposals via AUQ.
mergeCandidates — nothing else writes it..orchestrator/runtime/reconcile-candidates.jsonl is a mutable work-queue whose only sanctioned writer is mergeCandidates (scripts/lib/reconcile/idempotency.mjs); it is not a scratch pad, and no report, analysis run, or agent may append to it by hand. A hand-written record there corrupts downstream readers — the session-start reconcile nudge banner derives "last run" from created_at, so a foreign-shaped record makes a non-empty store report no reconcile run on record. Candidate analyses and dry-run reports write their findings to docs/reconcile/<date>-<topic>.md, never into the store. (Since 2026-07-31 a read-side shape guard drops records lacking learning_key/created_at and mergeCandidates reports the count as skipped — that guard is a backstop, not a licence.)
writer seams as the automatic session-end reconciliation phase — operator experience is consistent, and any fixes to the engine benefit both paths.
reconcile.enabled gates the AUTOMATIC session-end phase only. /reconcile is anon-demand command and runs regardless of reconcile.enabled. It still honours rule-expiry-days and confidence-floor from the reconcile config block.
Read skills/_shared/bootstrap-gate.md and execute the gate check. If the gate is CLOSED, invoke skills/bootstrap/SKILL.md and wait for completion before proceeding. If the gate is OPEN, continue to Phase 1.
<HARD-GATE> Do NOT proceed past Phase 0 if GATE_CLOSED. There is no bypass. Refer to skills/_shared/bootstrap-gate.md for the full HARD-GATE constraints. </HARD-GATE>
Read and parse Session Config per skills/_shared/config-reading.md. Store result as $CONFIG.
Extract the reconcile block from $CONFIG:
bash# rule-expiry-days defaults to EMPTY (not a number) so the engine falls back to # its per-type TTL (deriveExpiresAt, default 60d). A numeric override forces flat # N-day expiry — matching the `null` default of the reconcile: config resolver. RULE_EXPIRY_DAYS=$(echo "$CONFIG" | jq -r '.reconcile["rule-expiry-days"] // empty') CONFIDENCE_FLOOR=$(echo "$CONFIG" | jq -r '.reconcile["confidence-floor"] // 0.5') RECONCILE_MODE=$(echo "$CONFIG" | jq -r '.reconcile.mode // "warn"') MIN_RULE_DAYS=$(echo "$CONFIG" | jq -r '.reconcile["min-rule-days"] // 7') MIN_INSIGHT_CHARS=$(echo "$CONFIG" | jq -r '.reconcile["min-insight-chars"] // 24') MAX_PROPOSALS_PER_RUN=$(echo "$CONFIG" | jq -r '.reconcile["max-proposals-per-run"] // 10')
When RULE_EXPIRY_DAYS is empty, pass ruleExpiryDays: undefined to runReconcile so the engine uses its per-type TTL. Defaults when the reconcile block is absent or a field is missing:
rule-expiry-days: empty → per-type TTL (deriveExpiresAt, default 60d). Preserves FA2 behaviour; matches the null resolver default.confidence-floor: 0.5mode: warn (enum off | warn)min-rule-days: 7 — floor window (days) applied to a proposed rule's expires-at so anear-dead or already-elapsed natural expiry never produces a born-dead rule (issue #741.1).
min-insight-chars: 24 — opt-in minimum insight length gating the eligibilityplaceholder-insight check (issue #741.2).
max-proposals-per-run: 10 — volume brake (issue #900 D); the engine sorts eligiblelearnings by confidence DESC and proposes at most this many per run.
Note: reconcile.enabled is intentionally NOT checked — this on-demand command always runs.
Check $ARGUMENTS for --dry-run:
bashDRY_RUN=false if echo "$ARGUMENTS" | grep -q -- '--dry-run'; then DRY_RUN=true fi
Resolve $PLUGIN_ROOT per skills/_shared/config-reading.md (the standard resolution chain: $CLAUDE_PLUGIN_ROOT → $CODEX_PLUGIN_ROOT → $CURSOR_RULES_DIR → common install locations).
runReconcilejavascriptimport { runReconcile } from '$PLUGIN_ROOT/scripts/lib/reconcile/engine.mjs'; const { proposals, rejected, summary, error } = await runReconcile({ repoRoot, // absolute path from git rev-parse --show-toplevel ruleExpiryDays: RULE_EXPIRY_DAYS, // empty → undefined → engine per-type TTL minRuleDays: MIN_RULE_DAYS, // default 7 — floors a near-dead expires-at minInsightChars: MIN_INSIGHT_CHARS, // default 24 — opt-in placeholder-insight length gate maxProposalsPerRun: MAX_PROPOSALS_PER_RUN, // default 10 — volume brake (issue #900 D) now: new Date(), dryRun: DRY_RUN, // true → engine touches no disk (no idempotency sidecar write) }); // The engine does NOT apply a confidence floor — it proposes every eligible // learning and carries each one's `confidence` through. `confidence-floor` is a // DELIVERY gate: filter proposals here before the sidecar + AUQ (mirrors // session-end Phase 3.6.8). Use `surfaced` everywhere "proposals" appears below. const surfaced = proposals.filter((p) => typeof p.confidence === 'number' && p.confidence >= CONFIDENCE_FLOOR);
runReconcile NEVER throws — a top-level error populates result.error instead.
If error is present, surface it to the user and abort: > "Reconcile engine error: <error>. Check .orchestrator/metrics/learnings.jsonl and retry."
If summary.totalLearnings === 0: > "No learnings found in .orchestrator/metrics/learnings.jsonl. Run /evolve analyze first to extract session patterns." Exit cleanly.
If summary.eligible === 0 (learnings exist but none are eligible — already proposed, or wrong learning type — eligibility is type/file_paths-based, NOT confidence-based): > "No eligible learnings for rule proposals (total: summary.totalLearnings, already proposed or ineligible type: all). Run more sessions to accumulate evidence." Exit cleanly. Optionally list the rejection reasons from rejected[] (field reason) as an informational table.
If summary.eligible > 0 but surfaced.length === 0 (proposals exist but ALL fall below confidence-floor): > "No proposals above the confidence floor (CONFIDENCE_FLOOR). Lower reconcile.confidence-floor or run more sessions so the underlying learnings accrue confidence." Exit cleanly.
If summary.proposed === 0 but summary.eligible > 0 (emit/render failures consumed all candidates — unusual): > "Engine produced 0 proposals from N eligible learnings. See rejection log." List rejected[].reason and exit.
Only when DRY_RUN=true.
Print the proposals in a readable table. Do NOT write the sidecar, do NOT render an AUQ, and do NOT write candidates into .orchestrator/runtime/reconcile-candidates.jsonl — that store is mergeCandidates' alone (see Posture Contract). A dry-run write-up belongs in docs/reconcile/.
## Reconcile — Dry Run (N proposals, M rejected)
| # | Slug | Confidence | Learning Key | Rule Path |
|---|------|-----------|-------------|-----------|
| 1 | <slug> | 0.72 | <learningKey> | .claude/rules/<slug>.md |
| 2 | ... | ... | ... | ... |
Rejected (not eligible for proposal):
| Learning Key | Reason |
|-------------|--------|
| <key> | <reason> |
Re-run without --dry-run to enter the approval flow.Exit after printing. Do not proceed to Phase 4.
Write the proposals to .orchestrator/metrics/reconcile-pending.md as a human-readable record before presenting the AUQ. This sidecar is informational only — it lets the operator see the full proposal set in an editor alongside the AUQ prompt.
markdown# Reconcile Pending — <ISO date> Generated by `/reconcile` on <timestamp>. N proposals, M rejected. ## Proposals | # | Slug | Confidence | Learning Key | Rendered Rule Path | |---|------|-----------|-------------|-------------------| | 1 | <slug> | 0.72 | <key> | .claude/rules/<slug>.md | ... ## Rejected | Learning Key | Type | Reason | |-------------|------|--------| | <key> | <type> | <reason> | ...
Write via standard file write (not atomic, not lock-protected — this is a disposable sidecar, not a critical artifact).
Present proposals to the operator in batches of 4. Mirror the session-end Phase 3.6.3 / 3.6.8 multiSelect pattern exactly.
For each batch (proposals sliced into groups of 4):
AskUserQuestion({
questions: [{
question: "Which rule proposals should be written to .claude/rules/? (batch K of N)",
header: "Reconcile — Approve Rule Proposals",
options: [
{
label: "<slug>.md (confidence: 0.72)",
description: "Learning: <learningKey> | Path: .claude/rules/<slug>.md | <first 100 chars of rendered content>"
},
...up to 4 options per batch...
{
label: "Skip all in this batch",
description: "Decline all proposals in this batch — they are archived to the rejected log."
}
],
multiSelect: true
}]
})Collect responses across all batches:
approved[]rejected_by_operator[]> Codex CLI fallback: AskUserQuestion is unavailable in subagents and on Codex CLI > (AUQ-004). In those contexts, present proposals as a numbered Markdown list and ask > the operator to reply with the numbers they wish to approve.
writeApprovedRulesjavascriptimport { writeApprovedRules } from '$PLUGIN_ROOT/scripts/lib/reconcile/writer.mjs'; const { written, archived, errors } = await writeApprovedRules({ approved: approved, // proposals the operator approved rejected: rejected_by_operator, // proposals the operator declined repoRoot, sessionId: currentSessionId, // informational; from STATE.md or 'manual' });
writeApprovedRules NEVER throws — per-item failures are collected in errors[].
If errors.length > 0, surface each error to the operator: > "Warning: N rule(s) failed to write: <error list>. Successfully written: written. Archived: archived."
Log each error but do NOT abort — partial success is acceptable.
## Reconcile Complete
- Written: <written> rule file(s) to .claude/rules/
- Archived: <archived> declined proposal(s) to .orchestrator/reconcile.rejected.log
- Errors: <errors.length> (see warnings above, if any)
New rules take effect immediately — they are loaded by the wave-executor's rule-loader
on the next wave dispatch.If written === 0 and approved.length === 0: > "No proposals approved. No rules written."
writeApprovedRules before the operator has confirmed via AUQ — this is theonly write-protection gate for .claude/rules/.
dryRun: false to runReconcile and then skip the AUQ — the idempotencysidecar is written during the engine run; writing rules without AUQ confirmation would create an inconsistency between the sidecar and the actual rule files.
errors[] from writeApprovedRules — per-item isolation must notsilently swallow failures.
3.6.3 / 3.6.8 and keeps the operator prompt readable.
confidence-floor, rule-expiry-days, min-rule-days,min-insight-chars, and max-proposals-per-run from Session Config reconcile block — the engine reads these, but the skill must pass them explicitly.
.claude/rules/ without AUQ operator confirmation.reconcile.enabled — that flag gates the automatic session-end phase, notthis on-demand command.
alwaysApply: true — the engine structurally preventsit, but the reviewer should reject any proposal that would produce an always-on rule.
--dry-run is passed — the entire AUQ + write flowmust be bypassed.
runReconcile failures as fatal — check the error field and surface it,then exit cleanly.
Other measured skills in the registry, with their headline benchmark lift.