> 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.
Scanned 9/11/2026
Install to Claude Code
npx -y skills add Intense-Visions/harness-engineering --skill harness-compound --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Harness Compound?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/intense-visions-harness-compound-5bf41860)More formats (shields.io, HTML) on the badges page.
# 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>
# Optional (ADR 0100) — include ONLY if this fix landed or hardened an
# enforced rule; omit the key entirely otherwise (fill-forward).
# enforces: [STRENGTH-002]
---
```
3. **Rule-to-failure provenance (ADR 0100 — optional, never blocks).** If, and only if, the fix in this post-mortem _produced or hardened an enforced rule_ — a `STRENGTH-*` rule under `packages/core/src/harness-strength/rules/`, or an analogous rule descriptor such as an `arch:` / `sec:` constraint — add an `enforces:` list to the frontmatter naming those rule id(s):
```yaml
enforces: [STRENGTH-002]
```
This is the reciprocal of each rule's `origin` field; together they let `harness rules provenance` answer "why does this rule exist?" mechanically and detect dead rules. It is **advisory and optional** — if the fix landed no enforcement change, omit the key entirely (legacy docs stay valid; the field is fill-forward). Never block capture on it, and do not invent rule ids — cite only ids you can point to in the diff.
4. Replace the `# <Title>` placeholder with a concise problem statement.
5. For `bug-track`: fill `## Problem`, `## Root cause`, `## Solution`, `## Prevention`. Delete the knowledge-track sections (`## Context`, `## Guidance`, `## Applicability`).
6. For `knowledge-track`: fill `## Context`, `## Guidance`, `## Applicability`. Delete the bug-track sections.
7. 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 via the harness MCP server.** Call `acquire_compound_lock({ path: process.cwd(), category: '<category>' })`. On success it returns `{ acquired: true, token, lockPath }` — hold the `token` while you write the doc, then call `release_compound_lock({ token })` when finished. The MCP server holds the file handle and registers process-exit cleanup, so an abandoned agent does not leave a dangling lock-with-stale-PID. Lock path: `.harness/locks/compound-<category>.lock`. Fallback for environments without the MCP server (only works when `@harness-engineering/core` is resolvable from the project): `node -e "import('@harness-engineering/core').then(({ acquireCompoundLock }) => { const h = acquireCompoundLock('<category>'); /* write the doc here */ h.release(); })"`.
- On `{ acquired: false, error: 'CompoundLockHeldError', holderPid }` (or `CompoundLockHeldError` when invoked directly): report "compound lock for category `<category>` is held by pid `<holderPid>` — 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 via `release_compound_lock({ token })` (or `handle.release()` on the fallback shell-out path).
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 MCP tools consumed by this skill** (canonical execution path — no project-local `@harness-engineering/core` required):
- `acquire_compound_lock({ path, category })` — returns `{ acquired: true, token, lockPath }` on success or `{ acquired: false, error: 'CompoundLockHeldError', holderPid, lockPath }` on contention.
- `release_compound_lock({ token })` — releases the lock; idempotent on repeat calls with the same token.
- **`@harness-engineering/core` lock primitive** (only directly relevant when the MCP server is unavailable) — `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).
## Rationalizations to Reject
These are common rationalizations that sound reasonable but lead to incorrect results. When you catch yourself thinking any of these, stop and follow the documented process instead.
| Rationalization | Why It Is Wrong |
| ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| "This problem is obviously new — I'll skip the Phase 3 overlap-check and write the doc directly." | Phase 3 is mandatory, no exceptions. "Obviously new" is exactly the assumption that produces duplicate docs and erodes the corpus's value. List the existing docs in the target category and compute overlap before writing. |
| "No category fits perfectly — I'll invent one that describes this problem better." | The track/category enum in `packages/core/src/solutions/schema.ts` is the single source of truth. Inventing a category requires a PR (Decision 8). Stop and escalate; a mislabeled doc is worse than a delayed one. |
| "The compound lock is held by another pid — I'll retry in a loop until it frees." | The contract is report-and-stop, never auto-retry. The holding invocation will produce the canonical doc, and a later re-run on the same problem goes through Phase 3 and finds it. Busy-looping defeats the per-category serialization the lock exists to provide. |
| "Overlap is high, but my instance is a little different, so a fresh doc is cleaner." | Iron Law: one problem, one canonical doc. A near-duplicate splits the playbook across two files. Append a bullet under `## Solution`/`## Guidance` and bump `last_updated` instead of forking. |
| "The doc is written and the frontmatter looks right — I'll skip `harness validate`." | Frontmatter errors silently break the corpus (`validateSolutionsDir` is what catches them). Validation is a gate that must pass before exit; a doc that fails schema is not "done." |
## 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).
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!