Plugins
LLM plugins apply inference-aware controls and mutations on a per-request basis. Unlike fields directly under config.llm, every plugin has its own Condition:
name must be unique. isDisabled turns a plugin off without removing it. condition is required; use matchAny: true to invoke the plugin unconditionally.
Inference-specific plugins always run in the POST_AUTH phase. Setting phase: PRE_AUTH for one is rejected because they depend on an authenticated request and run after authorization has already decided whether it is allowed.
Order
Inference-specific plugins run in a fixed order regardless of their order in the YAML list:
Every
guardrail.Every
toolsplugin.The first matching
semanticRouter.Every
promptplugin.Every
modelplugin.Every
reasoningplugin.The first matching
semanticCache.Every matching
tokenRateLimit.
Plugins of the same ordinary type run in list order. The last matching model or reasoning plugin wins. The first matching semantic router or cache is used and the rest are skipped. Every matching token rate limit is enforced.
This order ensures that guardrails inspect downstream content before the Service mutates it, routing uses the downstream's semantic subject, the cache keys the exact request that would reach the model, and a cache hit consumes no inference quota.
Prompt
The prompt plugin controls system instructions and conversational messages. Content can be a static value, a CEL eval result or an OPA opa result. An evaluation error rejects the request; an empty result is a no-op.
Prompt plugins apply to conversational operations. Anthropic token counting is included so the returned count represents the request that the Service would actually send. They are a no-op for operations that carry no instructions or messages.
System Instructions
system.mode determines how plugin content is combined with instructions supplied by the downstream.
The default PREPEND mode inserts content before the downstream instructions:
APPEND inserts content after the downstream instructions:
REPLACE discards every downstream instruction carrier and uses the configured content:
STRIP removes downstream instructions and adds nothing:
REJECT rejects requests that carry downstream system instructions. If none are present, it inserts the configured content:
Use REJECT when the Service must own the instruction carrier. This controls which instructions reach the provider; it does not guarantee that a model obeys them or stop prompt injection carried by ordinary messages or tool results.
Messages
A message plugin selects USER or ASSISTANT messages, chooses their position and computes the inserted content:
The selectors are:
LAST, which is the default, selects the last message of the role.FIRSTselects the first message of the role.ALLselects every message of the role. It is unused withNEW_BEFOREandNEW_AFTER.
The positions are:
PREPENDandAPPENDinsert content into the selected message.NEW_BEFOREandNEW_AFTERinsert a new message beside the selected one. If the role does not exist, the new message is appended to the conversation.
Here is a conditional new message computed with CEL:
An ASSISTANT message can also be inserted. A trailing assistant message is a prefill, which is supported by ANTHROPIC but rejected for OPENAI. Provider-specific restrictions still apply to Anthropic prefills.
Tools
The tools plugin filters tool definitions supplied by the downstream, adds definitions owned by the Service and controls the requested tool choice.
Filters
Filters run in list order for every downstream tool, and the first matching filter decides its result. They can match a glob against the tool name or type:
The decisions are:
ALLOWpreserves the matched tool.REMOVE, which is the default, silently removes it.DENYrejects the entire request withdenyMessage.REPLACEreplaces the definition with a JSON object controlled by the Service.
When at least one filter exists, an unmatched provider-hosted tool is removed by default. Such tools usually have a non-function type and execute inside the provider, beyond any other Octelium Service. Allow the required type explicitly. A remote MCP declaration commonly has the coarse type mcp; use REPLACE to pin its complete endpoint and definition.
Replacing Definitions
The replacement is one serialized tool object in the request's own protocol. It can be static:
It can also be computed with CEL or OPA:
An evaluation error or a result that is not a JSON object rejects the request.
Adding Definitions
Definitions in tools.tools are owned by the Service and are not processed by filters:
An added tool can use value, eval or opa. APPEND is the default position.
Tool Choice
choice controls the choice supplied by the downstream:
PRESERVE, which is the default, keeps the downstream choice.NONEoffers the tools but prevents the model from calling one.AUTOdowngrades a forced or named choice to automatic selection.
A choice naming a removed or replaced tool is always reconciled with the remaining definitions.
Guardrail
A guardrail plugin inspects inference content and decides how to handle matched spans. It is a content control; use Policies for identity, operation and model authorization.
Legs and Scopes
leg can be REQUEST, RESPONSE or BOTH. REQUEST is the default. Request scopes are:
CONTENTfor messages, prompts and ordinary model input.INSTRUCTIONSfor system instructions.TOOL_DEFINITIONSfor names, descriptions and JSON Schemas.TOOL_RESULTSfor content returned by tools and supplied back to the model.ALLfor every scope.
On the response leg, the generated content is inspected and scopes is unused. A response guardrail buffers a complete streamed response before releasing it. This guarantees that matched output cannot reach the downstream, but delays the first token until generation completes.
Pattern Types
A pattern can use an RE2 regular expression:
It can use a built-in deterministic detector:
Or it can detect credentials, private keys and connection strings from hundreds of providers:
Secret rule identifiers are case-insensitive. An unknown exclusion is ignored.
Actions
DENY, which is the default, rejects the request or withholds the response. REDACT replaces a matched span with a detector-specific placeholder. STRIP removes it. REPLACE computes replacement text:
Replacement content can also be computed:
Rewriting actions only apply to request content. Responses and TOOL_DEFINITIONS are inspect-only and can only use DENY. A guardrail evaluation error rejects the request instead of bypassing the control.
Model and Reasoning
model and reasoning plugins are conditional versions of the defaults under config.llm. They accept every oneof described in model selection and reasoning.
Multiple matching plugins run in list order, and the last result wins. A model plugin overwrites a semantic router decision. ctx.request.llm.model continues to contain the downstream-requested model, so later conditions do not see the effective replacement.
Token Rate Limit
tokenRateLimit limits inference consumption rather than request count. It is a sliding-window quota, not a per-request ceiling; use limits for the latter.
The scopes are TOTAL, INPUT and OUTPUT. TOTAL is the default. Reasoning tokens reported by the provider count as output. Cached input tokens count as input wherever the provider includes them in its input count.
The key can be per Session, per User or computed with CEL:
Octelium reserves capacity before proxying so concurrent requests observe one another. The reservation uses the post-plugin input estimate and either the request's output maximum or defaultOutputTokens. After the response, Octelium reconciles it with provider-reported usage. Missing or partial usage keeps the safer reservation; an upstream error with no reported usage releases it.
A request that declares no output maximum and has no defaultOutputTokens reserves nothing for output. Add a default when output must be bounded before generation. Use an ordinary rateLimit plugin as well to limit request frequency.
Semantic Cache
semanticCache serves an earlier successful completion with the same meaning and exact execution context. It supports conversational generation operations and is a no-op for other operations.
An exact repeat is served without generating an embedding. A semantic lookup embeds only the subject of the request. The effective model, operation, instructions, preceding messages, tools, tool choice, response schema, reasoning, sampling parameters and unrecognized fields must match exactly.
Successful responses containing tool calls are never stored. Incomplete streams, oversized responses and responses rejected by a guardrail are also not stored. A cache hit consumes no inference tokens and still passes current response guardrails.
Cache Scopes
The scope determines who may receive a response generated for another request and is therefore a security boundary.
Per User is the default:
Per Session is the narrowest scope and is the default for anonymous requests:
A shared cache crosses identity boundaries:
Only use it when every authorized caller is entitled to every answer the Service can produce. A CEL result can define a tenant partition:
An empty evaluated partition is rejected rather than treated as shared.
minSimilarity is the cosine threshold from 0 to 1. Zero uses a conservative default. Tune it against the configured embedding model. ttl bounds staleness, and maxSize bounds the response stored. useXCacheHeader adds the X-Cache result header.
The plugin fails open: an embedding or vector-store error becomes a miss and reaches the model. It stores prompt-derived vectors, opaque execution-context digests and generated responses independently of access-log body visibility. A plugin-level embedding can override config.llm.embedding.
Semantic Router
semanticRouter selects a model by the meaning of a request. A common pattern is to route only requests whose downstream model is auto:
Each description and example is embedded independently. The highest-scoring route above its own minSimilarity, or the plugin default, wins. Several short examples generally define a route more precisely than one long description. A route must provide a description, examples or both.
If no route matches, fallbackModel is used. An empty fallback preserves the downstream-requested model. An embedding failure also fails open to the fallback. A plugin-level embedding overrides the global embedding for both route configuration and requests.
The router runs before prompt plugins so inserted instructions do not distort the semantic subject. A later model plugin can overwrite its choice. Routing decisions, similarity and selected models are recorded in access logs.
HTTP Plugins
The LLM mode also supports the HTTP extProc, lua, direct, rateLimit, jsonSchema and path plugins (read more here). They keep the same fields and behavior as in HTTP mode.
Generic plugins run before all inference-specific plugins on the request and after them on the response. A generic plugin can therefore create content that no LLM guardrail sees and must inspect that content itself when necessary. Generic plugin condition errors skip that plugin, while an inference-specific condition error rejects the request.
The generic HTTP cache is not supported because it does not understand inference authorization scopes, semantic equivalence or execution context. Use semanticCache instead.