Skip to main content

Overview

The Agents API lets any piece of sandbox code — tools, webhooks, jobs, preprocessors, postprocessors — invoke a target agent through the full chat pipeline. The invocation goes through billing, message persistence, preprocessors, postprocessors, and governance on the target agent, exactly as if a user had sent a message directly.
import { Agents } from 'lua-cli';

// Simplified: returns plain text
const reply = await Agents.invoke('sales-agent', 'Summarize the last order.');

// Full options: returns structured output
const result = await Agents.invoke('sales-agent', {
  prompt: 'Draft a reply to the latest order',
  threadId: 'order-123',
  systemPrompt: 'Be concise.',
});
console.log(result.text, result.usage);
Full Pipeline Execution: Unlike AI.generate, Agents.invoke routes through the target agent’s complete processing stack — including its skills, tools, preprocessors, postprocessors, and governance rules.

Import

import { Agents } from 'lua-cli';
// or
import { Agents } from 'lua-cli/skill';

Calling Contexts

ContextUser identityRecommended pattern
Tool (user turn)Caller’s user automatically usedAgents.invoke(id, prompt) — no userId needed
Dynamic Job (Jobs API)Caller’s user automatically usedAgents.invoke(id, prompt) — no userId needed
WebhookNo ambient userPass userId from event payload, or omit to run without a user identity
Pre-defined LuaJobNo ambient userPass userId from metadata, or omit to run without a user identity
PreProcessor / PostProcessorCaller’s user automatically usedAgents.invoke(id, prompt) — no userId needed
No user identity — when no userId is available (e.g. a webhook with no user context), the invocation runs without a user identity and no conversation history is stored. Use userId from your event payload whenever you have one.

Methods

Simplified: Agents.invoke(targetAgentId, prompt)

Invoke an agent with a plain text prompt. Returns the assistant’s response as a plain string.
targetAgentId
string
required
The target agent’s identifier (e.g. 'sales-agent', 'support-agent').
prompt
string
required
Plain-text message to send to the target agent.
Returns: Promise<string>
const summary = await Agents.invoke('sales-agent', 'Summarize the last order.');
console.log(summary); // "The last order was for 3 units of..."

Full options: Agents.invoke(targetAgentId, input)

Invoke an agent with full control over the request. Returns a structured output object.
targetAgentId
string
required
The target agent’s identifier.
input
AgentInvocationInput
required
Full invocation options — see below.
Returns: Promise<AgentInvocationOutput>

AgentInvocationInput

prompt
string
Plain-text user message. Mutually exclusive with messages.
messages
UserContent
AI SDK UserContent (array of TextPart, ImagePart, FilePart). Mutually exclusive with prompt.
systemPrompt
string
Override the target agent’s system prompt for this invocation only.
runtimeContext
string
Additional runtime context string attached to the request (e.g. serialised metadata).
threadId
string
Thread ID suffix for conversation scoping. When omitted, the invocation flows into the caller-user’s default chat thread with the target agent — the same behaviour as a direct message. Pass a custom value to isolate this invocation in its own thread.
channel
string
Channel identifier. Defaults to 'agent-invocation'.
identifier
string
Free-form request tag persisted on the stored message record (e.g. a UUID or external trace ID for correlation). Not a user identifier.
userId
string
Explicit user to invoke as. Use from context-less triggers — webhooks and pre-defined jobs — where there is no ambient user. When omitted from a webhook or pre-defined job, the invocation runs without user identity and no conversation history is stored. This field is ignored during a user-authenticated turn; the turn’s user is always used.

AgentInvocationOutput

FieldTypeDescription
textstringFinal response text (after the target agent’s postprocessors).
finishReasonstring?AI SDK FinishReason'stop', 'length', 'content-filter', 'preprocessor_blocked', 'governance_blocked', etc.
usageobject?Token usage: { inputTokens, outputTokens, totalTokens, reasoningTokens, cachedInputTokens }
toolsUsedstring[]?Names of tools invoked by the target agent during generation.
threadIdstring?Echoes back the threadId the caller passed in, if any.

Error Handling

Agents.invoke throws an Error for any non-success response. Wrap calls in try/catch and inspect the message:
import { Agents } from 'lua-cli';

async function delegateToAgent(agentId: string, prompt: string) {
  try {
    const result = await Agents.invoke(agentId, { prompt });

    if (result.finishReason === 'preprocessor_blocked') {
      return { blocked: true, message: result.text };
    }
    if (result.finishReason === 'governance_blocked') {
      return { blocked: true, message: result.text };
    }

    return { success: true, text: result.text };
  } catch (error) {
    // Network, timeout, target disabled, insufficient credits, etc.
    return { success: false, error: error.message };
  }
}
Common error causes:
CauseDescription
Target agent disabledThe target agent is not available to the invoking user.
Insufficient creditsThe account does not have enough credits to run the invocation.
TimeoutThe target agent took longer than 120 s to respond.
Service unreachableThe platform could not be reached (may occur during local lua test).

Complete Examples

Tool routing to a specialist agent

import { LuaTool, Agents } from 'lua-cli/skill';
import { z } from 'zod';

export default class EscalateToLegalTool implements LuaTool {
  name = 'escalate_to_legal';
  description = 'Send a legal query to the dedicated legal-review agent';

  inputSchema = z.object({
    query: z.string().describe('The legal question to review'),
    contractId: z.string().optional(),
  });

  async execute(input: z.infer<typeof this.inputSchema>) {
    const result = await Agents.invoke('legal-review-agent', {
      prompt: input.query,
      threadId: input.contractId ? `contract-${input.contractId}` : undefined,
      systemPrompt: 'Respond in plain language suitable for a non-lawyer.',
    });

    return {
      legalAdvice: result.text,
      tokensUsed: result.usage?.totalTokens,
    };
  }
}

Webhook delegating to an agent

When a webhook fires you typically have a user ID in the event payload — pass it via userId so the invocation runs in that user’s context:
import { LuaWebhook, Agents } from 'lua-cli';

const orderWebhook = new LuaWebhook({
  name: 'order-shipped-webhook',
  description: 'Notify users when their order ships',

  execute: async (event) => {
    const { orderId, customerId, trackingNumber } = event.body ?? {};

    if (!customerId) {
      return { skipped: true, reason: 'no customerId in payload' };
    }

    // Pass userId so the invocation runs as that user — conversation history
    // is stored and any per-user agent rules apply.
    await Agents.invoke('notification-agent', {
      prompt: `Order ${orderId} has shipped. Tracking: ${trackingNumber}. Notify the customer.`,
      userId: customerId,
    });

    return { notified: true, customerId };
  },
});

export default orderWebhook;

Pre-defined job with no user context

When no userId is available, omit it and the invocation runs without user identity. No conversation history is stored.
import { LuaJob, Agents } from 'lua-cli';

const dailyDigest = new LuaJob({
  name: 'daily-digest-generator',
  schedule: { type: 'cron', expression: '0 6 * * *' },

  execute: async (job) => {
    // No userId available — invocation runs without a user context
    const result = await Agents.invoke('digest-agent', {
      prompt: 'Generate the daily product digest for today.',
    });

    // Store the result for other processes to pick up
    return { digest: result.text, generatedAt: new Date().toISOString() };
  },
});

export default dailyDigest;

Multi-modal input (image analysis)

import { LuaTool, Agents } from 'lua-cli/skill';
import { z } from 'zod';

export default class AnalyseReceiptTool implements LuaTool {
  name = 'analyse_receipt';
  description = 'Send a receipt image to the expense-processing agent';

  inputSchema = z.object({
    imageUrl: z.string().url(),
  });

  async execute(input: z.infer<typeof this.inputSchema>) {
    const result = await Agents.invoke('expense-agent', {
      messages: [
        { type: 'text', text: 'Extract line items and total from this receipt.' },
        { type: 'image', url: input.imageUrl },
      ],
    });

    return { extraction: result.text };
  }
}

Limitations

  • No recursion guard — avoid infinite agent-invoke loops in your code.
  • No native fire-and-forgetAgents.invoke is always awaited. To run in the background, use the Jobs API to schedule a dynamic job.
  • skillOverride / preprocessorOverride not exposed — the target agent always runs with its configured skills and preprocessors.
  • Persona override not exposed — use systemPrompt to influence behaviour without replacing the full persona.
  • 120 s timeout — invocations that take longer than 120 seconds will throw a timeout error.

AI API

Generate text outside the agent pipeline

User API

Get or update user context

LuaWebhook

HTTP endpoints for external events

LuaJob

Pre-defined scheduled tasks

Jobs API

Dynamic job creation

PostProcessor

Format agent responses

See Also

  • AI API — isolated text generation without the full agent pipeline
  • LuaWebhook — triggering agents from external events
  • LuaJob — scheduled agent invocations