# Friction Diagnostics

> Log genuine surprise — reality diverged from what you predicted — as structured friction records with recurrence tracking. Use when: (1) A tool, command, instruction, document, or assumption behaved differently than expected, in any domain (code, non-code, writing, research, ops), (2) An action silently no-opped, half-applied, or succeeded without its intended effect, (3) Behavior differs across environments or runs in a way you did not predict, (4) An expected failure occurred for a different reason than predicted, (5) A known trap bit again — file --recur against the earlier event, or (6) The user asks to log a friction incident. Worth-logging test: would the record change what a future session does? Do not use for outcomes you predicted for the reason you predicted (intended test failures, expected error paths, probes), for task status, or for reviewing or mending accumulated friction (that is friction-mend).

- Skill: `devguyrash/friction-diagnostics` (Agent Skill, multi-file: 22 files)
- Install (CLI): `npx skillmds@latest add devguyrash/friction-diagnostics`
- Raw SKILL.md: https://api.skillmd.com/api/skills/devguyrash/friction-diagnostics/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Research & Search
- Author: DevGuyRash (https://skillmd.com/u/devguyrash)
- Updated: 2026-09-21
- Page: https://skillmd.com/skills/devguyrash/friction-diagnostics

---


# Friction Diagnostics

## Mission

You capture genuine surprise — the moment reality diverged from what you predicted — as a structured record that traces what you trusted and what you were missing, so a later session can mend the instruction, artifact, or assumption that misled you. You are done when the surprise is in the store with a receipt and the user knows what you filed.

Everything beyond logging — what to fix, how to work, what to try next — stays entirely with you. Logging is supplemental; it never limits how you operate.

WHEN reality diverges from what you predicted AND recording it would change a future session's behavior THEN you SHALL file an event at that moment, not batched at task end.

WHEN the divergence matches a known trap or an open event THEN you SHALL file `--recur <event-id>` instead of a new event.

WHEN friction looks transient AND it would matter if it recurred THEN you MAY file a minimal anchor event, short fields and all, so a later `--recur` has a target.

## Environment

Inside a git repo the store is `<repo>/.local/reports/friction/events.jsonl`; an existing `.local*` area is reused, and `--events-file` overrides. Outside git it is `<system-temp>/agent-friction/<cwd-hash>/events.jsonl`.

One append-only stream holds three record kinds: `friction` (full capture), `recurrence` (cheap repeat pointer), and `resolution` (mend provenance, written by friction-mend). The tool only appends; the sole in-place write is `--add-tags`/`--add-aliases`, which patches one record's tag arrays under the write lock.

`INDEX.md` and `known-traps.md` sit next to the stream and are regenerated by tooling. `known-traps.md` is at most 15 one-line traps, and reading it before you act is the cheapest way to avoid paying for a trap twice. The session-start hook prints the open-anchor count, the top recurring keys, and the traps path whenever the repo store has open events — those recurring keys are ready `--recur` targets.

`build-index.sh` and `lifecycle.py` are internal. Every piece of lifecycle state you need comes back from `query-friction.sh` or from the talkback; do not derive open or closed yourself.

The tool briefs you after every filing: similar prior events with counts, tag history, open clusters, and the traps count. You never need to remember or search the stream before filing — the store briefs you at the point of action.

The append is the commit point. `FRICTION_EVENT_ID=` on stdout means the record is in the store; the index rebuild runs after that line, and a rebuild that fails prints a warning while the record stays committed.

The write lock is bounded by `FRICTION_LOCK_TIMEOUT`, default 15 seconds. On timeout the error names the lock owner's pid and creation time.

| Code | Meaning |
| --- | --- |
| 0 | record written; the receipt names the event id |
| 1 | invalid arguments, unrecoverable validation failure, or the write lock was not acquired within the bounded wait |
| 2 | input error on `--from-json` — nothing was filed. Malformed JSON is saved to a quarantine file; empty stdin is not, because there was nothing to save |
| 3 | duplicate soft-stop: an identical recurrence key already exists. Nothing was written |

## Boundaries

You SHALL NOT file outcomes you predicted or engineered, nor task status.

WHEN you filed nothing AND the user did not raise friction diagnostics THEN you SHALL NOT narrate friction diagnostics — no mention of the skill, and none of the decision not to file. Considering whether to file is always in scope; only the narration is not.

You SHALL NOT propose or prescribe a fix anywhere in a record. Mending is a separate activity with its own skill; your own completed response is history and belongs in `decision`, while what the misleading sources should say instead belongs to mending.

You SHALL NOT write a quotation you did not read. When what you acted on was a recollection rather than something you consulted, the `memory` source kind is its honest home.

You SHALL NOT hand-edit `events.jsonl`, `INDEX.md`, or `known-traps.md` [`INDEX.md` is regenerated on every write and `known-traps.md` only by the mend publisher; the events file is the one genuinely exposed to an editor].

## Filing

JSON via stdin is the primary path: it is safe for backticks, quotes, and multiline text. The placeholders are the questions to answer.

```sh
printf '%s' '{
  "actual_outcome":    "<what actually happened - exact text when text exists; else a labeled observation, measurement, state change, or explicit non-occurrence>",
  "expected_outcome":  "<what you predicted, and what specifically grounded that prediction>",
  "reading":           "<from inside the decision: what you consulted, what you believed it said, what you did, the moment reality diverged>",
  "decision":          "<what you did about it: options seen, options set aside, the action taken - and, for any deviation from something documented as required, what made it feel permitted at the time>",
  "pivot_information": "<the single piece of information that would have changed the outcome, and where it lives - or the fact a future agent should check first, when you caught this before harm - or: none - unknowable in advance, because ...>",
  "sources":           [{"kind": "<artifact|instruction|tool|assumption|memory|observation>",
                         "ref": "<path, url, tool name, or belief label>",
                         "claim": "<what you believed it said or would do - the prior belief only>"}],
  "impact":            "<blocked|degraded|noisy|continued>",
  "recurrence_key":    "<2-5 hyphenated words naming the trap; omit if unsure>",
  "tags":              ["<optional labels>"],
  "note":              "<optional free slot: whatever mattered that no field asked for>"
}' | sh <skills-file-root>/scripts/report-friction.sh --from-json -
```

The placeholders and questions are prompts, not perimeters — if something mattered that no question asked about, include it; whatever fits no field goes in `note`. The question shape never excuses omission.

Write the evidence down before you explain it. The tool imposes the stored key order regardless of what you send, so the order in the skeleton is a discipline for you and not for the record: an explanation composed first reshapes the evidence it was supposed to describe.

Direct flags exist for short single-source payloads with no shell-sensitive text: `--actual-outcome`, `--expected-outcome`, `--reading`, `--decision`, `--pivot-information`, `--source-kind`, `--source-ref`, `--source-claim`, `--impact`, `--recurrence-key`, `--tags`, `--note`. `--title` is derived from `actual_outcome`, and leaving it off keeps the title and the evidence in step. `--interview` prints rotated eliciting questions when you are unsure what a field wants.

### Repeats

```sh
sh <skills-file-root>/scripts/report-friction.sh --recur evt-0142 --actual-outcome "<short verbatim of this occurrence>" [--note "<what differed this time>"]
```

A recurrence record is one line: it carries this occurrence's outcome and an optional note, and inherits the anchor's impact. The `--recur` path stores only those two fields, so the narrative cannot be recomposed through it.

When neither the talkback nor the session-start hook has already named an anchor, find one with `sh <skills-file-root>/scripts/query-friction.sh --key <recurrence-key>`.

## Fields

You SHALL file a record in which:

1. `actual_outcome` carries the best primary evidence you have: the exact output or wording where text exists, otherwise a labeled firsthand observation, measurement, state change, or explicit non-occurrence.
2. `expected_outcome` states the prediction you held before acting and what grounded it.
3. `reading` traces the account from inside the decision to the point where reality stopped matching your model.
4. `decision` reports in past tense the response you actually made — and, for any deviation from something documented as required, the license you held at the moment you chose.
5. `pivot_information` names a fact and where that fact lives, or states that the outcome was unknowable in advance and why.
6. `sources` lists at least one input that supported the prediction, each with the prior belief you held about it.
7. `impact` is `blocked`, `degraded`, `noisy`, or `continued`.
8. `recurrence_key` names the trap rather than this occurrence, or is omitted.

Short is fine when the evidence is short (`EPIPE`). Write-time redaction takes precedence over verbatimness, and a redacted quote is still the quote.

`<skills-file-root>/references/examples.md` defines what each field is, the kinds a source can be, and the structural ways each field goes wrong.

## Loop

WHEN the receipt line `FRICTION_EVENT_ID=` has printed THEN the record is in the store and you SHALL NOT file it again, whatever warnings follow it [the append is the commit point, and the index rebuild that runs after it reports its own failure as a warning].

IF the tool exits 3 AND this is the same trap as the event it names THEN you SHALL re-file it as `--recur <that event id>` ELSE you SHALL re-file it with `--distinct`.

IF the tool exits 2 AND it saved your payload to a quarantine file THEN you SHALL correct that file and re-file from it ELSE you SHALL recompose the payload and re-file. Nothing is written on exit 2, so re-filing cannot duplicate; the quarantine exists so a long narrative is not retyped.

WHEN the write lock cannot be acquired within the bounded wait THEN you SHALL read the owner named in the error before you remove anything. The order carries the hazard: a lock removed under a live writer interleaves two appends into one line.

WHEN filing fails for a reason you cannot resolve THEN you SHALL continue the task and report the unfiled surprise in your final response, with what you would have filed. This is the one case where friction reaches the user without a record behind it, and it is not a licence to narrate filing decisions generally.

## Verification

Read the record back as a stranger would. It is complete when, from the record alone:

1. someone with no access to your transcript can say what happened, what you expected, and where the two parted
2. `sources` names what shaped your prediction rather than what displayed the failure
3. a future session reading it would do something differently
4. the event id you cite in your summary is the one the receipt printed

## Precedence

WHEN filing at the moment of surprise conflicts with staying silent about friction diagnostics THEN filing prevails and silence yields: you file, and you say nothing about it. Silence governs your response, not your actions.

WHEN a direct instruction from the user conflicts with this skill THEN the user's instruction prevails and this skill yields, including an instruction to stop filing.

WHEN this file conflicts with a file under `<skills-file-root>/references/` THEN this file prevails on what a record must contain and the reference prevails on how the tool behaves.

WHEN clauses collide with no tiebreak written THEN the prohibition beats the mandate; failing that you SHALL take the more reversible course and escalate. Here the more reversible course is nearly always a minimal anchor and silence: an anchor costs one line, and a later `--recur` can build on it.

## Output contract

WHEN your task is complete AND you filed new records this session THEN you SHALL close your final response with a friction summary proportional to the count.

IF you filed one or two records AND the task itself was not friction work THEN you SHALL give one sentence each — event id, recurrence key, and what surprised you — ELSE you SHALL render the summary and include it:

```sh
sh <skills-file-root>/scripts/render-summary.sh --events-file <events-file> --after "<lower-bound-timestamp>"
```

`--output-format list` or `markdown` overrides the automatic table choice when the host renders tables badly, and `--date-from` with today's date works when no lower bound is known. The summary is how the user sees what went wrong without having to ask.

## References

- `<skills-file-root>/references/examples.md` — what each field is, and the structural ways each one goes wrong
- `<skills-file-root>/references/logging-spec.md` — storage contract, record kinds, lifecycle, caps, sanitization, exit codes
- `<skills-file-root>/references/integration.md` — host integration, session linkage, query and report flags, the optional ambient snippet

