tool
Define an app tool.
Input and output types are inferred from the schemas.
Import from @threads/core (TypeScript) or threads (Python).
function tool(options: {
name: Name;
description: string;
input: z.ZodType<Input>;
output?: z.ZodType<Output>;
runs?: "host" | "sandbox";
execute?: (input: Input, ctx: RunContext<Deps>) => Promise<Output>;
effect?: EffectClass;
dedupWindowMs?: PosInt;
reconcile?: { lookup: (effectKey: string, ctx: RunContext<Deps>) => Promise<LookupResult<Output>>; finality: "final" | "nonfinal" };
endsTurn?: boolean;
}): Tool<Input, Output, Deps>Parameters
nameNamerequiredThe name the model calls the tool by: lowercase letters, digits and underscores, starting with a letter, at most 64 characters. It must be unique within the agent.
descriptionstringrequiredWhat the tool does, shown to the model in its tool list. Write it for the model: when to call the tool and what it returns.
inputz.ZodType<Input>requiredTool arguments are parsed with it at the boundary; its JSON Schema is the pinned input_schema.
outputz.ZodType<Output>TypeScript only. Pinned output_schema. A result that fails it is an error result; the effect still happened. Omitted: the result isn't checked; a string is shown as is and any other value as canonical JSON.
runs"host" | "sandbox"default hostWhere execute runs. host (the default) runs it in the host process. sandbox is reserved and not supported in this release: a sandbox tool fails setup with ConfigError capability_missing (Python at tool(), TypeScript at agent check() or the first run) and never executes. Not pinned: host and an omitted runs give the same ToolSpec and config_hash. Only "host" is supported today; "sandbox" is a setup error.
execute(input: Input, ctx: RunContext<Deps>) => Promise<Output>The tool body for runs host, the only kind built today. Its return value is the result; a thrown error becomes an error tool_result the model sees. Omitting it is a setup error capability_missing.
execute(input)InputrequiredThe model's arguments, already parsed by the tool's input schema: the parsed Zod value in TypeScript, an instance of the input model in Python.
execute(ctx)RunContext<Deps>requiredThe run this call belongs to: deps, thread_id, branch_id, principal, call_id and effect_key (send effect_key to providers that dedup). In TypeScript it also carries an abort signal.
effectEffectClassdefault unguardedUndeclared tools are unguarded: uncertainty always parks.
dedupWindowMs / dedup_window_msPosIntMilliseconds within which the provider dedups a repeated request that carries the same effect key. Required exactly when effect is idempotent; omit it for any other effect, where giving it is a setup error invalid_config.
reconcile{ lookup: (effectKey: string, ctx: RunContext<Deps>) => Promise<LookupResult<Output>>; finality: "final" | "nonfinal" }Required exactly when effect is reconcilable; omit it for any other effect. finality is the tool's declared LookupCapability: the runtime accepts a not_found answer as final only when it is final; under nonfinal a not_found is treated as not_found_nonfinal and the call parks.
reconcile.lookup(effectKey: string, ctx: RunContext<Deps>) => Promise<LookupResult<Output>>requiredAsks the provider whether the call with this effect key took effect, after a crash or lost response. Returns a LookupResult: found with the value, not_found, or unknown.
reconcile.lookup(effectKey) / reconcile.lookup(effect_key)stringrequiredThe effect key of the call being checked, <branch_id>:<call_id>: the same key execute saw as ctx.effect_key.
reconcile.lookup(ctx)RunContext<Deps>requiredThe run the call belongs to: deps, thread_id, branch_id, principal and effect_key.
reconcile.finality"final" | "nonfinal"requiredWhether a not_found from lookup is trustworthy. final: not_found means the effect never happened. nonfinal: not_found may just not be visible yet, so the call parks for a person to settle.
endsTurn / ends_turnbooleandefault falsetrue: a successful result from this tool ends the turn with no further model call; an error result doesn't. Default false.