Middleware Customization

Purpose and Scope

Middleware is the extension layer you use when an agent needs behavior that is not just another prompt, model, or tool. In the LangChain JavaScript documentation, middleware is presented as the place for context engineering, harness customization, and runtime safety controls. That means it is appropriate for cross-cutting behavior such as summarizing long conversations, pausing before sensitive tool calls, retrying failed model or tool calls, filtering private information, or adding filesystem and subagent capabilities around an agent run.

For Deep Agents, customization starts from a pre-assembled harness rather than a blank runtime. The documented createDeepAgent surface accepts a model, system prompt, tools, memory files, skills, backend, permissions, subagents, structured response format, runtime context schema, human-interrupt settings, and extra middleware. The important design rule is that middleware is not a replacement for these primitives. It is the mechanism that changes how those primitives participate in execution, especially when the same policy must apply across multiple model calls or tool calls.

Relevant Source Files

  • libs/langchain/langchain_classic/document_loaders/parsers/language/javascript.py - Shows a compatibility extension pattern in the Python monorepo: a stable exported name is preserved while the implementation is dynamically resolved through create_importer and DEPRECATED_LOOKUP. This is useful as a code-level analogy for middleware packages that need stable public contracts while implementation ownership or package boundaries evolve.

Sources: libs/langchain/langchain_classic/document_loaders/parsers/language/javascript.py

Core Primitives

A customized agent harness normally has three layers. The first layer is the agent-facing contract: the model receives messages, may call tools, and eventually returns a response. The second layer is the configured harness: instructions, domain tools, memory sources such as AGENTS.md, skills directories, filesystem backends, path permissions, subagents, context schema, and structured response settings. The third layer is middleware, which observes or changes model calls, tool calls, context, and lifecycle behavior without forcing every individual tool or prompt to reimplement the same policy.

Built-in middleware covers common production needs. The official JavaScript docs list provider-agnostic middleware for summarization, human-in-the-loop approval, model call limits, tool call limits, model fallback, PII detection, to-do list planning, LLM-based tool selection, tool retry, model retry, LLM tool emulation, context editing, provider tool search, filesystem support, and subagents. Provider-specific middleware can also exist; the middleware integrations page calls out Anthropic prompt caching as an official provider integration and describes community middleware as separately maintained open-source packages.

Connections, MCP, and OpenAPI fit into this mental model as external capability surfaces rather than local middleware. A local tool is code you register directly with the agent. A connection authorizes or describes access to an external service. MCP exposes tools and resources through a protocol server. OpenAPI describes HTTP APIs that can be turned into callable capabilities. Middleware can govern how these capabilities are selected, approved, retried, or constrained, but the connection or protocol layer is still responsible for exposing the capability itself.

Customization Flow

A practical customization flow begins by defining the agent’s goal and stable domain capabilities before adding middleware. Start with the model and instructions, then add local tools, memory files, skills, and any external capability surfaces. Only after those pieces are clear should you add middleware for cross-cutting behavior. This sequencing keeps business functionality separate from operational policy: the search tool still searches, the filesystem still stores context, and middleware decides whether to summarize, approve, retry, limit, or redact during the run.

import { createDeepAgent } from "deepagents";
 
const agent = await createDeepAgent({
  model: "anthropic:claude-sonnet-4-6",
  systemPrompt: "You are a helpful assistant.",
  tools: [search, fetchUrl],
  memory: ["./AGENTS.md"],
  skills: ["./skills/"],
  middleware: [customSafetyMiddleware],
  interruptOn: { fetchUrl: true },
  contextSchema: RuntimeContextSchema,
});

In that shape, middleware extends the default Deep Agents stack rather than replacing the entire harness. interruptOn is a targeted human-approval control, while middleware can enforce broader policy across tool calls or model calls. contextSchema defines per-run runtime context such as user IDs, API keys, or feature flags; middleware can then read that context to choose stricter limits for one tenant, enable a feature for another, or route sensitive operations through approval.

System-to-Code Mapping

The requested repository source file is not an agent middleware implementation, but it demonstrates a pattern that matters for customization APIs: keep a stable public name while delegating implementation resolution to a controlled lookup path. The module defines DEPRECATED_LOOKUP for JavaScriptSegmenter, creates _import_attribute with create_importer, and routes unknown attribute access through __getattr__. It also declares __all__ so importers know which public symbol is intentionally exported.

Sources: libs/langchain/langchain_classic/document_loaders/parsers/language/javascript.py

That pattern is relevant when designing or consuming middleware packages because middleware integrations also need stable public entry points. A package can evolve internally, move implementation modules, or emit deprecation warnings while preserving the name applications import. The source file centralizes lookup behavior instead of scattering optional-import and deprecation logic across callers. For middleware authors, the equivalent principle is to expose a small, documented factory or middleware object and keep migration behavior inside the package boundary.

API Components and Extension Contracts

At the application level, the main extension points are the agent creation parameters and the middleware list. The documented Deep Agents parameters distinguish tools, which are callable domain capabilities, from skills, which are on-demand knowledge or workflows, and from middleware, which appends runtime behavior to the default stack. Other parameters such as backend, permissions, subagents, interruptOn, responseFormat, and contextSchema give middleware concrete state and policy surfaces to work with.

At the package level, the visible Python compatibility module exposes three concrete API mechanics. DEPRECATED_LOOKUP maps a public attribute name to the module that now owns it. _import_attribute is the dynamic importer configured with that lookup table. __getattr__(name: str) -> Any forwards runtime attribute access to the importer, and __all__ lists JavaScriptSegmenter as the public export. These are not middleware hooks, but they are a source-backed example of how LangChain packages preserve public contracts while delegating behavior.

Sources: libs/langchain/langchain_classic/document_loaders/parsers/language/javascript.py

Implementation Details and Next Steps

When adding custom middleware, keep it narrow and observable. A summarization middleware should focus on context compression, not tool authorization. A retry middleware should focus on retry policy, not prompt editing. A human-in-the-loop middleware should expose clear pause and resume points instead of burying approval inside a tool. This separation makes traces easier to read in LangSmith and makes it safer to combine built-in middleware with community packages or provider-specific integrations such as prompt caching.

Before writing a new middleware package, check whether the built-in list already covers the behavior. Use summarization for token pressure, call limits for cost control, fallback for model availability, PII detection for privacy policy, and tool retry for transient integration failures. If you need a domain-specific policy, define the stable public API first, then decide how it composes with the default Deep Agents stack and how it should behave around MCP tools, OpenAPI-derived tools, and connection-backed capabilities.

Next, read middleware-overview for the execution model, agent-configuration-instructions for the surrounding harness settings, human-in-the-loop for approval design, guardrails for validation and intervention patterns, and multi-agent-subagents if your middleware changes delegation behavior.