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

> Run a complete turn of another agent, or of this agent, from runtime code

`Agents.invoke` sends a prompt to a target [agent](/concepts/agents) and returns that agent's reply after a complete turn: its persona, skills and tools, processors, and governance all run. For a bare model call with no agent, use [`AI`](/reference/sdk/ai). The reply is returned, not delivered: nothing reaches a channel unless the target's tools send it or you pass `result.text` to [`Channels.send`](/reference/sdk/channels). Available in tools, jobs, webhooks, triggers, processors, and workflow code steps.

*Verified against lua-cli 3.33.0.*

```ts theme={null}
import { Agents } from 'lua-cli';
```

## Quick example

The short form returns the reply text; the options form returns the full result.

```ts theme={null}
import { Agents } from 'lua-cli';

const reply = await Agents.invoke('baseAgent_agent_1770000000000_k3xq9mz2p', 'Summarize the last order.');

const result = await Agents.invoke('baseAgent_agent_1770000000000_k3xq9mz2p', {
  prompt: 'Draft a reply to the latest order',
  threadId: 'order-4471',
  systemPrompt: 'Be concise.',
});
```

## Methods

### invoke(targetAgentId, prompt)

Runs one turn with a plain prompt and returns the reply text.

```ts theme={null}
Agents.invoke(targetAgentId: string, prompt: string): Promise<string>
```

<ParamField path="targetAgentId" type="string" required>
  The target's agent id, for example `baseAgent_agent_1770000000000_k3xq9mz2p`. Pass this agent's own id to invoke itself; there is no alias for the current agent. Your project's id is `agent.agentId` in `lua.skill.yaml` and `project.agentId` in `lua status --json --ci`; `lua agents --json` lists every agent you can access.
</ParamField>

<ParamField path="prompt" type="string" required>
  The end-user message for the turn.
</ParamField>

**Returns** — the reply text, after the target's postprocessors.

**Example**

```ts theme={null}
import { Agents, env } from 'lua-cli';

const digest = await Agents.invoke(env('DIGEST_AGENT_ID') ?? '', 'Write the daily digest.');
```

**Errors** — the same as `invoke(targetAgentId, input)`.

### invoke(targetAgentId, input)

Runs one turn with full options and returns the structured result.

```ts theme={null}
Agents.invoke(targetAgentId: string, input: AgentInvocationInput): Promise<AgentInvocationOutput>
```

<ParamField path="targetAgentId" type="string" required>
  The target's agent id. See the short form.
</ParamField>

<ParamField path="input.prompt" type="string">
  Plain end-user message. Pass `prompt` or `messages`, not both.
</ParamField>

<ParamField path="input.messages" type="UserContent">
  AI SDK v5 parts: `{ type: 'text', text }`, `{ type: 'image', image, mediaType? }`, `{ type: 'file', data, mediaType, filename? }`.
</ParamField>

<ParamField path="input.systemPrompt" type="string">
  Replaces the target's persona for this turn only.
</ParamField>

<ParamField path="input.runtimeContext" type="string">
  Extra context attached to the request, for example serialized metadata.
</ParamField>

<ParamField path="input.clientContext" type="{ timezone?: string }">
  The end user's IANA time zone, such as `Africa/Nairobi`. When omitted, the target uses the end user's stored profile, then UTC.
</ParamField>

<ParamField path="input.threadId" type="string">
  Thread suffix that isolates this turn's conversation. When omitted, the turn joins the end user's default thread with the target, exactly like a direct message.
</ParamField>

<ParamField path="input.channel" type="string" default="agent-invocation">
  The channel value the target's code reads from `Lua.request.channel`. It labels the turn; it doesn't deliver the reply anywhere.
</ParamField>

<ParamField path="input.identifier" type="string">
  Free-form tag stored on the message record, for example a trace id. Not a user id.
</ParamField>

<ParamField path="input.userId" type="string">
  The end user to run the turn as. In a tool or processor the turn's end user is used and this field isn't needed. In a job, webhook, or trigger there is no ambient end user: pass `userId` to run as that end user (conversation history is stored), or omit it to run without a user identity (no history, no `User` profile).
</ParamField>

<ParamField path="input.webhookPayload" type="unknown">
  Raw payload from the source event. The target's code reads it from `Lua.request.webhook.payload` for this turn; it isn't stored.
</ParamField>

<ParamField path="input.timeoutMs" type="number" default={120000}>
  Per-call timeout in milliseconds. The calling context's own budget caps it: a tool, job, webhook, or processor run is walled at 180 s, so larger values buy nothing.
</ParamField>

<ParamField path="input.model" type="string">
  A [model code](/concepts/models) for this turn. A code the platform doesn't approve falls back per its policy.
</ParamField>

**Returns**

<ResponseField name="result" type="AgentInvocationOutput">
  <Expandable title="properties">
    <ResponseField name="text" type="string">
      The reply text, after the target's postprocessors.
    </ResponseField>

    <ResponseField name="threadId" type="string">
      The `threadId` you passed; `undefined` when you passed none.
    </ResponseField>

    <ResponseField name="finishReason" type="string">
      An AI SDK finish reason such as `stop` or `length`.
    </ResponseField>

    <ResponseField name="usage" type="object">
      `inputTokens`, `outputTokens`, `totalTokens`, `reasoningTokens`, `cachedInputTokens`, each optional.
    </ResponseField>

    <ResponseField name="toolsUsed" type="string[]">
      Names of the tools the target called during the turn.
    </ResponseField>
  </Expandable>
</ResponseField>

**Example**

```ts theme={null}
import { Agents, LuaWebhook, env } from 'lua-cli';

export default new LuaWebhook({
  name: 'order-shipped',
  description: 'Tell the customer their order shipped',
  async execute(event) {
    const body = (event.body ?? {}) as { orderId?: string; customerId?: string };
    if (!body.customerId) return { skipped: true };

    const result = await Agents.invoke(env('NOTIFY_AGENT_ID') ?? '', {
      prompt: `Order ${body.orderId} has shipped. Tell the customer.`,
      userId: body.customerId,
      webhookPayload: event.body,
    });
    return { text: result.text, tools: result.toolsUsed ?? [] };
  },
});
```

**Errors** — the call throws. In a deployed agent the error is an `AgentInvocationError` with a `code` and, where an HTTP status applies, a `statusCode`; in `lua test` it is a plain `Error` carrying the server's message, or `Agent invocation failed`.

| `code`                                       | Status | When                                                                                  |
| -------------------------------------------- | ------ | ------------------------------------------------------------------------------------- |
| `MISSING_TARGET_AGENT`                       | —      | Empty target: `Target agentId is required`                                            |
| `MISSING_PROMPT`                             | —      | Neither `prompt` nor `messages`                                                       |
| `AGENT_DISABLED`                             | 423    | `Target agent <id> is disabled`                                                       |
| `PAYMENT_REQUIRED`                           | 402    | The organization has no credits for the turn                                          |
| `UNAUTHORIZED`                               | 401    | `Agent invocation authentication failed`                                              |
| `PREPROCESSOR_BLOCKED`, `GOVERNANCE_BLOCKED` | —      | The target's preprocessor or governance stopped the turn; `message` is the block text |
| `TIMEOUT`                                    | 504    | `Agent invocation timed out`                                                          |
| `SERVICE_UNAVAILABLE`                        | 503    | The platform could not be reached                                                     |
| `HTTP_ERROR`                                 | other  | Any other HTTP status: `Agent invocation failed (HTTP <status>)`                      |
| `UNEXPECTED_ERROR`                           | 500    | A failure that wasn't an HTTP response                                                |

A `userId` who cannot reach the target fails with a 403, `User <userId> cannot be delegated to agent <id>`.

<Note>
  In `lua test` the call is made with your developer credentials: `userId`, `model`, and `timeoutMs` are not sent, so you are always the end user and the default timeout applies. A blocked turn is returned with `finishReason` set to `preprocessor_blocked` or `governance_blocked` rather than thrown; deployed agents honor all three fields and throw.
</Note>

## Types

`AgentInvocationInput` and `AgentInvocationOutput` have the fields listed under `invoke(targetAgentId, input)`. They are not exported from `lua-cli`; declare your own variables with those fields, or let inference type the call.

## See also

* [`AI`](/reference/sdk/ai) — one model call without an agent
* [`Lua`](/reference/sdk/lua) — what the target reads from `channel` and `webhookPayload`
* [`LuaJob`](/reference/sdk/luajob) — invoke an agent on a schedule
* [Compose agents](/build/compose-agents) — routing, self-invocation, and identity patterns
* [REST chat API](/reference/rest/chat) — the same turn from outside the platform
