Skip to content

Add temporal-spring-ai module#2829

Open
donald-pinckney wants to merge 40 commits intomasterfrom
d/20260406-164203
Open

Add temporal-spring-ai module#2829
donald-pinckney wants to merge 40 commits intomasterfrom
d/20260406-164203

Conversation

@donald-pinckney
Copy link
Copy Markdown
Contributor

@donald-pinckney donald-pinckney commented Apr 6, 2026

Summary

Adds temporal-spring-ai module that integrates Spring AI with Temporal workflows, making AI model calls, tool execution, vector store operations, embeddings, and MCP tool calls durable Temporal primitives.

Design

The core problem

Spring AI's ChatModel.call() does two things in one method: it calls the LLM and executes any tools the LLM requests, looping until the model returns a final text response. In a Temporal workflow, these need to happen in different execution contexts:

  • LLM calls must run as activities (network I/O, retryable, durable)
  • Tool execution must run in the workflow (so tools can themselves be activities, sideEffects, or other Temporal primitives)

How ActivityChatModel solves this

ActivityChatModel implements Spring AI's ChatModel interface with the same recursive call()internalCall() pattern used by OpenAiChatModel and other Spring AI implementations. The difference is what happens inside:

  1. Model call: converts the prompt to serializable ChatModelTypes, sends it to ChatModelActivity (a Temporal activity), gets back the response
  2. Tool check: if the model requested tool calls, ToolCallingManager.executeToolCalls() runs the tools — these are our ActivityToolCallback, SideEffectToolCallback, etc. which execute as Temporal activities or Workflow.sideEffect()
  3. Loop: sends tool results back to the model (recursive internalCall()) until the model returns a final response

The activity-side ChatModelActivityImpl sets internalToolExecutionEnabled(false) so the actual LLM provider (OpenAI, Anthropic, etc.) only returns tool call requests without executing them — execution happens back in the workflow.

Tool classification

Tools passed to TemporalChatClient.builder().defaultTools() are automatically classified:

Type Detection Execution Use case
Activity stub instanceof ActivityInvocationHandler Temporal activity Network I/O, external APIs
Local activity stub instanceof LocalActivityInvocationHandler Temporal local activity Fast local operations
@DeterministicTool Annotation Direct call in workflow Pure functions (string ops, math)
@SideEffectTool Annotation Workflow.sideEffect() Timestamps, UUIDs, random values
Nexus service stub instanceof NexusServiceInvocationHandler Nexus operation Cross-namespace calls

Conditional auto-configuration

Optional integrations only load when their dependencies are on the classpath, matching Spring AI's own approach (where RAG, MCP, etc. are separate opt-in starters):

Auto-config class Condition Plugin Registers
SpringAiTemporalAutoConfiguration ChatModel + Worker SpringAiPlugin ChatModelActivity, ExecuteToolLocalActivity
SpringAiVectorStoreAutoConfiguration VectorStore bean VectorStorePlugin VectorStoreActivity
SpringAiEmbeddingAutoConfiguration EmbeddingModel bean EmbeddingModelPlugin EmbeddingModelActivity
SpringAiMcpAutoConfiguration McpSyncClient class McpPlugin McpClientActivity

Related

Test plan

  • Unit tests: type conversion — ChatModelActivityImplTest (10 tests)
  • Unit tests: tool detection — TemporalToolUtilTest (22 tests)
  • Unit tests: plugin registration — SpringAiPluginTest (9 tests)
  • Replay tests: workflow determinism via WorkflowReplayer — WorkflowDeterminismTest (2 tests)
  • Verify all 5 samples boot against Temporal dev server

🤖 Generated with Claude Code

Copy link
Copy Markdown
Contributor Author

@donald-pinckney donald-pinckney left a comment

Choose a reason for hiding this comment

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

Self-review: temporal-spring-ai plugin

What's done well

Determinism architecture is sound. The core design — routing all non-deterministic operations (LLM calls, vector store ops, embeddings, MCP tools) through Temporal activities — is exactly right. The three-tier tool system (@DeterministicTool for pure functions, @SideEffectTool for cheap non-determinism, activity stubs for durable I/O) maps cleanly onto Temporal's primitives.

Tool execution stays in the workflow. ChatModelActivityImpl sets internalToolExecutionEnabled(false) and only passes tool definitions to the model. The actual tool dispatch happens back in the workflow via ToolCallingManager, which means tool calls respect their Temporal wrapping (activity, sideEffect, etc.).

SideEffectToolCallback correctly wraps each call in Workflow.sideEffect(), recording the result in history on first execution and replaying the stored value.

ActivityChatModel.call() handles the tool loop correctly — it recursively calls itself when the model requests tools that don't returnDirect, maintaining proper conversation history.

Issues flagged inline below

*/
public class LocalActivityToolCallbackWrapper implements ToolCallback {

private static final Map<String, ToolCallback> CALLBACK_REGISTRY = new ConcurrentHashMap<>();
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Medium severity — static registry lifecycle risk.

This static ConcurrentHashMap holds live ToolCallback references. Callbacks are removed in a finally block after the local activity completes, but if the workflow is evicted from the worker cache mid-execution (before finally runs), callbacks leak. Worth documenting or adding periodic cleanup.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done — added javadoc documenting the eviction leak risk and pointing to getRegisteredCallbackCount() for monitoring.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Would a bounded size or TTL-based eviction strategy for the hash map make more sense, as opposed to just giving users a method to monitor the count? Even some (imo very high) limit like 10,000? Or maybe log a warning in case a user hits some huge number like that? Wdyt?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

most likely not going ahead with SandboxingAdvisor, which would also kill this.

Copy link
Copy Markdown
Contributor Author

@donald-pinckney donald-pinckney left a comment

Choose a reason for hiding this comment

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

Found an additional bug during testing.

@donald-pinckney donald-pinckney force-pushed the d/20260406-164203 branch 2 times, most recently from 6b01988 to 4fd80ec Compare April 7, 2026 20:12
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Obviously will delete this prior to merging ;)

donald-pinckney and others added 15 commits April 8, 2026 11:49
Adds a new module that integrates Spring AI with Temporal workflows,
enabling durable AI model calls, vector store operations, embeddings,
and MCP tool execution as Temporal activities.

Key components:
- ActivityChatModel: ChatModel implementation backed by activities
- TemporalChatClient: Temporal-aware ChatClient with tool detection
- SpringAiPlugin: Auto-registers Spring AI activities with workers
- Tool system: @DeterministicTool, @SideEffectTool, activity-backed tools
- MCP integration: ActivityMcpClient for durable MCP tool calls

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
T9: Add javadoc to LocalActivityToolCallbackWrapper explaining the leak
risk when workflows are evicted from worker cache mid-execution.

T11: Override stream() in ActivityChatModel to throw
UnsupportedOperationException with a clear message, since streaming
through Temporal activities is not supported.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
T1: ChatModelActivityImplTest (10 tests) - type conversion between
    ChatModelTypes and Spring AI types, multi-model resolution,
    tool definition passthrough, model options mapping.

T2: TemporalToolUtilTest (22 tests) - tool detection and conversion
    for @DeterministicTool, @SideEffectTool, stub type detection,
    error cases for unknown/null types.

T3: WorkflowDeterminismTest (2 tests) - verifies workflows using
    ActivityChatModel with tools complete without non-determinism
    errors in the Temporal test environment.

T4: SpringAiPluginTest (10 tests) - plugin registration with various
    bean combinations, multi-model support, default model resolution.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
T5: Replace UUID.randomUUID() with Workflow.randomUUID() in
LocalActivityToolCallbackWrapper to ensure deterministic replay.

T7: Convert recursive tool call loop in ActivityChatModel.call() to
iterative loop with MAX_TOOL_CALL_ITERATIONS (10) limit to prevent
infinite recursion from misbehaving models.

T14: Fix NPE when ChatResponse metadata is null by only calling
.metadata() on the builder when metadata is non-null.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Split the monolithic SpringAiPlugin into one core plugin + three
optional plugins, each with its own @ConditionalOnClass-guarded
auto-configuration:

- SpringAiPlugin: core chat + ExecuteToolLocalActivity (always)
- VectorStorePlugin: VectorStore activity (when spring-ai-rag present)
- EmbeddingModelPlugin: EmbeddingModel activity (when spring-ai-rag present)
- McpPlugin: MCP activity (when spring-ai-mcp present)

This fixes ClassNotFoundException when optional deps aren't on the
runtime classpath. compileOnly scopes now work correctly because
Spring skips loading the conditional classes entirely when the
@ConditionalOnClass check fails.

Also resolves T10 (unnecessary MCP reflection) — McpPlugin directly
references McpClientActivityImpl instead of using Class.forName().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use direct instanceof checks against the SDK's internal invocation
handler classes instead of string-matching on class names. Since the
plugin lives in the SDK repo, any handler rename would break
compilation rather than silently failing at runtime.

ChildWorkflowInvocationHandler is package-private so it still uses a
class name check (endsWith instead of contains for precision).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously the tests just ran workflows forward. Now they capture
the event history after execution and replay it with
WorkflowReplayer.replayWorkflowExecution(), which will throw
NonDeterministicException if the workflow code generates different
commands on replay.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove MAX_TOOL_CALL_ITERATIONS and the iterative loop. Use recursive
internalCall() matching Spring AI's OpenAiChatModel pattern. Temporal's
activity timeouts and workflow execution timeout already bound runaway
tool loops.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* <p>This plugin is conditionally created by auto-configuration when Spring AI's {@link
* VectorStore} is on the classpath and a VectorStore bean is available.
*/
public class VectorStorePlugin extends SimplePlugin {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is the primary reason for these being separate plugins because of spring configuration interaction? Otherwise I would think it makes more sense to have one with configurations.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

it's not just Spring config interaction, it's about not forcing transitive dependencies on users. A single plugin would either need all deps as implementation or use reflection/Object types to avoid class references (fragile/yucky). Given that normal (non-Temporal) Spring AI already ships things as separate artifacts, this seemed like the most natural spring-ai-native design.

Other option is to have implementation dependencies on Spring AI's various artifacts (for MCP, RAG, etc.), which is simpler but aligns less with Spring from what I've seen. Others could weigh in here though.

To be clear, you can't just have compileOnly deps on those optional dependencies, because Spring needs to load and introspect the plugin class at startup, so any field types that don't exist at runtime cause a ClassNotFoundException.

*
* @see ExecuteToolLocalActivity
*/
public class LocalActivityToolCallbackWrapper implements ToolCallback {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is there a good reason we are making local activities so primary?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

would be resolved following other discussion below

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new temporal-spring-ai module that integrates Spring AI with Temporal by making chat model calls, tool execution, embeddings, vector store operations, and MCP tool calls durable Temporal primitives.

Changes:

  • Introduces ActivityChatModel + ChatModelActivity to run LLM calls as activities while executing tools deterministically in workflows.
  • Adds tool conversion utilities (activity/local activity/Nexus stubs, @DeterministicTool, @SideEffectTool) plus a sandboxing advisor for unsafe tools.
  • Adds Spring Boot auto-configuration + worker plugins for ChatModel, VectorStore, EmbeddingModel, and MCP, along with unit + replay determinism tests.

Reviewed changes

Copilot reviewed 45 out of 45 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
temporal-spring-ai/src/main/java/io/temporal/springai/model/ActivityChatModel.java Workflow-safe ChatModel that delegates model calls to a Temporal activity and loops for tool execution.
temporal-spring-ai/src/main/java/io/temporal/springai/activity/ChatModelActivity.java Activity interface for invoking Spring AI chat models durably.
temporal-spring-ai/src/main/java/io/temporal/springai/activity/ChatModelActivityImpl.java Activity implementation converting between serializable types and Spring AI prompt/response types.
temporal-spring-ai/src/main/java/io/temporal/springai/model/ChatModelTypes.java Serializable request/response/message/tool types for activity communication.
temporal-spring-ai/src/main/java/io/temporal/springai/chat/TemporalChatClient.java ChatClient builder that auto-converts Temporal primitives into Spring AI tool callbacks.
temporal-spring-ai/src/main/java/io/temporal/springai/util/TemporalToolUtil.java Converts provided tool objects into appropriate ToolCallback implementations.
temporal-spring-ai/src/main/java/io/temporal/springai/util/TemporalStubUtil.java Detects Temporal stub types via proxy invocation handlers.
temporal-spring-ai/src/main/java/io/temporal/springai/tool/DeterministicTool.java Annotation marking tools safe to execute directly in workflows.
temporal-spring-ai/src/main/java/io/temporal/springai/tool/SideEffectTool.java Annotation marking tools to be executed via Workflow.sideEffect().
temporal-spring-ai/src/main/java/io/temporal/springai/tool/SideEffectToolCallback.java Wrapper executing tool callbacks inside Workflow.sideEffect() for determinism.
temporal-spring-ai/src/main/java/io/temporal/springai/tool/ActivityToolUtil.java Extracts Spring AI tool definitions from activity interfaces/stubs.
temporal-spring-ai/src/main/java/io/temporal/springai/tool/ActivityToolCallback.java Marker wrapper for callbacks backed by Temporal activity stubs.
temporal-spring-ai/src/main/java/io/temporal/springai/tool/NexusToolUtil.java Extracts Spring AI tool definitions from Nexus service stubs.
temporal-spring-ai/src/main/java/io/temporal/springai/tool/NexusToolCallback.java Marker wrapper for callbacks backed by Nexus service stubs.
temporal-spring-ai/src/main/java/io/temporal/springai/tool/ExecuteToolLocalActivity.java Local-activity interface used to execute otherwise-unsafe tool callbacks deterministically.
temporal-spring-ai/src/main/java/io/temporal/springai/tool/ExecuteToolLocalActivityImpl.java Local-activity implementation that looks up and executes registered callbacks.
temporal-spring-ai/src/main/java/io/temporal/springai/tool/LocalActivityToolCallbackWrapper.java Wraps arbitrary callbacks into a local activity using a static callback registry.
temporal-spring-ai/src/main/java/io/temporal/springai/advisor/SandboxingAdvisor.java Advisor that wraps non-Temporal-safe tool callbacks in local activities with warnings.
temporal-spring-ai/src/main/java/io/temporal/springai/plugin/SpringAiPlugin.java Core worker plugin registering ChatModel + tool-execution local activity.
temporal-spring-ai/src/main/java/io/temporal/springai/plugin/VectorStorePlugin.java Optional worker plugin registering VectorStore activity.
temporal-spring-ai/src/main/java/io/temporal/springai/plugin/EmbeddingModelPlugin.java Optional worker plugin registering EmbeddingModel activity.
temporal-spring-ai/src/main/java/io/temporal/springai/plugin/McpPlugin.java Optional worker plugin registering MCP client activity (supports deferred registration).
temporal-spring-ai/src/main/java/io/temporal/springai/activity/VectorStoreActivity.java Activity interface for vector store operations.
temporal-spring-ai/src/main/java/io/temporal/springai/activity/VectorStoreActivityImpl.java Vector store activity implementation + type conversions.
temporal-spring-ai/src/main/java/io/temporal/springai/model/VectorStoreTypes.java Serializable types for VectorStore activity calls.
temporal-spring-ai/src/main/java/io/temporal/springai/activity/EmbeddingModelActivity.java Activity interface for embedding operations.
temporal-spring-ai/src/main/java/io/temporal/springai/activity/EmbeddingModelActivityImpl.java Embedding activity implementation + conversions.
temporal-spring-ai/src/main/java/io/temporal/springai/model/EmbeddingModelTypes.java Serializable types for EmbeddingModel activity calls.
temporal-spring-ai/src/main/java/io/temporal/springai/mcp/McpClientActivity.java Activity interface for MCP client operations.
temporal-spring-ai/src/main/java/io/temporal/springai/mcp/McpClientActivityImpl.java MCP activity implementation delegating to Spring AI MCP sync clients.
temporal-spring-ai/src/main/java/io/temporal/springai/mcp/ActivityMcpClient.java Workflow-side wrapper around MCP activities (cached lookups).
temporal-spring-ai/src/main/java/io/temporal/springai/mcp/McpToolCallback.java ToolCallback implementation that calls MCP tools through activities.
temporal-spring-ai/src/main/java/io/temporal/springai/autoconfigure/SpringAiTemporalAutoConfiguration.java Core Spring Boot auto-config producing the main Spring AI plugin bean.
temporal-spring-ai/src/main/java/io/temporal/springai/autoconfigure/SpringAiVectorStoreAutoConfiguration.java Conditional auto-config for vector store plugin.
temporal-spring-ai/src/main/java/io/temporal/springai/autoconfigure/SpringAiEmbeddingAutoConfiguration.java Conditional auto-config for embedding plugin.
temporal-spring-ai/src/main/java/io/temporal/springai/autoconfigure/SpringAiMcpAutoConfiguration.java Conditional auto-config for MCP plugin.
temporal-spring-ai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports Registers the module’s Spring Boot auto-configurations.
temporal-spring-ai/src/test/java/io/temporal/springai/activity/ChatModelActivityImplTest.java Unit tests for serializable type conversions and tool-call preservation.
temporal-spring-ai/src/test/java/io/temporal/springai/util/TemporalToolUtilTest.java Unit tests for tool detection/conversion and stub detection utilities.
temporal-spring-ai/src/test/java/io/temporal/springai/plugin/SpringAiPluginTest.java Unit tests for plugin registration behavior.
temporal-spring-ai/src/test/java/io/temporal/springai/WorkflowDeterminismTest.java Replay test intended to validate determinism via captured history replay.
temporal-spring-ai/build.gradle New module build with Java 17 target and Spring AI dependencies.
temporal-bom/build.gradle Adds temporal-spring-ai to the BOM.
settings.gradle Includes the new temporal-spring-ai module in the build.
TASK_QUEUE.json Task tracking artifact for the module work items.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown

@DABH DABH left a comment

Choose a reason for hiding this comment

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

Looks like a ton of progress in the right direction! 🚀 Just a few questions/comments (on top of what others have already pointed out). Take them or leave them :)

description = '''Temporal Java SDK Spring AI Plugin'''

ext {
springAiVersion = '1.1.0'
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Probably clarify in the docs/readme the compatibility matrix for the module? (So - maybe not in this PR - but at least in the forthcoming docs PR)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good call — will add a compatibility matrix to the module README. Tracking as a follow-up task.

  • 🤖 Generated with Claude Code

*/
public class LocalActivityToolCallbackWrapper implements ToolCallback {

private static final Map<String, ToolCallback> CALLBACK_REGISTRY = new ConcurrentHashMap<>();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Would a bounded size or TTL-based eviction strategy for the hash map make more sense, as opposed to just giving users a method to monitor the count? Even some (imo very high) limit like 10,000? Or maybe log a warning in case a user hits some huge number like that? Wdyt?

* @see io.temporal.springai.tool.SideEffectTool
* @see LocalActivityToolCallbackWrapper
*/
public class SandboxingAdvisor implements CallAdvisor {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

If you do go ahead with this route, it looks like tests are lacking at the moment, is there a meaningful way to explicitly test adviseCall?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

most likely not going ahead with SandboxingAdvisor

Comment on lines +102 to +105
if (serverCapabilities == null) {
serverCapabilities = activity.getServerCapabilities();
}
return serverCapabilities;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Does it make sense to cache this in terms of replayability? If the workflow is replayed in some way where the activity's server capabilities have changed, we'd get a stale value? Is that a realistic thing we need to worry about?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This doesn't matter in terms of replayability. In fact, this cache won't be hit (initially) during replay, but Temporal itself will return the cached getServerCapabilities activity result.

When this cache will be hit is during multiple calls to getServerCapabilities throughout the life of the workflow: in those cases we don't do the whole activity and thus MCP remote call, we just return the cached thing.

IMO its pretty common practice to cache server capabilities with MCP, and it would be I think a bit confusing if the MCP client were able to react to new capabilities during the execution of a workflow, but not during later replay of a workflow.

Comment on lines +100 to +101
// Note: ToolContext cannot be passed through the activity, so we ignore it here.
// If context is needed, consider using activity parameters or workflow state.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Should we warn users about this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

also would go away

donald-pinckney and others added 6 commits April 9, 2026 17:24
Replace VectorStorePlugin and EmbeddingModelPlugin subclasses with
SimplePlugin.newBuilder().registerActivitiesImplementations() in the
auto-config classes. These plugins are trivial activity registrations
that don't need custom subclasses when the builder already supports
this.

SpringAiPlugin stays as a subclass (has getter API for chat models).
McpPlugin stays as a subclass (needs SmartInitializingSingleton for
deferred registration).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove all completed/reverted tasks. Add T15 for the tool execution
model change discussed in PR review (run plain tools in workflow
context by default, remove @DeterministicTool and SandboxingAdvisor).
Blocked on finalizing review discussion.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
T16-T21: fixes (NPE guard, error message, multi-ChatModel bug,
         replay test, duplicate MCP names, embedding boxing)
T22-T24: design discussions to have with Don (starter artifact,
         MCP caching, Object vs String)
T25-T27: replies (docs, SandboxingAdvisor tests, ToolContext drop —
         last two likely moot if T15 lands)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
donald-pinckney and others added 19 commits April 10, 2026 08:45
Multiple ChatModel beans without @primary caused startup failure.
ObjectProvider.getIfAvailable() returns null gracefully instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ToolCallingStubChatModel returns a tool call request on first call,
then final text after receiving the tool response. This verifies the
full model -> tool -> model loop replays deterministically.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Avoids boxing 1536+ Double objects per embedding. float[] matches
Spring AI's native embedding representation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
getIfAvailable() still throws NoUniqueBeanDefinitionException when
multiple beans exist without @primary. getIfUnique() returns null
in that case, which is what we want — SpringAiPlugin falls back to
the first entry in the map.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…sses

Use SimplePlugin's builder constructor (super(Builder)) so the classes
are named and user-creatable (new VectorStorePlugin(vectorStore)) while
still using the builder internally rather than overriding initializeWorker.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…lActivity

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Spring AI's Content.getText() returns String. We always cast to String
on both sides. Object type gave false flexibility that would
ClassCastException at runtime.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Edge CI sets edgeDepsTest which compiles temporal-sdk targeting Java 21.
Our module hardcoded Java 17, causing Gradle to reject the dependency
at resolution time. Now uses 21 when edgeDepsTest is set, 17 otherwise.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Docker CI runs Java 11 which can't compile --release 17. Conditionally
exclude the module from settings.gradle and BOM when the build JDK is
below 17.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tools passed to defaultTools() that aren't activity stubs, nexus stubs,
or @SideEffectTool now execute directly in workflow context. The user
is responsible for determinism — they can call activities, sideEffect,
child workflows, etc. from within their tool methods.

Removed:
- @DeterministicTool annotation (plain tools are the default now)
- SandboxingAdvisor (no more automatic wrapping)
- LocalActivityToolCallbackWrapper, ExecuteToolLocalActivity,
  ExecuteToolLocalActivityImpl (only used by SandboxingAdvisor)
- ExecuteToolLocalActivity registration from SpringAiPlugin

This matches how other Temporal AI integrations handle tools.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.

4 participants