tool()
tool() builds a Step with no model attached — it’s a plain, deterministic capability that an agent() calls, or that stands alone as a workflow() node.
Signature
Section titled “Signature”export interface Tool<Name, In, Out, Deps> extends Step<Name, In, Out, Deps> { readonly "~kind": "tool"; readonly description: string; readonly run: (input: InferOut<In>, ctx: ToolCtx<Deps>) => Promise<InferOut<Out>>; /** at-least-once durability contract: crash mid-tool re-issues → idempotency. */ readonly idempotencyKey?: (input: InferOut<In>) => string;}
export function tool< const Name extends string, In extends IO<any, any>, Out extends IO<any, any>, const D extends readonly (keyof LoopyDeps)[] = [],>(def: { name: Name; description: string; input: In; output: Out; deps?: D; idempotencyKey?: (input: InferOut<In>) => string; run: (input: InferOut<In>, ctx: ToolCtx<D[number]>) => Promise<InferOut<Out>>;}): Tool<Name, In, Out, D[number]>;Fields
Section titled “Fields”name— a string literal; preserved in the type (not widened tostring), so it shows up verbatim wherever the tool is used (e.g. as a key inagent()’sToolMap).description— a plain-language description of what the tool does. This is model-facing prose, not documentation for you.input/output—IO<...>schemas built withio<...>()(or any Standard-Schema-shaped validator).deps— an array of string-literal keys intoLoopyDeps. Defaults to[]. Determines exactly whatctx.depsexposes insiderun.idempotencyKey— optional. loopy’s durability model is at-least-once: if a process crashes mid-tool-call, the runtime re-issues the call on recovery. A tool whose effect isn’t naturally idempotent (e.g. “create a file,” “open a PR”) should provideidempotencyKeyso a re-issued call can be recognized and deduplicated, once the runtime implements this.run— the tool’s body. Receives the validated input and aToolCtx<Deps>(just{ deps }for a plain tool — see Human-in-the-loop for theinterruptextension used byteam()).
Example
Section titled “Example”import { tool, io } from "@loopyjs/core";import type { GitRepo } from "./deps";
export const editFile = tool({ name: "editFile", description: "Apply a find/replace edit to a file.", input: io<{ path: string; find: string; replace: string }>(), output: io<{ applied: boolean }>(), deps: ["repo"], idempotencyKey: (i) => `edit:${i.path}:${i.find}`, run: async (i, { deps }) => { const cur = await deps.repo.read(i.path); await deps.repo.write(i.path, cur.replace(i.find, i.replace)); return { applied: true }; },});(from examples/tools.ts)
- agent() — pass a tool into an agent’s
toolsarray. - Guides → Writing a tool