# Spring AI Alibaba Hooks And Interceptors Reference

This reference is distilled from the official Spring AI Alibaba Hooks and Interceptors tutorial and its linked example source. Use it when implementing, reviewing, or explaining customization points around `ReactAgent`.

## Sources

- Tutorial: https://java2ai.com/docs/frameworks/agent-framework/tutorials/hooks/
- Example code: https://github.com/alibaba/spring-ai-alibaba/blob/main/examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/tutorials/HooksExample.java

The tutorial page was last updated on January 19, 2026 according to the site footer.

## Mental Model

- Hooks expose agent lifecycle checkpoints around agent execution and model calls.
- Interceptors wrap model or tool calls like middleware.
- Use hooks when you need lifecycle awareness or interruptible control flow.
- Use interceptors when you need request/response wrapping, mutation, logging, retry, fallback, or caching.

## Built-In Implementations

### Built-in hooks

- `SummarizationHook`
  Use for long-running conversations approaching token limits.
  Key settings: `model`, `maxTokensBeforeSummary`, `messagesToKeep`.

- `HumanInTheLoopHook`
  Use when selected tools require approval or editing before execution.
  Important: pair it with a saver or checkpointer such as `RedisSaver` so paused execution can resume.

- `ModelCallLimitHook`
  Use to cap model-call loops and enforce budget limits.

- `PIIDetectionHook`
  Use to detect or redact sensitive user data in input or output.
  Common settings include `piiType`, `strategy`, and `applyToInput`.

### Built-in interceptors

- `ToolRetryInterceptor`
  Use to retry failed tool calls with configurable retry count and failure behavior.

- `TodoListInterceptor`
  Use to force a planning step before tool execution for multi-step tasks.

- `ToolSelectionInterceptor`
  Use when multiple tools overlap and an LLM should choose the best one for the current request.

- `ToolEmulatorInterceptor`
  Use to simulate tool outputs with an LLM during demos, testing, or low-risk development.

- `ContextEditingInterceptor`
  Use to trim, inject, or rewrite context before it reaches the model.

## Custom Extension Points

### `MessagesModelHook`

Use this first for message-only transformations. It works directly on `List<Message>` and returns `AgentCommand`, which is simpler than editing `OverAllState`.

Typical fit:

- message trimming
- system message injection
- message filtering
- lightweight summary or context shaping

Useful details:

- `UpdatePolicy.REPLACE` replaces the full message list
- `UpdatePolicy.APPEND` appends new messages
- `canJumpTo()` plus `JumpTo.end` can short-circuit execution before the model call

### `ModelHook`

Use when hook logic must read or update more than just messages.

Typical fit:

- stateful counters
- custom status flags
- global decision logic based on `OverAllState`
- coordinated before/after behavior around the model call

Useful details:

- methods typically return `CompletableFuture<Map<String, Object>>`
- use `ReplaceAllWith` when replacing message collections
- use `RemoveByHash` carefully when deleting messages while preserving message order

### `AgentHook`

Use for logic that belongs to the entire agent run rather than a single model call.

Typical fit:

- start and stop telemetry
- resource setup and cleanup
- total execution timing
- run-level initialization

Common hook positions:

- `HookPosition.BEFORE_AGENT`
- `HookPosition.AFTER_AGENT`

### `ModelInterceptor`

Use to wrap the model request/response path.

Typical fit:

- logging and performance timing
- moderation on input and output
- request rewriting
- dynamic tool injection or tool filtering

Useful details:

- `ModelRequest.builder(request)` can clone and mutate the incoming request
- the tutorial explicitly shows dynamic tool control through `dynamicToolCallbacks(...)` and `tools(...)`

### `ToolInterceptor`

Use to wrap actual tool execution.

Typical fit:

- retries
- failure translation
- caching
- per-tool performance metrics
- graceful fallback messages

Useful details:

- the call boundary is `handler.call(request)`
- interceptors can return a synthetic `ToolCallResponse` instead of propagating an exception

## Decision Guide

- Need resumable human approval: `HumanInTheLoopHook`
- Need automatic history compaction: `SummarizationHook`
- Need loop limits or stateful guards: `ModelCallLimitHook` or custom `ModelHook`
- Need request/response moderation: `ModelInterceptor`
- Need dynamic tool availability: `ModelInterceptor`
- Need tool retries or tool cache: `ToolInterceptor`
- Need to modify only messages: `MessagesModelHook`
- Need run-level timing or setup/teardown: `AgentHook`

## Execution Order

When several extensions are registered:

1. `beforeAgent` hooks run in registration order.
2. `beforeModel` hooks run in registration order.
3. model interceptors wrap the model call in registration order.
4. `afterModel` hooks run in reverse order.
5. tool interceptors wrap tool execution in registration order when tools are called.
6. `afterAgent` hooks run in reverse order.

Practical consequence:

- put broad, outer concerns first
- keep related before/after state in the same hook class
- avoid relying on accidental ordering between unrelated concerns

## RunnableConfig Context

`RunnableConfig.context()` is the shared per-run scratchpad shown in the tutorial for:

- counting model calls
- storing start timestamps
- accumulating total duration
- enforcing limits across several hook invocations

Guidelines:

- treat it as per-run transient state, not durable storage
- choose stable key names such as `__total_model_time__`
- manage types carefully because stored values are generic objects

## Example Patterns To Reuse

- content moderation with `ModelInterceptor`
- model and tool performance timing with interceptors
- tool result caching with `ToolInterceptor`
- early exit from `MessagesModelHook` with `JumpTo.end`
- call counting and limit enforcement with `ModelHook` plus `RunnableConfig.context()`

## Minimal Builder Example

```java
ReactAgent agent = ReactAgent.builder()
    .name("resilient_agent")
    .model(chatModel)
    .tools(searchTool, databaseTool)
    .hooks(ModelCallLimitHook.builder().runLimit(5).build())
    .interceptors(ToolRetryInterceptor.builder().maxRetries(2).build())
    .build();
```
