Whetstone.
Production-Ready AgentDynamic agents, and the catalogue
Module 3, Lesson 516 min

Dynamic agents, and the catalogue

Dynamic means the agent reconfigures itself per turn rather than being fixed at construction. There is no parameter for it, which is the point: wrap_model_call already holds the call, so it is the general mechanism, and every built-in that switches models or filters tools is built on it.

The shape

Rewriting the request on its way out
@wrap_model_call
def escalate_on_complexity(request, handler):
    if looks_hard(request):
        request = request.override(model=BIG_MODEL)
    return handler(request)

The hook receives the request, can rewrite the model, the prompt or the tool list, and hands it on. Because it wraps rather than observes, it can also inspect what came back and call the handler again, which is how fallbacks and retries work.

The sixteen built-ins

Built-in middlewares, from the Python package exports
SummarizationMiddleware        HumanInTheLoopMiddleware
ModelCallLimitMiddleware       ToolCallLimitMiddleware
ModelFallbackMiddleware        PIIMiddleware
TodoListMiddleware             LLMToolSelectorMiddleware
ToolErrorMiddleware            ToolRetryMiddleware
ModelRetryMiddleware           LLMToolEmulator
ContextEditingMiddleware       ProviderToolSearchMiddleware
ShellToolMiddleware            FilesystemFileSearchMiddleware

Grouped by what they protect you from, sixteen stops being sixteen things:

  • Context pressure: SummarizationMiddleware, ContextEditingMiddleware.
  • Runaway cost: ModelCallLimitMiddleware, ToolCallLimitMiddleware.
  • Flaky infrastructure: ModelFallbackMiddleware, ModelRetryMiddleware, ToolRetryMiddleware, ToolErrorMiddleware.
  • Safety and oversight: HumanInTheLoopMiddleware, PIIMiddleware.
  • Tool surface shaping: LLMToolSelectorMiddleware, LLMToolEmulator, ProviderToolSearchMiddleware, ShellToolMiddleware, FilesystemFileSearchMiddleware.
  • Planning: TodoListMiddleware.

Two are easy to misread from the name. LLMToolEmulator fakes a tool rather than calling it, which makes it a testing instrument for exercising an agent’s reasoning before a tool exists. ProviderToolSearchMiddleware deals with tools the model provider hosts, not ones you registered.

Defaults worth having exactly

A few more that are hard to search for under pressure. Both retry middlewares default to max_retries=2, backoff_factor=2.0, initial_delay=1.0, max_delay=60.0, jitter on, and on_failure="continue". PIIMiddleware defaults to strategy="redact" with apply_to_input=True but apply_to_output and apply_to_tool_results both off, which is a narrower default guard than most people assume.

The TypeScript gap, stated once

ShellToolMiddleware and FilesystemFileSearchMiddleware are genuinely absent from TypeScript. Everything else in the list exists in both, spelled camelCase.

That plus the authoring difference from lesson 1 is the whole gap: PascalCase classes with decorators in Python, camelCase factories in TypeScript, two missing entries. The hooks, the ordering rule, the jump targets and every threshold are identical.

Practice

Try it yourself

Quiz

Two limits, two different defaults

ModelCallLimitMiddleware and ToolCallLimitMiddleware look like siblings and their exit_behavior defaults differ.

  1. ABoth default to end, since a limit means the run is over
  2. BBoth default to error, since exceeding a cap is a failure
  3. CModelCallLimit defaults to end; ToolCallLimit defaults to continue
  4. DModelCallLimit defaults to continue; ToolCallLimit defaults to end
Show answer

Correct answer: C — ModelCallLimit defaults to end; ToolCallLimit defaults to continue

ModelCallLimitMiddleware defaults to end and ToolCallLimitMiddleware defaults to continue. The asymmetry is deliberate rather than an oversight: hitting a model-call ceiling means the agent cannot think any further, so ending is the only honest outcome, whereas a tool being exhausted still leaves the agent able to reason and answer without it. Assuming parity is the natural error and it is why this makes a good question.

Quiz

Choosing from the catalogue

An agent is looping, calling the same tool repeatedly, and burning budget. You want a hard stop rather than a nudge.

  1. AToolCallLimitMiddleware, because it caps the number of tool calls outright
  2. BSummarizationMiddleware, because the loop is a context problem
  3. CLLMToolSelectorMiddleware, because it narrows what the model can pick
  4. DModelFallbackMiddleware, because a different model may not loop
Show answer

Correct answer: A — ToolCallLimitMiddleware, because it caps the number of tool calls outright

ToolCallLimitMiddleware is the hard stop. LLMToolSelectorMiddleware is the tempting one because narrowing the tool list feels like it addresses the cause, but it shapes what the model chooses rather than bounding how many times it may choose, so a determined loop still runs forever. When the requirement says hard stop, the answer is a limit middleware.

Recall

How an agent becomes dynamic

Dynamic behaviour is not a create_agent parameter, which is the structural point of this lesson.

How do you change the model, the system prompt, or the available tools at runtime rather than at construction?

Reveal answer

Through a wrap_model_call middleware. It receives the request on its way to the model, so it can rewrite the model, the prompt or the tool list per turn based on state or context, then hand the modified request to the handler. This is why there is no dynamic_model or dynamic_prompt parameter on create_agent: the wrap hook already holds the call and is the general mechanism, and the built-in middlewares that switch models or filter tools are all built on exactly this hook.

Quiz

What PIIMiddleware guards by default

PIIMiddleware is added with only a pii_type and no other arguments. Which direction is protected?

  1. AInput, output and tool results, all guarded on by default
  2. BNothing until you set a strategy, which has no default
  3. COutput only, since that is what reaches the user
  4. DInput only; output and tool results are off by default
Show answer

Correct answer: D — Input only; output and tool results are off by default

apply_to_input defaults to True while apply_to_output and apply_to_tool_results both default to False, and the default strategy is redact. The third option is the intuitive one, because leaking is something you picture happening on the way out; the default guards what you send to the provider, which is a different threat model, and tool results being unguarded is the gap most likely to surprise you.

Quiz

Absent from TypeScript

Three of these ship in both languages. One is genuinely missing from the TypeScript package.

  1. ASummarizationMiddleware
  2. BHumanInTheLoopMiddleware
  3. CModelCallLimitMiddleware
  4. DShellToolMiddleware
Show answer

Correct answer: D — ShellToolMiddleware

ShellToolMiddleware is genuinely absent from TypeScript, alongside FilesystemFileSearchMiddleware. The other three are core and exist in both. The trap is that shell execution feels like the most universal capability imaginable, so its absence reads as impossible, which is precisely why it is worth knowing rather than guessing.

Check

Sort the catalogue by job

In a scratch file, group the sixteen built-ins under four headings - context, cost, reliability, and tool surface - without looking back at the list.

You should see

You placed at least twelve of the sixteen, and you can say in one sentence what each group is protecting you from.

Sign in to track your progress →