fix(claude-code): seed bank missions only when unset; never overwrite existing (#2492) - #2493
Conversation
… existing (vectorize-io#2492) The plugin stamped its configured bankMission/retainMission onto a bank on first touch, guarded only by a local state file. That clobbered per-bank reflect_mission/retain_mission authored out-of-band (control plane UI or PATCH /banks/{id}/config), and re-fired whenever the local state was lost (fresh machine, cleared cache). This is the Claude Code counterpart of the OpenClaw bug vectorize-io#1270 / fix vectorize-io#1473, which was never ported here. ensure_bank_mission is now server-aware: it GETs the bank's existing overrides and only fills mission fields that are not already set, so user-authored missions always win. The local bank_missions.json is demoted to a perf cache; correctness comes from the server check, and transient failures (unreachable server, timeout, non-404 errors) skip without marking reconciled so they retry. Also fixes a quirk where retainMission could not be seeded unless bankMission was also configured: set_bank_mission now sends each mission field independently, and get_bank_config treats a 404 (bank not created yet) as empty so brand-new banks are still seeded. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Closing: superseded by Coding Agents, which now does what this PR argued for — shipped in v0.2.1 (#3352), against your issue #2492. The rule there is seed-once. Before importing the bank template the client reads the bank-scoped overrides; if any of Retain strategies and entity labels are still re-applied, because those aren't preferences: the plugin writes documents under
|
Summary
Fixes #2492 — the Claude Code plugin overwrites per-bank missions authored out-of-band (control plane UI or
PATCH /banks/{id}/config) the first time it touches a bank. This is the Claude Code counterpart of the OpenClaw bug #1270 (fixed by #1473), which was never ported here even thoughscripts/lib/bank.pydocuments itself as a port of OpenClaw'sbanksWithMissionSetlogic.The bug
ensure_bank_missionunconditionally PATCHedreflect_mission+retain_missionfrom the plugin's configuredbankMission/retainMission, guarded only by a local state file (bank_missions.json) tracking "have I pushed a mission" — never "does the bank already have one server-side". Consequences:UserPromptSubmit) and retain (session end), so it can fire on the very first prompt.config || $1), so whatever the plugin sends wins.A straight port of #1473 was insufficient: that fix only stops stamping when the config field is empty, but the Claude Code
settings.jsonships non-empty mission defaults, so it would still clobber a differing per-bank mission.The fix
ensure_bank_missionis now server-aware: it reads the bank's existing overrides and only fills mission fields that are not already set — user-authored missions always win, the plugin merely seeds gaps.client.pyget_bank_config(bank_id)→ returns the bank's config; a404(bank not created yet — banks are lazily created on first retain) is treated as "empty" so brand-new banks are still seeded.set_bank_mission(...)now takesreflect_mission=/retain_mission=and sends only the fields provided (no-ops if none). This also fixes a pre-existing quirk whereretainMissioncould not be seeded unlessbankMissionwas also configured.bank.py—ensure_bank_missionGETsoverrides, computes per-field gaps, and PATCHes only the missing ones. The localbank_missions.jsonis demoted to a perf cache (avoids a GET on every hook); correctness now comes from the server check, so a wiped state file can no longer re-trigger a clobber.Behaviour matrix
Edge cases intentionally left as-is
overrides.retain_mission. But the server auto-appliesretain_default_strategywhen a retain call sends no strategy (memory_engine.py), andapply_strategyoverlays that strategy'sretain_missionover the root (config_resolver.py). So if the root retain mission is empty but a default strategy carries its own, the plugin still PATCHes a root retain mission. Impact is non-functional — the default strategy's mission still wins at retain time, so this only writes an otherwise-masked root value (semantic pollution, not a behaviour clobber). Fully resolving this would couple the client to server strategy-resolution internals (which also have global defaults), so it was deliberately deferred.404both when a bank doesn't exist and when the bank-config API is disabled (HINDSIGHT_API_ENABLE_BANK_CONFIG_API=false). In the disabled case the plugin does a harmless GET+PATCH that both 404, never marks reconciled, and retries each hook — no clobber, just mildly chatty at debug level.Scope
This PR is Patch 1 only (the behavioural fix). Blanking the shipped
settings.jsonmission defaults is a separate, independently-adoptable change and will come as its own PR.Tests
tests/test_bank.pyandtests/test_client.pyupdated/extended: new-bank seeding, existing-mission preservation, partial (gap-only) fill, retain-only config, local fast-path, opt-out, graceful-error-then-retry, plusget_bank_config(200 / 404 / non-404) and independent-fieldset_bank_mission. Full plugin suite: 201 passing.