Tool Architecture
Relevant source files
The following files were used as context for generating this wiki page:
- README.md
- packages/tools/src/factory/toolFactory.ts
- packages/shared/src/contracts/tools.ts
- packages/tools/src/registration/startup.ts
- packages/tools/PROBLEM_STATEMENT.md
- packages/core/src/tools/README.md
- packages/tools/REQUIREMENTS.md
- packages/tools/README.md
- apps/examples/definitions/todo-manager/tool-todo-write.ts
- packages/core/src/prompts/explainTools.ts
- apps/examples/settings/tools.ts
The Tool Architecture in the lucky project provides a unified, developer-focused framework for creating, registering, and executing tools within AI-powered agentic workflows. It is designed as a developer experience layer on top of standard AI SDKs, making it easy to define, test, and connect tools to workflows at runtime. The system supports two distinct types of tools: Code Tools (in-process TypeScript functions) and MCP Tools (out-of-process external servers), both accessible through a consistent interface.
The architecture prioritizes explicit, type-safe, and validated configurations over implicit "magic". Key design principles include startup-only manual registration, per-workflow tool instance isolation, and a clear separation of concerns between tool definition, registration, and execution. This robust design ensures reliability and scalability while providing a smooth development workflow.
Sources: packages/tools/PROBLEM_STATEMENT.md:5-11, packages/tools/REQUIREMENTS.md:10-12, README.md:82-85, packages/tools/README.md:5-11
Core Concepts
The framework is built around two primary tool types, providing flexibility for different use cases.
| Tool Type | Description | Benefits | Examples |
|---|---|---|---|
| Code Tools | TypeScript functions that execute directly within the same process as the workflow. There are 24 defined Code Tools. | Full type safety, easy debugging, shared context with the workflow, high performance for in-process tasks. | csvReader, todoWrite, searchGoogleMaps, humanApproval |
| MCP Tools | External processes or servers that communicate with the workflow via the Model Context Protocol (MCP). There are 8+ MCP toolkits. | Language agnostic, isolated execution, integration with the broader npm ecosystem and third-party services. | tavily (web search), firecrawl (web scraping), playwright (browser automation), filesystem |
Sources: README.md:82-93, packages/core/src/tools/README.md:52-65, packages/tools/README.md:41-80
This diagram illustrates the high-level distinction between the two tool types and their integration into the core system.
Tool Definition
All tools, regardless of type, are created using a unified defineTool pattern. This factory function ensures consistency, type safety, and automatic validation.
Sources: packages/core/src/tools/README.md:21-23, packages/tools/PROBLEM_STATEMENT.md:30-32
The defineTool Pattern
The defineTool function accepts a configuration object with the following key properties:
name: A unique string identifier for the tool.description: A detailed explanation of the tool's purpose, capabilities, and limitations, intended for the LLM to decide when to use it.params: Azodschema that defines the input parameters. This is used for runtime validation and to provide type safety.execute: Anasyncfunction containing the tool's implementation. It receives the validatedparamsand aToolExecutionContextobject.
Sources: packages/tools/PROBLEM_STATEMENT.md:33-42, apps/examples/definitions/todo-manager/tool-todo-write.ts:187-193
An example of the todoWrite tool definition:
// apps/examples/definitions/todo-manager/tool-todo-write.ts:187-259
const todoWrite = defineTool({
name: "todoWrite",
description:
"Create and manage structured task lists for coding sessions. Overwrites entire todo list, validates constraints (only one in_progress task, unique IDs). LIMITS: overwrites all todos, session-scoped only, no partial updates or merging.",
params: z.object({
todos: z.array(todoItemSchema).min(0, "Todos array is required"),
}),
async execute(params, externalContext): Promise<CodeToolResult<TodoWriteResult>> {
try {
const workflowInvocationId = externalContext.workflowInvocationId;
const { todos } = params;
// ... validation and implementation ...
// store todos in workflow scope
const store = createContextStore("supabase", workflowInvocationId);
await store.set("workflow", TODO_STORE_KEY, todos);
return Tools.createSuccess<TodoWriteResult>("todoWrite", {
success: true,
message,
todos,
});
} catch (error) {
return Tools.createFailure("todoWrite", {
location: "todoWrite:error",
error: error instanceof Error ? error.message : String(error),
});
}
},
});
Tool Contracts and Interfaces
The core tool interface ITool is defined in the @lucky/shared package, establishing a contract for all tool implementations.
// packages/shared/src/contracts/tools.ts:133-150
export interface ITool<
TInput = unknown,
TOutput = unknown,
TContext extends ToolExecutionContext = ToolExecutionContext,
> {
/** Unique name identifying this tool */
name: string;
/** Human-readable description of what the tool does */
description?: string;
/** JSON schema for input parameters */
inputSchema?: Record<string, unknown>;
/**
* Execute the tool with given input and context
*/
call(input: TInput, ctx: TContext): Promise<TOutput>;
}
The TOOLS constant serves as the single source of truth for all tool names and their descriptions, from which TypeScript types are derived.
// packages/shared/src/contracts/tools.ts:10-13
export const TOOLS = {
mcp: { /* ... */ },
code: { /* ... */ },
} as const;
// packages/shared/src/contracts/tools.ts:114-116
export type MCPToolName = keyof typeof TOOLS.mcp;
export type CodeToolName = keyof typeof TOOLS.code;
export type AllToolNames = MCPToolName | CodeToolName;
Tool Registration and Lifecycle
The system uses an explicit, manual registration process that occurs only at application startup. This approach, which replaced a previous auto-discovery mechanism, enhances reliability and control.
Sources: packages/tools/REQUIREMENTS.md:10-15, packages/tools/README.md:101-109
Registration Flow
- Define Tools: Tools are defined in their respective files, typically under
packages/tools/src/definitions/. - Group into Toolkits: Related tools are grouped into logical "toolkits".
- Register at Startup: The
registerAllToolsorregisterToolGroupsfunction is called once at application startup, passing in the defined toolkits. - Validation: The registration process immediately validates all tools for unique names, non-empty descriptions, and correct structure. It will throw an error if validation fails.
Sources: packages/tools/README.md:17-21, packages/tools/REQUIREMENTS.md:19-21, packages/tools/src/registration/startup.ts
The following diagram shows the sequence of registering tools.
Tool Registry and Isolation
A key architectural requirement is the isolation of tool instances for each workflow. The system is designed to move beyond a simple global Map of tools to a more robust registry that can provide sandboxed instances.
- Each workflow instance receives its own separate tool system.
- Tools are instantiated per-workflow, not shared as function references.
- A central registry holds all available tools, and workflows select a subset from it.
This model prevents cross-workflow interference and provides centralized control over tool availability.
Sources: packages/tools/REQUIREMENTS.md:40-50, packages/tools/PROBLEM_STATEMENT.md:103-137
Runtime Execution
During a workflow's execution, AI agents (nodes) are configured with a specific subset of the registered tools. The system provides these tools to the agent, ready for use.
Tool Provisioning for Nodes
The setupCodeToolsForNode function is responsible for preparing the AI SDK-compatible toolset for a given workflow node. It takes a list of tool names and an execution context.
Sources: packages/tools/README.md:23-36
Tool Execution Context
Every tool's execute method receives a ToolExecutionContext object, which provides crucial information about the current workflow invocation.
| Field | Type | Description |
|---|---|---|
workflowInvocationId | string | A unique ID for the current run of the workflow. |
workflowId | string | The ID of the workflow definition. |
workflowVersionId | string | The version ID of the workflow definition. |
workflowFiles | WorkflowFile[] | An array of files available to the workflow. |
mainWorkflowGoal | string | The top-level goal of the workflow. |
expectedOutputType | any | The expected Zod schema for the final output. |
Sources: packages/shared/src/contracts/tools.ts:121-129, packages/core/src/tools/README.md:30-34
AI-Powered Tool Selection
The system employs an "intelligent tool selection" mechanism. Based on the system prompt, workflow context, and available tools, an AI model determines the optimal tool usage strategy for each step. Strategies include auto, required, none, or specifying a list of tools. This allows for dynamic and context-aware tool use.
Sources: packages/core/src/tools/README.md:73-82, packages/core/README.md:200-210
The following diagram illustrates how a workflow node uses a tool at runtime.
Conclusion
The lucky Tool Architecture is a robust and developer-friendly system designed for building reliable AI agentic workflows. By enforcing explicit manual registration, providing a unified defineTool pattern, and ensuring per-workflow isolation, it addresses common pitfalls of tool management in AI systems. The clear separation of Code Tools and MCP Tools, combined with a single source of truth for tool contracts, creates a scalable and maintainable foundation. The architecture is intentionally designed to be extensible, with plans to evolve the registry and testing frameworks to further streamline the developer experience.
Sources: packages/tools/PROBLEM_STATEMENT.md:154-160, packages/tools/REQUIREMENTS.md, packages/tools/README.md:111-115