CodeNomad/dev-docs/SESSION_PRUNING_SAFETY.md
Pascal André 7a1fc90310
docs(agents): keep AGENTS.md to universal rules and agent docs to the current state (#1082)
AGENTS.md loaded about 33,000 tokens into every agent session, subagent and reviewer, mostly per-feature contracts, and the agent documentation around it had accumulated duplicates, dated logs and history of removed designs. This PR makes AGENTS.md a short set of rules that apply everywhere plus an index of area documents, and leaves AGENTS.md, dev-docs and the repository skills describing only the current state.

Three commits, reviewable separately:

1. **Move (lossless).** Every per-area rule moved verbatim from AGENTS.md into its area document (existing ones, or six new ones: UI_CONVENTIONS, UI_SHELL, COMPOSER, TRANSCRIPT, SETTINGS, PROJECTS). A script split the old file into 208 units, required one destination for each and checked all 199 content units verbatim at their destination. AGENTS.md went from 134,000 to 16,000 characters.
2. **Trim.** A per-area ponytail review removed rules stated two or three times (keeping the more precise copy), dated validation logs, per-version qualification sections below the current minimum, abandoned designs and self-references; seven documents whose surviving rules live elsewhere were deleted. It also fixed statements the code contradicts (locale registration, the Mission play/recovery condition, plugin package updates, cleanup progress UI, timeline index persistence, automation restart/targets, stale pins and minimums).
3. **Current state only.** The new AGENTS.md rule: no history (removed features and their names, former behavior, provenance, dated logs) and no denials; correct a contradicted statement at its source. DEVELOPER_MODE.md is merged into BROWSER_AUTOMATION.md and every Developer Mode mention is gone (including the README and the browser help page in ten locales). Names that only made sense against older designs are renamed with every reference updated, MISSIONS_ENVIRONMENT_ADMISSION is folded into SESSION_ENVIRONMENT, and INDEX lists every document. The server npm README now states the real OpenCode minimum (2.1.2).

AGENTS.md is now about 14,000 characters (roughly 3,500 tokens); AGENTS.md, dev-docs and the vendor README went from about 11,200 lines to under 4,700.

Validation: every relative Markdown link and every cited dev-docs path resolves; no reference to a renamed or deleted document remains; `npm run test:help` passes for all ten locales.

Review: three independent GPT-6.1 Sol (high) reviews by area found binding rules lost in the trim and current-state passes; 25 were restored as present-tense facts or prohibitions and four contradicting statements corrected, each checked against code. A Fable 5.1 gatekeeper review of the whole PR passed; its follow-up routes session recovery from the index, points to dev-docs/INDEX.md and documents the family identity read.
2026-10-21 16:59:43 +02:00

4.8 KiB

Pruning safety boundary

Pruning relies on OpenCode's internal storage contract described here. Runtime version labels never enable or disable pruning: actual storage, identity and claim checks decide each request. Custom Core patches and arbitrary direct SQL writers are unsupported.

Native ownership

  • session/execution.ts: Execution.Started commits store.claim before the runner. Success/failure/deliberate interruption releases on terminal commit. Shutdown keeps the claim for recovery. The plugin never impersonates these lifecycle events.
  • session/run-coordinator.ts: the started hook completes before the first drain; queued/steered successor drains stay within the same busy period. The coordinator is process-local, so the plugin never relies on its in-memory isActive value.
  • session/store.ts: the durable claim is session_v2.time_suspended. Under our SQLite write lock either the native claim committed first (refuse) or its write waits until we finish (the runner subsequently reads the pruned history).
  • session/history.ts / runner/index.ts: runner history is loaded from SQL, so a subsequent primary request and a restart both see pruned content (native smoke test).
  • session/projector.ts / bus.ts: native projections run transactionally. The plugin changes only an already-completed assistant row's content, with exact-data CAS; it never manufactures sequence values or emits replacement events.

Rather than relying on move/revert controls, the plugin rechecks directory/project/workspace identity and staged-revert/compaction state inside the transaction. Other native writes serialize on SQLite; the plugin preserves snapshots, metadata, IDs and ordering. A concurrently staged revert may apply after the prune, just as it can apply after another committed operation. It must not restore deleted payloads.

session/generate.ts previews history and issues a read-only auxiliary request without claiming or mutating the session. Readers that captured a pre-commit snapshot, including HTTP readers and auxiliary generations, may finish with that old snapshot, and requests already assembled or sent keep their content; only fresh reads observe the committed history. Titles and already-written summaries keep their text.

Identity, transaction and replay

  1. Require an explicit pruning request and compatible storage, an absolute existing local DB path, native session ownership matching the plugin location, and a fresh random ctx.storage challenge, visible through the candidate connection in the same DB's kv table (so a same-session backup copy fails).
  2. Use synchronous node:sqlite, busy_timeout=0, then BEGIN IMMEDIATE. Do not await anything while holding the write lock. This avoids blocking a native async transaction that needs the same JS event loop to complete.
  3. Recheck identity and claim. Reject unknown schema/query failures, attachments, triggers, remote event ownership or retained event rows for the aggregate. Never rewrite/replay events to repair them or mutate V1 tables.
  4. Bound/read the row, check assistant completion, completed/error tool state, known content kinds, selected technical indices and canonical SHA-256 revision. Update only $.content, with CAS against the exact stored row data.
  5. Insert a content-free receipt in the same transaction; commit or roll back both. Identical requests cannot accidentally delete the next block after indices shift. Receipts are retained indefinitely.
  6. Emit the custom event after commit. A failed notification cannot undo the write. The caller re-reads via the native API; reconnect reconciles authoritative data.

Coordination covers the native writers of the single shared daemon only. Use that daemon (other clients may connect to it normally); arbitrary tools writing opencode.db directly, other unsafe plugins and several Core servers sharing one DB are unsupported.

Meaning of deletion

  • UI: re-read stored history. The SDK projection is invalidated too, so a later native delta cannot resurrect deleted parts from its old cache.
  • Model context: future primary reads omit deleted technical blocks when that history is in the context window. Copied summaries, native checkpoints and forks keep their content. Provider-specific state continuation and budget estimates need their own validation.
  • Persistence: only selected V2 content arrays change. Usage/cost record past execution and stay unchanged.
  • Physical storage: freed SQLite pages become reusable. The DB file shrinks only through separately consented maintenance such as VACUUM; this plugin never runs such maintenance or restores backups.

SQLite integrity, application replay, UI cache state and provider payload correctness are distinct checks. Never point a real daemon at an offline-pruned database copy.