[GH-ISSUE #553] QuickJS middleware drops LangGraph config for skillsBackend and PTC tool calls #277

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

Originally created by @luizzappa on GitHub (May 23, 2026).
Original GitHub issue: https://github.com/langchain-ai/deepagentsjs/issues/553

Summary

createCodeInterpreterMiddleware() has two paths where the LangGraph tool config is available but not forwarded:

  1. skillsBackend setup calls prepareSkillsForEval() without config, causing getCurrentTaskInput() to fall back to AsyncLocalStorage.
  2. Programmatic Tool Calling inside the QuickJS REPL invokes bridged tools with t.invoke(rawInput) instead of t.invoke(rawInput, config).
    This breaks browser/web builds, or any environment without working AsyncLocalStorage, and also breaks bridged tools that need graph runtime context, such as DeepAgents’ task tool.

Versions

  • @langchain/quickjs: 0.4.0
  • deepagents: 1.10.2
  • @langchain/langgraph: 1.3.2

Environment

Browser / webpack build.

Reproduction

import { createDeepAgent, StateBackend } from "deepagents";
import { createCodeInterpreterMiddleware } from "@langchain/quickjs";
// Created this way because there is a bug when
// we use only `new StateBackend()`, see https://github.com/langchain-ai/deepagentsjs/issues/547
const stateBackend = (runtime) => new StateBackend(runtime);
const agent = createDeepAgent({
  model: "openai:gpt-5.4",
  backend: stateBackend,
  skills: ["/skills/"],
  middleware: [
    createCodeInterpreterMiddleware({
      skillsBackend: stateBackend,
      ptc: ["task"],
    }),
  ],
});

Then trigger either:

  • the interpreter tool with skills enabled
  • a PTC call from inside the interpreter, for example:
await tools.task({
  description: "...",
  subagent_type: "seo-agent",
});

Actual behavior

The skillsBackend path calls:

const taskInput = getCurrentTaskInput();

and the PTC bridge calls:

const result = await t.invoke(rawInput);

In the first case, getCurrentTaskInput() falls back to AsyncLocalStorage:

const runConfig = config ?? AsyncLocalStorageProviderSingleton.getRunnableConfig();

In browser/web environments this can throw:

Config not retrievable. This is likely because you are running in an environment without support for AsyncLocalStorage.
If you're running `getCurrentTaskInput` in such environment, pass the `config` from the node function directly.

In the PTC case, tools invoked through tools.* do not receive graph runtime config. For example, await tools.task(...) can fail with:

Error: Tool 'task' failed: BUG: internal scratchpad not initialized.

Expected behavior

The middleware should forward the existing tool config in both places:

  • prepareSkillsForEval() should call getCurrentTaskInput(config)
  • PTC tools invoked from the REPL should receive config via t.invoke(rawInput, config)

Root cause

Current quickjs skills flow drops config before reading task input:

tool(async (input, config) => {
  if (skillsBackend !== undefined) {
    const setupError = await prepareSkillsForEval(
      session,
      skillsBackend,
      input.code
    );
  }
});
async function prepareSkillsForEval(session, skillsBackend, code) {
  const taskInput = getCurrentTaskInput();
}

Current PTC bridge also drops config:
const result = await t.invoke(rawInput);

Proposed fix

Thread config through the skills setup:

async function prepareSkillsForEval(session, skillsBackend, code, config) {
  const taskInput = getCurrentTaskInput(config);
  const resolved = await resolveBackend(skillsBackend, {
    ...config,
    state: taskInput,
  });
}

Store the active eval config on the ReplSession and forward it to bridged tools:

session.setToolConfig(config);
const result = await t.invoke(rawInput, this.toolConfig);

Why this matters

This is especially important for browser/web builds, where AsyncLocalStorage may not exist or may be stubbed out. It also ensures PTC tools called from inside QuickJS receive the same graph context as direct top-level tool calls.

Originally created by @luizzappa on GitHub (May 23, 2026). Original GitHub issue: https://github.com/langchain-ai/deepagentsjs/issues/553 ## Summary `createCodeInterpreterMiddleware()` has two paths where the LangGraph tool `config` is available but not forwarded: 1. `skillsBackend` setup calls `prepareSkillsForEval()` without config, causing `getCurrentTaskInput()` to fall back to `AsyncLocalStorage`. 2. Programmatic Tool Calling inside the QuickJS REPL invokes bridged tools with `t.invoke(rawInput)` instead of `t.invoke(rawInput, config)`. This breaks browser/web builds, or any environment without working `AsyncLocalStorage`, and also breaks bridged tools that need graph runtime context, such as DeepAgents’ `task` tool. ## Versions - `@langchain/quickjs`: 0.4.0 - `deepagents`: 1.10.2 - `@langchain/langgraph`: 1.3.2 ## Environment Browser / webpack build. ## Reproduction ```ts import { createDeepAgent, StateBackend } from "deepagents"; import { createCodeInterpreterMiddleware } from "@langchain/quickjs"; // Created this way because there is a bug when // we use only `new StateBackend()`, see https://github.com/langchain-ai/deepagentsjs/issues/547 const stateBackend = (runtime) => new StateBackend(runtime); const agent = createDeepAgent({ model: "openai:gpt-5.4", backend: stateBackend, skills: ["/skills/"], middleware: [ createCodeInterpreterMiddleware({ skillsBackend: stateBackend, ptc: ["task"], }), ], }); ``` Then trigger either: - the interpreter tool with skills enabled - a PTC call from inside the interpreter, for example: ```js await tools.task({ description: "...", subagent_type: "seo-agent", }); ``` ## Actual behavior The skillsBackend path calls: `const taskInput = getCurrentTaskInput();` and the PTC bridge calls: `const result = await t.invoke(rawInput);` In the first case, `getCurrentTaskInput()` falls back to `AsyncLocalStorage`: `const runConfig = config ?? AsyncLocalStorageProviderSingleton.getRunnableConfig();` In browser/web environments this can throw: ``` Config not retrievable. This is likely because you are running in an environment without support for AsyncLocalStorage. If you're running `getCurrentTaskInput` in such environment, pass the `config` from the node function directly. ``` In the PTC case, tools invoked through tools.* do not receive graph runtime config. For example, await tools.task(...) can fail with: `Error: Tool 'task' failed: BUG: internal scratchpad not initialized.` ## Expected behavior The middleware should forward the existing tool config in both places: - `prepareSkillsForEval()` should call `getCurrentTaskInput(config)` - PTC tools invoked from the REPL should receive config via `t.invoke(rawInput, config)` ## Root cause Current quickjs skills flow drops config before reading task input: ```js tool(async (input, config) => { if (skillsBackend !== undefined) { const setupError = await prepareSkillsForEval( session, skillsBackend, input.code ); } }); async function prepareSkillsForEval(session, skillsBackend, code) { const taskInput = getCurrentTaskInput(); } ``` Current PTC bridge also drops config: `const result = await t.invoke(rawInput);` ## Proposed fix Thread config through the skills setup: ```js async function prepareSkillsForEval(session, skillsBackend, code, config) { const taskInput = getCurrentTaskInput(config); const resolved = await resolveBackend(skillsBackend, { ...config, state: taskInput, }); } ``` Store the active eval config on the `ReplSession` and forward it to bridged tools: ```js session.setToolConfig(config); const result = await t.invoke(rawInput, this.toolConfig); ``` ## Why this matters This is especially important for browser/web builds, where `AsyncLocalStorage` may not exist or may be stubbed out. It also ensures PTC tools called from inside QuickJS receive the same graph context as direct top-level tool calls.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/deepagentsjs#277