[PR #296] feat(deepagents): PTC via Worker repl #334

Open
opened 2026-06-05 17:22:42 -04:00 by yindo · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/langchain-ai/deepagentsjs/pull/296
Author: @christian-bromann
Created: 3/10/2026
Status: 🔄 Open

Base: cb/ptcHead: cb/ptc-repl


📝 Commits (3)

📊 Changes

12 files changed (+1115 additions, -60 deletions)

View changed files

📝 examples/sandbox/sandbox-ptc-node.ts (+1 -1)
📝 examples/sandbox/sandbox-ptc-python.ts (+1 -1)
examples/sandbox/sandbox-ptc-repl.ts (+182 -0)
📝 examples/sandbox/sandbox-ptc.ts (+2 -1)
📝 libs/deepagents/src/index.ts (+2 -0)
📝 libs/deepagents/src/sandbox-ptc/index.ts (+6 -1)
📝 libs/deepagents/src/sandbox-ptc/middleware.ts (+147 -51)
📝 libs/deepagents/src/sandbox-ptc/prompt.ts (+95 -4)
📝 libs/deepagents/src/sandbox-ptc/types.ts (+6 -1)
libs/deepagents/src/sandbox-ptc/worker-repl.test.ts (+173 -0)
libs/deepagents/src/sandbox-ptc/worker-repl.ts (+244 -0)
libs/deepagents/src/sandbox-ptc/worker-runtime.ts (+256 -0)

📄 Description

Summary

Extends createSandboxPtcMiddleware to work without any sandbox backend. When no backend is provided (or the backend doesn't support spawnInteractive()), the middleware adds a js_eval tool backed by an isolated Worker thread with toolCall() and spawnAgent() as async globals. The agent writes JavaScript with Promise.all() for parallel tool calls and subagent spawning — no Deno, Modal, Docker, or VFS needed.

// Before: required a sandbox backend
createSandboxPtcMiddleware({ backend: sandbox, ptc: true })

// After: backend is optional — Worker REPL is used when omitted
createSandboxPtcMiddleware({ ptc: true })

Motivation

The sandbox PTC from #287 requires a sandbox provider with spawnInteractive(). Many users just want programmatic tool calling and subagent spawning without setting up sandbox infrastructure. This PR adds a lightweight alternative: an in-process JavaScript REPL running in a Worker thread, with the same toolCall()/spawnAgent() API.

How it works

The middleware now detects the backend type and picks the right mode:

Backend Mode Tool Parallelism
Sandbox with spawnInteractive() Sandbox PTC execute (bash/python/node) & + wait
Non-sandbox backend or no backend Worker REPL js_eval (JavaScript) Promise.all()

In Worker REPL mode:

  1. The middleware registers a js_eval tool and hides all PTC tools (including task) from the model — the agent must use toolCall()/spawnAgent() inside js_eval
  2. js_eval creates a Node.js Worker Thread (or Web Worker in browsers) that evaluates the code with toolCall() and spawnAgent() injected as async globals
  3. Tool calls from the Worker are dispatched via postMessage to the main thread, which invokes the actual tool with the LangGraph config (so task/subagents work correctly), then sends the result back
  4. Promise.all() works naturally since toolCall() returns Promises and the Worker's event loop isn't blocked

Environment detection

// Prefers Web Worker if available (browser), falls back to Node.js worker_threads
if (typeof globalThis.Worker === "function") return "web";
const mod = globalThis.process?.getBuiltinModule?.("node:worker_threads");
if (mod?.Worker) return "node";

Usage

import { createDeepAgent, createSandboxPtcMiddleware, StateBackend } from "deepagents";

const agent = createDeepAgent({
  model: "claude-sonnet-4-5-20250929",
  backend: (config) => new StateBackend(config),
  subagents: [{ name: "analyst", description: "...", systemPrompt: "..." }],
  middleware: [
    createSandboxPtcMiddleware({ ptc: true }),
  ],
});

The agent gets filesystem tools from the StateBackend and js_eval from the PTC middleware. Inside js_eval, the agent writes:

const classifications = await Promise.all(
  employees.map(emp => toolCall("classify_record", emp))
);
const analyses = await Promise.all(
  classifications.map(c => spawnAgent(`Analyse: ${c}`, "analyst"))
);

Example trace: https://smith.langchain.com/public/71192fe6-3a0b-4068-b057-816ae384f053/r


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/langchain-ai/deepagentsjs/pull/296 **Author:** [@christian-bromann](https://github.com/christian-bromann) **Created:** 3/10/2026 **Status:** 🔄 Open **Base:** `cb/ptc` ← **Head:** `cb/ptc-repl` --- ### 📝 Commits (3) - [`d149040`](https://github.com/langchain-ai/deepagentsjs/commit/d1490408282415d47ddc900775f5d91c9e96a6b3) feat(deepagents): PTC via Worker repl - [`ececd4a`](https://github.com/langchain-ai/deepagentsjs/commit/ececd4ae4e534ee9a75836102feb9018814d347f) format - [`8e9ceaf`](https://github.com/langchain-ai/deepagentsjs/commit/8e9ceaf8ca99eafb34587e37090c7b8a893ecf5d) make worker thread secure ### 📊 Changes **12 files changed** (+1115 additions, -60 deletions) <details> <summary>View changed files</summary> 📝 `examples/sandbox/sandbox-ptc-node.ts` (+1 -1) 📝 `examples/sandbox/sandbox-ptc-python.ts` (+1 -1) ➕ `examples/sandbox/sandbox-ptc-repl.ts` (+182 -0) 📝 `examples/sandbox/sandbox-ptc.ts` (+2 -1) 📝 `libs/deepagents/src/index.ts` (+2 -0) 📝 `libs/deepagents/src/sandbox-ptc/index.ts` (+6 -1) 📝 `libs/deepagents/src/sandbox-ptc/middleware.ts` (+147 -51) 📝 `libs/deepagents/src/sandbox-ptc/prompt.ts` (+95 -4) 📝 `libs/deepagents/src/sandbox-ptc/types.ts` (+6 -1) ➕ `libs/deepagents/src/sandbox-ptc/worker-repl.test.ts` (+173 -0) ➕ `libs/deepagents/src/sandbox-ptc/worker-repl.ts` (+244 -0) ➕ `libs/deepagents/src/sandbox-ptc/worker-runtime.ts` (+256 -0) </details> ### 📄 Description ### Summary Extends `createSandboxPtcMiddleware` to work **without any sandbox backend**. When no backend is provided (or the backend doesn't support `spawnInteractive()`), the middleware adds a `js_eval` tool backed by an isolated Worker thread with `toolCall()` and `spawnAgent()` as async globals. The agent writes JavaScript with `Promise.all()` for parallel tool calls and subagent spawning — no Deno, Modal, Docker, or VFS needed. ```typescript // Before: required a sandbox backend createSandboxPtcMiddleware({ backend: sandbox, ptc: true }) // After: backend is optional — Worker REPL is used when omitted createSandboxPtcMiddleware({ ptc: true }) ``` ### Motivation The sandbox PTC from #287 requires a sandbox provider with `spawnInteractive()`. Many users just want programmatic tool calling and subagent spawning without setting up sandbox infrastructure. This PR adds a lightweight alternative: an in-process JavaScript REPL running in a Worker thread, with the same `toolCall()`/`spawnAgent()` API. ### How it works The middleware now detects the backend type and picks the right mode: | Backend | Mode | Tool | Parallelism | |---|---|---|---| | Sandbox with `spawnInteractive()` | Sandbox PTC | `execute` (bash/python/node) | `&` + `wait` | | Non-sandbox backend or no backend | Worker REPL | `js_eval` (JavaScript) | `Promise.all()` | In Worker REPL mode: 1. The middleware registers a `js_eval` tool and hides all PTC tools (including `task`) from the model — the agent must use `toolCall()`/`spawnAgent()` inside `js_eval` 2. `js_eval` creates a Node.js Worker Thread (or Web Worker in browsers) that evaluates the code with `toolCall()` and `spawnAgent()` injected as async globals 3. Tool calls from the Worker are dispatched via `postMessage` to the main thread, which invokes the actual tool with the LangGraph config (so `task`/subagents work correctly), then sends the result back 4. `Promise.all()` works naturally since `toolCall()` returns Promises and the Worker's event loop isn't blocked ### Environment detection ```typescript // Prefers Web Worker if available (browser), falls back to Node.js worker_threads if (typeof globalThis.Worker === "function") return "web"; const mod = globalThis.process?.getBuiltinModule?.("node:worker_threads"); if (mod?.Worker) return "node"; ``` ### Usage ```typescript import { createDeepAgent, createSandboxPtcMiddleware, StateBackend } from "deepagents"; const agent = createDeepAgent({ model: "claude-sonnet-4-5-20250929", backend: (config) => new StateBackend(config), subagents: [{ name: "analyst", description: "...", systemPrompt: "..." }], middleware: [ createSandboxPtcMiddleware({ ptc: true }), ], }); ``` The agent gets filesystem tools from the `StateBackend` and `js_eval` from the PTC middleware. Inside `js_eval`, the agent writes: ```javascript const classifications = await Promise.all( employees.map(emp => toolCall("classify_record", emp)) ); const analyses = await Promise.all( classifications.map(c => spawnAgent(`Analyse: ${c}`, "analyst")) ); ``` Example trace: https://smith.langchain.com/public/71192fe6-3a0b-4068-b057-816ae384f053/r --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
yindo added the pull-request label 2026-06-05 17:22:42 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/deepagentsjs#334