# Procedural memory

Procedural memories are first-class **`category: procedure`** items stored under `memoryDir/procedures/YYYY-MM-DD/` as markdown (same persistence path as other memories via `StorageManager.writeMemory`). They capture ordered **steps** (human-editable in the body) plus YAML frontmatter for lifecycle, provenance, and review state.

This feature tracks [issue #519](https://github.com/joshuaswarren/remnic/issues/519).

Also indexed from the repo [README](../README.md) (Features + Configuration), [Getting started](getting-started.md) (Next steps), and [Config reference](config-reference.md) (Procedural memory section) so operators and agents see the **`procedural.enabled`** gate without opening this page first.

## Enablement

Everything behavioral is gated by plugin config **`procedural.enabled`** (default **`true`** since issue #567 PR 4/5; previously `false`). When explicitly disabled (`false`, `"0"`, `"no"`, or `"off"`):

- Direct extraction does not emit new procedure memories.
- Intent-gated recall does not inject a procedure section.
- The nightly miner MCP entry returns without writing files.

Operators who want to stay opt-out must set `procedural.enabled: false` explicitly. Mirror the same keys under `openclaw.plugin.json` / host config as for other Engram-style toggles.

### Migration from default-off

If you are upgrading from a Remnic build where procedural memory shipped disabled (pre-#567), no action is required — existing memories and trajectory records continue to work. Fresh installs and any config that omits `procedural.enabled` now enable the feature using the safer-by-default thresholds from slice 3 (`successFloor=0.75`, `lookbackDays=14`, `recallMaxProcedures=2`). The lift number justifying the flip is documented in [`docs/benchmarks/procedural-recall.md`](./benchmarks/procedural-recall.md) and captured in the committed `packages/bench/baselines/procedural-recall-baseline.json` artifact.

## Taxonomy and filing

- `MemoryCategory` includes `"procedure"`.
- Default taxonomy exposes a **`procedures`** bucket (priority between principles and entities).
- `category-dir` maps `procedure` → `procedures/`.

## Extraction (user-taught workflows)

When the extractor proposes `category: "procedure"`, the **extraction judge** requires at least **two steps** and **explicit trigger-style** phrasing before the memory is accepted. Failed checks drop the candidate rather than downgrading silently.

## Recall (task initiation)

On prompts that look like **starting hands-on work** (deploy, ship, open a PR, run tests, etc.), the orchestrator may inject a **`## Relevant procedures`** block built from **active** procedure files only. **`pending_review`** miner suggestions are not injected by default.

Relevant config keys include:

- `procedural.recallMaxProcedures` — cap on injected procedure previews. **Default `2`** (lowered from the earlier `3` in issue #567 PR 3/5 to keep procedural injection from crowding out other recall sections once the feature is enabled by default).

See also: [Advanced retrieval](./advanced-retrieval.md) and [Retrieval pipeline](./architecture/retrieval-pipeline.md).

## Safer-by-default thresholds (issue #567 PR 3/5)

The procedural mining + recall defaults are tuned so the feature stays safe when it is enabled by default in the slice-4 config flip:

| Key | Default | Notes |
| --- | ------- | ----- |
| `procedural.minOccurrences` | `3` | At least three clustered trajectories before a candidate procedure is emitted. Set to `0` to disable mining entirely. |
| `procedural.successFloor` | `0.75` | Miner promotion requires ≥ 75% trajectory success. Raised from `0.7` in #567 PR 3 to reduce false positives. |
| `procedural.autoPromoteOccurrences` | `8` | When auto-promote is on, pending_review procedures wait for eight occurrences before becoming active. |
| `procedural.lookbackDays` | `14` | Trajectory miner window. Lowered from `30` in #567 PR 3 so mined procedures stay recent. |
| `procedural.recallMaxProcedures` | `2` | Cap on injected procedure previews per recall. Lowered from `3` in #567 PR 3. |

Operators who need to override any of these should do so explicitly; all fields accept CLI-style string inputs and JSON numbers.

## Mining (trajectories)

A dedicated miner clusters **causal trajectory** records (bounded lookback by `recordedAt` / `lookbackDays`) and can write **`status: pending_review`** procedure candidates. Promotion to **`active`** respects optional auto-promote rules and avoids clobbering user-edited bodies.

Automation is **not** part of `runMemoryGovernance`. Use the MCP tool **`engram.procedure_mining_run`** (and optional cron registration mirroring other nightly jobs) so procedural mining stays isolated from shadow/apply governance.

## Stats surface (issue #567 PR 5/5)

Operators can inspect procedural memory health via three matched surfaces that all return the same `ProcedureStatsReport` JSON shape (schema v1):

- **CLI:** `remnic procedural stats [--format json|text] [--memory-dir <path>]`
- **HTTP:** `GET /engram/v1/procedural/stats?namespace=<optional>`
- **MCP tool:** `remnic.procedural_stats` (with legacy alias `engram.procedural_stats`), argument `{ namespace?: string }`

The report includes:

- `counts` — procedure files by status: `total`, `active`, `pending_review`, `rejected`, `quarantined`, `superseded`, `archived`, `other`.
- `recent` — `lastWriteAt` (ISO 8601 or `null`), `writesLast7Days` (exclusive upper bound per CLAUDE.md rule 35), and `minerSourced` count (procedures whose `source=procedure-miner`).
- `config` — snapshot of the active `procedural.*` config so the caller can confirm the gate state and threshold floor in the same payload.

All three surfaces are read-only and namespace-scoped (CLAUDE.md rule 42). The HTTP endpoint resolves the namespace through the same layer used by `/recall/explain` and `/trust-zones/status`; the MCP tool uses the authenticated principal. The CLI reads from whatever `memoryDir` the current config / active-space resolves to, or an explicit `--memory-dir` override.

## Pattern reinforcement CLI (issue #687 PR 4/4)

The `remnic patterns` command group exposes the pattern-reinforcement output written by the maintenance job (PR 2/4).  Both subcommands read from the active `memoryDir` and require no extra config.

### `remnic patterns list`

Lists memories whose `reinforcement_count > 0`, sorted by count descending.

```bash
remnic patterns list [--limit N] [--category cat1,cat2] [--since ISO] [--format text|markdown|json]
```

| Flag | Description | Default |
| --- | --- | --- |
| `--limit N` | Maximum rows to show (positive integer) | 50 |
| `--category list` | Comma-separated category filter | all categories |
| `--since ISO` | Only include memories reinforced on or after this ISO 8601 timestamp | all time |
| `--format fmt` | Output format: `text`, `markdown`, or `json` | `text` |

### `remnic patterns explain <memoryId>`

Shows the full reinforcement picture for a single canonical: reinforcement count, `last_reinforced_at`, `derived_from` provenance chain (page-version refs stamped by PR 2/4), canonical body, and cluster members (memories whose `supersededBy` points at this canonical).

```bash
remnic patterns explain <memoryId> [--format text|markdown|json]
```

Exits with code `1` and a descriptive error if `<memoryId>` is not found or has no `reinforcement_count > 0`.

Invalid flag values (`--format xml`, `--limit 0`, `--since not-a-date`) throw a listed-options error rather than silently defaulting (CLAUDE.md rule 51).

## Benchmark

The **`procedural-recall`** benchmark in `@remnic/bench` scores:

1. **Task initiation gate** — deterministic intent classification vs. labeled prompts.
2. **Procedure section gate** — temp `memoryDir` round-trip: whether a non-null recall section is produced when expected (feature on/off and non-task prompts).

Run a quick pass:

```bash
npm run bench:run -- --quick procedural-recall
```
