Skip to content

fix(coding-agent): pickle callable Python skill modules by reference - #1278

Open
15297839035 wants to merge 1 commit into
PrimeIntellect-ai:mainfrom
15297839035:fix/1211-skill-module-snapshot
Open

fix(coding-agent): pickle callable Python skill modules by reference#1278
15297839035 wants to merge 1 commit into
PrimeIntellect-ai:mainfrom
15297839035:fix/1211-skill-module-snapshot

Conversation

@15297839035

@15297839035 15297839035 commented Aug 12, 2026

Copy link
Copy Markdown

Fixes #1211.

Root cause

The kernel bootstrap wraps every Python skill that exposes run() in _PrimeAgentCallableSkillModule, a types.ModuleType subclass (packages/coding-agent/src/core/tools/ipython.ts). dill saves modules by reference, but pickle's dispatch table is keyed on the exact type, so the subclass fell through to the generic reduce path and raised TypeError: cannot pickle '_PrimeAgentCallableSkillModule' object.

This predicts the reported split exactly: the five skills listed as skipped (attach-image, compact, edit, refine, websearch) are the ones defining run; the four listed as saved (agent-message, agent-observe, goal, rlm-heartbeat) do not, so they stayed plain modules and pickled by reference.

The part that actually loses data

The skill globals themselves are rebuilt on resume regardless — restoreState() runs before the bootstrap specifically so the bootstrap overwrites them with fresh live handles.

The real loss is that an unpicklable wrapper also poisons any user variable holding a reference to it. Reproduced in a real kernel:

tools = {'doubler': demo_skill}

tools was reported in skipped and dropped, with no bootstrap to rebuild it.

Fix

Give the wrapper a __reduce__ that restores through importlib.import_module. The reducer names a stdlib function, so it pickles by reference rather than dragging the wrapper class into the payload — which would reintroduce the same failure.

Tests

  • test/kernel-state-roundtrip.test.ts: real-kernel round-trip covering both the skill module and a user variable referencing it. Written first and confirmed failing against the unfixed code, with both demo_skill and tools in skipped.
  • test/suite/regressions/1211-callable-skill-module-pickle.test.ts: contract test on the generated bootstrap, so the guarantee also holds in the default shards where kernel-heavy is filtered out.

Verification

  • npm run test:kernel scope: 7/7 in kernel-state-roundtrip.test.ts.
  • 117 tests pass across 9 related kernel and skill test files.
  • npm run check passes.

Four failures in kernel-goal-skill.test.ts and acp-kernel-features.test.ts are pre-existing in my environment, not from this change: the same four fail at HEAD with the patch reverted. My kernel venv's rlm lacks the host bridge.

Known limitation

A variable restored before the bootstrap holds the unwrapped module, so tools['doubler'].run(21) works but calling tools['doubler'](21) directly does not. Closing that gap means switching the bootstrap from copy-wrapping to an in-place __class__ upgrade, which makes every existing reference callable and drops the __dict__ copy. I verified that works but left it out — it changes module-wrapping semantics for all users and is beyond this issue. Happy to send it separately if you want it.

Note

Fix pickling of callable Python skill modules in kernel state snapshots

Callable Python skill modules (those exposing run()) were being dropped from kernel state snapshots with a TypeError during pickling. A __reduce__ method is injected into the ModuleType subclass generated in ipython.ts, causing the wrapper to pickle by reference via importlib.import_module instead of triggering generic reduction. Regression tests verify both the generated bootstrap code and full kernel snapshot round-trip behavior.

Macroscope summarized f5c7aa5.

The kernel bootstrap wraps every Python skill that exposes run() in a
_PrimeAgentCallableSkillModule, a types.ModuleType subclass. dill saves
modules by reference, but pickle's dispatch table is keyed on the exact
type, so the subclass fell through to the generic reduce path and raised
TypeError: cannot pickle '_PrimeAgentCallableSkillModule' object.

Every such skill was therefore dropped from the kernel state snapshot,
and so was any user variable holding a reference to one - the skill
globals are re-created by the bootstrap on resume, but the user variable
was silently lost.

Give the wrapper a __reduce__ that restores through
importlib.import_module. The reducer names a stdlib function so it
pickles by reference and does not drag the wrapper class into the
payload.

fixes PrimeIntellect-ai#1211
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.

Kernel state snapshot silently drops 5 built-in skills: cannot pickle '_PrimeAgentCallableSkillModule'

1 participant