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

# Processors

> Preprocessors and postprocessors: your code that runs on every message before and after the model

A preprocessor runs your code on every incoming message before the model sees it, and a postprocessor runs on every reply before the end user sees it. They exist for rules that must hold in every conversation regardless of what the model decides: blocking, rate limits, redaction, enrichment, a disclaimer, channel-specific formatting.

## How a processor runs

Both are versioned primitives registered on `LuaAgent` as `preProcessors` and `postProcessors`, pushed with `lua push preprocessor --name <name>` or `lua push postprocessor --name <name>`, and made live with the matching `lua deploy` type or an agent version promote.

```ts src/preprocessors/officeHours.ts theme={null}
import { PreProcessor } from 'lua-cli';

export default new PreProcessor({
  name: 'office-hours',
  description: 'Answer WhatsApp messages outside office hours without calling the model',
  priority: 10,
  async execute(user, messages, channel) {
    const hour = new Date().getUTCHours();
    if (channel === 'whatsapp' && (hour < 8 || hour >= 18)) {
      return { action: 'block', response: 'We reply between 08:00 and 18:00 UTC.' };
    }
    return { action: 'proceed' };
  },
});
```

A preprocessor's `execute` receives the end user, the message as an array of parts (text, image, or file), and the channel. It returns `{ action: 'proceed' }`, optionally with `modifiedMessage`, a replacement array of parts that the next preprocessor and then the model receive, or `{ action: 'block', response }`. A block ends the turn: the model never runs, and `response` is sent to the end user as the reply and stored in the conversation. Leave `response` empty and what the end user sees depends on the client: the legacy stream shows nothing, the widget's stream shows the default text `User not eligible for agent response`, and a non-streaming chat call returns a `preprocessor_blocked` result with empty text. Set `response` whenever the end user should know why. `metadata` is accepted on both results but is not passed anywhere.

A postprocessor's `execute` receives the end user, the original message, the model's reply, and the channel, and must return `{ modifiedResponse }`; that text replaces the reply.

Active processors of each kind run one after another in ascending `priority` (default 100), each receiving the previous one's output. Two processors that share a priority run in the order the database returns them, which you cannot set, so give each a distinct number. `async` is accepted on a preprocessor but nothing reads it; every preprocessor runs in sequence before the model.

Failure is handled per channel. A preprocessor that throws or times out is skipped on text channels and the message continues unchanged; on a voice call the turn is blocked instead, with the spoken reply `Sorry, I'm having trouble right now. Could you try that again?`, so the caller never hears silence. A postprocessor that throws is skipped on every channel and the original reply is sent.

On streaming channels the end user sees the reply as the model produces it. Postprocessors run when the stream ends, and only if they changed the text does the client receive a `postprocess-complete` event with the final version; the stored reply is the post-processed one. When the end user must never see the unprocessed text, use the non-streaming [chat endpoint](/reference/rest/chat).

`lua test preprocessor --name <name> --input '{"message":"hello","channel":"web"}'` and `lua test postprocessor --name <name> --input '{"message":"hi","response":"Hello","channel":"web"}'` run one processor on your machine. `lua preprocessors` and `lua postprocessors` view, activate, deactivate, deploy, and delete them, named with `--preprocessor-name <name>` or `--postprocessor-name <name>`.

## Processors and skills

|                    | Processor                                  | Skill                               |
| ------------------ | ------------------------------------------ | ----------------------------------- |
| Runs               | On every message or reply                  | When the model chooses a tool       |
| Decided by         | Your code                                  | The model                           |
| Can block the turn | Yes (preprocessor)                         | No                                  |
| Use for            | Policy, rate limits, redaction, formatting | Actions and data the model requests |

For one tool's output, transform it inside the tool. For a rule the organization sets rather than you, or for gating tool calls, see [governance](/concepts/governance), which runs before your preprocessors.

## When to use it

* A rule must hold for every message: profanity, office hours, per-user rate limits, a handoff flag that pauses the agent while a person talks.
* Context the model always needs: append the end user's account status to the message.
* Every reply needs the same treatment on a channel: a footer, a length cap, WhatsApp-safe formatting.
* Don't use one for a decision the model should make with a tool, or for a transform that applies to a single tool.

## Limits

| Item                                                  | Value                                              |
| ----------------------------------------------------- | -------------------------------------------------- |
| `priority`                                            | Any number; lower runs first; default 100          |
| Execution time per processor                          | 180 s                                              |
| Postprocessor delivery budget on streaming UI clients | 10 s, then the original text is kept               |
| Preprocessor failure                                  | Skipped on text channels; blocks the turn on voice |
| Postprocessor failure                                 | Skipped; original reply sent                       |

## Next steps

<Columns cols={2}>
  <Card title="Add a processor" href="/build/add-a-processor">Write, test, push, and deploy a preprocessor or postprocessor.</Card>
  <Card title="PreProcessor reference" href="/reference/sdk/preprocessor">Config fields and the result shape.</Card>
  <Card title="PostProcessor reference" href="/reference/sdk/postprocessor">Config fields and the response shape.</Card>
  <Card title="Add human handoff" href="/build/add-human-handoff">Pause the agent with a flag and a blocking preprocessor.</Card>
</Columns>
