Skip to content

feat(agents): add Jupyter-backed code policy module - #3259

Closed
TomCC7 wants to merge 6 commits into
mainfrom
cc/feat/code-as-policy-interface
Closed

feat(agents): add Jupyter-backed code policy module#3259
TomCC7 wants to merge 6 commits into
mainfrom
cc/feat/code-as-policy-interface

Conversation

@TomCC7

@TomCC7 TomCC7 commented Jul 29, 2026

Copy link
Copy Markdown
Member

Contribution path

Problem

DimOS agents can call predefined skills, but they cannot submit a Python program that processes native observations, branches or retries, and composes deployed RPCs as one synchronous policy rollout.

Solution

  • Add a persistent CodePolicyModule exposing one synchronous python_exec(code, timeout_s) MCP skill.
  • Use the standard jupyter_client + ipykernel stack instead of a custom worker protocol.
  • Lazily bootstrap the parent PR's connected Dimos app handle and a Memory2 SqliteStore attached to the explicitly configured recorder database.
  • Preserve the Jupyter namespace across calls and ordinary Python errors.
  • On timeout, interrupt first and preserve the namespace when the kernel recovers; restart only an unresponsive or dead kernel.
  • Define a private, pure Recorder beside the xArm simulation-agent blueprint. It records the enabled joint-state and color-image streams to a stable database path without changing Memory2 or perception.
  • Add soft prompt guidance that prefers code execution for observation processing and multi-RPC control flow while retaining direct skills for atomic actions.
  • Use standard DimOS structured logs and Jupyter execution identity for submitted source, output, duration, timeouts, interrupts, and restarts.

ipykernel and jupyter-client are explicit dependencies of the agents extra and are imported lazily. The v1 interface is foreground-only, text-only, single-call-at-a-time, and intentionally not a security sandbox. Real-hardware blueprints are unchanged.

How to Test

Install and run the xArm simulation:

uv sync --extra agents --extra manipulation
uv run dimos --simulation --viewer none run xarm-perception-sim-agent \
  -o pickandplacemodule.visualization.backend=none

From another terminal:

uv run dimos mcp call python_exec --json-args \
  '{"code":"print(memory.list_streams()); print(app.skills.get_robot_state())"}'

Focused automated validation:

uv run pytest \
  dimos/agents/test_code_policy.py \
  dimos/codebase_checks/test_no_all.py \
  dimos/robot/manipulators/xarm/blueprints/test_agentic.py \
  dimos/robot/test_all_blueprints_generation.py

The focused suite passes 19 tests. Ruff, strict mypy over the changed source files, blueprint registry generation, staged pre-commit hooks, and git diff --check also pass. A live MCP smoke test verified that python_exec can read the latest joint state and color image from the active Recorder database and query bounded observation history. Earlier validation covered DimOS RPC calls, namespace persistence, and interrupt-first timeout recovery.

AI assistance

OpenAI Codex with GPT-5 was substantially involved in design discussion, implementation, tests, documentation, and validation. The author reviewed and approved the direction interactively.

Checklist

  • I have read and approved the CLA.

@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.95184% with 39 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
dimos/agents/code_policy.py 80.88% 31 Missing and 8 partials ⚠️
@@            Coverage Diff             @@
##             main    #3259      +/-   ##
==========================================
+ Coverage   75.20%   75.26%   +0.06%     
==========================================
  Files        1128     1131       +3     
  Lines      107954   108307     +353     
  Branches     9751     9771      +20     
==========================================
+ Hits        81188    81521     +333     
- Misses      23955    23965      +10     
- Partials     2811     2821      +10     
Flag Coverage Δ
OS-ubuntu-24.04-arm 69.10% <88.95%> (+0.24%) ⬆️
OS-ubuntu-latest 71.01% <88.95%> (+0.09%) ⬆️
Py-3.10 71.01% <88.95%> (+0.08%) ⬆️
Py-3.11 71.01% <88.95%> (+0.09%) ⬆️
Py-3.12 71.01% <88.95%> (+0.09%) ⬆️
Py-3.13 71.01% <88.95%> (+0.09%) ⬆️
Py-3.14 71.02% <88.95%> (+0.09%) ⬆️
Py-3.14t 71.01% <88.95%> (+0.09%) ⬆️
SelfHosted-Large 29.16% <27.19%> (+0.07%) ⬆️
SelfHosted-Linux 35.62% <27.19%> (-0.04%) ⬇️
SelfHosted-macOS 34.66% <27.19%> (-0.06%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
dimos/agents/test_code_policy.py 100.00% <100.00%> (ø)
dimos/robot/all_blueprints.py 100.00% <ø> (ø)
dimos/robot/manipulators/common/agent_prompts.py 100.00% <100.00%> (ø)
...imos/robot/manipulators/xarm/blueprints/agentic.py 100.00% <100.00%> (ø)
...robot/manipulators/xarm/blueprints/test_agentic.py 100.00% <100.00%> (ø)
dimos/agents/code_policy.py 80.88% <80.88%> (ø)

... and 3 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread dimos/agents/code_policy/policy_kernel.py Outdated
Comment thread dimos/agents/code_policy/policy_kernel.py Outdated
Comment thread dimos/agents/code_policy/policy_kernel.py Outdated
Comment thread dimos/agents/code_policy/policy_kernel.py Outdated
Comment thread dimos/agents/code_policy/policy_kernel.py Outdated
Comment thread dimos/agents/code_policy/policy_kernel.py Outdated
Comment thread examples/code_policy_xarm_sim.sh Outdated
@TomCC7 TomCC7 changed the title feat(agents): add persistent code policy kernel feat(agents): add Jupyter-backed code policy module Jul 29, 2026
@TomCC7
TomCC7 marked this pull request as ready for review July 29, 2026 03:46
Comment thread dimos/perception/detection/type/detection3d/object.py
Comment thread dimos/perception/manipulation_policy_recorder.py Outdated
@TomCC7

TomCC7 commented Jul 29, 2026

Copy link
Copy Markdown
Member Author

cleaned up, can check again @leshy

@TomCC7
TomCC7 requested a review from leshy July 29, 2026 20:19
Base automatically changed from cc/feat/repl-module to main July 29, 2026 22:45
@mintlify

mintlify Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
dimensional 🟢 Ready View Preview Jul 30, 2026, 1:01 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@TomCC7

TomCC7 commented Jul 30, 2026

Copy link
Copy Markdown
Member Author

@mintlify

mintlify Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
dimensional 🟡 Building Jul 30, 2026, 1:00 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@github-actions github-actions Bot added the ready-to-merge Required CI checks have passed on this PR label Jul 30, 2026
@TomCC7
TomCC7 marked this pull request as draft July 30, 2026 18:35
@github-actions github-actions Bot removed the ready-to-merge Required CI checks have passed on this PR label Jul 30, 2026
return f"In [{execution_count}] {state} in {duration_s:.2f}s\n\n{body}"


code_policy_module = CodePolicyModule.blueprint

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this?

super().start()

@skill
def python_exec(self, code: str, timeout_s: float = MAX_EXECUTION_TIMEOUT_S) -> str:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this cannot exceed the RPC timeout, which I think is 30 seconds. You might want to extend that for this call. I think Jeff added a way to extend RPC timeouts for particular methods.

Comment on lines +124 to +125
# Runtime objects remain None until this instance has been deployed into
# its worker, so Module serialization needs no custom hooks.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what this means... Why not just do:

self._execution_lock: threading.Lock = threading.Lock()

here?

Comment on lines +154 to +156
lock = self._execution_lock
if lock is None:
lock = self._execution_lock = threading.Lock()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is quite odd.

If you're mutating self._execution_lock from different threads, you need a lock for it... but it's odd to need a lock for a lock.

Of course self._execution_lock won't be none in practice because self.start() initializes it. But you can avoid all of this by just initializing it in __init__.

"""


class CodePolicyModule(Module):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, the architecture is a bit odd.

You have to run dimos run ..., which spawns several processes, one of which runs CodePolicyModule. CodePolicyModule spawns a Jupyter python process which runs Dimos.connect() which connects to the dimos run... process. A bit circular.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. You actually raised a very good opportunity to do separation here. We can actually just write a script to expose a single execute_python mcp skill to agent so agent won't get confused by all other mcp skills. I'll check if this is better



class CodePolicyModule(Module):
"""Execute trusted agent-authored Python against a running DimOS system."""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't read the full paper, but I think a key part is being able to generate code when an undefined function is called.

Central to our approach is prompting hierarchical code-gen (recursively defining undefined functions),

Image Image

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original code as policy paper is in 2022, which is even pre-chatgpt era (1000 years ago in AI lol). So you might want to check this instead: https://research.nvidia.com/labs/gear/aspire/

And there is indeed a naming issue of this module, it's only a small part of the code-as-policy flow.

@spomichter

Copy link
Copy Markdown
Contributor

pretty cool feature

@Jerrybery

Copy link
Copy Markdown

Really nice direction — a persistent, agent-authored Python action space is a big unlock beyond one-shot skill calls. I ran the branch on a sim xArm setup (minimal blueprint + an OpenAI-compatible LLM backend) and the core loop works well: one python_exec call could command joints via RPC, poll memory.streams["coordinator_joint_state"] for convergence, and replan — the ASPIRE-style execution backend is real. A few thoughts from the code-as-policy literature, plus what the hands-on run surfaced.

On the naming / scope discussion (paul-nechifor's point about hierarchical code-gen): agreed this module is not full CaP — but I'd argue hierarchical code-gen is not the part of the 2022 paper worth chasing. Two more recent data points reframe where the leverage is:

  • CaP-X (2026) systematically ablated the CaP stack and found most of the performance comes from the hand-designed primitive scaffolding, not the model — stripping abstraction layers drops closed-model success from 57% to 18%. The implication for this PR: the real asset is the primitive surface exposed to the agent (app, memory, RPC discovery), and this PR gets that right. Naming it after the execution primitive rather than the full CaP flow seems fine.
  • ASPIRE (NVIDIA GEAR, 2026) — which TomCC7 already linked — shows the large wins come from the closed loop around execution, not one-shot synthesis: per-primitive multimodal traces → targeted repair → distilled skill library. Their execution engine alone moved LIBERO-Pro macro success 14% → 62%, and the accumulated skill library transferred zero-shot to long-horizon tasks (31% vs ~4%). From that perspective this module is best framed as the execution backend of such a loop — and the loop is currently missing its other half.

What the loop's other half would look like (v2 candidates, non-blocking):

  1. Structured per-call traces. Today the agent gets a bounded text transcript, and a single python_exec can contain several RPCs plus a polling loop — when it fails, neither the agent nor the logs can localize which call failed. I confirmed the structured log only records duration_s / execution_count / output per execution; there is no per-RPC record. ASPIRE's biggest single win was giving the debugging agent structured, per-primitive evidence (return codes + observation snapshots) instead of raw output. A per-RPC trace record — args, result/error, timestamps, memory cursor — would double as eval data, and the existing structured logging feels like the natural place to hang it.

  2. Persistence of verified programs. The kernel namespace dies with the run, so every run starts from zero — the exact failure mode ASPIRE's skill library solves. A Voyager/ASPIRE-style pattern — save successful programs as named, callable skills retrievable in later sessions — would compound value across runs. memory2 feels like the natural store for it.

  3. Surface the app RPC interface to the agent. Cheapest item on this list, and the one my run actually tripped on: the agent sees the MCP tool schemas but has no visibility into what app offers inside the kernel. Mine hallucinated app.robot.move_to_joints(...) in a retry branch (real name: app.ManipulationModule, different arg shape) — it only survived because the retry path never executed. Injecting the available module names + RPC signatures into the kernel bootstrap preamble or the python_exec tool description would eliminate this whole class of errors.

  4. Timeout shape. Echoing paul-nechifor: MAX_EXECUTION_TIMEOUT_S = 110 hugs the default RPC timeout, and a long rollout with several blocking RPCs could exceed it. Longer term, an async task handle (submit → poll/notify, along the lines of the Task model in Agentic Task Spec #1231) may fit better than stretching a synchronous call — it also composes with cancellation (Ruthwik/fix/agent interaction reliability #3379).

  5. Trust boundary. The docs are refreshingly explicit (v1 = sim-only, not a sandbox). When this moves toward real hardware, the existing CapabilityRegistry / uses= arbitration might be a natural gate for which RPCs submitted code may call (read-only observation vs motion).

Related work worth a look if you haven't: CodeAct (Wang et al., ICML 2024, arXiv:2402.01030) — evidence that code as the action format outperforms JSON/text tool calls for multi-step control flow in agents, which is exactly the bet this PR makes; and Graph-as-Policy (2026) for a contrasting persistent-execution representation.

Two operational notes from my run: (a) while testing I hit an unrelated daemon-mode Zenoh bug that makes the kernel bootstrap fail with "No running DimOS coordinator found" under --daemon — filed separately as #3395 with a root-cause analysis; anyone reproducing this branch in daemon mode will hit it. (b) On a headless box the sim module needs headless=True (the passive viewer's GLFW init fails without DISPLAY and the adapter then times out waiting for ready) — might be worth a docs line.

Happy to prototype the structured-trace piece against this branch if there's interest.

@TomCC7

TomCC7 commented Aug 7, 2026

Copy link
Copy Markdown
Member Author

closed in lieu of #3378

@TomCC7 TomCC7 closed this Aug 7, 2026
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.

5 participants