Optional options: { Optional builder configuration.
Optional strictWhen true, unresolved reference expressions throw an error instead of falling through as literal strings.
Whether a step naming this tool may be chained right now.
Whether any step of the spec names a tool that has side effects right now, or a tool that is itself a composition (whose own steps may).
Build an ITool-compatible object from a ComposableToolSpec.
The returned tool can be registered directly with any tool orchestrator that
accepts ITool. Its execute method runs the step pipeline sequentially,
threading outputs through the reference resolution system.
Machine-readable tool name exposed to the LLM (e.g. "research_topic").
Natural language description of what the composite tool does.
JSON Schema describing the arguments the composite tool accepts.
The composable pipeline specification to execute.
A fully-formed ITool instance whose execute method runs the pipeline.
const tool = builder.build(
'fetch_and_summarise',
'Fetch a URL then return a one-paragraph summary.',
{ type: 'object', properties: { url: { type: 'string' } }, required: ['url'] },
spec,
);
Run a pipeline. Every step is re-checked against the chainability rule
before it runs, because a tool is replaced by name, and the instance that
was checked is the one the gate runs. With options.dry (forge-time
tests), a step whose tool has side effects, or is itself a composition,
is not executed: its output is taken from stepOutputs, and the step is
listed as an effect that would have run. options.signal, when given,
reaches every step's run.
Compositions nest no deeper than MAX_COMPOSITION_DEPTH: the depth
travels in context.sessionData.emergentDepth, and a run at the limit is
refused with nesting_too_deep, a code no composition is suspended for (a
chain that long is not a cycle; the engine refuses a composition that
reaches itself at its own call).
A refusal or a failure ends the run; details then names the step, the
steps that completed before it, and the effects so far.
Optional dry?: { Optional signal?: AbortSignalValidate a ComposableToolSpec before building.
Performs structural checks only — it does not verify that the referenced tool names are actually registered in any registry. Use this method to give early, actionable feedback before attempting to build a tool.
Checks performed:
spec.steps must be a non-empty array.tool string.The spec to validate.
{ valid: true } when the spec passes all checks, or
{ valid: false, errors: string[] } with one message per failing check.
Optional errors?: string[]const result = builder.validate(spec);
if (!result.valid) {
console.error('Invalid spec:', result.errors);
}
Readonly strictWhen true, unresolved $-prefixed reference expressions throw instead
of silently passing through as literal strings. Useful for development
and testing to catch typos in inputMapping expressions early.
Builds composite
IToolinstances by chaining existing tool invocations.Each invocation is described by a ComposableStep that maps values from a shared pipeline context into the step tool's arguments via a lightweight reference expression syntax:
"$input.foo"args.foofrom the composite tool's own input"$input""$prev.bar"barfrom the previous step's output"$prev""$steps[0].output.data"output.datafrom the first step's output"$steps[0]"Reference expressions nested inside plain objects are resolved recursively, so
{ query: "$input.topic", limit: 10 }becomes{ query: "actual-topic", limit: 10 }.Every step runs through a StepGate, which is where the host's checks live, and is checked first against the chainability rule: a tool that declares no side effects is chained freely, a tool with side effects only when the host lists it, and a tool that declares nothing by no one.
Example