Skip to content

feat: folding-strategy migration utility (kv-stable ⇄ kv-unified) - #94

Draft
Tengro wants to merge 2 commits into
anima-research:mainfrom
Tengro:feat/folding-migrator
Draft

Tengro wants to merge 2 commits into
anima-research:mainfrom
Tengro:feat/folding-migrator

Conversation

@Tengro

@Tengro Tengro commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

What

Adds context-manager-migrate — a CLI (bin, mirroring the agent-framework-recover pattern) plus src/migration/ library exports for switching an existing store between folding strategies. Three subcommands, all dry-run by default (--apply to write, --json for tooling):

  • validate — dry-runs the strict kv-unified CanonicalSummaryForest against the store (via previewContext, which commits nothing) under each treeification policy (strict / treeify / preserve-gaps) and recommends the first that builds. kv-stable's SummaryTree tolerates scarred chronicles that the strict forest rejects (Phase-0 found 11 non-contiguous summary nodes on a real export), so this is the go/no-go check — and it's useful standalone as a fleet-wide forest health inventory.
  • to-unified — synthesizes the initial presentation-receipt chain from the carried autobio:resolutions frontier, recording the same raw:{id} / summary:{summaryId} (repHash, level) pairs the kv-unified draft builder would. This gives the first kv-unified solve a continuity baseline instead of a one-shot presentation jump. No cache reference — the provider cache is genuinely cold after a solver switch. Resolutions pointing at unreachable levels degrade deterministically to the deepest reachable level, with warnings. Refuses to overwrite an existing chain unless --overwrite (exit 4).
  • to-stable — clears ${ns}/kvunified:presentation-receipt, closing the flip-back landmine: nothing reads or clears the slot after a switch away from kv-unified, so a later flip back would resurrect a stale chain as its continuity anchor. Never opens a ContextManager (no on-load repairs); the chronicle is append-only, so the cleared chain stays recoverable from state history.

Design notes

  • A store's folding "flavor" is exactly one slot — the receipt chain. Everything else the adaptive path persists (summaries, chunks, pins, locks, calibration, resolutions) is shared between solvers, which is what makes this migration tractable at all.
  • Determinism: every operation is deterministic given store contents and explicit inputs. The one non-derivable input — the synthesized receipt's acceptedAt — is a required parameter, never a clock read; dry-run and apply produce byte-identical receipt hashes (asserted in tests).
  • validate/to-unified open the store through the strategy and therefore run its standard on-load canonicalization (identical to any agent restart) — the CLI help says loudly to run against a copy.
  • Includes one small strategy fix: buildPicker's [picker-config-drift] alarm no longer fires while _previewInFlight — a preview's foldingStrategy override is the caller's requested what-if, not drift (previously any preview overriding the solver, e.g. from the conhost panel, produced the scary stack trace).

Testing

10 new tests in test/folding-migration.test.ts — pure-function coverage (presentation synthesis, L1-ledger + sourceIds fallback, unreachable-level degradation, chain determinism) plus integration tests that are, as far as I can tell, the first coverage of the cross-strategy switch path at all: a store compiled under kv-stable is migrated, then loaded and compiled under kv-unified with the synthesized chain as its continuity baseline; refusal/overwrite and to-stable idempotence are covered. Full suite: 768/769 pass — the 1 failure (snapshot cadence retune) is pre-existing drift against the installed chronicle build (missing getStateItemJson-family API), unrelated to this change.

Draft until we've exercised all three subcommands against a copy of a real deployed instance's datastore.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Ej5yriRtwnzjYWNy3YeXeE

Tengro and others added 2 commits September 14, 2026 10:27
A store's folding flavor is exactly one state slot —
`${ns}/kvunified:presentation-receipt`; everything else the adaptive path
persists is shared between solvers. This adds the utility that handles
the two things a bare foldingStrategy config flip does not:

- validate: dry-runs the strict CanonicalSummaryForest (via
  previewContext, committing nothing) under each treeification policy
  (strict / treeify / preserve-gaps) and recommends the first that
  builds. kv-stable's SummaryTree tolerates scarred chronicles the
  forest rejects, so this is the go/no-go check before any migration.

- to-unified: synthesizes the initial receipt chain from the carried
  `autobio:resolutions` frontier — the same (repHash, level) pairs the
  kv-unified draft builder records — so the first solve has a
  continuity baseline instead of a one-shot presentation jump. No cache
  reference (the provider cache is genuinely cold after a solver
  switch). Resolutions pointing at unreachable levels degrade
  deterministically with warnings. Refuses to overwrite an existing
  chain unless --overwrite. acceptedAt is an explicit required input —
  no clock reads, migrations are reproducible.

- to-stable: clears the receipt slot, closing the flip-back landmine
  where a stale chain would be resurrected as the continuity anchor
  (nothing else reads or clears it). Never opens a ContextManager, so
  no on-load repairs run.

Shipped as bin `context-manager-migrate` (validate / to-unified /
to-stable, dry-run by default, --apply to write, --json for tooling),
mirroring the agent-framework-recover pattern.

The integration tests are the first coverage of the cross-strategy
switch path at all: a store compiled under kv-stable is migrated and
then loaded + compiled under kv-unified.

Also: buildPicker's [picker-config-drift] alarm no longer fires while a
previewContext override is in flight — a mid-preview foldingStrategy
swap is the caller's requested what-if, not drift.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ej5yriRtwnzjYWNy3YeXeE
Field-tested against a production resident store (137MB, 6k messages,
2.5 weeks of life). Two fixes:

- A solver capacity limit (SparseLabelCeilingError /
  ExactEnumerationLimitError) hit AFTER forest construction now counts
  as a pass with a reported note, not a validate failure: the forest is
  built at solve start, and the forest is what validate is for. The
  real deployment tunes its own solver config.

- The canned validation solver config is sized for real stores instead
  of the unit-test toys it started as (the production store blew the
  10k label ceiling at 10,471): coarse 2k buckets, 500k ceiling.

Field results, for the record: the store's strict forest carries two
non-contiguous-ownership scars (validate correctly recommends a
tolerance policy — matching the Sep 9 production incident where
kv-unified went hard-down on exactly this forest), and to-unified
correctly refused the existing receipt chain left behind by the Sep 10
revert to kv-stable — the flip-back landmine to-stable exists to clear.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ej5yriRtwnzjYWNy3YeXeE
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.

1 participant