One sentence describing the tool’s purposeHelps the AI understand when to use this tool
// ✅ Gooddescription = "Get current weather conditions for any city worldwide";description = "Create a new product in the catalog with price and details";description = "Search for products by name, category, or description";// ❌ Baddescription = "Gets data"; // Too vaguedescription = "Does weather stuff"; // Unclear
If the function throws an error, the tool is disabled (fail-closed)
Has access to all Platform APIs (User, Data, Products, etc.)
Use conditions to dynamically enable/disable tools based on:
User subscription status (premium features)
User verification or account status
Feature flags or A/B testing
Region-specific functionality
Time-based access
import { LuaTool, User } from 'lua-cli';import { z } from 'zod';export default class PremiumSearchTool implements LuaTool { name = "premium_search"; description = "Advanced search with filters - premium users only"; inputSchema = z.object({ query: z.string(), filters: z.object({ minPrice: z.number().optional(), maxPrice: z.number().optional() }).optional() }); // Only show this tool to premium users condition = async () => { const user = await User.get(); return user.data?.isPremium === true; }; async execute(input: z.infer<typeof this.inputSchema>) { // This only runs if condition returned true return { results: [] }; }}
Fail-closed behavior: If your condition function throws an error or times out (30s), the tool is automatically disabled. This ensures tools aren’t accidentally exposed when conditions can’t be evaluated.
When multiple agents share the same tool logic — same API, same auth, same shape — but differ on a couple of config values, declare the shared parts on an abstract base and have each agent’s tool extend it. The compiler walks the full extends chain, so a leaf class is detected as a tool whether it extends LuaTool directly or through any number of intermediate bases.
// Shared base — published once, imported by every agentimport { LuaTool } from 'lua-cli';import { z } from 'zod';export abstract class SearchTool implements LuaTool { name = 'search'; description = 'Search the knowledge base.'; inputSchema = z.object({ query: z.string() }); // Subclasses override this. The default lets the type-check pass // for the abstract base; concrete subclasses must set a real value. searchPath: string = '/default/search'; async execute(input: { query: string }) { const r = await fetch(`https://api.example.com${this.searchPath}`, { method: 'POST', body: JSON.stringify({ q: input.query }), }); return r.json(); }}
// Per-agent leaf — only what differsimport { SearchTool } from '@my-org/shared';export class BlackshipSearchTool extends SearchTool { name = 'search_blackship'; searchPath = '/blk/search';}
Register the leaf class on a skill — pass the class itself, not an instance:
import { LuaSkill } from 'lua-cli';import { BlackshipSearchTool } from './tools/BlackshipSearch';export default new LuaSkill({ name: 'support', description: 'Customer support tools', context: 'Use search_blackship to look things up.', tools: [BlackshipSearchTool],});
Field initializers on the leaf run during construction, so this.searchPath inside the parent’s execute resolves to '/blk/search'. The leaf inherits inputSchema, description, and execute from the parent unless it overrides them.
Don’t pass constructor arguments at the reference site:
// ❌ The string is silently dropped — one tool artifact is shared across// every reference and there's no carrier for per-reference args.tools: [new BlackshipSearchTool('/blk/search')]
The compiler reports lua/constructor-args-dropped if it sees this. Use a subclass with a field override instead.
async execute(input: any) { // Validate business logic if (input.amount <= 0) { throw new Error("Amount must be positive"); } // API errors const response = await fetch(url); if (!response.ok) { throw new Error(`API error: ${response.statusText}`); } // Not found errors const item = await findItem(input.id); if (!item) { throw new Error(`Item not found: ${input.id}`); } return result;}
// ✅ Good - Structuredreturn { success: true, data: { id, name, price }, metadata: { timestamp, version }};// ❌ Bad - Unstructured stringreturn "Product created with ID 123";