Tools
Types of tools
Every agent's tools are one of four kinds, merged together into one
ToolDefinition[] before a turn runs:
- Local tools — code under
agents/<name>/tools/, exported from that folder'sindex.ts. The ordinary case: a tool that calls your own API, queries your own database, or wraps any other integration code you actually own — hand-written, or generated by the Web UI's HTTP tool builder below, which produces this exact same shape of file. - System tools —
core/system-tools/(readFile,system_ask_user). Merged into every agent unconditionally, the same waysystemToolsis spread into the tool list regardless of whatAgentConfig.toolsresolves to — not opt-out-able the normal way. - Gateway tools —
core/gateway-tools.tsregisters external tool gateways (Composio today — Nango, Arcade, Scalekit are meant to slot in later as thin adapters, same shape, once this mechanism is proven) against an agent, no hand-writtentools/index.tsrequired for those specific integrations.core/mcpplug.tsis the MCP-shaped adapter layer underneath it — formerly a standalone package, folded in once no second consumer ever materialized. - Agent as a tool —
core/agent-as-tool.tswraps a whole other agent — its own system prompt, tools, permission rules, and ReAct loop — as a singleToolDefinition. The calling agent only ever sees a request in, a final answer out: the wrapped agent's own turns, tool calls, and permission decisions never surface to it. See Discovery & Registry for thesubagents/folder convention built on top of this.
Running tools in parallel: safe: true
core/toollane.ts's ToolLane schedules a batch of already-approved
tool calls — everything the model requested in one turn that made it
past the gate — into lanes: consecutive calls the batch classifies
as safe merge into one shared parallel lane (run via Promise.all);
anything else, or a run of unsafe calls, gets its own solo lane,
awaited one at a time. Batch order is always preserved across lanes —
this only ever decides what runs together, never reorders anything.
Set safe: true on a ToolDefinition for a read-only call with no
side effects and no shared mutable state — a lookup, a search — never
for anything that mutates something (a refund, an email, a write):
import type { ToolDefinition } from 'loopengine'
export const lookupOrder: ToolDefinition = {
name: 'lookup_order',
description: "Look up an order's total and status",
input_schema: { type: 'object', properties: { orderId: { type: 'string' } }, required: ['orderId'] },
execute: async (input) => orders[input.orderId as string] ?? { error: 'not found' },
safe: true,
}
(agents/customer-service/tools/lookup_order.ts's real version —
get_shipment_details next to it is safe: true too, so a turn that
calls both together runs them concurrently instead of one after the
other.) AgentConfig.isSafeTool, if set, takes full precedence over
every tool's own safe flag — it's a function of the whole call, not a
static per-tool default, so it can vary by agent or by the call's own
args; omit it and each tool's own safe flag is looked up by name
instead, and omitting both runs every tool solo.
Adding a tool
Four ways, in increasing order of how much loopengine does for you:
- Text editor — hand-write a
ToolDefinitionunderagents/<name>/tools/and export it from that folder'sindex.ts. Full control, but you own the integration code and its tests. See Configure an Agent's Text editor section. - Web UI, "Create an HTTP tool" — a constrained form in the Tools
tab (method, a
{field}-templated URL, headers — a header value can reference{{ENV_VAR}}, read fromprocess.envat call time, never persisted as a literal secret — optional JSON body, and a response path to extract). No hand-written code:web/http-tool-admin.tsgenerates a real, readable.tsfile from the spec — the same kind of file a human would've written by hand for this one well-understood shape, never arbitrary code — writes it toagents/<name>/tools/, registers it in that folder'sindex.ts, and splices the liveToolDefinitionstraight into the running agent registry, no restart needed. A sidecar<name>.http-tool.jsonis what lets the tab's Edit button repopulate the form later; hand-editing the generated file directly still works, it just falls out of sync with that sidecar. - Web UI, Gateway Tools tab — pick a connected app (Composio
toolkit) and the specific tools you want from a picker, no code and
no generated file either. Submits to
POST /agents/:name/gateway-tools, which live-connects and resolves real tool descriptions before adding them — right for an integration Composio already covers, rather than one this repo has to describe itself. loopengine add-ability— installs a whole tool and its skill and permission rules together as one unit, rather than a tool alone. Right when the tool needs an accompanyingSKILL.mdoractauth.ymlrules to be usable safely, not just the tool file itself — see Abilities and Ability System.