feat(claude-code): mid-session dataset switch (seal bridge + re-register) (SDK-306) - #292
Open
rshkarin wants to merge 5 commits into
Open
Conversation
…ter) Add mid-session dataset switching for the Claude Code plugin: seal the old (dataset, session_id) bridge, re-register the agent on the new dataset, and preserve conversation context — without orphaning state or duplicating graph writes. The switch keeps the same Cognee session_id and conn_uuid, so the session cache (keyed by session_id, not dataset) is untouched and recall still returns prior-conversation context. Only where new graph writes land changes. - scripts/dataset-switch.py: three-phase orchestrator (seal old -> switch active -> re-register), idempotent no-op when the dataset is unchanged, best-effort so it never breaks a hook. Works in HTTP and local-SDK modes. - _plugin_common.py: per-session switch-state ledger + seal_bridge_state() for HTTP mode (flush old bucket before switch, mark sealed) and high-water baseline helpers. - config.py: set_active_dataset() persists the new dataset (global config + project picker if present); persist_session_cache_to_graph() now honors the per-(session,dataset) baseline so the new dataset receives only post-switch turns; seal_session_bridge_local() for local-SDK mode. - hook.log records "old bridge sealed" + "agent re-registered". - skills/cognee-dataset-switch + README section. - tests/test_dataset_switch.py (14 tests, fully mocked, no live server). Closes #3689
…ia the launch map The mid-session switch persisted the new dataset to the global config.json, os.environ (own process only), and the .cognee picker "if present". None reach a subsequent hook on main: load_config() drops the file's dataset key (_file_excluded), load_resolved() carries no dataset, the env write dies with the switch process, and no picker reader exists on main. So every later hook kept resolving the old/default dataset via get_dataset(load_config()) — the switch never actually switched. Persist the active dataset into the existing host-keyed launch map record (the store every hook already reads through resolve_cognee_session_id) and make get_dataset() consult it. Precedence: mid-session switch > COGNEE_PLUGIN_DATASET env > config default. The switch is the latest explicit user action, so it wins even over a launch-time COGNEE_PLUGIN_DATASET — which on main is the only working way to pick a non-default dataset and would otherwise make the switch a silent no-op. A launch that never switched has no record entry and resolves exactly as before; the override is keyed by host_key so it never leaks into other launches (unlike the machine-global config file). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…and config/picker writes None of this state was consumed by dataset resolution, so it added surface without moving any acceptance criterion: - switch_state.json ledger + the per-session "active" field (read only by tests; dataset resolution never consulted it — the redirect now lives in the launch map record). - The per-(session,dataset) high-water baseline: a no-op in HTTP mode (per-dataset bridge buckets already partition writes, and it was seeded from bridge-bucket counts but consumed by a slice of the SDK session cache — a different store); in local mode the primary path is cognee.improve(), so the baseline slice only ran on the legacy older-cognee TypeError fallback. Reverted the persist_session_cache_to_graph slice to main. - The "sealed"/"sealed_at" markers (write-only; re-seal safety comes from the _state digest, not a flag). - set_active_dataset's config.json write (ignored by load_config's _file_excluded) and its .cognee/session-config.json picker write (no picker reader exists on main; couples to the still-draft SDK-304 / #185). Seal now just flushes the old dataset's bucket (digest-deduped) and logs "old bridge sealed" in both HTTP and local modes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e sibling convention The author's suite used pytest monkeypatch/tmp_path fixtures, so it could not run under plain `python3 tests/test_*.py` the way the sibling suites (and the plugin tree's only runner) do. Rewrite it fixture-free: redirect every plugin path under a temp home with save/restore, stub the network, and add a globals()-iterating __main__ runner so it runs under both `python3` and `pytest`. Retargets the assertions at the reworked behavior: get_dataset follows the launch-scoped switch (including over a COGNEE_PLUGIN_DATASET env pin), the old bridge is sealed (+ "old bridge sealed"), the agent is re-registered in place (same conn_uuid + session, new dataset, + "agent re-registered"), same/empty switches are no-ops, and a never-switched launch resolves the configured dataset unchanged. Drops the tests that exercised the removed ledger/baseline state. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… redirect The skill still described the removed config.json/picker writes and the dropped "marked sealed" flag. Describe what the reworked switch actually does: record the new dataset in the launch's session map record (read by every later hook), taking precedence over a launch-time COGNEE_PLUGIN_DATASET and scoped to this launch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Review + rework of community PR #186 (by @wiz-abhi) for cognee hackathon issue topoteretes/cognee#3689 — mid-session dataset switch (seal bridge + re-register). Tracked as SDK-306. Base branch
main(this repo has nodev).Switching datasets mid-session repoints where new memory is written without restarting Claude and without losing the current conversation: seal the old
(dataset, session_id)bridge, re-register the agent on the new dataset, and keep the samesession_idso prior-turnrecallstill works.The original commit is preserved as the base (attribution intact); maintainer improvements are layered on top as separate single-concern commits.
Critical finding in #186 — the switch didn't actually switch on
mainThe original Phase 2 persisted the new dataset to the global
config.json(plusos.environin the switch's own short-lived process, plus.cognee/session-config.jsonif present). None of those reach a subsequent hook on currentmain:load_config()deliberately drops the file'sdatasetkey (_file_excluded = {"dataset"}), soget_dataset(load_config())never sees the written value.load_resolved()carries nodataset, so every write/recall hook falls back toget_dataset(load_config()).os.environwrite cannot cross into a sibling hook process, and no.cogneepicker reader exists onmain(that's the still-draft feat(claude-code): add project-level dataset picker (#3686) #185 / SDK-304).So after the switch,
store-to-session/session-context-lookup/ the final sync kept writing to the old/default dataset. The seal and re-register ran and logged, but the feature was a no-op. (Verified with an adversarial multi-agent read of the code, including an empiricalload_config()run.)What changed (maintainer commits)
fix: redirect subsequent hooks via the launch map. Persist the active dataset into the existing host-keyed launch map record — the store every hook already reads throughresolve_cognee_session_id— and haveget_dataset()consult it. Precedence mid-session switch >COGNEE_PLUGIN_DATASETenv > config default: the switch is the latest explicit user action and wins even over a launch-time env pin (onmainthat env var is the only working way to select a non-default dataset, so otherwise the switch would silently no-op). The override is keyed byhost_key, so it never leaks into other launches (unlike the machine-global config file). A launch that never switched resolves exactly as before.refactor: drop inert state and scope creep. Removed theswitch_state.jsonledger and its write-onlyactive/sealed/sealed_atfields, the per-(session,dataset)high-water baseline (a no-op in HTTP mode — per-dataset bridge buckets already partition writes — and in local mode only reachable on the legacy older-cogneecognee.improvefallback), the ignoredconfig.jsondataset write, and the.cognee/session-config.jsonpicker write (no reader onmain; coupled to unmerged feat(claude-code): add project-level dataset picker (#3686) #185). Seal now just flushes the old dataset's bucket (digest-deduped) and logsold bridge sealed.test: fixture-free rewrite. The original suite used pytestmonkeypatch/tmp_pathfixtures and couldn't run underpython3 tests/test_*.py(the plugin tree's only runner). Rewrote it to the sibling convention (temp-home save/restore, stubbed network,globals()-iterating__main__runner) and retargeted the assertions at the reworked behavior.docs: align the skill with the launch-map redirect (it still described the removed config/picker writes).Kept from #186 (correct and required by the issue): the seal in both HTTP and local modes (+
old bridge sealed), the in-place re-register with the sameconn_uuid+session_id(+agent re-registered), and context preservation (the switch never re-mintssession_id/conn_uuid, and recall is alreadysession_id-scoped). Net production surface ~517 → ~333 lines.Verification
python3 integrations/claude-code/tests/test_dataset_switch.py— 9/9 pass (also underpytest). Fullclaude-codesuite green except the two pre-existing, unrelated urllib-context=failures onmain(test_bridge_poll,test_improve_sync).ruff check integrations/+ruff format --checkclean (line-length 100) — the sole automated gate for these plugin-tree dirs.proj-y, a separatepython3process resolvesget_dataset → proj-y; a different launch still resolves the default; a switch wins over aCOGNEE_PLUGIN_DATASETpin.Out of scope / follow-ups
cognee.improvepath reads the session cache bysession_id, so fully preventing pre-switch turns from being re-emitted into the new dataset depends on cognee core scoping session data by dataset (SDK-283 / cognee PR #4176) — the plugin can't do that client-side without breaking context-preserving recall.COGNEE_SYNC_DATASETat spawn): interactive hooks + per-turn writes follow the switch immediately; restarting the watcher on the new dataset is a follow-up aligned with the SDK-212 pattern.codextwin.Closes: rework of #186 · gh cognee#3689 · pairs with the picker #185 / SDK-304 · depends on core SDK-283 for full local-mode dedup.
🤖 Generated with Claude Code