TL;DR: When an AI agent must query live data and then perform conditional or repeated actions, repeatedly returning intermediate results to the model can add latency and consume context. In Yolo, my personal AI-native productivity app, I implemented a local form of Programmatic Tool Calling: the model generates a small JavaScript program, and Yolo executes it inside a QuickJS WebAssembly sandbox. The program can orchestrate only explicitly exposed application tools, while the host application continues to enforce schema validation, permissions, confirmation, execution limits, and undo policies.
1. The Real Bottleneck: Model-Mediated Orchestration
A common AI-agent architecture uses an iterative modelβtool loop:
User Request
β
βΌ
LLM βββΊ Tool Call βββΊ Host Application
β² β
βββββββ Tool Result βββββββ
This is often described as a ReAct-style loop, although ReAct has a more specific meaning: it interleaves model-generated reasoning traces, actions, and observations from an external environment. See ReAct: Synergizing Reasoning and Acting in Language Models.
The pattern works well for simple actions:
βCreate a task called βSubmit the reportβ for Friday.β
The model can produce one tool call, the application executes it, and the model returns the result.
The situation becomes more interesting when later actions depend on live data:
βFind all overdue tasks in my βWorkβ category and reschedule them to next Monday.β
A direct tool-calling workflow may look like this:
- The model calls
list_tasks. - Yolo returns the matching tasks.
- The model inspects those results and creates the required
update_taskcalls. - Yolo executes the updates.
- The model summarizes the outcome.
Modern tool-calling APIs can return multiple independent tool calls in one model response, so updating 15 tasks does not necessarily require 15 model turns. Independent calls may be issued together and executed in parallel.
The core bottleneck is sequential dependency depth.
Because the model cannot construct data-dependent calls until it receives intermediate query results, each dependent step forces another model turn:
- Turn 1: The model calls
list_tasksto fetch live data. - Turn 2: Upon receiving the task list, the model inspects the results and issues the
update_taskcalls in parallel. - Turn 3+: If a later decision depends on the outcome of those updates, another inference turn is required.
Thus, model turns scale with the number of sequential decision pointsβnot the volume of data records.
Where the overhead comes from
- Model latency: Every dependent phase requires another network request and model generation.
- Context growth: Intermediate tool results (large task lists, execution traces) accumulate in conversation context, consuming tokens on every subsequent turn.
- Repeated orchestration: The model must repeatedly parse intermediate observations into the next set of actions. For deterministic loops and filtering, code is a much clearer control mechanism.
- Unnecessary exposure of intermediate data: The model may only need a final summary, but direct tool calling often sends intermediate datasets back into the modelβs context.
A Quick Real-World Sidebar: I ran into this exact issue when setting up periodic log analysis with autonomous agents like Hermes Agent. Whenever the agent pulled raw, uncleaned logs directly into its context, it burned through massive token budgets just sifting through noise. The fix was simple: having a local script scrub and filter the logs first, then passing only the condensed summary back to the model. Programmatic tool calling takes that exact pattern and bakes it into a first-class, automated runtime capability.
2. Programmatic Tool Calling
Instead of asking the model to select every operation across multiple inference phases, Yolo exposes one meta-tool:
execute_program
The model writes a short JavaScript program that can:
- Query application data.
- Filter and transform results.
- Execute loops.
- Branch on conditions.
- Call multiple application tools.
- Return a compact structured result.
The generated program becomes an executable control plan.
This approach draws on the concept of Programmatic Tool Calling as documented by Anthropic, in which a model writes code that invokes tools inside a code-execution environment while intermediate results remain outside the modelβs context.
Cloudflare describes a closely related architecture as Code Mode: a model receives a code-execution tool and writes a program that composes typed tools, processes their results, and returns only the information needed for the response.
The broader idea also appears in research. CodeAct treats executable code as an agent action space, while PAL delegates deterministic computation to an interpreter instead of requiring the language model to perform every reasoning step itself.
Yolo adapts this paradigm into a local, model-agnostic runtime using JavaScript and QuickJS, allowing different configured LLMs to use the same orchestration runtime, while security remains enforced by the host application.
3. A Concrete Example
Suppose the user asks:
βFind all overdue Work tasks and move them to next Monday.β
Yolo asks the model to generate a program body. The execution engine wraps that body inside an asynchronous function, so both await and return are valid statements within the generated code.
A simplified program body generated by the model looks like this:
const targetDate = "2026-07-27";
const tasks = await list_tasks({
scope: "overdue",
category: "Work",
});
if (!tasks.ok) {
return {
ok: false,
error: tasks.error,
};
}
let applied = 0;
let queued = 0;
const failures = [];
for (const task of tasks.data) {
const update = await update_task({
task_id: task.id,
due_date: targetDate,
});
if (!update.ok) {
failures.push({
taskId: task.id,
error: update.error,
});
} else if (update.queued) {
queued += 1;
} else {
applied += 1;
}
}
log(
`Matched ${tasks.data.length} tasks: ` +
`${applied} applied, ${queued} queued, ` +
`${failures.length} failed.`,
);
return {
ok: failures.length === 0,
matched: tasks.data.length,
applied,
queued,
failed: failures.length,
failures,
targetDate,
};
The host execution engine evaluates this generated body by wrapping it inside an immediately invoked asynchronous function:
(async () => {
// generated program body
})();
From the modelβs perspective, the tool interface is ordinary asynchronous JavaScript:
const tasks = await list_tasks({ scope: "overdue" });
The typical execution path contains two model inference phases:
- Generate the program.
- Summarize the program result.
The important distinction is not that 15 database writes become one database write. The example still executes update_task once per task.
Instead, Yolo moves the loop and intermediate decisions out of repeated model inference and into an explicitly structured program executed locally.
A purpose-built bulk operation such as bulk_update_tasks could reduce domain-tool calls further. Programmatic tool calling is most useful when the workflow requires flexible filtering, branching, composition, or error handling that cannot be expressed cleanly through one fixed bulk API.
4. Architecture
Yolo is a desktop application built with Tauri v2, React, and TypeScript.
Below the model, the programmatic runtime has four main layers:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β AI Model β
β Generates a JavaScript program β
ββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Program Validation Layer β
β Size limits, syntax checks, allowed entry point β
ββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β QuickJS WebAssembly Sandbox β
β β
β Loops Β· Conditions Β· Temporary State Β· JSON Processing β
β β
β Exposed capabilities: β
β list_tasks Β· update_task Β· log β
ββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββ
β Explicit host-function bridge
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Yolo Standard Tool Registry β
β β
β Schema validation Β· Permissions Β· Confirmation Β· Undo β
ββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β SQLite / Tauri / App State β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
A. Capability containment
Executing model-generated JavaScript through eval in Yoloβs normal frontend environment would give that code access to anything already available in the same JavaScript realm.
Depending on the application, that might include:
- Application globals.
- DOM APIs.
- Browser storage.
- Network APIs.
- Functions that invoke Tauri commands.
Tauri still applies its own capability and runtime-authority checks to IPC commands. A webview does not automatically gain unrestricted native access merely because JavaScript is running inside it.
However, executing generated code in the main application realm would unnecessarily expose a much larger attack surface.
Yolo therefore executes the program inside a separate QuickJS instance compiled to WebAssembly.
WebAssembly modules have no ambient access to their host environment. The embedder determines which capabilities become available by controlling the imported functions and objects.
The QuickJS program does not automatically receive access to:
- Node.js.
- The DOM.
- The filesystem.
- Network APIs.
- Tauri IPC.
- Dynamic package imports.
- Yoloβs application state.
It receives only the host functions that Yolo explicitly installs.
This is capability containment: the program retains normal language features such as loops, variables, arrays, and conditions, but its external authority is restricted to a small allowlist.
B. Bridging asynchronous host tools
The generated program uses standard async/await, but the underlying bridge requires explicit handling.
A plain quickjs-emscripten host callback cannot simply return a native JavaScript Promise or a normal JavaScript object and expect QuickJS to adopt it automatically.
One supported approach is:
- Create a promise inside the QuickJS context.
- Start the asynchronous host operation.
- Convert the result into a QuickJS value.
- Resolve or reject the QuickJS promise.
- Execute pending QuickJS jobs so the guest program can resume.
The library also provides Asyncify-based builds for cases where the entire WebAssembly execution must suspend around asynchronous host work. Asyncified builds have additional size, performance, reentrancy, and suspension constraints, so the choice should be made deliberately.
The following is a simplified version of the deferred-promise bridge:
type JsonValue =
| null
| boolean
| number
| string
| JsonValue[]
| { [key: string]: JsonValue };
type ToolExecutor = (
args: Record<string, JsonValue>,
options: { signal: AbortSignal },
) => Promise<JsonValue>;
function installJsonTool(
name: string,
execute: ToolExecutor,
signal: AbortSignal,
): void {
const hostFunctionName = `__host_${name}`;
const hostFunction = vm.newFunction(
hostFunctionName,
(argsHandle) => {
const args = vm.dump(argsHandle) as Record<string, JsonValue>;
const deferred = vm.newPromise();
void execute(args, { signal }).then(
(result) => {
try {
const jsonString = JSON.stringify(result ?? null);
const resultHandle = vm.newString(jsonString);
deferred.resolve(resultHandle);
resultHandle.dispose();
} catch (err: unknown) {
const message =
err instanceof Error ? err.message : String(err);
const errorHandle = vm.newString(message);
deferred.reject(errorHandle);
errorHandle.dispose();
}
},
(error: unknown) => {
const message =
error instanceof Error
? error.message
: String(error);
const errorHandle = vm.newString(message);
deferred.reject(errorHandle);
errorHandle.dispose();
},
);
deferred.settled.then(() => {
vm.runtime.executePendingJobs();
});
return deferred.handle;
},
);
vm.setProp(
vm.global,
hostFunctionName,
hostFunction,
);
hostFunction.dispose();
const toolNameLiteral = JSON.stringify(name);
const hostNameLiteral = JSON.stringify(hostFunctionName);
const wrapperResult = vm.evalCode(`
globalThis[${toolNameLiteral}] = async (args) => {
const json = await globalThis[${hostNameLiteral}](args);
return JSON.parse(json);
};
`);
vm.unwrapResult(wrapperResult).dispose();
}
From the modelβs perspective, this bridge mechanism is entirely invisible. The LLM simply writes idiomatic asynchronous JavaScript using schema-described tool interfaces (such as await list_tasks(...)), while the host runtime handles handle lifecycle management, JSON serialization, and job queue awakening.
5. Preserving Application Safety Policies
The programmatic runtime is an alternative orchestration mechanism. It is not a privileged back door.
Every exposed function routes through Yoloβs existing tool registry.
Schema validation
The registry validates tool arguments before executing the underlying operation.
The sandbox cannot bypass a toolβs input schema merely because the call originated from generated code.
Permission modes
Yolo supports different execution modes:
- Plan: Generate and display a proposed operation without applying it.
- Ask: Queue mutations and request user confirmation.
- Auto: Execute eligible operations automatically.
The same mode applies whether the tool was called directly by the model or through execute_program.
Human confirmation
In Plan or Ask mode, a mutating tool can return:
{
"ok": true,
"queued": true
}
Yolo then renders a preview card instead of silently changing application data.
Destructive operations still require confirmation.
Undo and version checks
Successful mutations can return an UndoOp containing enough information to reverse the operation.
Yolo also records the post-write updated_at value. Before applying an undo operation, it can verify that the record has not subsequently changed.
This prevents an old undo action from overwriting a newer user edit.
Implemented safeguards
To prevent infinite loops or excessive resource consumption, Yolo enforces strict runtime safeguards:
- Memory & Stack Limits: Hard caps on QuickJS heap memory and call stack depth.
- Execution Timeouts: Hard deadlines enforced via QuickJS interrupt handlers for guest execution, alongside host-side
AbortSignalcancellation for pending I/O. - Payload Quotas: Caps on maximum program size, total tool calls, and output payload size.
6. Security Model
A WebAssembly sandbox is useful, but it is only one layer of the security design.
The real security boundary consists of:
- The QuickJS/Wasm isolation layer.
- The set of injected host functions.
- Tool argument validation.
- Application permission checks.
- Confirmation policies.
- Resource quotas.
- Tauri capabilities.
- Audit and undo behavior.
The most important rule is:
Generated code must never receive more authority than the user and the application policy intended to grant.
The host-function bridge is therefore part of the attack surface.
For example, a safe update_task function should not accept an arbitrary SQL fragment. It should accept a narrow, validated object:
{
task_id: string;
due_date: string;
}
The sandbox limits where the code can execute. The tool registry limits what the code can do.
Both are necessary.
7. Partial Failures and Idempotency
A program may execute several mutations before one of them fails.
For example:
Task 1 updated
Task 2 updated
Task 3 updated
Task 4 failed
Task 5 not yet attempted
The runtime must not report this as a simple success or failure. It should return a structured execution summary:
{
"matched": 5,
"applied": 3,
"queued": 0,
"failed": 1,
"notAttempted": 1
}
Future hardening work
For reliable bulk workflows, Yolo also needs to consider:
- Per-call execution IDs.
- Idempotency keys.
- Retry policies.
- Ordering requirements.
- Concurrent modification checks.
- Rate limits.
- Compensation or rollback behavior.
- Whether execution stops after the first failure.
A sandbox can contain the program, but it cannot automatically make the business operation transactional.
8. Direct Tool Calling vs. Programmatic Tool Calling
Let:
Nbe the number of domain operations.Dbe the number of sequential model-dependent decision phases.
| Dimension | Direct Tool Calling | Programmatic Tool Calling |
|---|---|---|
| Model inference phases | Usually grows with dependency depth (D), not necessarily item count (N) | Commonly one phase to generate the program and one to summarize it |
| Domain-tool executions | O(N), unless a bulk tool is available | O(N), unless a bulk tool is available |
| Parallel operations | Supported when calls are independent | Possible when the host bridge and business rules permit it |
| Intermediate results | Frequently enter the model context | Can remain inside the execution environment |
| Control flow | Distributed across model responses | Expressed explicitly through JavaScript |
| Model latency | Added at every dependent decision phase | Reduced when local code handles intermediate decisions |
| Tool latency | Still present | Still present |
| Execution overhead | Tool serialization and model orchestration | Tool serialization, sandbox startup, and VM execution |
| Failure handling | Model or host coordinates each phase | Program can aggregate failures, but generated logic may itself be wrong |
| Security surface | Tool schemas and host policies | Tool schemas and host policies, plus a sandbox and code bridge |
| Best use case | Small, fixed, easily reviewed actions | Data-dependent loops, filtering, branching, and multi-tool composition |
Programmatic tool calling is therefore not automatically faster in every situation.
Its primary benefit appears when:
- Intermediate data is large.
- Several operations depend on live results.
- The workflow contains loops or branching.
- Returning every intermediate result to the model is wasteful.
- A fixed bulk API would be too narrow.
9. When Not to Use It
Direct tool calling is usually better when:
- The request requires only one or two simple actions.
- Every action should be reviewed individually.
- The generated program would be more complex than the task itself.
- A reliable bulk API already exists.
- The operation requires a strong database transaction.
- The model needs to ask the user questions between steps.
- The workflow contains high-risk external side effects.
For example, the best implementation of:
βMark every selected task as complete.β
may simply be:
complete_tasks({
task_ids: selectedTaskIds,
});
There is no reason to generate a program when a narrow, well-tested domain operation already expresses the userβs intent.
Programmatic tool calling should complement good tool design, not replace it.
Conclusion
Programmatic tool calling does not turn N database operations into one operation.
It moves data-dependent orchestration out of repeated model inference and into an explicitly structured program executed within a constrained environment.
In Yolo, QuickJS provides the JavaScript runtime, WebAssembly helps establish an isolated execution boundary, and the existing tool registry preserves permissions, validation, confirmation, and undo behavior.
The result is a hybrid architecture:
- The model interprets intent and writes a plan.
- JavaScript handles deterministic control flow.
- The sandbox limits ambient capabilities.
- The host application retains authority over every side effect.
For complex agent workflows, that separation is more important than simply reducing the number of tool calls.
References
- Yao et al., βReAct: Synergizing Reasoning and Acting in Language Modelsβ
- Anthropic, βProgrammatic Tool Callingβ
- Anthropic, βIntroducing Advanced Tool Use on the Claude Developer Platformβ
- Anthropic, βCode Execution with MCP: Building More Efficient AI Agentsβ
- Cloudflare, βCode Modeβ
- Cloudflare, βCreate a Durable Code Mode Runtimeβ
- Wang et al., βExecutable Code Actions Elicit Better LLM Agentsβ
- Gao et al., βPAL: Program-Aided Language Modelsβ
- quickjs-emscripten Documentation
- WebAssembly Core Specification
- Tauri v2 Capability Reference
- Tauri v2 Runtime Authority