Skip to main content

Custom Tools

A guide to building and configuring custom tools in the thunk builder, including AI-based, code-based, and other tool types.

Custom Tools

A custom tool is a capability defined by the thunk builder that the AI agent can invoke during a workflow run. The AI agent calls the tool by name, passes structured inputs, and acts on the return value.

From + Add New Tool you can create several types, including Custom AI Tool, Custom Code Tool, Document Template Tool, Spreadsheet Lookup Tool, Custom Database Tool, and Tool Wrapper. You can also build a new tool to match one entry of an interface — a file you upload, or one already used by a plug-in connection on this thunk. Thunk fills in that tool's name, description, and inputs from the interface and leaves you to implement it; those three then stay fixed. See Reuse thunk interfaces and plug-in connections. This article focuses on AI and Code tools. For filling Word or Google Doc templates, see Generate documents from templates. For looking up rows in a spreadsheet by the columns you choose, see How to build a Spreadsheet Lookup tool. For querying a connected SQL database, see How to build a Custom Database tool. For wrapping an existing tool with fixed arguments and inputs of your own, see How to build a Tool Wrapper. Not sure which type fits? See the comparison in AI Tools.

A tool is not finished until it has tests. When you build a tool, give it test cases, each with at least one assertion on what it should return. See Automated tests for custom tools.

An AI tool executes a natural language instruction. A code tool executes a JavaScript function. Both are built on the same tool page. What differs is the logic in the Details section of the Implementation tab, described below.


Tool Builder

Open Custom-built tools in the left nav and select a tool. Its page has these tabs:

  • Definition — the tool's name, description, and intent, and whether it is turned on.

  • Implementation — what the tool takes, what it returns, and how it does the work.

  • Try It — run the tool with sample values before you rely on it in a workflow.

  • Tests — saved test cases that check the tool after every change. See Automated tests for custom tools.

  • Call history — the tool's past runs.

Definition

Tool name is the snake_case identifier the AI agent uses to select the tool. The name should describe the decision, computation, or action the tool performs.

Description is the plain-language explanation of what the tool does and what it returns. The AI agent reads the description to determine when to call the tool. A precise description improves invocation accuracy. A vague description produces inconsistent behavior.

Logic/Intent describes, in plain language, what the tool should do and how. The AI that helps you build the tool uses it to design the inputs, outputs, and implementation. It is not the instruction or code the tool runs — that is on the Implementation tab.

The Enabled switch at the end of the tab turns the tool on or off.

Implementation tab

The Implementation tab is split into sections you can open and close independently: Inputs, Outputs, Details, Tools, and Behavior. Only Details starts open. Each closed section shows a short summary in its header — for example Inputs (2 fields) or Tools (2 connections enabled) — so you can see what is set without opening it. Not every tool type has every section: Tools appears only for code tools and document template tools, and some tool types have no Outputs.

Inputs and Outputs each have a Show raw JSON schema? switch, so you can see either one as a JSON schema instead of a list of fields.

Inputs

Each input is defined as a named field with a description and a type. The AI agent reads the description of each input to determine what value to pass. Field names are case-sensitive and must match how they are referenced in the implementation.

A Conversation input lets a custom AI tool or a custom code tool read and message a conversation on the current work item. Use type Conversation, or a Reference that points at a Conversation property (the reference is the precise choice when a work item has more than one conversation). How the nested AI sees the transcript, how a code tool sends and waits, and examples of both are in How to build a tool that uses a conversation. While a custom AI or custom code tool is waiting for a reply, the calling step's activity log shows that tool's own log live — not only after the tool finishes.

Format constraints belong in the input description. If a date field expects DD/MM/YYYY, that constraint should appear in the description. If a field accepts only a fixed set of values, those values should be listed. If a field may be null and null has a specific meaning, that should be stated.

Details

The Details section contains the logic for the tool. The form it takes depends on the tool type and is described in the sections below.

Tools

A code tool can call other tools. Turn on the connections it may use in the Tools section; its header shows how many are on.

Behavior

The Behavior section applies to all tool types:

  • Allow batch processing? lets the AI agent send the tool several requests in one call. It is on by default. While it is on, Requests run at a time sets how many of those requests run at once.

  • Behavior hints for AI agents tell an outside AI agent how the tool behaves when your thunk is published as an MCP server: whether it is read-only, whether it can overwrite or delete data, whether it is safe to repeat, and whether it reaches outside systems or people. Agents such as ChatGPT and Claude use these hints to decide what they can call without asking first. Each hint is a switch that starts at a default; turn it on or off wherever you know how the tool behaves. The hints do not change approval or whether the tool is available.


Naming and Description Style Guide

The tool name, tool description, and input descriptions are read by the AI agent at runtime. Poorly written metadata produces incorrect or inconsistent invocations regardless of how well the implementation is written.

Tool Names

Tool names use snake_case. The name should express what the tool does, not how it does it.

Example

✅ Good name

is_email_send_required, is_continued_tracking_required, final_go_live_check

❌ Bad name

check, calculate, process

Names that describe the question the tool answers or the computation it performs work well. Generic names that do not distinguish the tool from others in the same thunk do not.

Tool Descriptions

The tool description should state what the tool does and what it returns, in one or two sentences. The AI agent uses this description to decide whether to call the tool at a given point in the workflow.

Example

✅ Good description

Produce a JSON object representing the semantic and syntactic structure of the input document.

❌ Bad description

Text analysis tool.

Descriptions that spell out the tool's purpose and what it returns let the AI know what to expect when calling it. Vague descriptions that could apply to many tools produce inconsistent behavior.

Input Descriptions

Each input description should state what the field contains and any constraints on its value. Three categories of constraint matter most:

  • Format. If the value must follow a specific format, state it explicitly.

    • ✅ The submission date in DD/MM/YYYY format.

    • ❌ The submission date.

  • Allowed values. If the field accepts only a fixed set of values, list them.

    • ✅ The current status of the request. Expected values: "Pending", "Approved", "Rejected".

    • ❌ The current status of the request.

  • Null semantics. Each input has an Is Required option in the tool builder. If Is Required is enabled, the field will never be null and no null handling is needed. If Is Required is not enabled, the field may be null. When null has a specific meaning, state it in the description.

    • ✅ The date the record was closed. Null if the record is still open.

    • ❌ The date the record was closed.

Input names use PascalCase by convention when they correspond to fields in the thunk's data schema. This makes it easier to trace which schema field each input maps to.


How the AI Agent Calls a Custom Tool

The AI agent decides to call the tool based on the tool name and description. It constructs the input object by reading each input's description and populating it from available data. It then receives the return value and uses it in subsequent steps.


AI-based Tools Implementation

An AI tool implements its logic as a natural language instruction. The AI agent reads the instruction and produces a result. AI tools are appropriate when the task requires language understanding, judgment, or interpretation that cannot be expressed as fixed rules.

In addition to Inputs, an AI tool requires an Outputs section. Outputs are defined the same way as inputs: each output is a named field with a description and a type. The AI agent reads the output definitions to understand what values it is expected to produce. A well-written output description tells the agent both what the field contains and the format it should be in. Without defined outputs, the agent has no schema to produce values against.

The instruction can refer to inputs by name. The tool's AI agent receives the input values as a JSON object alongside the instruction, together with the input definitions; a name in the instruction is not replaced with its value. The instruction is prose that describes what to evaluate, what to produce, and how to handle edge cases.

When the input is unstructured or the outcome requires interpretation, the logic cannot be expressed as code. produce_structured_document in the Benchmark Invoice Processor sample illustrates this. It extracts structured fields from an invoice.

Tool name:produce_structured_document

Inputs:

Name

Description

Type

fileUrl

URL of the document being processed.

TEXT

scenario

The scenario the document is part of, including descriptions of the entities involved and their roles.

TEXT

expectedLanguage

The language the document is expected to be written in.

TEXT

Outputs:

Name

Description

Type

DocumentStructure

A JSON object capturing all fields and values extracted from the source document, preserving the document's original structure and terminology.

TEXT

The thunk reads each named output field from the agent's response and makes it available to subsequent steps.

Read the document and produce a structured JSON object that captures all relational
information. Extract text exactly as it appears in the source document. Do not
modify values to any canonical form — if a field has a currency symbol, keep it;
if it has numerical punctuation, keep it. If a field is not present in the document,
return null for that field rather than inferring it from other values.

The instruction defines not just what to extract but how to behave when the document is incomplete or inconsistent.


Code-based Tools Implementation

A code tool is appropriate when the logic is rule-driven and the outcome is fully determined by the inputs. Use code when the decision can be expressed as conditions, comparisons, or calculations, and when the same inputs should always produce the same output. If the task requires reading unstructured text, exercising judgment, or interpreting ambiguous content, use an AI tool instead.

For the JavaScript format, available globals (getTypedInput(), tools, thunk.dates, openFile, and others), and how to return a result, see How custom code tools work.

A common shape is a single async function:

async function run(input) {
  return result;
}

The function is declared async; its name does not matter. You can also write only the function body, which is the preferred form: see How custom code tools work. The input parameter is an object whose keys correspond to the input names defined in the Inputs section. No external imports are available. Standard JavaScript and modern syntax are supported, including optional chaining, destructuring, and template literals.

Input fields can be null even when they are expected to have values. Guard against null inputs using optional chaining (?.) or fallback expressions (|| ""). Normalize string inputs with .trim() before comparison to avoid mismatches caused by whitespace. Guard clauses at the top of the function exit early when preconditions are not met, keeping the main logic path uncluttered.

Example: Schedule Wait Gate

A common pattern in scheduled workflows is a tool that decides whether a workflow instance should wait for the next scheduled run or conclude. The tool checks whether the item has reached a terminal status and returns a value the AI agent uses to route the workflow.

Basic Version

Tool name:is_continued_tracking_required

Inputs:

Name

Description

Type

CurrentStatus

The current status of the item. Expected values: "In Progress", "Pending Review", "Completed", "Cancelled", "Closed".

TEXT

async function run(input) {
  const terminalStatuses = ['Completed', 'Cancelled', 'Closed'];
  const status = (input.CurrentStatus || '').trim();
  if (terminalStatuses.includes(status)) {
    return 'NO';
  }
  return 'YES';
}

'YES' signals that tracking should continue; 'NO' signals that the workflow instance can conclude.

Enhanced Version

The tool can be extended to return a structured object when downstream steps require additional data.

Tool name:is_continued_tracking_required

Inputs:

Name

Description

Type

CurrentStatus

The current status of the item. Expected values: "In Progress", "Pending Review", "Completed", "Cancelled", "Closed".

TEXT

StartDate

The date the item was opened, in DD/MM/YYYY format.

TEXT

CurrentDate

Today's date, in DD/MM/YYYY format.

TEXT

Two new inputs provide the dates needed to compute how long the item was open. On conclusion, the AI agent uses FinalStatus and DaysOpen to update the record before the workflow concludes.

async function run(input) {
  const terminalStatuses = ['Completed', 'Cancelled', 'Closed'];
  const status = (input.CurrentStatus || '').trim();  if (!terminalStatuses.includes(status)) {
    const result = { continueTracking: true };
    return result;
  }  let daysOpen = null;
  if (input.StartDate && input.CurrentDate) {
    const [d1, m1, y1] = input.StartDate.split('/');
    const [d2, m2, y2] = input.CurrentDate.split('/');
    const start = new Date(y1, m1 - 1, d1);
    const end = new Date(y2, m2 - 1, d2);
    daysOpen = Math.round((end - start) / (1000 * 60 * 60 * 24));
  }  const result = {
    continueTracking: false,
    FinalStatus: status,
    DaysOpen: daysOpen
  };
  return result;
}

When the item is still active, the function returns early with continueTracking: true. When the item has reached a terminal status, it computes DaysOpen and returns the outcome as a structured object. The AI agent reads the object fields by name and uses them independently.


Examples in the Sample Library

  • Benchmark Invoice Processor — uses an AI tool, produce_structured_document, to read an invoice and return a structured JSON object capturing all relational data.

  • Lead Enrichment for Gyms — uses an AI tool, classify_image, to evaluate gym photos and categorize them based on content rules defined in the instruction.

Did this answer your question?