How the Tool-Matching Algorithm Selects Tools in Craft Agents OSS
The tool-matching algorithm employs a stateless, ID-based indexing system that registers every tool_use block in an append-only ToolIndex, then matches tool_result blocks to their originating calls via unique identifiers rather than FIFO queues or mutable stacks.
The Craft Agents OSS platform relies on a deterministic matching mechanism to decide which tools an agent can invoke. Unlike traditional queue-based systems, this algorithm uses immutable identifiers to track tool relationships across asynchronous streams and concurrent sessions. The implementation centers on two core functions—extractToolStarts and extractToolResults—defined in packages/shared/src/agent/tool-matching.ts.
The Stateless ID-Based Architecture
At the heart of the system lies the ToolIndex class, which maintains an append-only registry of tool metadata. This stateless design ensures that tool selection depends solely on identifiers, not message ordering or mutable state.
The ToolIndex Registry
The ToolIndex class (defined in packages/shared/src/agent/tool-matching.ts) maps each tool-use-id to its corresponding tool name and input arguments. When the SDK emits a tool_use block, the register method captures this relationship immediately:
// From packages/shared/src/agent/tool-matching.ts lines 42-55
class ToolIndex {
private index = new Map<string, { toolName: string; input: unknown }>();
register(toolUseId: string, toolName: string, input: unknown): void {
this.index.set(toolUseId, { toolName, input });
}
lookup(toolUseId: string): { toolName: string; input: unknown } | undefined {
return this.index.get(toolUseId);
}
}
This registry enables instant lookup regardless of when blocks arrive, supporting out-of-order processing in concurrent environments.
Extracting Tool Start Events
The extractToolStarts function processes assistant message content blocks to create tool_start events. It walks the content array, identifies tool_use blocks, and records them in the shared ToolIndex:
- Parent relationship detection: The algorithm extracts the
parent_tool_use_idfield from the SDK. If this field isnull, it falls back to a single active task heuristic (lines 58-68). - Event creation: Each valid
tool_useblock generates atool_startevent containing thetoolUseId, tool name, and enriched metadata.
Matching Tool Results to Originating Calls
When a user message contains tool_result blocks, the extractToolResults function (lines 39-49) handles the pairing. It looks up the corresponding tool_use_id in the ToolIndex to retrieve the original tool name and input:
// Conceptual implementation from the source
function extractToolResults(
content: ContentBlock[],
parentToolUseId: string | null,
fallbackResult: unknown,
toolIndex: ToolIndex,
turnId: string
): ToolResultEvent[] {
return content
.filter(block => block.type === 'tool_result')
.map(block => {
const startInfo = toolIndex.lookup(block.tool_use_id);
return {
toolUseId: block.tool_use_id,
toolName: startInfo?.toolName,
result: block.content ?? fallbackResult,
turnId
};
});
}
Because each result carries its own ID, the system pairs results with starts without assuming message order or maintaining complex state stacks.
Handling Parent-Task Relationships
Certain tools act as sub-agent launchers (such as Task or Agent tools). The algorithm detects these via the isParentTaskTool function in packages/shared/src/utils/toolNames.ts.
This function checks against a read-only set called PARENT_TASK_TOOLS (lines 35-38):
// From packages/shared/src/utils/toolNames.ts
export const PARENT_TASK_TOOLS = new Set(['Task', 'Agent', 'SubAgent']);
export function isParentTaskTool(toolName: string): boolean {
return PARENT_TASK_TOOLS.has(toolName);
}
When extractToolStarts encounters these tools, it marks them accordingly, enabling background-task detection and correct parent-child relationship wiring across the agent hierarchy.
Deduplication and Metadata Enrichment
The algorithm handles duplicate tool_start events through the emittedToolStartIds set. This Set tracks which tool IDs have already been processed:
- Duplicate detection: If the same
tool_use_idappears in both a streaming response and the final assistant message, the set prevents double-processing. - Metadata updates: When a duplicate arrives with richer input or newly available metadata (such as intent or display name), the algorithm re-emits an updated event (lines 71-82 in
tool-matching.ts).
This ensures the agent always sees the most complete tool information while maintaining idempotency.
Generating Human-Readable Tool Names
The UI layer obtains readable tool names via getToolDisplayName in packages/shared/src/utils/toolNames.ts (lines 71-77). This function:
- Checks an explicit mapping object
TOOL_DISPLAY_NAMESfor a human-friendly string - Falls back to generic title-case conversion if no mapping exists
For example, a tool named web_search might display as "Web Search" in the agent interface, while internal processing continues using the canonical ID.
Implementation Example
The following pattern demonstrates how to integrate the tool-matching components in your own agent implementation:
import { ToolIndex, extractToolStarts, extractToolResults } from
'@craft-agent/shared/src/agent/tool-matching';
// 1️⃣ Build an index for the current session
const toolIndex = new ToolIndex();
// 2️⃣ Process an assistant message (contains tool_use blocks)
const assistantEvents = extractToolStarts(
assistantMessage.content, // ← ContentBlock[]
assistantMessage.parent_tool_use_id, // ← string | null
toolIndex,
new Set(), // emittedToolStartIds – empty at start
turnId,
activeParentTools // e.g. Set of currently running Task IDs
);
// 3️⃣ Later, when the user replies with results
const resultEvents = extractToolResults(
userMessage.content,
userMessage.parent_tool_use_id,
userMessage.tool_use_result, // fallback field
toolIndex,
turnId
);
// 4️⃣ UI can now show the tools that were started
assistantEvents.forEach(ev => {
console.log(`Tool offered: ${ev.toolName} (id=${ev.toolUseId})`);
});
Key implementation details:
ToolIndexis shared between start and result extraction, guaranteeing consistent ID-based lookups.extractToolStartsautomatically derives parent relationships from the SDK'sparent_tool_use_idfield.extractToolResultspairs each result with its originating start via the same identifier, regardless of message order.
Summary
- Stateless ID matching: The algorithm uses
ToolIndexto maptool_use_idvalues to tool metadata, eliminating reliance on FIFO queues or mutable stacks. - Bidirectional extraction:
extractToolStartscreates events from assistant messages, whileextractToolResultspairs user message results to their origins. - Parent-task detection: The
PARENT_TASK_TOOLSset intoolNames.tsidentifies sub-agent launchers for hierarchical relationship tracking. - Deduplication: The
emittedToolStartIdsSet prevents duplicate events while allowing metadata enrichment when duplicates carry additional information. - Display abstraction:
getToolDisplayNameconverts internal tool identifiers to human-readable labels for UI presentation.
Frequently Asked Questions
How does the algorithm handle out-of-order tool results?
The system handles out-of-order results through the ToolIndex registry. Because extractToolResults looks up the tool_use_id in the shared index rather than searching a sequential queue, results can arrive before or after their corresponding starts without affecting the matching accuracy. The index maintains the mapping until the session completes, allowing asynchronous pairing across concurrent streams.
What determines if a tool is treated as a parent-task tool?
A tool qualifies as a parent-task tool when its name exists in the PARENT_TASK_TOOLS Set defined in packages/shared/src/utils/toolNames.ts. The isParentTaskTool function performs this check, identifying tools like Task or Agent that spawn sub-processes. This classification affects how the algorithm establishes parent-child relationships and detects background tasks in the execution graph.
How does the system prevent duplicate tool start events?
The emittedToolStartIds Set tracks which tool IDs have already generated events. When extractToolStarts encounters a tool_use_id already present in this Set, it skips creating a duplicate event. However, if the duplicate contains richer metadata (such as an updated display name or intent), the algorithm re-emits the event with the enriched payload, ensuring the agent sees the most complete information without duplication.
Where does the human-readable tool name come from?
The getToolDisplayName function in packages/shared/src/utils/toolNames.ts generates UI-friendly names by first checking the TOOL_DISPLAY_NAMES mapping object for explicit labels. If no mapping exists, it falls back to formatting the internal tool name (for example, converting snake_case to Title Case). This abstraction layer ensures that agents see descriptive names while the underlying matching logic continues using stable identifiers.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →