Skip to content

Agent: give every task a persistent python scratchpad - #1410

Draft
AnthonyRonning wants to merge 2 commits into
agent/python-runtimefrom
agent/python-tool
Draft

AnthonyRonning wants to merge 2 commits into
agent/python-runtimefrom
agent/python-tool

Conversation

@AnthonyRonning

@AnthonyRonning AnthonyRonning commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

Second layer of the CodeMode catch-up stack, on PR #1409 (the runtime). It gives every Maple Work task python: a persistent CPython scratchpad that sits beside the shell, read and edit tools. This is the feature PR #897 built on Goose, re-ported onto the Pi runtime.

What the model gets

  • One interpreter per loaded task, started on its first call in the task's project directory, with the shell tool's environment (the login shell's PATH and AGENT_SESSION_ID).
  • Variables, functions, imports and background asyncio work persist between calls and turns. The last non-None expression is shown and kept in _. Top-level await runs on one long-lived event loop.
  • Input is {code, reset?}. Results carry stdout, stderr, the value or traceback, bounded output with truncation counts, and a state-loss notice whenever earlier state is gone.
  • The description steers project commands, tests and long-running work to the shell, and explains installing wheels outside the bundle. The name python is the one OpenAI's gpt-oss models were trained on.
  • The transcript shows calls as ordinary tool rows titled "Python: "; ACP reports them as execute tool calls. There is no Python-specific UI.

What changed from #897

Goose forced most of #897's integration; Pi needs much less of it.

  • Ordering: the tool declares Pi's Sequential execution mode. That replaces the Goose fork's ordered-call scheduler and its 32-call cap. Pi runs the whole batch in call order, so other tools in a batch with a python call wait their turn too. Prime Agent, a Pi fork with the same design, does exactly this. The pi crates are unchanged.
  • Approvals: Maple Work no longer asks before tool calls, so the ask-before policy and the permission card are gone. python runs like bash.
  • No user-facing Python state: the Reset Python menu, status and capacity errors are gone. The model keeps reset.
  • Capacity: the runtime still allows four interpreters per host, and desktop and ACP now share one host. When a task needs one while four exist, the least recently used idle scratchpad of another task is discarded, and that task's next result says so. A call fails only when all four are running cells.
  • Lifetime: archiving a task, or archiving or removing its project, discards its scratchpad. Unloading it (idle eviction, deletion, trust changes) or stopping Maple retires it. A task reloaded with history starts fresh, and its first result says earlier state is gone; state is never serialized.
  • Kept from Port bundled CPython and ordered CodeMode into Maple Agent #897: the binding's lazy start, reset and Stop semantics (Stop retires an unfinished cell), result text, the error bound, and the transcript title, which is Port bundled CPython and ordered CodeMode into Maple Agent #897's hunk.

Why this design (research summary)

We compared CodeMode in upstream Pi (codemode: stateless JavaScript in QuickJS, for composing tool calls), Codex (exec), OpenCode (execute), Goose (code_execution), Prime Agent (python_repl) and oh-my-pi (eval).

  • The JavaScript designs are sandboxes for composing tool calls. Sandboxing is out of scope here, and we want a scratchpad.
  • The two persistent designs, Prime Agent and oh-my-pi, match Port bundled CPython and ordered CodeMode into Maple Agent #897: a CPython subprocess with a JSON protocol, sequential calls, and no approvals. Prime Agent's state revival truncated user files, so we keep "never serialize". All of them tell the model when its state is lost.
  • MCP tools and a Maple SDK inside the scratchpad remain follow-ups. They fit as host requests on the worker protocol, routed through Pi's normal tool path.

Builds

just build and just run prepare the pinned runtime beside the debug binary (target/debug/runtime/python). An executable without a bundled runtime offers no python tool, so release builds are unchanged until the packaging layer lands.

Validation

  • cargo test -p maple-agent tools::python: persistence across calls, explicit reset, the notice on reload with history, freeing a slot by discarding the least recently used idle scratchpad (it reports the loss on its next call), archive discard, reset-only calls without starting Python, and the environment. All run against the real CPython 3.14.7 interpreter.
  • python_title_tests: the title test ported from Port bundled CPython and ordered CodeMode into Maple Agent #897.
  • The full pre-commit hook (just ci-lint and just ci-test) passed. One app test failed once under the hook and passed on rerun (all 1,179 app tests pass).

End-to-end with a real model (local stack, over ACP)

The setup:

  • A debug build of this branch.
  • Local OpenSecret, Continuum and billing, signed in with the Pro fixture.
  • Model deepseek-v4-1-flash.
  • A project with a 600-row orders.csv.

The run:

  1. The prompt doesn't name the tool: "Which region has the highest total revenue … and how did that region's monthly revenue change from June to September? Keep the parsed data loaded."
    • The model inspected the file with the shell, then used python twice. The rows read "Python: import csv, collections" and are ACP kind execute.
    • The first result names the interpreter: CPython 3.14.7 (pbs-20260929-aarch64-apple-darwin), its executable and the project directory.
    • The answer was correct; I checked it independently. The model told the user the parsed rows "are still loaded in the Python scratchpad".
  2. Reuse: "Using the data you already have loaded, what is the median order value per region? Please don't re-read the file."
    • One python call reused rows and collections; it was execution 3 of generation 1. The medians were correct.
  3. Restart: the ACP client exited, which stopped the host; no maple-agent or Python processes remained. Then the same session was loaded in a new host.
    • The first python call raised NameError, with "State loss: This is a fresh Python scratchpad: Maple restarted or reloaded this task, so variables, imports and background work from any earlier python calls are gone".
    • The model re-read the file in its next call, answered correctly, and told the user: "The scratchpad was reset by the environment (variables gone), so I had to reload orders.csv once."

🤖 Generated with Claude Code

@AnthonyRonning AnthonyRonning changed the title Give every task a persistent python scratchpad Agent: give every task a persistent python scratchpad Nov 1, 2026
AnthonyRonning and others added 2 commits November 2, 2026 01:04
Port #897's task binding onto Pi as Maple's `python` tool. Each loaded
task gets one bundled-CPython interpreter, started on its first call in
the task's project with the shell's PATH and AGENT_SESSION_ID, keeping
variables, imports and background asyncio work between calls. The tool
declares Pi's sequential execution mode, which replaces #897's Goose
scheduler: a batch with a python call runs in the order the model made
it.

Goose-era choices are dropped: there is no approval card or Reset
Python menu, and capacity no longer surfaces to the user. When all four
interpreters exist, the least recently used idle scratchpad of another
task is discarded. Archiving a task, archiving or removing its project,
unloading it and stopping Maple discard or retire it, and the model is
told on its next call; a task reloaded with history starts fresh and
says so. The result text, reset and Stop semantics, and the transcript
title come from #897. Builds without a bundled runtime offer no tool;
`just build` prepares one beside the debug binary.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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