Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.ixo.world/llms.txt

Use this file to discover all available pages before exploring further.

A middleware wraps the agent’s LLM call. Return one from getMiddlewares(ctx) and it runs on every model turn.
1

Build the middleware with createMiddleware

Import createMiddleware from langchain. Set a name (appears in error messages) and any of the three hooks.
import { type AgentMiddleware, type PluginContext } from '@ixo/oracle-runtime';
import { createMiddleware } from 'langchain';

export function buildWeatherMiddleware(ctx: PluginContext): AgentMiddleware {
  let startedAt = 0;
  return createMiddleware({
    name: 'WeatherLoggingMiddleware',
    beforeModel: async () => {
      startedAt = Date.now();
      ctx.logger.log('model call started');
    },
    afterModel: async () => {
      const elapsed = startedAt > 0 ? Date.now() - startedAt : -1;
      ctx.logger.log(`model call complete (${elapsed}ms)`);
    },
  });
}
Canonical source: weather-middleware.ts.
2

Pick the right hook

Each hook receives the LangGraph state. Use the one whose timing matches your work.
createMiddleware({
  name: 'MyMiddleware',
  beforeModel: async (state) => {
    // Loading context, mutating state, blocking unsafe inputs
  },
  afterModel: async (state) => {
    // Logging, observation, emitting events
  },
  onError: async (error, state) => {
    // Recovery, metrics, fallback content
  },
});
For state mutations, return a partial state object from the hook — same protocol as LangChain’s AgentMiddleware.
3

Return it from getMiddlewares(ctx)

The runtime appends your middleware after the four always-on framework middleware.
import { OraclePlugin, type AgentMiddleware, type PluginContext } from '@ixo/oracle-runtime';

export class WeatherPlugin extends OraclePlugin {
  override getMiddlewares(ctx: PluginContext): AgentMiddleware[] {
    return [buildWeatherMiddleware(ctx)];
  }
}
See getMiddlewares in weather.plugin.ts.
4

Attach a middleware to a sub-agent (optional)

A sub-agent can carry its own middleware — it only runs inside that sub-agent’s loop.
import { createSummarizationMiddleware } from '@ixo/oracle-runtime';

const subAgent: PluginSubAgent = {
  name: 'long_research_agent',
  // ...
  middlewares: [createSummarizationMiddleware()],
};

What to know before shipping

  • The four always-on middleware (tool-validation, tool-retry, page-context, safety-guardrail) always run first. They are not removable.
  • Plugin middleware runs in topological dependency order — encode ordering via dependsOn if it matters.
  • Middleware fires per LLM turn, not per individual tool invocation. Wrap the tool handler directly for per-tool behaviour.
  • Closure-scoped timers interleave across concurrent model calls. For accurate timing, push start times onto a per-call ID.
  • Don’t reimplement auth in a middleware — auth runs at the HTTP layer (AuthHeaderMiddleware), not in the agent loop.

Add a sub-agent

Sub-agent-scoped middleware.

State schema

What’s in state when your hooks fire.