[Plugin] run_code (PTC): programmatic tool calls — experimental, DANGEROUSLY_ gated#30
Draft
eshwar-sundar-glean wants to merge 1 commit into
Draft
[Plugin] run_code (PTC): programmatic tool calls — experimental, DANGEROUSLY_ gated#30eshwar-sundar-glean wants to merge 1 commit into
eshwar-sundar-glean wants to merge 1 commit into
Conversation
eshwar-sundar-glean
force-pushed
the
ptc_batch_tools
branch
from
June 25, 2026 11:37
3d15701 to
1e8a4b3
Compare
eshwar-sundar-glean
force-pushed
the
ptc_batch_tools
branch
2 times, most recently
from
June 25, 2026 12:42
bdcdbdf to
3a117f0
Compare
eshwar-sundar-glean
force-pushed
the
ptc_batch_tools
branch
2 times, most recently
from
June 25, 2026 13:48
95f9609 to
547fcec
Compare
…nly, DANGEROUSLY_ gated
run_code lets the host LLM write real JS where each Glean tool is an async
PTC_<TOOL>() function; the plugin runs the real run_tool/direct MCP call in the
trusted parent and feeds results back, so loops/filtering/data-flow happen in
one turn with large payloads kept out of context.
- Entire feature gated behind DANGEROUSLY_ENABLE_UNSTABLE_RUN_CODE_FEATURE
(default off → deployed run_tool behavior untouched). node:vm is NOT a
security boundary yet (deferred: worker/QuickJS isolation).
- Failures throw: a failed PTC_ call (tool isError, transport error, declined
approval) throws `PTC_<tool> failed: <reason>`. The cell is self-contained —
resolves to a value or throws; ok = !errorMessage. No execution ledger.
- Result envelope is { ok, value?, stdout?, session?, error?:{message} },
emitted on BOTH MCP channels: structuredContent (typed) + content[].text,
with a loose outputSchema. Value/stdout returned VERBATIM — no file
redirection (host/harness handles oversized output).
- ToolResult: .text/.json()/.get()/.format; no .isError. inspect(x) for shapes.
- Bulk pre-scan approval + JIT allowlist backstop, gated by ENABLE_HITL; shared
invokeTool/discoverTools with run_tool. Model told to call run_code serially.
- run_code.ts split into focused modules under src/tools/run-code/ (limits,
shape, output, envelope, preamble); the entry keeps the stateful engine.
eshwar-sundar-glean
force-pushed
the
ptc_batch_tools
branch
from
June 25, 2026 13:57
547fcec to
011524b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
run_code(Programmatic Tool Calls / PTC) lets the host LLM write real JavaScript in which each Glean tool is an in-scope async functionPTC_<TOOL_NAME>(args). The plugin intercepts each call, performs the realrun_tool(or direct) MCP call in the trusted parent, and feeds the result back into the running cell. Loops, filtering, fan-out, and chaining happen in code, in one turn, with large payloads kept out of the model's context.It's added alongside
run_tool(not replacing it):run_toolfor a single one-off call,run_codefor batches (2+ calls / chaining / a loop).The four runtime flows
Flow A — Discovery (
find_skillsin code mode →PTC_bindings)sequenceDiagram participant M as Model participant H as find_skills handler participant D as disk (skills cache) participant R as Glean gateway M->>H: find_skills({queries}) H->>R: callRemoteTool("find_skills") R-->>H: skills + tool JSON H->>D: writeSkillsToDisk(skills) H->>D: writeCoreTools(headTools) → _core/tools/<name>.json (direct:true) H->>H: formatAvailableSkillsPrompt(index, {codeMode:true}) H-->>M: <available_skills> + code-mode guide<br/>(read tool JSON → call PTC_<NAME>(args)) Note over M,D: Each tool JSON becomes a PTC_<NAME> binding at run_code time<br/>(discoverTools reads the cache; head tools bind as direct PTC_search/etc.)Flow B — Happy-path execution (single or batch)
sequenceDiagram participant M as Model participant H as run_code handler (parent) participant V as node:vm cell participant B as __ptcDispatch (host bridge) participant R as Glean gateway / remote M->>H: run_code({ code, reset? }) H->>H: acquireLock (FIFO mutex) H->>H: ensureContext(reset) — persistent vm ctx + PREAMBLE (ToolResult/inspect/print) H->>H: writeCoreTools + discoverTools → toolsByName H->>H: scanReferencedTools(code); bindingsSource(names) + async-IIFE wrapper H->>V: vm.runInContext(script) (Promise.race vs wall-clock timeout) V->>B: await PTC_FOO(args) B->>B: meta lookup · budget (MAX_CALLS) · deadline check alt meta.direct (head tool) B->>R: callRemoteTool(name, args) else skill tool B->>R: invokeTool → run_tool gateway end R-->>B: CallToolResult { content[].text, structuredContent? } B-->>V: { content, text, structured } → __mkResult → ToolResult (r) Note over V: r.json()/r.get(path,fb)/r.text/.format · inspect(r) for shape<br/>bare assignment (x = …) persists across run_code calls V-->>H: cell returns <value> (and/or print() → stdout) H->>H: value + stdout VERBATIM (host/harness handles oversized output) H-->>M: structuredContent + content[].text: { ok:true, value, stdout?, session? }Flow C — Failure path (a failed
PTC_call throws)sequenceDiagram participant M as Model participant H as run_code handler participant V as node:vm cell participant B as __ptcDispatch M->>H: run_code({ code }) H->>V: run cell V->>B: await PTC_FOO(args) B->>B: tool isError? transport throw? approval declined? B-->>V: throw Error("PTC_FOO failed: <reason>") alt uncaught V-->>H: rejects with `PTC_FOO failed: <reason>` H->>H: read thrown message H-->>M: { ok:false, error:{ message: "PTC_FOO failed: <reason>" } } else caught (self-contained batch) Note over V: try { await PTC_FOO() } catch (e) { out.push({error: e.message}) } V-->>H: cell returns the report it built H-->>M: { ok:true, value: out } ← failures captured IN the value end Note over B,H: No execution ledger. Failure rides the normal exception channel.<br/>Writes already made before a throw are NOT rolled back.Flow D — Approval / HITL (
ENABLE_HITL=true)sequenceDiagram participant M as Model participant H as run_code handler participant E as elicitInput (host) participant V as node:vm cell participant B as __ptcDispatch M->>H: run_code({ code }) H->>H: scan → needApproval (requires_approval, not already granted) opt needApproval && HITL && canElicit (BEFORE the cell) H->>E: ONE bulk card listing all PTC_ tools that will run alt accept E-->>H: accept → grant for the session (sessionApproved) else decline / channel error E-->>H: decline → { ok:false, "Bulk approval declined; nothing ran." } (fail-closed) end end H->>V: run cell (only if not declined) V->>B: await PTC_DYNAMIC(args) (a tool the static scan missed) opt requires_approval && not granted (INSIDE the cell — JIT backstop) B->>E: requestToolApproval(failClosed:true) alt declined / elicitation-error B-->>V: throw "Approval declined for PTC_DYNAMIC." else accept E-->>B: granted (session) end endSuggested review order (dependency order)
The engine file was split into focused modules; read in this order:
src/tools/run-code/limits.ts— env-overridable limits + shape tuning.src/tools/run-code/shape.ts— shape inference (powersinspect).src/tools/run-code/output.ts— value serialization (serialize),extractText,normalizeForSummary.src/tools/run-code/envelope.ts—RunCodeEnvelope+ assembly helpers.src/tools/run-code/preamble.ts— the in-VMPREAMBLE(ToolResult/inspect/print),bindingsSource(PTC_ binding generator),scanReferencedTools.src/tools/run-code.ts— the engine: persistentvmcontext,ensureContext, the__ptcDispatchbridge,handleRunCodeorchestrator.src/skill-tools.ts—discoverTools/findToolMeta/writeCoreTools(NEW; not used bymainyet).src/tools/run-tool.ts— additiveinvokeTool+requestToolApproval(used by run_code;run_tool's own path unchanged).src/index.ts—RUN_CODE_TOOLdef,RUN_CODE_ENABLEDgate, registration + dispatch (all flag-gated).src/tools/find-skills.ts+src/skill-writer.ts— additivecodeModebranch (inert when off).tests/run-code.test.ts— 22 tests incl. throw model, verbatim output, structuredContent, approval.Result envelope
Emitted on both MCP channels:
structuredContent(typed object) + the same JSON incontent[].text. A looseoutputSchemais declared on the tool. Values/stdout come back verbatim — no file redirection; the host/harness handles oversized output.{ "ok": true|false, "value": <return value VERBATIM>, // any JSON type; omitted on error "stdout": "...", // print() output; only when non-empty "session": { "fresh": true }, // only on a fresh/just-reset context "error": { "message": "PTC_X failed: …" } // only on a throw }Env flags
DANGEROUSLY_ENABLE_UNSTABLE_RUN_CODE_FEATURErun_codeis exposed alongsiderun_tool.ENABLE_HITLGLEAN_PTC_TIMEOUT_MSGLEAN_PTC_MAX_CALLSSecurity & deferred work
node:vmis not a security boundary. Injected host bridges are reachable from the cell; the sandbox shares the process (and the OAuth token on disk). Acceptable only because the feature is off by default + experimental — hence theDANGEROUSLY_/UNSTABLEflag name.readFile/writeFilethat denies the token dir. Process isolation alone doesn't close token exfiltration; the capability boundary is the real fix.ptc-e2e-fake-backend.🤖 Generated with Claude Code