| title | AI Agent Building Guide |
|---|---|
| description | Complete reference for AI Agents and AI Coding IDEs (Cursor, Windsurf, GitHub Copilot) to build, test, and deploy agents on the Lua platform |
This guide is specifically designed for AI Agents and AI Coding IDEs (Cursor, Windsurf, GitHub Copilot, etc.) to build agents on the Lua platform using non-interactive CLI commands.
All commands in this guide use flags and arguments that work without interactive prompts, enabling full automation.
- Node.js 18+ installed
- npm, yarn, or pnpm
- Lua CLI installed:
npm install -g lua-cli - Authentication configured (see below)
Authenticate → lua init → Write Code → lua test → iterate → lua chat sandbox → push → deploy
↓
(Optional) lua integrations connect → Agent gets third-party tools instantly
**Must authenticate before running any other CLI command.**
If the user already has an API key:
lua auth configure --api-key <api-key>This validates and saves the API key immediately.
If the user doesn't have an API key yet:
Step 1 - Request OTP:
lua auth configure --email user@example.comThis sends a 6-digit OTP to the email.
Step 2 - Verify OTP (user provides the code they received):
lua auth configure --email user@example.com --otp 123456This verifies the OTP and generates + saves an API key.
- Ask user if they have an API key
- If yes:
lua auth configure --api-key <key> - If no:
- Get user's email
- Run
lua auth configure --email <email> - Ask user for the OTP they received via email
- Run
lua auth configure --email <email> --otp <code>
- Continue with
lua initonce authenticated
**Run `lua init` only ONCE per project.** After initialization, you have your agent and codebase - work on it from there.
Creates a brand new agent from scratch:
# Create agent in existing organization
lua init --agent-name "My Agent" --org-id org_abc123
# Create agent + new organization
lua init --agent-name "My Agent" --org-name "My Company"
# With example code included
lua init --agent-name "My Agent" --org-id org_abc123 --with-examplesLinks to an agent that already exists:
lua init --agent-id agent_abc123
# Override existing project
lua init --agent-id agent_abc123 --force| Flag | Description |
|---|---|
--agent-id <id> |
Use existing agent |
--agent-name <name> |
Name for new agent |
--org-id <id> |
Existing organization ID |
--org-name <name> |
Create new organization |
--with-examples |
Include example code |
--force |
Override existing project |
project/
├── lua.skill.yaml # Config manifest (managed by CLI, don't edit manually)
├── package.json
├── tsconfig.json
├── .env # Local environment variables
└── src/
└── index.ts # LuaAgent configuration
The main configuration lives in src/index.ts:
import { LuaAgent, LuaSkill } from 'lua-cli';
export const agent = new LuaAgent({
name: 'my-agent',
persona: `You are a helpful assistant.
Your role:
- Help users with their tasks
- Provide accurate information
Communication style:
- Friendly and professional
- Clear and concise`,
skills: [mySkill],
// Optional components:
// webhooks: [myWebhook],
// jobs: [myJob],
// preProcessors: [myPreProcessor],
// postProcessors: [myPostProcessor],
});| Property | Required | Description |
|---|---|---|
name |
Yes | Agent identifier |
persona |
Yes | Personality, behavior, capabilities, limitations |
model |
No | AI model to use — string like 'google/gemini-2.5-flash' or a function (request) => string for dynamic selection |
skills |
Yes | Array of LuaSkill instances |
webhooks |
No | HTTP endpoints for external events |
jobs |
No | Scheduled cron tasks |
preProcessors |
No | Message filters before agent processes |
postProcessors |
No | Response formatters after agent responds |
mcpServers |
No | External MCP tool servers (also auto-created via lua integrations) |
A Skill is a collection of related tools:
import { LuaSkill } from 'lua-cli';
const mySkill = new LuaSkill({
name: 'my-skill',
description: 'Brief description of the skill',
context: `Detailed instructions for when to use these tools.
- Use tool_a when user asks about X
- Use tool_b when user wants to do Y`,
tools: [new ToolA(), new ToolB()]
});A Tool is a single function the AI can call:
import { LuaTool } from 'lua-cli';
import { z } from 'zod';
class GetWeatherTool implements LuaTool {
name = 'get_weather';
description = 'Get current weather for a city';
inputSchema = z.object({
city: z.string().describe('City name'),
units: z.enum(['metric', 'imperial']).optional().default('metric')
});
async execute(input: z.infer<typeof this.inputSchema>) {
const { city, units } = input;
// Implementation here
return { temperature: 22, condition: 'sunny', city };
}
}HTTP endpoints for external events:
import { LuaWebhook } from 'lua-cli';
import { z } from 'zod';
const paymentWebhook = new LuaWebhook({
name: 'payment-webhook',
description: 'Handle Stripe payment events',
bodySchema: z.object({
type: z.string(),
data: z.any()
}),
execute: async (event) => {
const { body } = event;
// Handle the webhook
return { received: true };
}
});Scheduled cron tasks:
import { LuaJob } from 'lua-cli';
const dailyReport = new LuaJob({
name: 'daily-report',
description: 'Generate daily report',
schedule: { type: 'cron', expression: '0 9 * * *' }, // 9 AM daily
execute: async (job) => {
// Generate report
return { status: 'completed' };
}
});import { PreProcessor, PostProcessor } from 'lua-cli';
const profanityFilter = new PreProcessor({
name: 'profanity-filter',
description: 'Filter inappropriate content',
execute: async (user, messages, channel) => {
// Return { action: 'block', response } to block, or { action: 'proceed' } to continue
const text = messages.map(m => m.type === 'text' ? m.text : '').join(' ');
if (containsProfanity(text)) {
return { action: 'block', response: 'Message blocked due to inappropriate content.' };
}
return { action: 'proceed' };
}
});
const addDisclaimer = new PostProcessor({
name: 'add-disclaimer',
description: 'Add legal disclaimer to responses',
execute: async (user, message, response, channel) => {
return { modifiedResponse: response + '\n\n_This is not legal advice._' };
}
});This is one of the most important sections. Understanding when to use `lua test` vs `lua chat` is critical.
Purpose: Unit test for individual blocks (tools, jobs, webhooks, processors)
How it works:
- Creates a local VM and executes code directly
- No AI involved - just runs the
executefunction - Fast - no network latency, no AI processing time
Use for: Quick testing, quick debugging, building components in isolation
Input format: Must match the tool's inputSchema exactly
# Test a tool
lua test skill --name get_weather --input '{"city": "London"}'
# Test a webhook
lua test webhook --name payment-webhook --input '{"query": {}, "headers": {}, "body": {"type": "payment.completed"}}'
# Test a job
lua test job --name daily-report
# Test a preprocessor
lua test preprocessor --name profanity-filter --input '{"message": "hello", "channel": "web"}'
# Test a postprocessor
lua test postprocessor --name add-disclaimer --input '{"message": "hi", "response": "hello", "channel": "web"}'Purpose: Real agent requests involving AI
How it works:
- HTTP requests for streaming/generating responses
- Full integration - multiple components may execute (skills, tools, pre/post-processors)
Two modes:
| Mode | Description |
|---|---|
sandbox |
Free, unbilled, can override local code without push/deploy |
production |
Uses pushed and deployed versions |
# Test in sandbox (uses local code)
lua chat -e sandbox -m "What's the weather in London?"
# Test in production (uses deployed code)
lua chat -e production -m "What's the weather in London?"| Command | What it does | AI involved? | Speed |
|---|---|---|---|
lua test |
Runs execute function directly in VM | No | Fast |
lua chat |
Full agent request with AI | Yes | Slower |
Each --thread creates a completely independent conversation context. This also means multiple tests can run concurrently (5-10 at a time) without interfering with each other.
# Scope to an explicit thread ID
lua chat -e sandbox -m "Test scenario A" --thread scenario-a
# Auto-generate a UUID thread (printed at start so you can log/reuse it)
lua chat -e sandbox -m "Test scenario B" --thread
# Isolated test with automatic cleanup after response
lua chat -e sandbox -m "Test scenario C" -t scenario-c --clear
# Run 10 isolated tests sequentially, each with a clean context
for i in $(seq 1 10); do
lua chat -e sandbox -m "test scenario $i" -t "test-$i" --clear
done--thread flags:
| Flag | Description |
|---|---|
-t, --thread [id] |
Scope to a named thread. Omit the ID to auto-generate a UUID. Thread ID is printed at session start. |
--clear, --clear-thread |
Clear the thread's history when the session ends (interactive: on exit, non-interactive: after response). Requires --thread. |
Because each --thread is a fully isolated conversation, you can run multiple lua chat invocations in parallel without any state collision. This is especially useful for AI agents that need to validate multiple conversation scenarios at once:
# Run 5 tests in parallel (background jobs)
lua chat -e sandbox -m "test flow A" -t test-a --clear &
lua chat -e sandbox -m "test flow B" -t test-b --clear &
lua chat -e sandbox -m "test flow C" -t test-c --clear &
lua chat -e sandbox -m "test flow D" -t test-d --clear &
lua chat -e sandbox -m "test flow E" -t test-e --clear &
wait # wait for all to complete# Clear all conversation history
lua chat clear --force
# Clear specific user's history
lua chat clear --user user@email.com --force
lua chat clear --user +1234567890 --force
lua chat clear --user user_abc123 --force
# Clear a specific thread's history
lua chat clear --thread my-test-scenario --forceWhen to use lua chat clear vs --thread:
| Approach | When to use |
|---|---|
lua chat clear --force |
Reset all history before a new development session |
--thread <id> |
Isolate individual test runs without touching other contexts |
--clear |
Auto-clean up thread history at end of each automated test |
# 1. Write/modify component code
# 2. Quick isolated test
lua test skill --name my_tool --input '{"param": "value"}'
# 3. Check logs if issues
lua logs --type skill --name my_tool --limit 5
# 4. Iterate until component works
# 5. Test with full agent in an isolated thread (no need to clear history)
lua chat -e sandbox -m "Test message" -t dev-session
# 6. Run a suite of scenario tests concurrently
lua chat -e sandbox -m "scenario 1" -t s1 --clear &
lua chat -e sandbox -m "scenario 2" -t s2 --clear &
wait
# 7. When ready: push and deploy
# 8. Verify in production
lua chat -e production -m "Test message" -t prod-verify --clearUnderstanding this distinction is critical. Many users confuse these commands.
- Compiles local code and uploads to Lua platform server/database
- Creates a version on the server
- Code now exists on server, not just locally
- Does NOT make it live yet
# Push a specific skill with version
lua push skill --name mySkill --set-version 1.0.0 --force
# Push all components
lua push all --force
# Push and immediately deploy
lua push all --force --auto-deploy- Makes a previously pushed version live/active
- Only 1 version can be active at a time per primitive
- This is what production uses
# Deploy specific version
lua deploy skill --name mySkill --set-version 1.0.5 --force
# Deploy latest version
lua deploy skill --name mySkill --set-version latest --force| Command | What it does |
|---|---|
lua push |
Upload version to server (staging) |
lua deploy |
Make that version live for all users |
| Environment | Source |
|---|---|
| Sandbox | Local .env file or CLI env variables |
| Production | Stored on server (set via lua env production) |
# List variables
lua env sandbox --list
lua env production --list
# Set variable
lua env sandbox -k API_KEY -v "sk-test-xxx"
lua env production -k API_KEY -v "sk-live-xxx"
# Delete variable
lua env production -k OLD_KEY --deleteSet environment variables as needed when code requires them.
**This is the core debugging command.** All `console.log`, `console.error`, etc. output appears here.
Works for ALL request types: lua test, lua chat, production requests.
# View all logs
lua logs --type all --limit 50
# Filter by type
lua logs --type skill --name mySkill --limit 10
lua logs --type job --name healthCheck --json
lua logs --type webhook --limit 20
# Pagination
lua logs --type all --limit 20 --page 2| Type | Description |
|---|---|
all |
All logs |
skill |
Skill/tool executions |
job |
Job executions |
webhook |
Webhook executions |
preprocessor |
PreProcessor executions |
postprocessor |
PostProcessor executions |
user_message |
User messages |
agent_response |
Agent responses |
When using lua chat -e sandbox, the following can be overridden with local compiled code without needing to push/deploy:
| Component | How it works |
|---|---|
| Skills | Local compiled code pushed to Redis cache |
| Persona | Local persona overrides deployed production persona |
| PreProcessors | Local code pushed to Redis cache |
| PostProcessors | Local code pushed to Redis cache |
| Environment | Local .env file used |
This allows testing changes instantly without the push/deploy cycle.
The CLI uses esbuild to bundle each execute function:
- Standard TypeScript code
- Imports from
package.jsondependencies - Relative imports within your project
- Not all Node.js file structures are supported
- Each execute function's code is extracted and bundled separately
lua-cliimports are stripped - APIs likeUser,Products,Dataare sandbox globals
| Issue | Meaning |
|---|---|
| Empty bundles (< 100 bytes) | Something went wrong during bundling |
| "Could not resolve" errors | Check imports/dependencies |
| "Transform failed" | TypeScript syntax issues |
Run with --debug for verbose compilation output:
lua compile --debugThese are available as globals in the VM sandbox (don't import from lua-cli):
| API | Description |
|---|---|
User |
Per-user persistent storage — store onboarding state, preferences, workflow progress, or any custom data that persists across conversations |
Data |
Custom data collections |
Products |
Product catalog |
Baskets |
Shopping cart |
Orders |
Order management |
AI |
AI generation |
Lua |
Request context (Lua.request.channel, Lua.request.webhook) |
Jobs |
Job scheduling |
Templates |
Message templates |
CDN |
File upload/retrieval |
BasketStatus |
Enum for basket statuses (ACTIVE, CHECKED_OUT, ABANDONED, EXPIRED) |
OrderStatus |
Enum for order statuses (PENDING, CONFIRMED, FULFILLED, CANCELLED) |
env(key) |
Environment variable access |
process.env |
Environment variables as an object (alternative to env()) |
fetch |
HTTP requests |
console |
Logging (appears in lua logs) |
Any property you set on the user object persists forever (across conversations, sessions, days, months) until you explicitly change it. This makes it the go-to primitive for:
- Onboarding flows — track
user.onboardingStep,user.completedSteps - Multi-step workflows — accumulate data across tool calls with
user.collectedData - Session state — store
user.lastIntent,user.pendingAction - User preferences — persist
user.theme,user.language,user.notificationSettings
// In any tool — read and write arbitrary user state
const user = await User.get(); // User is a sandbox global
// These properties persist FOREVER until you change them
user.onboardingStep = 'identity_verification';
user.collectedData = { name: 'Jane', company: 'Acme' };
user.completedSteps = ['welcome', 'personal_info'];
await user.save();
// Next conversation, next day, next month — it's still there
const user = await User.get();
console.log(user.onboardingStep); // 'identity_verification'
console.log(user.completedSteps); // ['welcome', 'personal_info']State machine pattern — use user.onboardingStep (or any field) to track where the user is in a multi-step flow, then resume from that point in any future conversation:
const user = await User.get();
const step = user.onboardingStep || 'not_started';
if (step === 'not_started') {
user.onboardingStep = 'collecting_info';
await user.save();
return { message: "Let's get started!" };
} else if (step === 'collecting_info') {
user.companyName = input.companyName;
user.onboardingStep = 'awaiting_verification';
await user.save();
return { message: 'Now let\'s verify your identity.' };
}
// ... continue for each stepSee the full User API reference for all methods (update(), save(), send(), clear()).
**This is one of the most powerful features of the Lua platform.** With a single command, you can give your agent access to 250+ third-party services (Linear, Discord, Google Calendar, HubSpot, Slack, GitHub, and more) - **without writing any code**.
Instead of:
- Writing API integration code
- Managing OAuth tokens and refresh logic
- Building tools for each third-party service
- Handling rate limits and error handling
You simply run:
lua integrations connect --integration linear --auth-method oauth --scopes all --triggers task_task.createdAnd your agent instantly gets:
- Tools like
linear_create_issue,linear_list_projects,linear_update_task, etc. - Triggers that wake up your agent when events occur (e.g., when a new issue is created)
- Connect - Authenticate with the third-party service via OAuth or API token
- Auto-MCP - An MCP (Model Context Protocol) server is automatically created
- Instant Tools - Your agent can immediately use tools from that integration
- Triggers - Optionally enable triggers to wake up your agent on events
Before connecting, use discovery commands to understand what's available:
# List all available integrations
lua integrations available
# Get detailed info about an integration (scopes and triggers)
lua integrations info linear
# Get info as JSON (for parsing)
lua integrations info linear --json
# List available trigger events
lua integrations webhooks events --integration linear
lua integrations webhooks events --integration linear --jsonJSON output is especially useful for AI coding assistants to programmatically discover:
- Available OAuth scopes with friendly descriptions
- Available trigger events with friendly descriptions
- Webhook types (native vs virtual)
# View available integrations
lua integrations available
# Connect with OAuth and triggers (recommended)
lua integrations connect --integration linear --auth-method oauth --scopes all \
--triggers task_task.created,task_task.updated
# Connect with all available triggers
lua integrations connect --integration linear --auth-method oauth --scopes all --triggers all
# Connect with specific scopes only (no triggers)
lua integrations connect --integration linear --auth-method oauth --scopes "task_task_read,task_task_write"
# Connect with API token
lua integrations connect --integration linear --auth-method token
# List connected integrations
lua integrations list
# Update scopes on existing connection
lua integrations update --integration linear --scopes all
# Disconnect
lua integrations disconnect --connection-id <id>Triggers are one of the easiest ways to make your agent reactive. When enabled, your agent automatically wakes up when events occur in connected services.
# Enable triggers during connection
lua integrations connect --integration linear --auth-method oauth --scopes all \
--triggers task_task.created,task_task.updated
# Or add triggers after connection
lua integrations webhooks create --connection <id> --object task_task --event created
# List active triggers
lua integrations webhooks list
# Delete a trigger
lua integrations webhooks delete --webhook-id <id>What happens when a trigger fires:
- An event occurs in the connected service (e.g., a new Linear issue is created)
- Unified.to sends a webhook to your agent
- Your agent wakes up with the event data in
runtimeContext - The agent can respond based on what happened
After connecting, test immediately. Use --thread to keep each test isolated:
# Test integration tools in isolated threads (can run concurrently)
lua chat -e sandbox -m "Create a Linear issue titled 'Test from Lua' in my default project" -t test-linear --clear
lua chat -e sandbox -m "List my upcoming Google Calendar events" -t test-gcal --clear
lua chat -e sandbox -m "Send a message to the #general channel on Discord" -t test-discord --clearCommon integrations include:
| Category | Examples |
|---|---|
| Task Management | Linear, Asana, Jira, Monday.com, ClickUp, Notion |
| Communication | Discord, Slack, Microsoft Teams, Telegram |
| Calendar | Google Calendar, Outlook Calendar, Calendly |
| CRM | HubSpot, Salesforce, Pipedrive, Zoho CRM |
| Development | GitHub, GitLab, Bitbucket |
| Storage | Google Drive, Dropbox, OneDrive, Box |
| Support | Zendesk, Intercom, Freshdesk |
| Gmail, Outlook, SendGrid |
Run lua integrations available to see all available integrations for your workspace.
Each integration creates an MCP server. You can manage them:
# List MCP servers and their status
lua integrations mcp list
# Deactivate (hide tools from agent)
lua integrations mcp deactivate --connection <id>
# Reactivate
lua integrations mcp activate --connection <id># 1. Discover available integrations and their capabilities
lua integrations available
lua integrations info linear --json
# 2. Connect Linear with triggers
lua integrations connect --integration linear --auth-method oauth --scopes all \
--triggers task_task.created,task_task.updated
# (Complete OAuth in browser)
# 3. Verify connection and triggers
lua integrations list
lua integrations webhooks list
# 4. Test the integration in isolated threads
lua chat -e sandbox -m "List all Linear projects I have access to" -t test-list --clear
lua chat -e sandbox -m "Create a task in Linear: Review documentation updates" -t test-create --clear
# 5. The agent now has full Linear capabilities and reacts to events!-
Use discovery commands to understand what's available:
lua integrations available- list integrationslua integrations info <type> --json- get scopes and triggerslua integrations webhooks events --integration <type> --json- get trigger events
-
Enable triggers during connection for event-driven workflows:
--triggers task_task.created,task_task.updated- specific triggers--triggers all- all available triggers
-
Connect integrations before writing custom tool code - the integration might already provide what's needed
-
Test with
lua chatafter connecting to verify tools are available -
One connection per integration type per agent - use
updateto change scopes
# 0. Authenticate (one-time setup)
# Option A: With API key
lua auth configure --api-key <your-api-key>
# Option B: With email (2 steps)
lua auth configure --email user@example.com
# (user receives OTP via email)
lua auth configure --email user@example.com --otp 123456
# 1. Initialize project (one-time per agent)
lua init --agent-id agent_abc123
# 2. Set environment variables (as needed)
lua env sandbox -k OPENAI_KEY -v "sk-test-xxx"
# 3. (OPTIONAL BUT POWERFUL) Connect third-party integrations
lua integrations available # See what's available
lua integrations connect --integration linear --auth-method oauth --scopes all
lua integrations connect --integration discord --auth-method oauth --scopes all
lua integrations list # Verify connections
# 4. Write your code in src/
# 5. Test individual components
lua test skill --name get_order --input '{"orderId": "123"}'
# 6. Check logs if issues
lua logs --type skill --name get_order --limit 5
# 7. Test with full agent in isolated threads (sandbox)
lua chat -e sandbox -m "Get order 123" -t test-get-order --clear
lua chat -e sandbox -m "Create a Linear issue for order 123 review" -t test-linear --clear # If Linear connected
# Or run both concurrently
lua chat -e sandbox -m "Get order 123" -t test-get-order --clear &
lua chat -e sandbox -m "Create a Linear issue" -t test-linear --clear &
wait
# 8. Push to server
lua push skill --name order-service --set-version 1.0.0 --force
# 9. Deploy to production
lua deploy skill --name order-service --set-version latest --force
# 10. Set production env vars
lua env production -k OPENAI_KEY -v "sk-live-xxx"
# 11. Test production in isolated thread
lua chat -e production -m "Get order 123" -t prod-verify --clear
# 12. Monitor logs
lua logs --type skill --name order-service --limit 10 --json| Task | Command |
|---|---|
| Authenticate with API key | lua auth configure --api-key <key> |
| Request email OTP | lua auth configure --email <email> |
| Verify email OTP | lua auth configure --email <email> --otp <code> |
| Initialize with existing agent | lua init --agent-id <id> |
| Initialize with new agent | lua init --agent-name <name> --org-id <id> |
| Test a tool | lua test skill --name <name> --input '<json>' |
| Test a webhook | lua test webhook --name <name> --input '<json>' |
| Test a job | lua test job --name <name> |
| Chat in sandbox | lua chat -e sandbox -m "<message>" |
| Chat in production | lua chat -e production -m "<message>" |
| Chat in isolated thread | lua chat -e sandbox -m "<message>" -t <thread-id> |
| Chat in auto-generated thread | lua chat -e sandbox -m "<message>" -t |
| Isolated test with auto-cleanup | lua chat -e sandbox -m "<message>" -t <id> --clear |
| Clear all conversation history | lua chat clear --force |
| Clear user's history | lua chat clear --user <email|mobile|userId> --force |
| Clear specific thread history | lua chat clear --thread <thread-id> --force |
| View available integrations | lua integrations available |
| Get integration info (JSON) | lua integrations info <type> --json |
| List trigger events (JSON) | lua integrations webhooks events --integration <type> --json |
| Connect with triggers | lua integrations connect --integration <type> --auth-method oauth --scopes all --triggers <events> |
| Connect integration (OAuth) | lua integrations connect --integration <type> --auth-method oauth --scopes all |
| Connect integration (Token) | lua integrations connect --integration <type> --auth-method token |
| List connected integrations | lua integrations list |
| Update integration scopes | lua integrations update --integration <type> --scopes all |
| Disconnect integration | lua integrations disconnect --connection-id <id> |
| List triggers | lua integrations webhooks list |
| Create trigger | lua integrations webhooks create --connection <id> --object <type> --event <event> |
| Delete trigger | lua integrations webhooks delete --webhook-id <id> |
| List integration MCP status | lua integrations mcp list |
| Push all | lua push all --force |
| Push and deploy | lua push all --force --auto-deploy |
| Deploy specific version | lua deploy skill --name <name> --set-version <ver> --force |
| Set env variable | lua env <sandbox|production> -k <key> -v <value> |
| View logs | lua logs --type <type> --name <name> --limit <n> |
Complete CLI command documentation All non-interactive flags and CI/CD examples Connect 250+ third-party services Complete LuaAgent configuration reference User, Data, Products, and other runtime APIs Manage MCP servers for external tools