# Harness Compound

> 5-phase post-mortem capture. When a problem is solved, distill it into a structured doc at `docs/solutions/<track>/<category>/<slug>.md` so the next person (or agent) finds the playbook before re-deriving it.

## When to Use

- Manually, after solving a non-trivial problem (a bug fix that took >1 commit, a debugging session, an architectural decision worth preserving)
- When the orchestrator's step 6b mechanical triggers fire (deferred until Phase 7 wires it)
- When the weekly `compound-candidates` scanner surfaces a candidate (deferred until Phase 5)
- NOT for trivial fixes (typos, lint, one-line obvious)
- NOT for facts that belong in `docs/knowledge/` (use `harness-knowledge-pipeline`; the boundary is: knowledge-pipeline extracts structural facts FROM CODE; compound captures post-mortem playbooks WRITTEN AFTER A FIX)
- NOT for ephemeral session notes (`.harness/learnings.md` still exists for that)

## Process

### Iron Law

**One problem, one canonical doc.** Phase 3 (overlap-check) is mandatory. If overlap is high, UPDATE the existing doc (bump `last_updated`, append to relevant section). Do not create a duplicate.

---

### Phase 1: IDENTIFY

Extract the problem and the solution from available context:

1. Read recent conversation, the active debug session at `.harness/debug/active/*.md` if present, and recent `git log --oneline -20`.
2. Distill into a 1-2 sentence problem statement and 1-2 sentence solution statement.
3. Note the affected `module` (package name or area, e.g. `orchestrator`, `cli/validate`).

**Output:** `{ problem, solution, module, candidateTags }`.

---

### Phase 2: CLASSIFY

Read `docs/solutions/references/schema.yaml` for the authoritative track/category enum and `docs/solutions/references/category-mapping.md` for examples of which problems land in which category.

1. Choose `track`:
   - `bug-track` — a thing was broken; you fixed it. Concrete failure, concrete cause.
   - `knowledge-track` — a pattern, convention, or decision worth preserving as guidance, not a fix.
2. Choose `category` from the enum for that track. If nothing fits, **stop and escalate** — adding categories requires a PR (Decision 8).

**Output:** `{ track, category }`.

---

### Phase 3: OVERLAP-CHECK

1. List every existing `docs/solutions/<track>/<category>/*.md`.
2. For each, read the `# <Title>` heading and the first 200 chars of the `## Problem` (bug-track) or `## Context` (knowledge-track) section.
3. Compute overlap heuristically: shared `module`, shared `problem_type`, similar problem statement (Jaccard on bag-of-words >= 0.5 is a reasonable threshold; the agent uses judgment).
4. If high overlap with one existing doc:
   - Open the existing doc.
   - Update relevant sections (append a new bullet under `## Solution` or `## Guidance` describing the new instance, bump `last_updated`).
   - Skip Phase 4 and most of Phase 5 (only the write-with-lock step runs).
5. If no overlap, proceed to Phase 4.

---

### Phase 4: ASSEMBLE

1. Copy `docs/solutions/assets/resolution-template.md` to a working buffer.
2. Fill the frontmatter:

   ```yaml
   ---
   module: <from Phase 1>
   tags: [<from Phase 1 candidateTags, lowercase, hyphenated>]
   problem_type: <short noun phrase, e.g. 'race-condition'>
   last_updated: '<YYYY-MM-DD, today>'
   track: <bug-track | knowledge-track>
   category: <from Phase 2>
   ---
   ```

3. Replace the `# <Title>` placeholder with a concise problem statement.
4. For `bug-track`: fill `## Problem`, `## Root cause`, `## Solution`, `## Prevention`. Delete the knowledge-track sections (`## Context`, `## Guidance`, `## Applicability`).
5. For `knowledge-track`: fill `## Context`, `## Guidance`, `## Applicability`. Delete the bug-track sections.
6. Cite commit SHAs and `file:line` where helpful.

---

### Phase 5: WRITE (lock-protected)

1. Compute slug: kebab-case from the title; if a file with that slug already exists in the target directory, append `-2`, `-3`, etc.
2. **Acquire the per-category lock by shelling out.** Run a Node one-liner that imports `acquireCompoundLock` from `@harness-engineering/core`, holds the handle while you write the doc, then releases it. Example: `node -e "import('@harness-engineering/core').then(({ acquireCompoundLock }) => { const h = acquireCompoundLock('<category>'); /* write the doc here */ h.release(); }).catch(err => { console.error(err.message); process.exit(1); })"`. Lock path: `.harness/locks/compound-<category>.lock`. The MCP tool `acquire_compound_lock` is planned for a later phase — **do not attempt to call it yet**; the shell-out is the only supported path right now.
   - On `CompoundLockHeldError`: report "compound lock for category `<category>` is held by pid `<N>` — wait for it to release or run `/harness:compound` for a different category" and stop. **Do not retry automatically.** A second invocation on the same problem after release will go through Phase 3 and find the doc the first invocation produced.
3. Re-run a quick Phase 3 overlap-check inside the lock (defends against TOCTOU when the first overlap-check returned "no overlap" but another invocation completed in the meantime; the re-check is cheap).
4. Write the file at `docs/solutions/<track>/<category>/<slug>.md`.
5. Validate frontmatter against `SolutionDocFrontmatterSchema` by running `harness validate` (which runs `validateSolutionsDir`).
6. Release the lock.
7. Surface to chat: file path created (or updated), category, and a one-sentence summary.

## Harness Integration

- **`harness validate`** — Run after writing the doc; the solutions validator catches frontmatter errors before commit.
- **`harness check-deps`** — Not required (no new module imports introduced by writing a doc).
- **`@harness-engineering/core` lock primitive** — `acquireCompoundLock(category, { cwd })` returns a release handle; throws `CompoundLockHeldError` on contention. See `packages/core/src/locks/compound-lock.ts`.
- **Schema authority** — `packages/core/src/solutions/schema.ts` is the single source of truth for tracks and categories. `docs/solutions/references/schema.yaml` mirrors it for human reading.
- **Boundary with `harness-knowledge-pipeline`** — Knowledge-pipeline extracts structural facts FROM CODE. Compound captures post-mortem playbooks WRITTEN AFTER A FIX. Compound's knowledge-track output is a _candidate input_ to the pipeline (Phase 7 of the spec wires this).
- **Boundary with `.harness/learnings.md`** — The file remains for ephemeral session notes. It is no longer the canonical sink for compounding knowledge — that's `docs/solutions/`.

## Success Criteria

- A new solution doc is written to `docs/solutions/<track>/<category>/<slug>.md` with valid frontmatter (passes `validateSolutionsDir`).
- Two concurrent invocations on the same category cannot both succeed: one writes, the other returns `CompoundLockHeldError`.
- A second invocation on the same problem updates the existing doc instead of creating a duplicate.
- The skill never invents a new category — unknown categories are escalated.
- PII is not written into the doc (the agent reads from local conversation/commits; no remote queries).

## Examples

### Example: bug-track

Input: "Stalled lease cleanup in orchestrator caused stuck issues. Fix was to add a 5-minute lease TTL with a sweep at startup. Took 4 commits, debugged via `harness-debugging`."

- Phase 1: `module=orchestrator`, problem="stuck issues from stalled leases", solution="lease TTL + startup sweep".
- Phase 2: `track=bug-track`, `category=integration-issues` (lease coordination is integration-shaped).
- Phase 3: no overlap.
- Phase 4: fill Problem / Root cause / Solution / Prevention.
- Phase 5: write `docs/solutions/bug-track/integration-issues/stalled-lease-cleanup.md` under lock.

### Example: knowledge-track

Input: "We standardized on `Result<T, E>` returns for all I/O paths in `packages/core`. Document the convention and when not to use it."

- Phase 1: `module=core`, problem="when to use Result vs throwing", solution="convention doc".
- Phase 2: `track=knowledge-track`, `category=conventions`.
- Phase 3: no overlap.
- Phase 4: fill Context / Guidance / Applicability.
- Phase 5: write `docs/solutions/knowledge-track/conventions/result-type-for-io.md` under lock.

### Example: overlap-check updates existing doc

Input: "Hit the same stalled-lease bug today on a different code path."

- Phase 1, 2 produce the same `bug-track/integration-issues` target.
- Phase 3 finds `stalled-lease-cleanup.md` with high overlap.
- Append a bullet to `## Solution` describing the new instance, bump `last_updated`, do not create a new file.

## Gates

- **Phase 3 is mandatory.** No exceptions. Skipping overlap-check produces duplicate docs and erodes the value of the corpus.
- **No invented categories.** Unknown categories require a PR to `packages/core/src/solutions/schema.ts`. Escalate.
- **Lock must wrap Phase 5.** Without the lock, two concurrent invocations on the same category race on overlap-check and produce duplicates.
- **`harness validate` must pass before exit.** Frontmatter errors silently break the corpus.

## Escalation

- **Cannot decide track/category:** Escalate to the user. Show the candidate (track, category) pairs and the rationale for each. Wait for selection.
- **Lock held by another invocation:** Report and stop. Do not retry. The user re-runs after release.
- **`validateSolutionsDir` rejects the doc:** Show the validator error, fix the frontmatter, re-validate. Do not commit a doc that fails validation.
- **Problem does not fit any category:** Escalate. Adding a category requires a PR (Decision 8 of the feedback-loops spec).
