Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 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
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
145 changes: 145 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,145 @@
{
"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 — user is responsible for determinism. Remove @DeterministicTool annotation (no longer needed since direct execution is the default). Remove SandboxingAdvisor and LocalActivityToolCallbackWrapper. 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": "blocked",
"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": "T16",
"title": "Fix NPE on assistantMessage.getMedia() in ActivityChatModel",
"description": "ASSISTANT branch in toActivityMessages() calls assistantMessage.getMedia().stream() without null guard. Other places in the module use CollectionUtils.isEmpty(). Add the guard.",
"severity": "medium",
"category": "bugfix",
"depends_on": [],
"status": "todo",
"notes": "Copilot review comment: https://github.com/temporalio/sdk-java/pull/2829/files#r3053789704"
},
{
"id": "T17",
"title": "Fix error message in TemporalToolUtil to mention Nexus stubs",
"description": "The IllegalArgumentException for unknown tool types doesn't mention Nexus service stubs as supported, even though they are. Update the message.",
"severity": "low",
"category": "bugfix",
"depends_on": [],
"status": "todo",
"notes": "Copilot review comment: https://github.com/temporalio/sdk-java/pull/2829/files#r3053789727. May be partly superseded by T15 if the error message changes entirely."
},
{
"id": "T18",
"title": "Fix NoUniqueBeanDefinitionException with multiple ChatModels",
"description": "SpringAiTemporalAutoConfiguration injects @Autowired(required=false) ChatModel primaryChatModel, which fails with NoUniqueBeanDefinitionException when multiple ChatModel beans exist without @Primary. Use ObjectProvider<ChatModel> instead.",
"severity": "high",
"category": "bugfix",
"depends_on": [],
"status": "todo",
"notes": "Real bug — hit this in the multimodel sample, worked around with auto-config exclusions. Copilot review comment: https://github.com/temporalio/sdk-java/pull/2829/files#r3053789744"
},
{
"id": "T19",
"title": "Make replay test actually exercise tool calls",
"description": "workflowWithTools_replaysDeterministically uses a StubChatModel that never requests tool calls, so tools are never invoked. Adjust the stub to return tool call requests first, then a final response, so the test actually validates tool execution determinism.",
"severity": "medium",
"category": "tests",
"depends_on": [],
"status": "todo",
"notes": "Copilot review comment: https://github.com/temporalio/sdk-java/pull/2829/files#r3053789763"
},
{
"id": "T20",
"title": "Handle duplicate MCP client names in McpClientActivityImpl",
"description": "Collectors.toMap() in the constructor throws IllegalStateException on duplicate keys. Two MCP clients with the same name() would crash. Should validate or handle gracefully.",
"severity": "medium",
"category": "bugfix",
"depends_on": [],
"status": "todo",
"notes": "DABH review comment: https://github.com/temporalio/sdk-java/pull/2829/files#r3054031855"
},
{
"id": "T21",
"title": "Use primitive arrays in EmbeddingModelTypes instead of List<Double>",
"description": "Converting float[] to List<Double> creates 1536+ boxed Double objects per embedding. Use float[] or double[] in EmbeddingModelTypes to avoid boxing overhead and reduce serialization size.",
"severity": "low",
"category": "improvement",
"depends_on": [],
"status": "todo",
"notes": "DABH review comment: https://github.com/temporalio/sdk-java/pull/2829/files#r3054058971"
},
{
"id": "T22",
"title": "Discuss: temporal-spring-ai-starter artifact for easier onboarding",
"description": "temporal-sdk is compileOnly, so users must declare it separately. DABH asks if a starter that pulls in the right transitive deps would reduce friction. Need to decide if this is worth it or if documentation is sufficient.",
"severity": "medium",
"category": "discussion",
"depends_on": [],
"status": "todo",
"notes": "DABH review comment: https://github.com/temporalio/sdk-java/pull/2829/files#r3053808755. Discuss with Don."
},
{
"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 — which is correct). But worth confirming the design intent.",
"severity": "low",
"category": "discussion",
"depends_on": [],
"status": "todo",
"notes": "DABH review comment: https://github.com/temporalio/sdk-java/pull/2829/files#r3054027377. Discuss with Don."
},
{
"id": "T24",
"title": "Discuss: Change ChatModelTypes.rawContent from Object to String",
"description": "rawContent is Object but always cast to String. DABH suggests making it String. Cleaner but changes the JSON schema. If we ever need non-string content (multimodal), we'd have to change it back.",
"severity": "low",
"category": "discussion",
"depends_on": [],
"status": "todo",
"notes": "DABH review comment: https://github.com/temporalio/sdk-java/pull/2829/files#r3054049714. Discuss with Don."
},
{
"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": "todo",
"notes": "DABH review comment: https://github.com/temporalio/sdk-java/pull/2829/files#r3053818533"
},
{
"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": "todo",
"notes": "DABH review comment: https://github.com/temporalio/sdk-java/pull/2829/files#r3053836427"
},
{
"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": "todo",
"notes": "DABH review comment: https://github.com/temporalio/sdk-java/pull/2829/files#r3054036773"
}
]
}
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ include 'temporal-testing'
include 'temporal-test-server'
include 'temporal-opentracing'
include 'temporal-kotlin'
include 'temporal-spring-ai'
include 'temporal-spring-boot-autoconfigure'
include 'temporal-spring-boot-starter'
include 'temporal-remote-data-encoder'
Expand Down
1 change: 1 addition & 0 deletions temporal-bom/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ dependencies {
api project(':temporal-sdk')
api project(':temporal-serviceclient')
api project(':temporal-shaded')
api project(':temporal-spring-ai')
api project(':temporal-spring-boot-autoconfigure')
api project(':temporal-spring-boot-starter')
api project(':temporal-test-server')
Expand Down
56 changes: 56 additions & 0 deletions temporal-spring-ai/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
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)

// 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
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

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

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

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')
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Could this increase friction though if a Spring user is just trying to get started and doesn't realize they need to import the sdk as well? Is there a temporal-spring-ai-starter planned that would pull in the right transitive dependencies to help with easy onboarding? Maybe not necessary but just a thought

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