import { LuaSkill } from "lua-cli";const coffeeSkill = new LuaSkill({ name: "coffee-shop-skill", description: "Coffee shop assistant with menu, ordering, and loyalty features", context: ` This skill helps customers of Java Junction Coffee Shop. - Use show_menu to display available drinks and food - Use create_order to take customer orders - Use check_loyalty_points to show rewards balance - Use redeem_reward to apply loyalty discounts Always mention daily specials. Ask about size preferences for drinks. `, tools: [ new ShowMenuTool(), new CreateOrderTool(), new CheckLoyaltyTool(), new RedeemRewardTool() ]});
The context field guides the AI’s decision-making. Write it like instructions to a smart assistant:
context: ` This skill manages customer orders for a coffee shop. Tool Usage: - show_menu: Use when customers ask what's available. Returns drinks and food items. - create_order: Use when taking an order. Confirm items and sizes first. - modify_order: Use to add/remove items. Ask which item to modify. - finalize_order: Use when order is confirmed. Returns total and wait time. Guidelines: - Always ask about size for drinks (small/medium/large) - Mention daily special when showing menu - Confirm total before finalizing - Ask about dietary restrictions for food items`
Optional async function that determines if the tool should be available
async condition(): Promise<boolean>
Use for premium features, feature flags, channel-specific tools, or user-specific availability.Access the current channel via Lua.request.channel and raw webhook data via Lua.request.webhook?.payload - see Lua API.
When you build multiple agents that share behaviour — same search backend, same external API, same auth flow — factor the shared parts into a base class and have each agent declare a thin subclass that overrides only the bits that differ.
Shared base
Per-agent leaf
Register the leaf
// packages/shared/SearchKnowledgeBaseTool.tsimport { LuaTool } from 'lua-cli';import { z } from 'zod';export abstract class SearchKnowledgeBaseTool implements LuaTool { name = 'search_knowledge_base'; description = "Semantic search over the agent's knowledge base."; inputSchema = z.object({ query: z.string() }); // Override this in subclasses to point at a specific backend. ragSearchPath: string = '/default/search'; async execute(input: { query: string }) { const res = await fetch(`https://api.example.com${this.ragSearchPath}`, { method: 'POST', body: JSON.stringify({ q: input.query }), }); return res.json(); }}
// agents/blackship/src/tools/BlackshipSearch.tsimport { SearchKnowledgeBaseTool } from '@my-org/shared';export class BlackshipSearchTool extends SearchKnowledgeBaseTool { name = 'search_blackship'; ragSearchPath = '/blk/search';}// agents/whitestar/src/tools/WhitestarSearch.tsexport class WhitestarSearchTool extends SearchKnowledgeBaseTool { name = 'search_whitestar'; description = 'Semantic search over Whitestar product docs.'; ragSearchPath = '/wht/docs';}
import { LuaSkill } from 'lua-cli';import { BlackshipSearchTool } from './tools/BlackshipSearch';export default new LuaSkill({ name: 'support', description: 'Support tools', context: 'Use search_blackship to find product info.', tools: [BlackshipSearchTool], // pass the class itself, not new BlackshipSearchTool()});
How it works:
The lua compiler walks the full extends chain. A leaf that extends a shared base which itself extends LuaTool is detected as a tool.
Field initializers on the leaf (ragSearchPath = '/blk/search') override the parent’s defaults. When execute() runs and reads this.ragSearchPath, it sees the leaf’s value — JavaScript’s normal inheritance semantics.
The leaf inherits the parent’s inputSchema, description, and execute unless it overrides them. No copy-paste.
Don’t pass per-reference constructor arguments inside a tools: [...] array:
// ❌ The string argument is silently dropped — the compiler builds// one shared tool artifact per project, reused across every reference.tools: [new BlackshipSearchTool('/blk/search')]
The compiler will warn (lua/constructor-args-dropped) if it sees this pattern. Use a subclass with a field override instead.
import { LuaAgent, LuaSkill } from 'lua-cli';// Create your skillconst coffeeSkill = new LuaSkill({ name: "coffee-shop-skill", description: "Coffee shop assistant", tools: [/* your tools */]});// Create agentexport const agent = new LuaAgent({ name: "coffee-assistant", persona: `You are a friendly barista at Java Junction Coffee Shop.Your role:- Help customers browse the menu- Take and confirm orders- Suggest popular items- Answer questions about ingredientsCommunication style:- Warm and welcoming- Enthusiastic about coffee- Patient and helpful- Always mention daily specials`, skills: [coffeeSkill]});
Use LuaAgent to configure persona, welcome message, and skills together in one unified configuration.
const dataSkill = new LuaSkill({ name: "data-management-skill", description: "Complete CRUD operations", tools: [ new CreateTool(), new ReadTool(), new UpdateTool(), new DeleteTool(), new SearchTool() ]});
const integrationSkill = new LuaSkill({ name: "external-integrations-skill", description: "Third-party service integrations", tools: [ new SendEmailTool(), // SendGrid new ProcessPaymentTool(), // Stripe new GetWeatherTool(), // Weather API new TrackShipmentTool() // Shipping API ]});