Skip to content

feat(deepagents): allow overriding default middleware for deepagent and subagents#204

Open
alvedder wants to merge 1 commit intolangchain-ai:mainfrom
alvedder:alvedder/middleware-override
Open

feat(deepagents): allow overriding default middleware for deepagent and subagents#204
alvedder wants to merge 1 commit intolangchain-ai:mainfrom
alvedder:alvedder/middleware-override

Conversation

@alvedder
Copy link
Contributor

@alvedder alvedder commented Feb 8, 2026

Adds middleware override capability for DeepAgents and subagents, allowing users to replace specific default middleware by name while preserving the middleware execution order.

Motivation

Previously, middleware arrays were merged through simple concatenation ([...defaults, ...custom]), making it difficult to replace or customize default middleware behavior as langchain's createAgent throws an error if detects multiple same-named middleware.

Users who wanted to modify a specific middleware (e.g., filesystem, skills, memory) had to either:

  • Accept the default behavior entirely, or
  • Manually reconstruct the entire middleware chain

This commit introduces a smarter merge strategy that:

  • Allows overriding default middleware by matching the name property
  • Preserves the position of overridden middleware in the chain
  • Appends new custom middleware at the end (as it was previously)
  • Maintains backward compatibility for users who don't override anything

This gives users fine-grained control over agent behavior without requiring deep knowledge of the internal middleware ordering.

Changes

libs/deepagents/src/middleware/utils.ts

Added the mergeMiddleware function that implements the merge strategy:

  • Creates a map of custom middleware by name for efficient lookups
  • Replaces default middleware in-place when a custom override exists
  • Appends non-overriding custom middleware at the end
  • Returns a new array without mutating inputs

Example behavior:

const defaults = [mw1, mw2, mw3];
const custom = [mw2Override, mw4];
const result = mergeMiddleware(defaults, custom);
// Result: [mw1, mw2Override, mw3, mw4]

libs/deepagents/src/middleware/utils.test.ts

Added test coverage for mergeMiddleware with 10 test cases covering:

  • Empty array handling (both, either, or neither empty)
  • In-place replacement of named middleware
  • Appending new custom middleware
  • Mixed override and addition scenarios
  • Full replacement when all defaults are overridden
  • Order preservation for non-overridden middleware
  • Input array immutability

libs/deepagents/src/agent.ts

Updated createDeepAgent function:

  • Replaced array concatenation with mergeMiddleware for the main agent's runtime middleware
  • Applied the same pattern to subagent skills middleware processing
  • Maintained the same middleware execution order for backward compatibility

Before:

const runtimeMiddleware: AgentMiddleware[] = [
  ...builtInMiddleware,
  ...skillsMiddlewareArray,
  ...memoryMiddlewareArray,
  ...(interruptOn ? [humanInTheLoopMiddleware({ interruptOn })] : []),
  ...(customMiddleware as unknown as AgentMiddleware[]),
];

After:

const runtimeMiddleware: AgentMiddleware[] = mergeMiddleware(
  [
    ...builtInMiddleware,
    ...skillsMiddlewareArray,
    ...memoryMiddlewareArray,
    ...(interruptOn ? [humanInTheLoopMiddleware({ interruptOn })] : []),
  ],
  customMiddleware as unknown as AgentMiddleware[],
);

libs/deepagents/src/middleware/subagents.ts

Updated getSubagents function:

  • Replaced array concatenation with mergeMiddleware for subagent middleware
  • Imported the new utility function
  • Consistent behavior with main agent middleware merging

Before:

const middleware = agentParams.middleware
  ? [...defaultSubagentMiddleware, ...agentParams.middleware]
  : [...defaultSubagentMiddleware];

After:

const middleware = agentParams.middleware
  ? mergeMiddleware(defaultSubagentMiddleware, agentParams.middleware)
  : [...defaultSubagentMiddleware];

Copy link
Member

@hntrl hntrl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @alvedder! I think we're fine to merge this, but we're likely not going to document this anywhere. We are of the opinion that customization with a harness like this is actually a footgun. What exactly are you trying to customize with the summarization logic? Anything we can upstream?

@alvedder
Copy link
Contributor Author

alvedder commented Feb 11, 2026

@hntrl thanks for reviewing! basically I have 2 cases:

Case 1

I want to separate backends for filesystem, skills, memory and some subagents so that the deep agent can do whatever it wants on the filesystem and at the same time cannot override its skills and can only controllably override its memory and some subagents also have restrictions, hence I need different backends for filesystem, skills and memory middlewares and some subagents.

While I can in fact specify different backends for the deep agent like that:

createDeepAgent({
      backend: sandboxWorkspace,
      middleware: [
        // works becase `memory` is not set
        createMemoryMiddleware({
          backend: restrictedHostFilesystem, 
          sources: ['memory/AGENTS.md'],
        }),
        // works because `skills` is not set
        createSkillsMiddleware({
          backend: readonlyHostFilesystem,
          sources: ['skills/'],
        }),
      ],
    })

I cannot do the same for subagents because they always inhert parent's backend.

Also I don't think that in the current state it was intended to override middleware like that since, although possible, it changes the execution order of middlewares.

Case 2

Even if one goes with the default "same backend for deep agent and subagents", current createFilesystemMiddleware implementation accepts these params:

interface FilesystemMiddlewareOptions {
  backend?: BackendProtocol | BackendFactory;
  systemPrompt?: string | null;
  customToolDescriptions?: Record<string, string> | null;
  toolTokenLimitBeforeEvict?: number | null;
}

so I can technically override deep agent's filesystem middleware with my custom options, but subagents do not inherit the middleware instance itself, they inherit only backend param while recreating their own filesystem middleware with this one.

Same goes for example for summarizationMiddleware provided to subagents agent.ts#L219-L223, its params are baked and cannot be overriden.

@hntrl
Copy link
Member

hntrl commented Feb 11, 2026

@alvedder We let you compose different backends like this already without you having to override middleware:

createDeepAgent({
  backend: new CompositeBackend(sandboxWorkspace,
    "/memory/": restrictedHostFilesystem,
    "/skills/": readonlyHostFilesystem,
  }),
  memory: ["memory/AGENTS.md"],
  skills: ["skills/"]
});

What's the motivation for overriding the backends in subagents?

@alvedder
Copy link
Contributor Author

@hntrl Thanks for pointing out the option utilizing CompositeBackend

Regarding the motivation for overriding the backends in subagents

Case 1

there might be a QA-like subagent that should be able to read but not write to the fs

Case 2

Some agents including the main agent (deep agent) are provided with a docker container sandbox.
This sandbox has common restrictions and user's workspace mounted to it.
Their prompts say to respect user's workspace, always write files there, do not delete files unless asked, etc.

Some specific subagents are provided with a different, less restricted, sandbox with no workspace mounted.
This container sandbox has some specific CLI tools that are quite heavy to put in the main container and unnecessary since the other agents don't really know about them. We can also base such containers on different images with specific hardware setup e.g. allocate more CPU cores, more RAM or connect GPUs.
Such specific subagents are instructed to use those tools, do isolated work and return their findings.

Example

a data-scientist subagent is assigned with a task "write a python script that does X Y Z, install any dependencies that are needed to accomplish the task, then run the script with data A B C and return the result". This subagent is not restricted to use one specific folder or only available CLI tooling and we don't need to worry about it recklessly overriding user data.

Summary

So my main points are:

  • specific subagents might need a different software setup,
  • different hardware setup,
  • different sandbox security restrictions
  • different workspaces mounted (or no workspaces, just a temporary fs)

@alvedder
Copy link
Contributor Author

alvedder commented Feb 15, 2026

Anyway, even without those advanced scenarios it would be nice to set custom params for filesystem middleware, especially systemPrompt and customToolDescriptions, as well as for summarization middleware on subagents

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants