icm-maintain
A context workspace decays under use: hot files regrow, catalog rows point
at moved files, a review date passes and nobody notices, a ledger entry
expires in silence, a briefing goes stale while still sounding current.
This skill is the routine that keeps that from compounding — mechanical
first, judgment second, and never more authority than an unattended session
can be trusted with. The system should not wait for someone to remember it.
This skill operates on an ICM workspace — Interpretable Context Methodology,
Van Clief & McDermott, arXiv:2603.16021 (https://arxiv.org/abs/2603.16021).
Build the workspace with their icm-architect skill
(https://github.com/RinDig/icm-architect), or by hand from the paper: a
front desk (CLAUDE.md / AGENTS.md), 00-catalog/CATALOG.md, and dated
markdown files with frontmatter — the example/ tree in the icm-ops repo
is a complete minimal one. This pack is a layer on top of the method, not
part of it.
The checker
python <skill folder>/scripts/icm_check.py <workspace-root>
python <skill folder>/scripts/icm_check.py <workspace-root> --json
The skill folder is usually .claude/skills/icm-maintain/,
.agents/skills/icm-maintain/, ~/.claude/skills/icm-maintain/, or
skills/icm-maintain/ in a clone of the icm-ops repo.
Options: --json (the findings as a machine-readable object for a test or
a hook), --config PATH (an icm-ops.json that is not at the root),
--today YYYY-MM-DD (pins the clock for tests and for reproducing an old
report; the header line says so), --version.
Exit 0 = no FAIL findings; 1 = at least one FAIL; 2 = usage or
configuration error (bad icm-ops.json, missing catalog; nothing on
stdout); 3 = internal error (traceback on stderr; --json still prints
an object with an error key). The checker is read-only: it reports
and never fixes. Its report is the worklist. It covers:
| Finding |
Level |
What it means |
| Catalog row points at a missing path |
FAIL |
The map lies. |
| Markdown file with no catalog coverage |
WARN |
Invisible to every session. |
| Relative link does not resolve (inline or reference-style; code is not scanned) |
FAIL |
A pointer to nowhere. |
| Missing frontmatter |
WARN |
Type and date unknown. |
| Hot file over its binding budget |
FAIL |
Compaction is due (below). |
hot_budgets key that names no file |
WARN |
A budget guarding nothing is a rule switched off, usually by a typo. |
| Other file over the size target |
WARN / FAIL |
Doing two jobs; historical records WARN, working files FAIL. |
Decision without review-by: / past it |
WARN |
Owner call needed. |
| Ledger L/D entry past REVIEW-BY or KNOW-BY and still open |
WARN |
Resolve or mark EXPIRED. |
File in the ledger folder that is not a YYYY-MM.md month file |
WARN |
Not read as a ledger; its entries and readouts are invisible. |
BRIEFING.md older than the newest readout |
FAIL |
A stale correction wearing a fresh voice. |
checked: DATE · source · stale after: N days window blown |
WARN |
An external number describing the past as the present. |
| Newest log dated more than one day ahead of the clock |
FAIL |
A date written from a wrong clock; one day of grace covers time zones (a log dated tomorrow is INFO). |
Protected file below 40% of its .bak |
FAIL |
Possible truncation — recover, do not overwrite the .bak. |
| Malformed date at any site |
WARN |
Not YYYY-MM-DD or not a calendar date. |
| Readout heading without a parseable date |
WARN |
The staleness check would otherwise switch itself off. |
| Unreadable file, unlisted directory, skipped link or loop |
WARN |
Part of the tree was not audited. |
The header line prints the clock (or the --today override); the newest
log's age is an INFO line. Dates are claims; the header line is the clock.
On ledger entries the checker reads only an entry's id, its dates, its
open marker and the presence of the required field lines; it does not
judge the content of CLAIM, FALSIFIER or COST. Formats: FORMAT.md in the
icm-ops repo, sections "Checker output" (FORMAT.md#checker-output) and
"icm-ops.json" (FORMAT.md#icm-opsjson).
Budgets are FAILs, not WARNs, by design: a WARN that stays yellow for weeks
becomes wallpaper. One operator measured the decay (2026-08-25): a state
file regrew from 232 to 273 lines in the seven days after a trim, with WARNs
firing the whole time. Binding budgets plus a five-minute routine held it
after that.
The checker owns the line-count definition (wc -l semantics) because it
is the tool that fails the build; an off-by-one at a binding threshold is
real.
Configuration — icm-ops.json at the workspace root (optional)
Defaults are the ICM conventions. Override per workspace with any subset:
| Key |
One line |
hot_budgets |
Map of path → max lines that FAIL when exceeded. Default: 04-memory/STATE.md 250, 00-catalog/CATALOG.md 155, 04-memory/maintenance-log.md 60. |
protected |
Paths that get a .bak after a clean run and a truncation FAIL if they collapse. Default: front desk, CATALOG, CONVENTIONS, STATE, BRIEFING, maintenance-log, plus the newest ledger month and newest log. |
deliverable_markers |
Path substrings marking client-facing content inside artifacts; exempt from orphan and frontmatter checks (their parent row covers them), and on size treated as a record: never a FAIL, a WARN past twice the target. Default: none (empty list). Example: ["/product/", "/sample/"]. |
frontmatter_exempt |
Root-level files that need no frontmatter. Default: CLAUDE.md, AGENTS.md, README.md. |
size_target_lines |
Soft size target; over it WARNs, over twice it FAILs (working files) or WARNs (historical). Default: 200. |
historical_dirs |
Directories whose files are records, not working files — oversize is a WARN there. Default: 04-memory/log/, plus ledger month files. |
log_dir |
Where daily YYYY-MM-DD.md logs live; the clock check and the newest-log protection read it. Default: 04-memory/log/. |
ledger_dir |
Where month files, readouts, and BRIEFING.md live. Default: 04-memory/ledger/. |
decisions_dir |
Where numbered decision records with review-by: live. Default: 01-context/decisions/. |
catalog |
The map the coverage and row checks read. Default: 00-catalog/CATALOG.md. |
Raising a budget is an owner call, recorded with a reason in the workspace.
A budget raised to dodge a compaction is the wallpaper failure wearing a
number.
First run
With no icm-ops.json the defaults above apply. Create
04-memory/maintenance-log.md from the template (the maintenance-log rule
below names it) and give it a catalog row. Expect the "no .bak yet" INFO
lines until a clean supervised run writes the .bak copies. A workspace
with no ledger yet follows icm-ledger's own First run section.
Run maintenance — the workspace fixes its own errors, within limits
A scheduled session runs this daily or weekly (the example workspace runs
weekly); any session may run it on demand. About fifteen minutes.
Compaction (supervised) logs to the daily log; unattended runs log to the
maintenance log.
- Run the checker. The report is the worklist.
- Authorized to fix without asking — additive or reversible ONLY;
nothing on this list can lose information:
- Hot file over budget → detect and STAGE, never apply. Write the
proposed keep/move diff to
04-memory/maintenance-log.md under
"Proposed compaction," and stop. Compaction is the one subtractive
operation in the system and it takes the keep/move judgment; an
unattended session that cuts a live guardrail produces a green check
that the next unattended run trusts — the producer declaring itself
verified, daily, forever. A person or a supervised session applies it.
- Missing frontmatter → add it (type by folder,
updated: from the
file's mtime).
- Catalog row pointing at a missing file → move the dead row to the
maintenance log under Needs owner. Orphan file → add a row if its
purpose is evident from the file itself; else list it for the owner.
- Broken relative link → repair if the target is findable by name
search; else report.
- Ledger entry past its date, still open → mark
EXPIRED per
icm-ledger and list it under Needs owner with any evidence pointer
found in the logs; an unattended run never attaches an outcome.
- Decision past
review-by: → do NOT reopen it; add one line flagging it
for the owner and renew nothing.
- Stale
BRIEFING.md (FAIL) → regenerate it AND the front-desk block
from the newest readout, per icm-ledger.
- Never: raise budgets, delete files, edit money surfaces (listings,
proposals, anything a client or a platform reads), publish anything,
resolve D-entries (both parties present for that), or edit decisions.
Judgment stays with sessions the owner is driving. A maintenance session
that improvises is worse than one that waits.
- Re-run the checker. It must end 0 FAIL — except a hot-file budget FAIL,
which ends as a staged proposal awaiting a human.
- Log to
04-memory/maintenance-log.md, not the daily log — one line
per run, newest first. Anything needing a human goes under "Needs owner"
at the top of the same file, where it sits until a person clears it.
- Back up the protected files — ONLY after the checker is clean. Copy
each protected file to
<file>.bak (last-known-good). Order matters:
if the checker raised a POSSIBLE TRUNCATION FAIL, do not back up —
recover the file from its existing .bak, then re-run. Never refresh a
.bak from a file the checker flagged.
The maintenance-log rule. One physical line per run where possible,
detail in the daily log; keep as many run lines as fit under the budget
(60 by default, binding in the checker); fold the rest into the single
"…and N earlier runs" counter at the bottom. Start the file from
assets/maintenance-log-template.md. The immune system must not become
the disease: 365 daily lines in a budget-enforced workspace would make
the bloat-watcher the biggest source of bloat.
Compact the hot files — five minutes, whenever the checker says so
The point is making shrinkage ordinary: a trim that is a project happens
once a quarter; a trim that is a chore happens when the checker says so.
- Run the checker. Note which hot file is over budget and by how much.
- Read the on-disk file before anything else — never compact from memory
of it.
- Apply the one test to the oldest and fattest sections: "if a session
never read this line, would it do something wrong?"
- KEEP: corrections that overturn wrong beliefs, prohibitions and
guardrails, open threads, next actions, live deadlines, pointers.
- SHRINK: any recently-changed entry older than about seven days
collapses to its headline plus a pointer to the log that owns the
story.
- MOVE what already has an owner, and nothing else. Before cutting a
line, verify its content exists at the owner — whitespace-normalized
check, not naive substring (the wrapped-phrase scar in
icm-verifier). If it has no owner, it stays or gets one. Nothing is
ever deleted to nowhere.
- Edit section by section; never regenerate the whole file. Update the
updated: frontmatter from the CLOCK, not the session's memory of the
date. A correction must replace the stale line, not merely follow it —
an appended correction leaves the lie standing upstream of the truth.
- Re-run the checker; confirm the FAIL cleared. One line in today's log:
what moved, where its owners are, before and after line counts.
Writing files safely — the rule the truncation guard exists for
- Write atomically: temp file, then replace. Never overwrite a file with a
single failable write; the failure mode is total, not partial.
- Always UTF-8. A default-codepage write dies on
→ · — and takes the file
with it.
- Prefer append and edit tooling over read-then-rewrite round trips.
- A suspiciously small file right after a write is a data-loss signal, not
a relief. Read it back.
What this skill does not do
It does not judge content. A file can be perfectly cataloged, linked,
budgeted, dated, and wrong; that is icm-verifier's job for claims about
work and icm-ledger's for claims about the future. It does not enforce
anything on an agent that does not run it — wire it into a session start,
a scheduled run, or a hook (copy-paste lines for cron, schtasks and a
session-start hook: references/scheduling.md), and read the "Needs
owner" section when it tells you to.
1---2name: icm-maintain3description: Keep an ICM workspace structurally true across sessions — a read-only integrity checker (catalog rows, orphan files, broken links, frontmatter, binding line budgets on hot files, decision review-by clocks, expired ledger entries, stale briefing, stale external-number stamps, date drift, truncation against .bak copies), a bounded maintenance run with an explicit "authorized to fix" list and a "never" list, a five-minute compaction routine for over-budget hot files, and a budgeted maintenance log. Use when (1) starting or ending a session in an ICM workspace ("run the check", "is the workspace clean"); (2) a scheduled or unattended maintenance session runs; (3) the checker FAILs a hot-file budget and something must be compacted; (4) a file was just written and may have been truncated; (5) the owner asks "what needs me", "what drifted", or "why is the catalog out of date"; or (6) setting per-workspace budgets and protected files in icm-ops.json. Detects and stages; a person applies the subtractive fixes.4license: MIT5---67# icm-maintain89A context workspace decays under use: hot files regrow, catalog rows point10at moved files, a review date passes and nobody notices, a ledger entry11expires in silence, a briefing goes stale while still sounding current.12This skill is the routine that keeps that from compounding — mechanical13first, judgment second, and never more authority than an unattended session14can be trusted with. The system should not wait for someone to remember it.1516This skill operates on an ICM workspace — Interpretable Context Methodology,17Van Clief & McDermott, arXiv:2603.16021 (https://arxiv.org/abs/2603.16021).18Build the workspace with their `icm-architect` skill19(https://github.com/RinDig/icm-architect), or by hand from the paper: a20front desk (`CLAUDE.md` / `AGENTS.md`), `00-catalog/CATALOG.md`, and dated21markdown files with frontmatter — the `example/` tree in the icm-ops repo22is a complete minimal one. This pack is a layer on top of the method, not23part of it.2425## The checker2627```28python <skill folder>/scripts/icm_check.py <workspace-root>29python <skill folder>/scripts/icm_check.py <workspace-root> --json30```3132The skill folder is usually `.claude/skills/icm-maintain/`,33`.agents/skills/icm-maintain/`, `~/.claude/skills/icm-maintain/`, or34`skills/icm-maintain/` in a clone of the icm-ops repo.3536Options: `--json` (the findings as a machine-readable object for a test or37a hook), `--config PATH` (an `icm-ops.json` that is not at the root),38`--today YYYY-MM-DD` (pins the clock for tests and for reproducing an old39report; the header line says so), `--version`.4041Exit `0` = no FAIL findings; `1` = at least one FAIL; `2` = usage or42configuration error (bad `icm-ops.json`, missing catalog; nothing on43stdout); `3` = internal error (traceback on stderr; `--json` still prints44an object with an `error` key). The checker is **read-only: it reports45and never fixes.** Its report is the worklist. It covers:4647| Finding | Level | What it means |48|---|---|---|49| Catalog row points at a missing path | FAIL | The map lies. |50| Markdown file with no catalog coverage | WARN | Invisible to every session. |51| Relative link does not resolve (inline or reference-style; code is not scanned) | FAIL | A pointer to nowhere. |52| Missing frontmatter | WARN | Type and date unknown. |53| Hot file over its **binding budget** | FAIL | Compaction is due (below). |54| `hot_budgets` key that names no file | WARN | A budget guarding nothing is a rule switched off, usually by a typo. |55| Other file over the size target | WARN / FAIL | Doing two jobs; historical records WARN, working files FAIL. |56| Decision without `review-by:` / past it | WARN | Owner call needed. |57| Ledger L/D entry past REVIEW-BY or KNOW-BY and still open | WARN | Resolve or mark EXPIRED. |58| File in the ledger folder that is not a `YYYY-MM.md` month file | WARN | Not read as a ledger; its entries and readouts are invisible. |59| `BRIEFING.md` older than the newest readout | FAIL | A stale correction wearing a fresh voice. |60| `checked: DATE · source · stale after: N days` window blown | WARN | An external number describing the past as the present. |61| Newest log dated more than one day ahead of the clock | FAIL | A date written from a wrong clock; one day of grace covers time zones (a log dated tomorrow is INFO). |62| Protected file below 40% of its `.bak` | FAIL | Possible truncation — recover, do not overwrite the `.bak`. |63| Malformed date at any site | WARN | Not YYYY-MM-DD or not a calendar date. |64| Readout heading without a parseable date | WARN | The staleness check would otherwise switch itself off. |65| Unreadable file, unlisted directory, skipped link or loop | WARN | Part of the tree was not audited. |6667The header line prints the clock (or the `--today` override); the newest68log's age is an INFO line. Dates are claims; the header line is the clock.69On ledger entries the checker reads only an entry's id, its dates, its70open marker and the presence of the required field lines; it does not71judge the content of CLAIM, FALSIFIER or COST. Formats: `FORMAT.md` in the72icm-ops repo, sections "Checker output" (`FORMAT.md#checker-output`) and73"icm-ops.json" (`FORMAT.md#icm-opsjson`).7475Budgets are FAILs, not WARNs, by design: a WARN that stays yellow for weeks76becomes wallpaper. One operator measured the decay (2026-08-25): a state77file regrew from 232 to 273 lines in the seven days after a trim, with WARNs78firing the whole time. Binding budgets plus a five-minute routine held it79after that.80The checker owns the line-count definition (`wc -l` semantics) because it81is the tool that fails the build; an off-by-one at a binding threshold is82real.8384## Configuration — `icm-ops.json` at the workspace root (optional)8586Defaults are the ICM conventions. Override per workspace with any subset:8788| Key | One line |89|---|---|90| `hot_budgets` | Map of path → max lines that FAIL when exceeded. Default: `04-memory/STATE.md` 250, `00-catalog/CATALOG.md` 155, `04-memory/maintenance-log.md` 60. |91| `protected` | Paths that get a `.bak` after a clean run and a truncation FAIL if they collapse. Default: front desk, CATALOG, CONVENTIONS, STATE, BRIEFING, maintenance-log, plus the newest ledger month and newest log. |92| `deliverable_markers` | Path substrings marking client-facing content inside artifacts; exempt from orphan and frontmatter checks (their parent row covers them), and on size treated as a record: never a FAIL, a WARN past twice the target. Default: none (empty list). Example: `["/product/", "/sample/"]`. |93| `frontmatter_exempt` | Root-level files that need no frontmatter. Default: `CLAUDE.md`, `AGENTS.md`, `README.md`. |94| `size_target_lines` | Soft size target; over it WARNs, over twice it FAILs (working files) or WARNs (historical). Default: 200. |95| `historical_dirs` | Directories whose files are records, not working files — oversize is a WARN there. Default: `04-memory/log/`, plus ledger month files. |96| `log_dir` | Where daily `YYYY-MM-DD.md` logs live; the clock check and the newest-log protection read it. Default: `04-memory/log/`. |97| `ledger_dir` | Where month files, readouts, and `BRIEFING.md` live. Default: `04-memory/ledger/`. |98| `decisions_dir` | Where numbered decision records with `review-by:` live. Default: `01-context/decisions/`. |99| `catalog` | The map the coverage and row checks read. Default: `00-catalog/CATALOG.md`. |100101Raising a budget is an owner call, recorded with a reason in the workspace.102A budget raised to dodge a compaction is the wallpaper failure wearing a103number.104105## First run106107With no `icm-ops.json` the defaults above apply. Create108`04-memory/maintenance-log.md` from the template (the maintenance-log rule109below names it) and give it a catalog row. Expect the "no .bak yet" INFO110lines until a clean supervised run writes the `.bak` copies. A workspace111with no ledger yet follows `icm-ledger`'s own First run section.112113## Run maintenance — the workspace fixes its own errors, within limits114115A scheduled session runs this daily or weekly (the example workspace runs116weekly); any session may run it on demand. About fifteen minutes.117Compaction (supervised) logs to the daily log; unattended runs log to the118maintenance log.1191201. Run the checker. The report is the worklist.1212. **Authorized to fix without asking** — additive or reversible ONLY;122 nothing on this list can lose information:123 - Hot file over budget → **detect and STAGE, never apply.** Write the124 proposed keep/move diff to `04-memory/maintenance-log.md` under125 "Proposed compaction," and stop. Compaction is the one subtractive126 operation in the system and it takes the keep/move judgment; an127 unattended session that cuts a live guardrail produces a green check128 that the next unattended run trusts — the producer declaring itself129 verified, daily, forever. A person or a supervised session applies it.130 - Missing frontmatter → add it (type by folder, `updated:` from the131 file's mtime).132 - Catalog row pointing at a missing file → move the dead row to the133 maintenance log under Needs owner. Orphan file → add a row if its134 purpose is evident from the file itself; else list it for the owner.135 - Broken relative link → repair if the target is findable by name136 search; else report.137 - Ledger entry past its date, still open → mark `EXPIRED` per138 `icm-ledger` and list it under Needs owner with any evidence pointer139 found in the logs; an unattended run never attaches an outcome.140 - Decision past `review-by:` → do NOT reopen it; add one line flagging it141 for the owner and renew nothing.142 - Stale `BRIEFING.md` (FAIL) → regenerate it AND the front-desk block143 from the newest readout, per `icm-ledger`.1443. **Never**: raise budgets, delete files, edit money surfaces (listings,145 proposals, anything a client or a platform reads), publish anything,146 resolve D-entries (both parties present for that), or edit decisions.147 Judgment stays with sessions the owner is driving. A maintenance session148 that improvises is worse than one that waits.1494. Re-run the checker. It must end 0 FAIL — except a hot-file budget FAIL,150 which ends as a staged proposal awaiting a human.1515. **Log to `04-memory/maintenance-log.md`, not the daily log** — one line152 per run, newest first. Anything needing a human goes under "Needs owner"153 at the top of the same file, where it sits until a person clears it.1546. **Back up the protected files — ONLY after the checker is clean.** Copy155 each protected file to `<file>.bak` (last-known-good). Order matters:156 if the checker raised a POSSIBLE TRUNCATION FAIL, do not back up —157 recover the file from its existing `.bak`, then re-run. Never refresh a158 `.bak` from a file the checker flagged.159160**The maintenance-log rule.** One physical line per run where possible,161detail in the daily log; keep as many run lines as fit under the budget162(60 by default, binding in the checker); fold the rest into the single163"…and N earlier runs" counter at the bottom. Start the file from164`assets/maintenance-log-template.md`. **The immune system must not become165the disease**: 365 daily lines in a budget-enforced workspace would make166the bloat-watcher the biggest source of bloat.167168## Compact the hot files — five minutes, whenever the checker says so169170The point is making shrinkage ordinary: a trim that is a project happens171once a quarter; a trim that is a chore happens when the checker says so.1721731. Run the checker. Note which hot file is over budget and by how much.1742. Read the on-disk file before anything else — never compact from memory175 of it.1763. Apply the one test to the oldest and fattest sections: **"if a session177 never read this line, would it do something wrong?"**178 - KEEP: corrections that overturn wrong beliefs, prohibitions and179 guardrails, open threads, next actions, live deadlines, pointers.180 - SHRINK: any recently-changed entry older than about seven days181 collapses to its headline plus a pointer to the log that owns the182 story.183 - MOVE what already has an owner, and nothing else. Before cutting a184 line, verify its content exists at the owner — whitespace-normalized185 check, not naive substring (the wrapped-phrase scar in186 `icm-verifier`). If it has no owner, it stays or gets one. Nothing is187 ever deleted to nowhere.1884. Edit section by section; never regenerate the whole file. Update the189 `updated:` frontmatter from the CLOCK, not the session's memory of the190 date. A correction must replace the stale line, not merely follow it —191 an appended correction leaves the lie standing upstream of the truth.1925. Re-run the checker; confirm the FAIL cleared. One line in today's log:193 what moved, where its owners are, before and after line counts.194195## Writing files safely — the rule the truncation guard exists for196197- Write atomically: temp file, then replace. Never overwrite a file with a198 single failable write; the failure mode is total, not partial.199- Always UTF-8. A default-codepage write dies on `→ · —` and takes the file200 with it.201- Prefer append and edit tooling over read-then-rewrite round trips.202- A suspiciously small file right after a write is a data-loss signal, not203 a relief. Read it back.204205## What this skill does not do206207It does not judge content. A file can be perfectly cataloged, linked,208budgeted, dated, and wrong; that is `icm-verifier`'s job for claims about209work and `icm-ledger`'s for claims about the future. It does not enforce210anything on an agent that does not run it — wire it into a session start,211a scheduled run, or a hook (copy-paste lines for cron, `schtasks` and a212session-start hook: `references/scheduling.md`), and read the "Needs213owner" section when it tells you to.