PreProcessor runs your code on the end user’s messages before the model sees them, and returns whether the turn proceeds, proceeds with different messages, or stops with a reply of its own. Register instances on LuaAgent under preProcessors. Inside execute, the runtime objects (User, Data, Lua, env) are available as in a tool. For where a preprocessor sits in a turn, see processors.
Verified against lua-cli 3.33.0.
Quick example
A preprocessor that blocks a refund request until it carries an order number:src/preprocessors/OrderIdGuard.ts
Constructor
Creates a preprocessor from aPreProcessorConfig. The class is also exported under the alias LuaPreprocessor.
PreProcessor requires a non-empty \name` (used as the server-side identifier).whenname` is missing or blank.
Configuration
string
required
Server-side identifier, and the name
lua push preprocessor --name and lua preprocessors address. Kebab-case, for example order-id-guard.string
required
Short description shown in listings. Not seen by the model.
(user, messages, channel) => Promise<PreProcessorResult>
required
Called once per turn with the end user’s
UserDataInstance, the turn’s ChatMessage[], and the channel identifier ('web', 'whatsapp', 'email', …). Must resolve to a PreProcessorResult.number
default:100
Execution order. Lower numbers run first; preprocessors with the same priority run in the order the server stores them, not the order of the
preProcessors array.boolean
default:false
Stored with the pushed version. The deployed runtime runs every preprocessor in sequence and waits for each result regardless of this flag.
Result
execute returns one of two shapes; there is no 'allow' or 'modify' action.
Proceed
{ action: 'proceed' } hands the current messages to the next preprocessor, or to the model when this is the last one. Set modifiedMessage to replace them; the replacement is what every later preprocessor and the model receive.
Block
{ action: 'block', response } stops the chain. The model is not called, later preprocessors do not run, and response is sent to the end user as the agent’s reply.
Empty response
{ action: 'block', response: '' } stops the chain, but what the end user sees depends on the path the turn came in on. The web widget and other UI-stream channels show the default text User not eligible for agent response; a caller of the generate endpoint or Agents.invoke receives a preprocessor_blocked result with empty text; only the legacy streaming path sends no text. There is no channel-independent way to block silently.
Message types
messages is a ChatMessage[]; every member carries a type discriminator.
image and data hold a URL or base64 content; mediaType is the MIME type, for example image/png or application/pdf.
Instance methods
Execution order
- Only preprocessors that are pushed and active run (
lua preprocessors activate). They run in ascendingpriority, one at a time; each receives the messages the previous one returned. - The first
blockends the chain. AproceedwithoutmodifiedMessagepasses the current messages through unchanged. - A preprocessor that throws or times out is logged (
lua logs --type preprocessor) and skipped, and the chain continues with the current messages. On a voice channel the same failure blocks the turn instead, with the spoken replySorry, I'm having trouble right now. Could you try that again?andmetadata: { failedPreprocessor: '<name>', failClosed: true }. executehas 180 seconds to return.lua chatin the sandbox sends aPreProcessorOverridefor every preprocessor listed inlua.skill.yaml, so the sandbox turn runs your sandbox version in place of the production one. You never construct overrides yourself.
CLI commands
The
lua test input object accepts message and channel; the message is wrapped as a single TextMessage.
Types
All of these are exported from'lua-cli'.
interface
{ name; description; async?; priority?; execute } as documented under Configuration.PreProcessorBlockResponse | PreProcessorProceedResponse
The union
execute returns. PreProcessorAction is 'proceed' | 'block'.type
{ action: 'block'; response: string; metadata?: Record<string, any> }.type
{ action: 'proceed'; modifiedMessage?: ChatMessage[]; metadata?: Record<string, any> }.type
The message shapes under Message types.
interface
{ preprocessorId: string; sandboxId: string }. Sent by lua chat to run a sandbox version.alias
The same class as
PreProcessor.See also
PostProcessor— the same chain on the model’s reply- Processors — order, blocking, and processor versus skill
- Add a processor — how-to
User— theuserargument and its methodslua preprocessors— activate, deactivate, versions

