Skip to main content

tools/

Each file in tools/ becomes one model-facing tool. Add a tool when the agent needs to do work through reviewed app-runtime code.

The filename stem is the tool name: tools/record_note.ts compiles to the record_note tool. Use snake_case filenames so tool names match what the model sees.

Minimal Example

// tools/echo.ts
import { defineTool } from "@assemblyline-agents/core";

export default defineTool({
description: "Echo a message back to the caller.",
inputSchema: {
type: "object",
properties: {
message: { type: "string" }
},
required: ["message"]
},
async execute(input: { message: string }) {
return { message: input.message };
}
});

Full Options

FieldType / valuesDefaultEffect
descriptionstring (required)Model-facing purpose statement; drives tool selection.
inputSchemaJSON Schema (required)Argument contract enforced before execute.
outputSchemaJSON SchemaDeclared result contract.
execute(input, ctx) => resultTool body; runs in the trusted app runtime.
toModelOutput(output) => projectionBounded projection shown to the model; full result is persisted.
needsApprovalboolean | ApprovalPolicyfalseApproval gate. approvalRequired(reason, sideEffect?) builds an always-approve policy; sideEffect defaults to "external".
defaultEnabledbooleaninferredForces the tool visible up front instead of behind deferred discovery.
sideEffect"none" | "idempotent" | "external"Side-effect class recorded for approval and audit surfaces.
capability{ visibility?, execution?, namespace?, tags?, aliases? }Discovery metadata. visibility: auto | always | deferred | skill | hidden. execution: auto | direct | sandbox | both.

Return values must be JSON-serializable. A thrown error marks the tool call failed, surfaces { error } to the model, and fails the run — an authored tool throwing means the agent's own code broke. Return an error-shaped result instead of throwing when the condition is something the model should recover from.

Execution Model

Tool code runs in the trusted app runtime by default. Use ctx.getSandbox() only when the tool needs isolated filesystem or shell work.

Assembly Line starts model runs with core harness tools and keeps authored tools behind deferred discovery unless you list them in agent.ts defaultTools or declare capability metadata.

Approval Gates

Use approval gates for durable side effects or sensitive operations:

import { approvalRequired, defineTool } from "@assemblyline-agents/core";

export default defineTool({
description: "Record a durable note.",
inputSchema: {
type: "object",
properties: { note: { type: "string" } },
required: ["note"]
},
needsApproval: approvalRequired("Recording a note is a durable side effect.", "idempotent"),
async execute(input: { note: string }, ctx) {
await ctx.emit("note.recorded", {
note: input.note,
idempotencyKey: ctx.idempotencyKey("record-note")
});
return { recorded: true };
}
});

Authored tools that perform non-idempotent external writes should use ctx.idempotencyKey(...) or a destination-level dedupe key derived from the tool-call id.

Durable Steps

Use ctx.step(...) to cache completed substeps inside the current run. If the same run is retried or resumed and the same step key is reached again, Assembly Line returns the persisted JSON result and records durable_step.replayed instead of running the body again.

import { defineTool } from "@assemblyline-agents/core";

export default defineTool({
description: "Hydrate a customer profile once per run.",
inputSchema: {
type: "object",
properties: { customerId: { type: "string" } },
required: ["customerId"]
},
async execute(input: { customerId: string }, ctx) {
const profile = await ctx.step(
`hydrate-customer:${input.customerId}`,
async () => {
const response = await fetch(`https://api.example.com/customers/${input.customerId}`, {
headers: { "Idempotency-Key": ctx.idempotencyKey(`customer:${input.customerId}`) }
});
return response.json();
},
{ metadata: { customerId: input.customerId } }
);

return { profile };
}
});

Step keys are scoped to the current run, and step results must be JSON-serializable. A crash inside the step body can still run the body again, so external writes should still use ctx.idempotencyKey(...) or a destination dedupe key. ctx.step(...) is completed-step replay, not universal deterministic workflow replay.

Safe Model Output

Use toModelOutput when the runtime should persist rich results but show the model only a bounded projection:

export default defineTool({
description: "Look up a record and return a safe summary.",
inputSchema: {
type: "object",
properties: { lookup: { type: "string" } },
required: ["lookup"]
},
async execute(input: { lookup: string }) {
return {
summary: `Found ${input.lookup}`,
internalScore: 0.98,
internalTrace: ["vector", "rerank", "policy"]
};
},
toModelOutput(output: { summary: string }) {
return { summary: output.summary };
}
});

Execution Context

execute(input, ctx) receives a ToolExecutionContext:

MemberEffect
ctx.runId, ctx.agentRevisionIdentity of the current run and compiled revision.
ctx.approvedToolCalltrue when this call has passed the runtime approval gate.
ctx.askQuestion(question, options?)Suspends the run to ask the user; returns Promise<never> — code after it never runs in this call.
ctx.emit(eventType, data?)Records a durable run event.
ctx.idempotencyKey(scope)Stable per-run dedupe key for external writes.
ctx.step(key, fn, options?)Completed-step replay cache (see Durable Steps).
ctx.getSandbox()Acquires the run sandbox for isolated file/shell work.
ctx.blob, ctx.memory, ctx.resourcesBlob, durable memory, and resource APIs when available.
ctx.spawnSubagent({ name, task, expectedOutput?, constraints? })Runs a compiled subagent; see Subagents.
ctx.connections, ctx.channelResolved connections and the originating channel view.
ctx.selfImprovement, ctx.automationManager, ctx.connectionManagerSkill-writing, dynamic-automation, and dynamic-connection APIs; present only when the matching agent.ts policy enables them.

Conventions

  • One tool per file; keep each tool focused on one capability.
  • Put provider event parsing in channels/, not tools.
  • Put reusable procedures in skills/, not long tool descriptions.
  • Gate non-idempotent external writes behind needsApproval and use idempotency keys.