fix(soul): restore plan-mode tool bindings after /init creates throwaway soul#2489
Open
nankingjing wants to merge 3 commits into
Open
fix(soul): restore plan-mode tool bindings after /init creates throwaway soul#2489nankingjing wants to merge 3 commits into
nankingjing wants to merge 3 commits into
Conversation
Author
|
Reviewed — the rebind fix in a finally block correctly restores shared plan-mode tools after /init, and the tests are thorough. Ready for review. |
This was referenced Jul 13, 2026
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.
Bug
Fixes #2478 — when
/initruns, it creates a throwawayKimiSoul(soul.agent, ...)that shares the live soul's agent (and therefore the same tool instances). The throwaway soul's__init__calls_bind_plan_mode_tools(), which rebinds the sharedExitPlanMode,EnterPlanMode,WriteFile, andStrReplaceFileinstances to closures that reference the throwaway soul's state.After
/initcompletes and the throwaway soul is discarded, those shared tools are left pointing at stale closures whose_plan_modeis alwaysFalse. This creates a state split:soul.plan_modeon the live soul) correctly shows "Plan mode is active".ExitPlanMode(reading the stale closure on the dead soul) reports"Not in plan mode".The workaround "call
EnterPlanModeagain, thenExitPlanMode" works becauseEnterPlanModetoggles the stale throwaway soul's_plan_modetoTrue.Fix
New public
KimiSoul.rebind_plan_mode_tools()method insrc/kimi_cli/soul/kimisoul.py— wraps the existing_bind_plan_mode_tools()for external callers.Call
soul.rebind_plan_mode_tools()in the/inithandler (src/kimi_cli/soul/slash.py) inside atry/finallyso bindings are always restored even if the INIT prompt run fails.Two regression tests in
tests/core/test_plan_mode.py:test_second_soul_clobbers_binding_and_rebind_restores— demonstrates that constructing a second soul over a shared agent clobbers theExitPlanModebinding, andrebind_plan_mode_tools()restores it.test_init_slash_command_preserves_plan_mode_binding— end-to-end: runs the actual/inithandler and verifies the live soul can enter plan mode afterwards.Root cause
The plan-mode tools (
ExitPlanMode/EnterPlanMode/WriteFile/StrReplaceFile) are mutable instances stored on the shared agent toolset. Theirbind()methods store closures over a specificKimiSoulvia capturingselfin_bind_plan_mode_tools. When two souls share the same agent, whichever soul calls_bind_plan_mode_toolslast "wins" — the other soul loses its bindings.Verification
KimiSoulconstruction sites examined; only/initshares a live agent. The binding mechanism (closures capturingself._plan_mode) and the over-write-on-rebind behavior confirmed. The workaround mechanism (EnterPlanMode flips the stale soul's flag) also explained.