Skip to content

Slice 3: session retention that relocates + the generated session index (#795) - #815

Draft
aarontrowbridge wants to merge 7 commits into
mainfrom
slice3-retention-index
Draft

Slice 3: session retention that relocates + the generated session index (#795)#815
aarontrowbridge wants to merge 7 commits into
mainfrom
slice3-retention-index

Conversation

@aarontrowbridge

@aarontrowbridge aarontrowbridge commented Sep 5, 2026

Copy link
Copy Markdown
Member

What

The D4 product layer (spec-20260905-045114, slice 3, issue #795): session retention as a CLI contract instead of hand SQL, plus the generated session index that retires the hand-written SESSION-INDEX.md anti-pattern.

New verb on the amico router (packages/amico-run): amico sessions

  • list [--archived] [--limit] [--cursor] [--db] — the visibility rules the product respects: default excludes archived, --archived is the explicit opt-in, pages are bounded (default 100, hard cap 1000), recent tail first.
  • archive [--days <n>] [--apply]relocates, never deletes: stamps the engine's time_archived. Dry-run by default (an agent running it against the live hub DB without --apply moves nothing). Deletion is out of the vocabulary.
  • restore <id> — clears the one field; idempotent on active sessions, 64 on unknown ids.
  • index [--out] [--db] — regenerates SESSION-INDEX.md from the DB: complete (every row exactly once), directory provenance (full paths + counts in the distribution header, basenames in the table), per-row archive state, deterministic apart from the stamp.
  • prefs [--days] — the archive cutoff as a workspace preference: $AMICODE_OPS_DIR/session-retention.json, default 30 days, failing safe on absent/malformed/out-of-range files.

DB resolution mirrors the open-threads skill (the store's other reader): --db → $OPENCODE_DB → XDG default. Reads open read-only; the vault ledger plane and the coordination board are never touched (D4 disjointness, tested).

Acceptance criteria (issue #795)

Criterion Test State
Archiving removes from default lists; explicit opt-in lists them; clearing the field restores sessions_verb.test.ts visibility-matrix block (bundle, seeded DB) ✅ green
Archive affects product lists only — no vault/coordination-plane state change disjointness block: sibling planes byte-identical, full-row snapshot diff = exactly the one time_archived column ✅ green
Archive cutoff is a workspace preference, default 30 days preference core + end-to-end prefs→archive block (7-day cutoff honored) ✅ green
Index command regenerates a complete, provenance-carrying index matching the DB regeneration-vs-DB block (seeded, incl. subagent rows + archived) ✅ green
Boot list fetch remains paginated under a seeded 1000+ store pagination block: 1200-seed walk, bounded pages, exactly-once, no archived leakage ✅ green

Live smoke (read-only, no writes to the live DB): amico sessions list and sessions index against the real hub DB — 1504 sessions indexed, 741 visible · 763 archived (the 2026-09-05 consolidation's counts, now generated).

CI status (after the node-20 portability fix)

  • SQLite strategy: the verb drives SQLite via a python3 stdlib sqlite3 bridge (sqlite_bridge.ts), not node:sqlite. Why: CI pins node 20 (ci.yml, all jobs) where node:sqlite does not exist (added in 22.5), engines say >= 20, and the product's other DB reader (open-threads) already uses python3 stdlib sqlite3 — zero new dependencies, one code path for node 20 and 22+, and writes go through the real sqlite engine (no whole-file rewrite, unlike a WASM driver). Interpreter resolution reuses the product's existing convention ($AMICO_PYTHONpython3, per pasqal_launch.ts).
  • Verified under node 20.20.2 locally (CI's exact version) before pushing: 18/18 sessions tests, full amico-run suite at the pre-slice baseline, typecheck green, read-only live-DB smoke (list + index) under node 20.
  • CI after the fix: amico-run green in the fast job (81 files passed, run 33970818654). The remaining fast red is the Slice 4: posture-aware context injection — the Active mode block #808/feat(mode-block): posture-aware context injection — the Active mode block (#808) #814 session-API fixture (mode_block.test.ts H4), failing on this PR 4/4 while passing on main — evidence it is not this branch: the merge-vs-main delta is 5 amico-run files only; the fixture passes 3/3 locally on both pristine main and this branch's merge state; on CI it records records_seen: 0 (probe plugin never loaded) and dies in a cleanup rmSync race (ENOTEMPTY) that masks the real boot timeout. Full evidence trail + suggested Slice 4: posture-aware context injection — the Active mode block #808 follow-ups in the CI comment.

Not green / notes

  • agent_spawn.test.ts (1 test) fails in this environment pre-existing at origin/main: the session env leaks OPENCODE_CONFIG_CONTENT/OPENCODE_DB into a real-spawn assertion. Unrelated to this branch; suite is otherwise 1363 passed / 1 skipped.
  • Finding (engine capability, not forked): D4's restore-pairs-with-slice-1 invariant — "restoring an archived session re-resolves its project row by worktree even if the home was pruned" — needs the D1 project-row machinery (slice 1); restore here clears the field only. The project table's worktree column is what the re-resolution will key on.
  • Finding (node:sqlite / vite-node): vite-node's builtin list (vitest 2.1) predates node:sqlite, so the module graph can't load it; the verb lazy-imports it at call time (tests exercise the esbuild bundle, where node loads it natively). On node < 22.5 the verb degrades to an honest 64 with a message.
  • Engine-side archived mechanics (the field + the list endpoint's archived query param) already exist in the vendored engine's SDK surface — used as-is, nothing forked.
  • Out of scope (per the issue): the extension panel's archived toggle UI, and wiring the boot fetch through this verb — the engine endpoint remains the panel's fetch path; this slice is the deterministic product surface + the index.

Closes #795 (on review).

@coderabbitai

coderabbitai Bot commented Sep 5, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

The archive cutoff is a workspace preference, not a constant:
$AMICODE_OPS_DIR/session-retention.json, default 30 days, failing safe on
absent/malformed/out-of-range files. Plus the deterministic session-index
renderer (used by the index verb in the next commit). Issue #795,
spec-20260905-045114 D4.
…(D4 slice 3, AC 1)

The archive visibility rules the product respects, as CLI verbs over the
session table (dry-run-by-default archive, --apply to relocate; restore
clears the one field; --archived is the explicit opt-in). node:sqlite stays
out of vitest's graph via a lazy import; DB resolution mirrors the
open-threads skill (--db → $OPENCODE_DB → XDG default). Issue #795.
The generated index is complete (every DB row exactly once), carries
directory provenance (full paths in the distribution header, basenames in
the table), reflects per-row archive state, and is deterministic apart from
the generated-at stamp. Verified against seeded copy DBs. Issue #795.
…C 5)

The default page is bounded (100), --limit is honored up to a hard cap,
cursor walking covers the whole store exactly once, and archived rows never
leak into a default walk. D4: the boot fetch stays bounded as the active
list grows. Issue #795.
…3, AC 2)

Archive --apply + restore leave the vault ledger plane, the coordination
board, and the claims ledger byte-identical, write no vault-plane files,
and change exactly one session column (time_archived) with project rows
untouched. Verified by full-row snapshot diffing on a seeded copy DB.
Issue #795.
…D4 slice 3, AC 3)

prefs --days writes session-retention.json; archive --apply honors it
(7-day cutoff verified against a seeded DB); --days overrides per call
without writing. Issue #795.
…ode 20 support

CI pins node 20 (ci.yml), where node:sqlite does not exist (added 22.5) —
the verb and all 11 bundle tests failed on the fast check. Replace the
node:sqlite access with a python3 stdlib sqlite3 bridge (sqlite_bridge.ts):
zero new dependencies, works on the CI pin and everything newer, and it is
the store's existing reader convention (open-threads: file:<db>?mode=ro,
timeout=5; AMICO_PYTHON→python3 per pasqal_launch). One python invocation
per verb operation, statements batched, JSON over stdio; reads open
read-only, writes commit through the real sqlite engine (no whole-file
rewrite, unlike a WASM driver).

Verified under node 20.20.2 locally (the CI version): 18/18 sessions tests,
full amico-run suite 1363 passed (the 1 failure is the pre-existing
env-leak in agent_spawn), and a read-only live-DB smoke (list + index).
Issue #795.
@aarontrowbridge

Copy link
Copy Markdown
Member Author

CI status after the node-20 fix (evidence trail)

This slice's package is green on CI node 20: after the python3-sqlite bridge (792c616/rebased e1662e2), packages/amico-run passes the fast job fully — Test Files 81 passed (81) in run 33970818654, including all 18 sessions_verb tests. Verified locally under node 20.20.2 (CI's exact version) before pushing: 18/18 sessions tests, full package suite identical to the pre-slice baseline, typecheck green, plus a read-only live-DB smoke under node 20.

The remaining fast red is the #808/#814 session-API fixture, not this slice. test/mode_block.test.ts H4 fixture fails on this PR's CI 4/4 (runs 33970030012, 33970818654 ×2 incl. rerun, 33971327134) while passing on main's own #814 run (33958624179). Evidence it is not caused by this branch:

  • The merge-vs-main content delta is exactly 5 files, all packages/amico-run (git diff origin/main HEAD --stat) — nothing the fixture's boot reads.
  • Locally, on both pristine main and this branch rebased onto main (the exact merge CI tests), the fixture passes 3/3 single-file and 1/1 full-suite — on the same machine, same vendored binary (v1.18.10-amicode.21).
  • On every CI failure the fixture records contract: "insufficient" with records_seen: 0 — the probe plugin produced zero records — and the test dies at ~12.3s: the shape of POST /session (10s AbortSignal.timeout) aborting while the binary installs the external plugin's npm deps into the temp home, followed by the finally's rmSync throwing ENOTEMPTY (racing the still-running dep installer) which masks the real error.

So the durable-record design (A2) is working as intended — it's telling us the live boot didn't prove the contract on the runner — and the cleanup race hides why. Suggested follow-ups for #808's owners (not done here, per no-drive-by discipline): a longer timeout on POST /session (or a boot-wait for the plugin's dep install), rmSync(..., { maxRetries, retryDelay }) on the temp-home cleanup so the real assertion error surfaces, and consideration of an artifact upload for DURABLE_DIR on failure. Until then this fixture is CI-latency-sensitive and can red any PR against main stochastically — this PR is simply the first to hit it (first PR-merge run since #814 landed).

The fixture failure does not gate the slice's acceptance criteria: all five are bound to packages/amico-run tests, which are green on CI's node 20.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Slice 3: session retention that relocates + the generated session index

1 participant