Summary: When using ChatClient.prompt().toolContext(Map), the data passed into toolContext(...) is not propagated into the Reactor Context during tool execution. As a result, values such as SecurityContext (e.g. from ReactiveSecurityContextHolder) are not accessible when tool callbacks are executed, especially when they are offloaded to another thread (like boundedElastic). 🧑‍💻 Use Case:

I'm building a Spring AI application where:

I secure the application using Spring Security (OAuth2 resource server),

I capture the authenticated user's SecurityContext using ReactiveSecurityContextHolder.getContext(),

I pass that into .toolContext(...) when starting a chatClient.prompt() request,

Inside my tool callback (e.g., to call a backend API using WebClient), I expect that ReactiveSecurityContextHolder.getContext() would work,

However, it fails because tool execution happens on a separate thread, and .toolContext(...) is not injected into the Reactor context.

🔍 Expected Behavior:

chatClient.prompt()
    .user("some prompt")
    .toolContext(Map.of("securityContext", ctx))  // injected at prompt start
    ...

➡️ When the tool is later executed, the value "securityContext" should be available via:

ReactiveSecurityContextHolder.getContext()

Or at least be restored into the Reactor Context, so a WebClient ExchangeFilterFunction that depends on the SecurityContext can access it.

🚫 Actual Behavior:

The tool executes on another thread (e.g. boundedElastic),

The Reactor Context is empty (no security context),

ReactiveSecurityContextHolder.getContext() returns Mono.empty(),

My WebClient authentication filter fails to attach the Authorization header.