Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
4f4df9e
Add temporal-spring-ai module for Spring AI integration
donald-pinckney Apr 6, 2026
31bc77e
Document callback registry lifecycle risk and add stream() override
donald-pinckney Apr 6, 2026
079089a
Add tests for temporal-spring-ai (T1-T4)
donald-pinckney Apr 6, 2026
b62adfa
Update TASK_QUEUE.json: T1-T4, T9, T11 completed
donald-pinckney Apr 6, 2026
e538674
Add T14 (NPE bug) to TASK_QUEUE.json
donald-pinckney Apr 6, 2026
c98af78
Fix UUID non-determinism, null metadata NPE, and unbounded tool loop
donald-pinckney Apr 7, 2026
54a5d40
Split SpringAiPlugin into conditional auto-configuration (T6)
donald-pinckney Apr 7, 2026
58804ad
Update TASK_QUEUE.json: T5, T6, T7, T10, T14 completed
donald-pinckney Apr 7, 2026
f4b1028
Update TASK_QUEUE.json: T12 completed
donald-pinckney Apr 7, 2026
e509673
Replace fragile string matching with instanceof in TemporalStubUtil (T8)
donald-pinckney Apr 7, 2026
0cc143e
Update TASK_QUEUE.json: T8 completed
donald-pinckney Apr 7, 2026
b09d2ff
Use WorkflowReplayer for proper replay determinism tests
donald-pinckney Apr 7, 2026
8ba4eb0
Simplify stream() exception message
donald-pinckney Apr 7, 2026
4b7aa19
Revert tool call iteration limit, match Spring AI's recursive pattern
donald-pinckney Apr 7, 2026
969aabd
Fix javadoc reference for publishToMavenLocal
donald-pinckney Apr 8, 2026
615ff92
Use SimplePlugin builder for VectorStore and EmbeddingModel plugins
donald-pinckney Apr 9, 2026
f6d781c
Clean up TASK_QUEUE.json: remove completed tasks, add T15
donald-pinckney Apr 9, 2026
336cc7b
Add link to proposed design for T15
donald-pinckney Apr 9, 2026
6d4d166
Triage Copilot and DABH review comments into TASK_QUEUE
donald-pinckney Apr 9, 2026
32e6f99
T16: Guard assistantMessage.getMedia() against null
donald-pinckney Apr 9, 2026
d9b5002
T17: Include Nexus stubs in unrecognized tool type error message
donald-pinckney Apr 9, 2026
bec50f8
T18: Use ObjectProvider to fix NoUniqueBeanDefinitionException
donald-pinckney Apr 10, 2026
9ebc25b
T19: Make replay test exercise tool calls
donald-pinckney Apr 10, 2026
6c3a107
T20: Handle duplicate MCP client names with clear error
donald-pinckney Apr 10, 2026
4e6003d
T21: Use float[] instead of List<Double> in EmbeddingModelTypes
donald-pinckney Apr 10, 2026
5d19df9
T18 fix: Use getIfUnique() instead of getIfAvailable()
donald-pinckney Apr 10, 2026
52c6d4b
Add T28: Restore plugin classes as public API per tconley review
donald-pinckney Apr 10, 2026
01fec57
Clean up TASK_QUEUE: remove completed, mark superseded
donald-pinckney Apr 10, 2026
1b5c56e
T28: Restore VectorStorePlugin and EmbeddingModelPlugin as public cla…
donald-pinckney Apr 10, 2026
bc6275f
T23: Resolved — MCP capability caching is correct
donald-pinckney Apr 10, 2026
60f2941
T24: Change from discussion to fix — rawContent should be String
donald-pinckney Apr 10, 2026
528e538
T22: Defer starter artifact to after PR merge
donald-pinckney Apr 10, 2026
d58700f
T25: Replied to DABH about docs. Added T29 for README follow-up.
donald-pinckney Apr 10, 2026
4e600da
T29: Bump to high priority, do in this PR
donald-pinckney Apr 10, 2026
e19463b
T15: Also remove LocalActivityToolCallbackWrapper and ExecuteToolLoca…
donald-pinckney Apr 10, 2026
2c97806
T24: Change ChatModelTypes.Message rawContent from Object to String
donald-pinckney Apr 10, 2026
70c8594
T29: Add README with compatibility matrix and quick start
donald-pinckney Apr 10, 2026
b11249a
T30: Fix edge CI Java version mismatch
donald-pinckney Apr 10, 2026
42f46d6
T31: Skip temporal-spring-ai on JDK < 17
donald-pinckney Apr 10, 2026
7cb1dbc
T15: Plain tools execute in workflow context by default
donald-pinckney Apr 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 129 additions & 0 deletions TASK_QUEUE.json
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 ;)

Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
{
"project": "temporal-spring-ai",
"tasks": [
{
"id": "T13",
"title": "Remove includeBuild from samples-java",
"description": "Once temporal-spring-ai is published to Maven Central, remove the includeBuild('../sdk-java') block from samples-java/settings.gradle and the grpc-util workaround from core/build.gradle.",
"severity": "low",
"category": "cleanup",
"depends_on": [],
"status": "blocked",
"notes": "Blocked on SDK release. Not actionable yet."
},
{
"id": "T15",
"title": "Change default tool execution to run in workflow context",
"description": "Currently unannotated tools passed to defaultTools() are rejected. Change so they execute directly in workflow context by default \u2014 user is responsible for determinism. Remove @DeterministicTool annotation (no longer needed since direct execution is the default). Remove SandboxingAdvisor, LocalActivityToolCallbackWrapper, ExecuteToolLocalActivity, and ExecuteToolLocalActivityImpl. Remove ExecuteToolLocalActivityImpl registration from SpringAiPlugin. Keep @SideEffectTool as a convenience for wrapping in Workflow.sideEffect(). Keep activity stub / nexus stub auto-detection as shortcuts.",
"severity": "high",
"category": "refactor",
"depends_on": [],
"status": "completed",
"notes": "Blocked on PR review discussion with tconley1428. Agreed on direction but need to finalize details before implementing. Proposed design: https://github.com/temporalio/sdk-java/pull/2829#discussion_r3060711651"
},
{
"id": "T22",
"title": "Discuss: temporal-spring-ai-starter artifact for easier onboarding",
"description": "Create a temporal-spring-ai-starter artifact (POM-only, no code) that transitively pulls in temporal-spring-ai, temporal-sdk, and temporal-spring-boot-starter. Prevents version mismatches and ClassNotFoundException for users starting from scratch.",
"severity": "medium",
"category": "discussion",
"depends_on": [],
"status": "blocked",
"notes": "DABH review comment: https://github.com/temporalio/sdk-java/pull/2829/files#r3053808755. Do after merging the main PR \u2014 not urgent for initial landing."
},
{
"id": "T23",
"title": "Discuss: ActivityMcpClient capability caching and replay",
"description": "ActivityMcpClient caches getServerCapabilities() after first call. DABH asks if stale cache is a replay concern. Probably fine (activity result is in history, so replay uses the original value \u2014 which is correct). But worth confirming the design intent.",
"severity": "low",
"category": "discussion",
"depends_on": [],
"status": "completed",
"notes": "Replied to DABH: cache prevents non-determinism (live vs replay would diverge without it). Standard MCP practice."
},
{
"id": "T24",
"title": "Discuss: Change ChatModelTypes.rawContent from Object to String",
"description": "Change ChatModelTypes.Message rawContent from Object to String. Spring AI's Content.getText() returns String. We always cast to String on both sides anyway. Object type gives false flexibility that would ClassCastException at runtime.",
"severity": "low",
"category": "bugfix",
"depends_on": [],
"status": "completed",
"notes": "DABH review comment: https://github.com/temporalio/sdk-java/pull/2829/files#r3054049714. Verified Spring AI Message interface uses String, not Object."
},
{
"id": "T25",
"title": "Reply: compatibility matrix in docs",
"description": "DABH suggests documenting the compatibility matrix (Java version, Spring Boot version, Spring AI version). Acknowledge and defer to a docs PR.",
"severity": "low",
"category": "reply",
"depends_on": [],
"status": "completed",
"notes": "Replied to DABH acknowledging. Follow-up task T29 created."
},
{
"id": "T26",
"title": "Reply: SandboxingAdvisor lacks tests",
"description": "DABH notes SandboxingAdvisor has no tests. Likely moot if T15 removes it. Reply explaining that.",
"severity": "low",
"category": "reply",
"depends_on": [
"T15"
],
"status": "superseded",
"notes": "DABH review comment: https://github.com/temporalio/sdk-java/pull/2829/files#r3053836427 Moot once T15 removes these classes."
},
{
"id": "T27",
"title": "Reply: ToolContext silently dropped in LocalActivityToolCallbackWrapper",
"description": "DABH notes ToolContext is silently ignored. Likely moot if T15 removes LocalActivityToolCallbackWrapper. Reply explaining that.",
"severity": "low",
"category": "reply",
"depends_on": [
"T15"
],
"status": "superseded",
"notes": "DABH review comment: https://github.com/temporalio/sdk-java/pull/2829/files#r3054036773 Moot once T15 removes these classes."
},
{
"id": "T28",
"title": "Restore VectorStorePlugin and EmbeddingModelPlugin as public classes",
"description": "Restore the plugin subclasses so users not using auto-config can create them manually (e.g. new VectorStorePlugin(vectorStore)). Auto-config uses them too. Reverts the builder-inline approach from the earlier refactor.",
"severity": "medium",
"category": "refactor",
"depends_on": [],
"status": "completed",
"notes": "tconley1428 review comments on SpringAiPlugin.java and SpringAiVectorStoreAutoConfiguration.java"
},
{
"id": "T29",
"title": "Add README with compatibility matrix to temporal-spring-ai module",
"description": "Document supported versions: Java 17+, Spring Boot 3.x+, Spring AI 1.1.0, Temporal SDK 1.33.0+.",
"severity": "high",
"category": "docs",
"depends_on": [],
"status": "completed",
"notes": "Do in this PR, not a follow-up."
},
{
"id": "T30",
"title": "Fix CI: Edge build fails with Java version mismatch",
"description": "Edge CI sets edgeDepsTest which compiles temporal-sdk at Java 21. Our module hardcodes Java 17, causing Gradle to reject the dependency. Fix: use 21 when edgeDepsTest is set, 17 otherwise.",
"severity": "high",
"category": "bugfix",
"depends_on": [],
"status": "completed",
"notes": "Edge CI log showed: Dependency resolution is looking for a library compatible with JVM runtime version 17, but temporal-sdk is only compatible with JVM runtime version 21 or newer."
},
{
"id": "T31",
"title": "Fix CI: Docker build fails with Java 11 (release 17 not supported)",
"description": "Docker CI runs Java 11 which cannot compile --release 17. Conditionally exclude temporal-spring-ai from settings.gradle and BOM when JDK < 17.",
"severity": "high",
"category": "bugfix",
"depends_on": [],
"status": "completed",
"notes": "Docker CI log showed: error: release version 17 not supported. JAVA_HOME was Java 11."
}
]
}
5 changes: 5 additions & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ include 'temporal-testing'
include 'temporal-test-server'
include 'temporal-opentracing'
include 'temporal-kotlin'
// temporal-spring-ai requires Java 17+ (Spring AI dependency).
// Exclude from builds running on older JDKs.
if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_17)) {
include 'temporal-spring-ai'
}
include 'temporal-spring-boot-autoconfigure'
include 'temporal-spring-boot-starter'
include 'temporal-remote-data-encoder'
Expand Down
3 changes: 3 additions & 0 deletions temporal-bom/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ dependencies {
api project(':temporal-sdk')
api project(':temporal-serviceclient')
api project(':temporal-shaded')
if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_17)) {
api project(':temporal-spring-ai')
}
api project(':temporal-spring-boot-autoconfigure')
api project(':temporal-spring-boot-starter')
api project(':temporal-test-server')
Expand Down
54 changes: 54 additions & 0 deletions temporal-spring-ai/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# temporal-spring-ai

Integrates [Spring AI](https://docs.spring.io/spring-ai/reference/) with [Temporal](https://temporal.io/) workflows, making AI model calls, tool execution, vector store operations, embeddings, and MCP tool calls durable Temporal primitives.

## Compatibility

| Dependency | Minimum Version |
|---|---|
| Java | 17 |
| Spring Boot | 3.x |
| Spring AI | 1.1.0 |
| Temporal Java SDK | 1.33.0 |

## Quick Start

Add the dependency (Maven):

```xml
<dependency>
<groupId>io.temporal</groupId>
<artifactId>temporal-spring-ai</artifactId>
<version>${temporal-sdk.version}</version>
</dependency>
```

You also need `temporal-spring-boot-starter` and a Spring AI model starter (e.g. `spring-ai-starter-model-openai`).

The plugin auto-registers `ChatModelActivity` with all Temporal workers. In your workflow:

```java
@WorkflowInit
public MyWorkflowImpl(String goal) {
ActivityChatModel chatModel = ActivityChatModel.forDefault();
this.chatClient = TemporalChatClient.builder(chatModel)
.defaultSystem("You are a helpful assistant.")
.defaultTools(myActivityStub)
.build();
}

@Override
public String run(String goal) {
return chatClient.prompt().user(goal).call().content();
}
```

## Optional Integrations

These are auto-configured when their dependencies are on the classpath:

| Feature | Dependency | What it registers |
|---|---|---|
| Vector Store | `spring-ai-rag` | `VectorStoreActivity` |
| Embeddings | `spring-ai-rag` | `EmbeddingModelActivity` |
| MCP | `spring-ai-mcp` | `McpClientActivity` |
62 changes: 62 additions & 0 deletions temporal-spring-ai/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
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

// Spring AI requires Spring Boot 3.x / Java 17+
springBootVersionForSpringAi = "$springBoot3Version"
}

// Spring AI requires Java 17+, override the default Java 8 target from java.gradle.
// When edgeDepsTest is set, use 21 to match other modules (avoids Gradle JVM compatibility rejection).
ext {
springAiJavaVersion = project.hasProperty("edgeDepsTest") ? JavaVersion.VERSION_21 : JavaVersion.VERSION_17
springAiRelease = project.hasProperty("edgeDepsTest") ? '21' : '17'
}

java {
sourceCompatibility = springAiJavaVersion
targetCompatibility = springAiJavaVersion
}

compileJava {
options.compilerArgs.removeAll(['--release', '8'])
options.compilerArgs.addAll(['--release', springAiRelease])
}

compileTestJava {
options.compilerArgs.removeAll(['--release', '8'])
options.compilerArgs.addAll(['--release', springAiRelease])
}

dependencies {
api(platform("org.springframework.boot:spring-boot-dependencies:$springBootVersionForSpringAi"))
api(platform("org.springframework.ai:spring-ai-bom:$springAiVersion"))

// this module shouldn't carry temporal-sdk with it, especially for situations when users may be using a shaded artifact
compileOnly project(':temporal-sdk')
compileOnly project(':temporal-spring-boot-autoconfigure')

api 'org.springframework.boot:spring-boot-autoconfigure'
api 'org.springframework.ai:spring-ai-client-chat'

implementation 'org.springframework.boot:spring-boot-starter'

// Optional: Vector store support
compileOnly 'org.springframework.ai:spring-ai-rag'

// Optional: MCP (Model Context Protocol) support
compileOnly 'org.springframework.ai:spring-ai-mcp'

testImplementation project(':temporal-sdk')
testImplementation project(':temporal-testing')
testImplementation "org.mockito:mockito-core:${mockitoVersion}"
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.ai:spring-ai-rag'

testRuntimeOnly group: 'ch.qos.logback', name: 'logback-classic', version: "${logbackVersion}"
testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}

tasks.test {
useJUnitPlatform()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.temporal.springai.activity;

import io.temporal.activity.ActivityInterface;
import io.temporal.activity.ActivityMethod;
import io.temporal.springai.model.ChatModelTypes;

/**
* Temporal activity interface for calling Spring AI chat models.
*
* <p>This activity wraps a Spring AI {@link org.springframework.ai.chat.model.ChatModel} and makes
* it callable from within Temporal workflows. The activity handles serialization of prompts and
* responses, enabling durable AI conversations with automatic retries and timeout handling.
*/
@ActivityInterface
public interface ChatModelActivity {

/**
* Calls the chat model with the given input.
*
* @param input the chat model input containing messages, options, and tool definitions
* @return the chat model output containing generated responses and metadata
*/
@ActivityMethod
ChatModelTypes.ChatModelActivityOutput callChatModel(ChatModelTypes.ChatModelActivityInput input);
}
Loading
Loading