NOT FOR REVIEW [Plugin] run_code (PTC): programmatic tool calls — experimental, DANGEROUSLY_ gated - #30
Draft
eshwar-sundar-glean wants to merge 1 commit into
Draft
NOT FOR REVIEW [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
3 times, most recently
from
June 25, 2026 13:57
547fcec to
011524b
Compare
eshwar-sundar-glean
force-pushed
the
ptc_batch_tools
branch
5 times, most recently
from
August 12, 2026 13:59
ce707a9 to
46641c9
Compare
eshwar-sundar-glean
force-pushed
the
ptc_batch_tools
branch
from
August 13, 2026 17:13
46641c9 to
ba2c07e
Compare
run_code lets the host LLM execute familiar local Node.js programs and call Glean tools as async PTC_<TOOL>() functions. Filesystem/archive work, binary transforms, child processes, networking, loops, filtering, fan-out, and tool data flow can stay in one turn while large payloads remain in the runtime. - Gate the entire experimental experience behind exact DANGEROUSLY_ENABLE_UNSTABLE_RUN_CODE_FEATURE=true. The shipped config does not force-enable it: when off, tools/list is the baseline find_skills, run_tool, and setup surface; discovery emits no Node/PTC instructions; direct run_code calls are rejected; and glean_run follows its standard run_tool workflow. - Keep glean_run's always-visible description and normal instructions at the baseline wording. Its only experimental content is a tiny runtime dispatcher: when and only when run_code is exposed, Read a bundled guide outside the auto-discovered skills tree. Detailed routing, APIs, examples, PTC behavior, and safety text therefore enter context only in enabled sessions. - Avoid dynamic skill-shell injection: plugin-root substitution plus lazy Read works when shell injection is disabled and uses the actual MCP tool surface as the authority, including flags configured only in the MCP child environment. - Expose real CommonJS require(), standard Node/web globals, and prebound fs/path. Builtins including node:zlib and node:child_process work directly; third-party packages must be physically installed/resolvable from the plugin bundle. - Capture Node Console output in the result envelope so console.log/error do not corrupt MCP stdout. Direct process.stdout remains unrestricted and unsafe. - Code has full OS permissions; side effects are immediate, not rolled back, and not individually HITL-gated. The enabled-only tool/guide makes this explicit. - Prefer one run_code program for filesystem/system work, local Node operations, 2+ Glean calls, chaining, fan-out, loops, transforms, retries, and batching. Restrict run_tool guidance to one isolated call with no local work/state. - Resolve PTC names exactly first, then through a unique case-insensitive alias; e.g. PTC_GET_AGENT dispatches canonical backend name get_agent. Ambiguous case-only names fail rather than guessing. - Scan tool metadata from the managed/current cache first and Claude's launch project .claude/tmp/glean-skills-cache second. The launcher exports the project root, read paths are de-duplicated, and diagnostics log/report scanned roots. - Log runCodeEnabled with every tools/list result so feature-gate decisions are diagnosable without inferring them from counts. - Tests cover full Node execution, cache/casing behavior, exact-true gating, disabled discovery/config/static-skill contracts, enabled discovery guidance, and isolation of detailed instructions in the lazily loaded bundled guide. - Bump plugin manifests to 0.2.47 so Claude does not reuse stale install caches.
eshwar-sundar-glean
force-pushed
the
ptc_batch_tools
branch
from
August 14, 2026 03:57
ba2c07e to
8ca3127
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