Appearance
Tools
Tools are functions that agents can call during conversation to interact with external systems, search memory, manage execution plans, and more. They follow the Vercel AI SDK tool() pattern with Zod schemas for input validation.
Built-in Tools
The SDK ships a complete set of tool factories in @lota-sdk/core/tools/*:
| Tool | Factory | Description |
|---|---|---|
| Memory Search | createMemorySearchTool | Semantic search across organization and agent memories |
| Conversation Search | createConversationSearchTool | Full-text search across prior chat messages by role |
| Remember Memory | createRememberMemoryTool | Saves a durable memory fact with automatic importance scoring |
| Memory Block | createMemoryBlockTool | Appends a short-term note to the workstream memory block |
| Consult Team | createTeamThinkTool | Fan-out consultation to all team agents in parallel |
| Research Topic | researchTopicTool | Delegates web research to a dedicated researcher agent |
| Fetch Webpage | fetchWebpageTool | Fetches and extracts content from a single webpage via Firecrawl |
| Search Web | searchWebTool | Web search via Firecrawl |
| Read File Parts | readFilePartsTool | Reads file parts from indexed repositories |
| User Questions | userQuestionsTool | Presents structured questions to the user |
| Create Execution Plan | createCreateExecutionPlanTool | Creates a contract-driven execution plan and starts a run |
| Replace Execution Plan | createReplaceExecutionPlanTool | Replaces the active run with a newly compiled plan |
| Submit Node Result | createSubmitExecutionNodeResultTool | Submits the result for a running execution node |
| Get Active Plan | createGetActiveExecutionPlanTool | Retrieves the current execution run and its state |
| Resume Run | createResumeExecutionPlanRunTool | Resumes an interrupted execution run from its latest checkpoint |
| Log Hello World | createLogHelloWorldTool | Demo tool with needsApproval: true for testing the approval flow |
Import Paths
ts
// Search tools
import { createMemorySearchTool, createConversationSearchTool } from '@lota-sdk/core/tools/search-tools'
// Memory tools
import { createRememberMemoryTool } from '@lota-sdk/core/tools/remember-memory.tool'
import { createMemoryBlockTool } from '@lota-sdk/core/tools/memory-block.tool'
// Execution plan tools
import {
createCreateExecutionPlanTool,
createReplaceExecutionPlanTool,
createSubmitExecutionNodeResultTool,
createGetActiveExecutionPlanTool,
createResumeExecutionPlanRunTool,
} from '@lota-sdk/core/tools/execution-plan.tool'
// Delegated tools (pre-built, no factory needed)
import { researchTopicTool } from '@lota-sdk/core/tools/research-topic.tool'
import { fetchWebpageTool } from '@lota-sdk/core/tools/fetch-webpage.tool'
import { searchWebTool } from '@lota-sdk/core/tools/search-web.tool'
import { readFilePartsTool } from '@lota-sdk/core/tools/read-file-parts.tool'
// User interaction
import { userQuestionsTool } from '@lota-sdk/core/tools/user-questions.tool'Tool Anatomy
Every tool follows the Vercel AI SDK tool() pattern: a Zod input schema, a description string, and an execute function.
ts
import { tool } from 'ai'
import { z } from 'zod'
const myTool = tool({
description: 'Searches the knowledge base for relevant information',
inputSchema: z.object({
query: z.string().describe('The search query'),
limit: z.number().optional().default(5).describe('Maximum results to return'),
}),
execute: async ({ query, limit }) => {
const results = await knowledgeBase.search(query, limit)
return { query, count: results.length, results }
},
})Key conventions:
inputSchema: Zod schema defining the tool parametersdescription: prompt guidance for the model about when to use the toolexecute: implementation function, either async or generator-basedtoModelOutput: optional transformation that sends a simplified result back to the model while keeping the full payload for the client
Factory Pattern
Most built-in tools use a factory pattern because they need runtime context such as organization ID, workstream ID, and agent name:
ts
const memorySearch = createMemorySearchTool(orgIdString, agentName, { fastMode: false })
const conversationSearch = createConversationSearchTool(workstreamId)
const rememberMemory = createRememberMemoryTool({ orgId, agentName })
const memoryBlock = createMemoryBlockTool({ workstreamId, agentLabel: 'CEO' })Registering Custom Tools
There are two ways to provide tools to agents.
Via toolProviders
Register tools globally for all agents through the runtime config:
ts
const runtime = await createLotaRuntime({
toolProviders: {
myCustomTool: tool({
description: 'A custom tool available to all agents',
inputSchema: z.object({ input: z.string() }),
execute: async ({ input }) => ({ result: `Processed: ${input}` }),
}),
},
})Tools in toolProviders are merged into the tool set for every agent on every turn.
Via buildAgentTools
For per-agent tool assignment, use buildAgentTools in the agents config:
ts
agents: {
buildAgentTools: (agentId, context) => {
const baseTools = {
memorySearch: createMemorySearchTool(context.orgIdString, agentId),
rememberMemory: createRememberMemoryTool({ orgId: context.orgId, agentName: agentId }),
}
if (agentId === 'chief') {
return {
...baseTools,
consultTeam: createTeamThinkTool(context),
createExecutionPlan: createCreateExecutionPlanTool(context),
}
}
return baseTools
},
}Both mechanisms can be used together. toolProviders sets the baseline; buildAgentTools adds agent-specific tools.
Tool Approval
Native SDK Approval (needsApproval)
Tools can declare needsApproval: true to require user approval before execution. This uses the Vercel AI SDK's built-in approval flow:
ts
import { tool } from 'ai'
import { z } from 'zod'
const dangerousTool = tool({
description: 'Deletes a resource. Requires user confirmation.',
inputSchema: z.object({ resourceId: z.string() }),
needsApproval: true,
execute: async ({ resourceId }) => {
await deleteResource(resourceId)
return { deleted: resourceId }
},
})Flow:
- The agent calls the tool — it enters
approval-requestedstate. - The client renders approve/deny buttons via
addToolApprovalResponse. - The user clicks approve or deny.
- The
sendAutomaticallyWhencallback (set tolastAssistantMessageIsCompleteWithApprovalResponsesby default inuseThreadChat) auto-submits the response. - The server routes this as
native-tool-approvalkind, which continues the agent turn with the approval attached. - If approved, the tool executes. If denied, the agent receives the denial and responds accordingly.
The logHelloWorld demo tool (createLogHelloWorldTool) demonstrates this pattern.
Execution Plan Approval
Execution plan tools (createExecutionPlan, replaceExecutionPlan) use a separate approval mechanism based on plan node states. These are routed as approval-continuation kind on the server, which persists the approval response without re-running the agent turn.
Generator Tools
Tools can be async generators that yield intermediate results before returning the final output:
ts
const progressiveTool = tool({
description: 'A tool that yields progress updates',
inputSchema: z.object({ task: z.string() }),
execute: async function* ({ task }) {
yield { status: 'starting', message: 'Beginning work...' }
const intermediate = await doFirstStep(task)
yield { status: 'in-progress', message: 'First step complete', data: intermediate }
const result = await doFinalStep(intermediate)
return { status: 'complete', result }
},
toModelOutput: ({ output }) => ({ type: 'text', value: output.result }),
})consultTeam uses this pattern to stream individual agent responses as they complete.
Shared Tool Schemas
The @lota-sdk/shared/schemas/tools module exports Zod schemas and TypeScript types for all core tool inputs and outputs. These are used by both the server and the client.
ts
import {
UserQuestionsArgsSchema,
ConsultTeamArgsSchema,
ConsultTeamResultDataSchema,
CreateExecutionPlanArgsSchema,
ReplaceExecutionPlanArgsSchema,
SubmitExecutionNodeResultArgsSchema,
GetActiveExecutionPlanArgsSchema,
ResumeExecutionPlanRunArgsSchema,
ExecutionPlanToolResultDataSchema,
} from '@lota-sdk/shared/schemas/tools'
import type {
CoreChatTools,
UserQuestionsArgs,
ConsultTeamArgs,
ConsultTeamResultData,
CreateExecutionPlanArgs,
ReplaceExecutionPlanArgs,
SubmitExecutionNodeResultArgs,
GetActiveExecutionPlanArgs,
ResumeExecutionPlanRunArgs,
ExecutionPlanToolResultData,
} from '@lota-sdk/shared/schemas/tools'CoreChatTools
The CoreChatTools type map defines the input and output contract for each SDK-owned tool:
ts
type CoreChatTools = {
userQuestions: { input: UserQuestionsArgs; output: unknown }
consultSpecialist: { input: ConsultSpecialistArgs; output: ConsultSpecialistResultData }
consultTeam: { input: ConsultTeamArgs; output: ConsultTeamResultData }
createExecutionPlan: { input: CreateExecutionPlanArgs; output: ExecutionPlanToolResultData }
replaceExecutionPlan: { input: ReplaceExecutionPlanArgs; output: ExecutionPlanToolResultData }
submitExecutionNodeResult: { input: SubmitExecutionNodeResultArgs; output: ExecutionPlanToolResultData }
getActiveExecutionPlan: { input: GetActiveExecutionPlanArgs; output: ExecutionPlanToolResultData }
resumeExecutionPlanRun: { input: ResumeExecutionPlanRunArgs; output: ExecutionPlanToolResultData }
}Tool Name Constants
ts
import {
USER_QUESTIONS_TOOL_NAME, // 'userQuestions'
CONSULT_TEAM_TOOL_NAME, // 'consultTeam'
CONSULT_SPECIALIST_TOOL_NAME, // 'consultSpecialist'
CREATE_EXECUTION_PLAN_TOOL_NAME, // 'createExecutionPlan'
REPLACE_EXECUTION_PLAN_TOOL_NAME, // 'replaceExecutionPlan'
SUBMIT_EXECUTION_NODE_RESULT_TOOL_NAME, // 'submitExecutionNodeResult'
GET_ACTIVE_EXECUTION_PLAN_TOOL_NAME, // 'getActiveExecutionPlan'
RESUME_EXECUTION_PLAN_RUN_TOOL_NAME, // 'resumeExecutionPlanRun'
} from '@lota-sdk/shared/schemas/tools'Use these constants instead of hardcoding tool names in clients.
Citations
Tools that return sourced data can attach citations using the CitationSchema:
ts
import { CitationSchema } from '@lota-sdk/core/tools/tool-contract'
import type { Citation } from '@lota-sdk/core/tools/tool-contract'
const citation: Citation = {
source: 'Company Wiki',
sourceId: 'wiki:article:42',
version: 'v3',
retrievedAt: new Date().toISOString(),
}