> ## Documentation Index
> Fetch the complete documentation index at: https://docs.heylua.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Agents API

> Invoke another agent from within your skill, tool, job, webhook, or processor

## 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.

```typescript theme={null}
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);
```

<Note>
  **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.
</Note>

## Import

```typescript theme={null}
import { Agents } from 'lua-cli';
// or
import { Agents } from 'lua-cli/skill';
```

## Calling Contexts

| Context                          | User identity                    | Recommended pattern                                                      |
| -------------------------------- | -------------------------------- | ------------------------------------------------------------------------ |
| **Tool** (user turn)             | Caller's user automatically used | `Agents.invoke(id, prompt)` — no `userId` needed                         |
| **Dynamic Job** (Jobs API)       | Caller's user automatically used | `Agents.invoke(id, prompt)` — no `userId` needed                         |
| **Webhook**                      | No ambient user                  | Pass `userId` from event payload, or omit to run without a user identity |
| **Pre-defined LuaJob**           | No ambient user                  | Pass `userId` from metadata, or omit to run without a user identity      |
| **PreProcessor / PostProcessor** | Caller's user automatically used | `Agents.invoke(id, prompt)` — no `userId` needed                         |

<Note>
  **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.
</Note>

## Methods

### Simplified: `Agents.invoke(targetAgentId, prompt)`

Invoke an agent with a plain text prompt. Returns the assistant's response as a plain string.

<ParamField path="targetAgentId" type="string" required>
  The target agent's identifier (e.g. `'sales-agent'`, `'support-agent'`).
</ParamField>

<ParamField path="prompt" type="string" required>
  Plain-text message to send to the target agent.
</ParamField>

**Returns:** `Promise<string>`

```typescript theme={null}
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.

<ParamField path="targetAgentId" type="string" required>
  The target agent's identifier.
</ParamField>

<ParamField path="input" type="AgentInvocationInput" required>
  Full invocation options — see below.
</ParamField>

**Returns:** `Promise<AgentInvocationOutput>`

## `AgentInvocationInput`

<ParamField path="prompt" type="string">
  Plain-text user message. Mutually exclusive with `messages`.
</ParamField>

<ParamField path="messages" type="UserContent">
  AI SDK `UserContent` (array of `TextPart`, `ImagePart`, `FilePart`). Mutually exclusive with `prompt`.
</ParamField>

<ParamField path="systemPrompt" type="string">
  Override the target agent's system prompt for this invocation only.
</ParamField>

<ParamField path="runtimeContext" type="string">
  Additional runtime context string attached to the request (e.g. serialised metadata).
</ParamField>

<ParamField path="threadId" type="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.
</ParamField>

<ParamField path="channel" type="string">
  Channel identifier. Defaults to `'agent-invocation'`.
</ParamField>

<ParamField path="identifier" type="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.
</ParamField>

<ParamField path="userId" type="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.
</ParamField>

## `AgentInvocationOutput`

| Field          | Type        | Description                                                                                                              |
| -------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------ |
| `text`         | `string`    | Final response text (after the target agent's postprocessors).                                                           |
| `finishReason` | `string?`   | AI SDK `FinishReason` — `'stop'`, `'length'`, `'content-filter'`, `'preprocessor_blocked'`, `'governance_blocked'`, etc. |
| `usage`        | `object?`   | Token usage: `{ inputTokens, outputTokens, totalTokens, reasoningTokens, cachedInputTokens }`                            |
| `toolsUsed`    | `string[]?` | Names of tools invoked by the target agent during generation.                                                            |
| `threadId`     | `string?`   | 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:

```typescript theme={null}
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:**

| Cause                 | Description                                                            |
| --------------------- | ---------------------------------------------------------------------- |
| Target agent disabled | The target agent is not available to the invoking user.                |
| Insufficient credits  | The account does not have enough credits to run the invocation.        |
| Timeout               | The target agent took longer than 120 s to respond.                    |
| Service unreachable   | The platform could not be reached (may occur during local `lua test`). |

## Complete Examples

### Tool routing to a specialist agent

```typescript theme={null}
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:

```typescript theme={null}
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.

```typescript theme={null}
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)

```typescript theme={null}
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-forget** — `Agents.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.

## Related APIs

<CardGroup cols={2}>
  <Card title="AI API" href="/api/ai" icon="brain">
    Generate text outside the agent pipeline
  </Card>

  <Card title="User API" href="/api/user" icon="user">
    Get or update user context
  </Card>

  <Card title="LuaWebhook" href="/api/luawebhook" icon="webhook">
    HTTP endpoints for external events
  </Card>

  <Card title="LuaJob" href="/api/luajob" icon="clock">
    Pre-defined scheduled tasks
  </Card>

  <Card title="Jobs API" href="/api/jobs" icon="plus">
    Dynamic job creation
  </Card>

  <Card title="PostProcessor" href="/api/postprocessor" icon="paper-plane">
    Format agent responses
  </Card>
</CardGroup>

## See Also

* [AI API](/api/ai) — isolated text generation without the full agent pipeline
* [LuaWebhook](/api/luawebhook) — triggering agents from external events
* [LuaJob](/api/luajob) — scheduled agent invocations
