Skip to main content
This document introduces the Xpert Agent middleware capabilities and its plugin-based implementation approach, comparable to LangChain Middleware (refer to official documentation: overview, built-in). Through middleware, you can insert cross-cutting logic (logging, caching, rate limiting, security, prompt governance, etc.) at key nodes in the Agent lifecycle without modifying core orchestration logic.

Core Concepts

  • Strategy / Provider: Each middleware implements an IAgentMiddlewareStrategy, marked with @AgentMiddlewareStrategy('<provider>') and registered to AgentMiddlewareRegistry (plugin-sdk).
  • Middleware Instance: The AgentMiddleware object returned by createMiddleware, containing state Schema, context Schema, optional tools, and lifecycle hooks.
  • Node-based Integration: Connect WorkflowNodeTypeEnum.MIDDLEWARE nodes to Agents in the workflow graph. At runtime, they are loaded and executed in connection order via getAgentMiddlewares.
  • Configuration & Metadata: TAgentMiddlewareMeta describes name, i18n labels, icon, description, and configuration Schema. The UI renders configuration panels accordingly.

Lifecycle Hooks & Capabilities

Additionally, you can declare:
  • stateSchema: Persistable middleware state (Zod object/optional/with defaults).
  • contextSchema: Runtime-only readable context, not persisted.
  • tools: Dynamically injected DynamicStructuredTool list.
  • JumpToTarget: Return jumpTo in hooks to control jumps (e.g., model, tools, end).

StateSchema Deep Dive

What is StateSchema?

StateSchema is a runtime state validation schema defined using the Zod library. It defines the state data structure that needs to be maintained and transmitted during middleware execution.

Zod Introduction

Zod is a TypeScript-first data validation library:

StateSchema Example: SkillsMiddleware

The SkillsMiddleware’s stateSchema is defined as follows:
Field Description Use Cases
  1. Runtime Dynamic Skill Selection: During Agent execution, dynamically select which skills to enable based on user input or context
  2. Workspace Isolation: Support loading skills from different workspaces, enabling multi-tenancy and workspace isolation
  3. State Persistence: Maintain user skill selection history for later queries and recovery
State Resolution Flow In SkillsMiddleware, state is resolved through the resolveRuntimeSkillSelection() method:
The flow includes:
  1. Read selectedSkillIds and selectedSkillWorkspaceId from current state and initial state
  2. Sanitize and normalize data using sanitizeSkillIds() and sanitizeWorkspaceId()
  3. Prioritize current state values, fallback to initial state
  4. Return resolution results and whether runtime selection exists

StateSchema Definition Best Practices

  1. Use optional(): For optional fields, explicitly mark them as optional
  2. Type Matching: Ensure defined types match actual data types in use
  3. Validation & Cleanup: Validate and clean state data before use
  4. Documentation: Add detailed comments explaining each state field

Why StateSchema Matters

Advantages
  • Type Safety: Check state data types at compile time
  • Runtime Validation: Validate state data completeness and correctness at execution
  • Auto Transformation: Automatically transform and clean state data
  • IDE Support: Better code completion and type hints
Application Scenarios
  • Maintain middleware runtime configuration
  • Share state across middleware chain
  • Implement stateful complex logic
  • Support conditional execution and dynamic behavior

Writing a Middleware

  1. Define Strategy & Metadata
  1. Implement createMiddleware
  1. Register as Plugin Module
  1. Runtime Integration
  • During development/deployment, add the plugin to plugin environment variables.
  • In the workflow editor, add middleware nodes to the Agent and configure order (execution order can be adjusted by arrangement).

Middleware Lifecycle

Initialization Phase

Execution Phase

Built-in Examples

  • SummarizationMiddleware
    Detects conversation length in beforeModel, triggers compression and replaces historical messages; supports triggering by token/message count or context ratio, and records compression to execution trace via WrapWorkflowNodeExecutionCommand.
  • todoListMiddleware Injects write_todos tool, and appends system prompts in wrapModelCall to guide LLM in planning complex tasks. Tool returns Command to update Agent state.

Best Practices

  • Implement only necessary hooks, keep them idempotent, avoid heavy blocking operations within hooks.
  • Use stateSchema to strictly declare persistent data, preventing state interference between different middleware/Agents.
  • In wrapModelCall/wrapToolCall, prioritize calling the passed handler to ensure default chain works, then add custom logic.
  • For ratio-triggered logic, rely on model profile.maxInputTokens; fallback to absolute token limits when unavailable (see Summarization example).
  • Following LangChain Middleware approach: decompose cross-cutting concerns like logging, auditing, caching, rate limiting, and gradual model switching into independent middleware, composing them by connection order.
Through this approach, you can seamlessly migrate LangChain’s Middleware model into Xpert’s plugin system, reusing existing orchestration, workflow, and UI configuration capabilities.

Reference Resources