--- name: compact-now description: Operator-triggered proactive compaction — Chrono externalizes load-bearing state (active decisions, open tasks, next action) to a snapshot + a durable Vault learning note before invoking Claude Code's native /compact, then resumes from the snapshot. Use when the operator says "/compact-now" / "compact now" or when Chrono has flagged context pressure against shared/lifecycle.md rule 8. type: skill --- # /compact-now Chrono-side proactive compaction. The operator triggers via slash phrase. Chrono: 1. Reads the live board partition via `chrono_state.registry.registry_view()` and confirms no live work is in flight. Whether context pressure warrants compacting is Chrono's judgment against `shared/lifecycle.md` § 8. The context tripwire hook (`bin/context-tripwire.sh`) already reports the measured token count on every prompt once it passes 400k, so a WARN/HARD line in the current turn is the direct signal; below that, pressure is inferred from the § 9 proxy signals. 2. If blockers exist (live dispatches), surfaces them to the operator and asks whether to proceed anyway — likewise for any unclassified registry status, which the partition cannot vouch for. 3. Externalizes load-bearing state to the Vault via the current `record("learning", {...})` writer — captures active decisions (authority), open tasks, pending approvals, and the next action. 4. Snapshots the same state to `_state/chrono/compaction/.json` via `chrono_state.compaction.snapshot()`. 5. Invokes Claude Code's native `/compact`. 6. After compact, reads the snapshot via `chrono_state.compaction.recover()` and re-anchors on the next operator turn — **never bulk-re-reading conversation history.** ## When to invoke - Operator types `/compact-now` (explicit) - Operator types `compact now` / `please compact` / `let's compact` in prose (intent-recognition) - After Chrono has flagged context pressure against `shared/lifecycle.md` § 8 and the operator nudges affirmatively ## When NOT to invoke - Live dispatches running (`registry_view()["live"]` non-empty) — surface blockers first - Mid-task (Chrono still processing) — wait for a task boundary - Below the `shared/lifecycle.md` § 8 threshold — no benefit, just cost ## Implementation Chrono runs this skill inline (not a subagent dispatch), operator-triggered. It uses the `chrono_state` helpers (in `scripts/python/chrono_state/`) and Chrono's existing chrono-vault MCP access. A `PreCompact` hook is OPTIONAL and only fires if `.claude/settings.json` declares one — do not depend on it; externalize eagerly here. ```python # Chrono inline (not dispatched) import json, sys sys.path.insert(0, "scripts/python") from chrono_state.compaction import snapshot # noqa: E402 from chrono_state.registry import registry_view # noqa: E402 from chrono_state.decisions import active_decisions # noqa: E402 # ONE classification pass over the LIVE registry (_state/active-tasks.json). # registry_view() is the current API for this gate. The dead bounded-registry helper # load_active() was removed; it read _state/tasks/active.json, which the live board # does not feed. registry_view() partitions by the real vocabulary in # registry.LIVE_STATUSES / DEFERRED_STATUSES; never hand-write status literals here, # or the gate filters on a status the board does not emit and passes vacuously. board = registry_view() # Live work is a hard blocker and IS mechanical -- the board knows it, so ask the # board. Whether context pressure warrants compacting at all is Chrono's judgment # against shared/lifecycle.md rule 8; there is no predicate for it and no token # counter to feed one (rule 9: proxy signals only). blockers = [t["id"] for t in board["live"]] if blockers: surface_to_operator(f"Blockers: {blockers}. Proceed anyway?") if not operator_confirms: return # An unclassified status is owed work the partition cannot see — surface it loudly # rather than compacting over it (same contract as the resume capsule). if board["unclassified"]: surface_to_operator(f"UNKNOWN registry statuses: {board['unclassified']}. Proceed anyway?") state = { "next_action": next_action, "active_decisions": active_decisions(), # AUTHORITY — not Vault evidence "active_tasks": board["live"], "deferred_tasks": board["deferred"], # owed work; stalled, not executing "latest_turn": latest_operator_turn, "pending_approvals": pending_approvals, } # 1) Durable Vault note via the CURRENT API — record(note_type, fields), NOT the stale # record_finding(role=, canonical_name=, ...) signature. fields require title/body/ # target/attack_class; unknown fields are rejected. mcp__chrono_vault__record("learning", { "title": f"compact-now externalization ({session_id})", "body": json.dumps(state, indent=2), "target": "chrono-orchestrator", "attack_class": "session-continuity", "keywords": ["compaction", "resume", "chrono"], "source_task": current_task_id, }) # 2) Atomic snapshot to _state/chrono/compaction/.json snapshot(session_id, state) # 3) Invoke Claude Code's native /compact trigger_native_compact() # 4) After compact: recover ONLY from the snapshot, never bulk-re-read history. # from chrono_state.compaction import recover; recover(session_id) ``` ## Cross-references - Snapshot helpers: `scripts/python/chrono_state/compaction.py` (`snapshot`, `recover`). The threshold policy is prose, not code: `shared/lifecycle.md` § 8. - Decision authority (separate from Vault): `scripts/python/chrono_state/decisions.py` - Live board partition: `scripts/python/chrono_state/registry.py` (`registry_view` — live / deferred / unclassified; `LIVE_STATUSES` is the only status vocabulary. The dead bounded-file helper `load_active()` was removed; `registry_view()` is the correct API for this blocker gate.) - Resume capsule generator: `scripts/python/chrono_state/resume.py` - Vault writer API: `record(note_type, fields)` — see `plugins/chrono-vault/README.md` - Resume canary (acceptance proof): `scripts/python/tests/test_resume_canary.py` - PreCompact hook: OPTIONAL; only if `.claude/settings.json` declares one