Skip to content

Commit ed23567

Browse files
temporal-spring-ai: plan — per-model ActivityOptions registry
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 9bb3866 commit ed23567

1 file changed

Lines changed: 111 additions & 0 deletions

File tree

temporal-spring-ai/PLAN.md

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# Plan: Per-model activity options (timeouts, retries)
2+
3+
## Scope
4+
5+
Today all `ActivityChatModel` instances share a single
6+
`DEFAULT_TIMEOUT = Duration.ofMinutes(2)` and `DEFAULT_MAX_ATTEMPTS = 3`.
7+
With multiple chat models registered via `SpringAiPlugin` (e.g. a fast 4o-mini
8+
and a slow reasoning model), one timeout does not fit both. The integration
9+
guide explicitly calls out: *"you may wish to specify a different
10+
`start_to_close_timeout` depending on the model, and for example, whether it
11+
is in thinking mode or not."*
12+
13+
## Design
14+
15+
Introduce a per-model options registry on `SpringAiPlugin` that
16+
`ActivityChatModel.forModel(name)` consults when building its stub.
17+
18+
Key choice: the options are registered **on the plugin** (worker-side) but
19+
must be resolved **in the workflow** when `Workflow.newActivityStub(...)` is
20+
called. Because the plugin is a worker-side object, we either need to:
21+
22+
- (A) Ship the options map across the serialization boundary — awkward,
23+
`ActivityOptions` isn't trivially serializable.
24+
- (B) Expose a registry lookup on the plugin via a static accessor that
25+
`ActivityChatModel.forModel` calls. Workers run both workflow and plugin in
26+
the same JVM, and `ActivityChatModel.forModel` is already called from
27+
workflow code — it can safely read a static registry populated at plugin
28+
construction time.
29+
30+
Go with (B). Plugin construction happens before any workflow executes on the
31+
worker, so the registry is populated in time.
32+
33+
## Files to change
34+
35+
- `src/main/java/io/temporal/springai/plugin/SpringAiPlugin.java`
36+
- Accept an optional `Map<String, ActivityOptions> perModelOptions` on a
37+
new constructor / builder.
38+
- On construction, publish the map to a package-private static
39+
`SpringAiPluginOptions.register(map)` (or similar). Clearing on plugin
40+
shutdown is nice-to-have but not strictly required for a worker lifecycle.
41+
42+
- New: `src/main/java/io/temporal/springai/plugin/SpringAiPluginOptions.java`
43+
- Thread-safe registry: `register(Map)`, `optionsFor(String modelName)`,
44+
returns `Optional<ActivityOptions>`.
45+
46+
- `src/main/java/io/temporal/springai/model/ActivityChatModel.java`
47+
- `forModel(String modelName)` checks `SpringAiPluginOptions.optionsFor(
48+
modelName)`. If present, use those. Otherwise fall back to the existing
49+
default (2 min, 3 attempts).
50+
- Caller-supplied `ActivityOptions` (from the overload added in the
51+
`spring-ai/retry-and-options` branch) always wins over the registry.
52+
53+
- `src/main/java/io/temporal/springai/autoconfigure/SpringAiTemporalAutoConfiguration.java`
54+
- Accept a `Map<String, ActivityOptions>` bean if present
55+
(`ObjectProvider`) and forward it to the plugin constructor.
56+
57+
## Config example (documented in the PR body)
58+
59+
```java
60+
@Bean
61+
Map<String, ActivityOptions> springAiActivityOptions() {
62+
return Map.of(
63+
"reasoning", ActivityOptions.newBuilder()
64+
.setStartToCloseTimeout(Duration.ofMinutes(15))
65+
.setHeartbeatTimeout(Duration.ofMinutes(1))
66+
.build(),
67+
"fast", ActivityOptions.newBuilder()
68+
.setStartToCloseTimeout(Duration.ofSeconds(20))
69+
.build());
70+
}
71+
```
72+
73+
## Coordination with other branches
74+
75+
This branch layers on top of `spring-ai/retry-and-options`, which adds the
76+
`forModel(name, ActivityOptions)` overload. If that branch merges first,
77+
this one depends on the new overload. If this branch merges first, implement
78+
the overload here and the retry branch will rebase cleanly.
79+
80+
## Test plan
81+
82+
- Unit test: register a plugin with `perModelOptions = {"slow": 10min}`,
83+
build an `ActivityChatModel.forModel("slow")` in a workflow, and assert
84+
(via test env history inspection) the scheduled activity's
85+
`startToCloseTimeout` is 10 min, not 2 min.
86+
- Negative: `forModel("unknown-name")` falls back to default 2 min.
87+
- Explicit override: caller passes `ActivityOptions` — registry is ignored.
88+
89+
## PR
90+
91+
**Title:** `temporal-spring-ai: per-model ActivityOptions registry`
92+
93+
**Body:**
94+
95+
```
96+
## What was changed
97+
- `SpringAiPlugin` accepts an optional `Map<String, ActivityOptions>`
98+
keyed by chat-model name.
99+
- `ActivityChatModel.forModel(name)` consults that registry; an
100+
explicit `ActivityOptions` argument still takes precedence.
101+
- Auto-configuration forwards a user-provided
102+
`Map<String, ActivityOptions>` bean into the plugin.
103+
104+
## Why?
105+
A single default (2 min start-to-close, 3 attempts) doesn't fit every
106+
model. Reasoning and thinking-mode models routinely need 10+ minutes;
107+
fast models want shorter timeouts so retries recover quickly.
108+
The Temporal AI integration guide specifically calls this out as a
109+
capability partners should expose. With this change, users register
110+
one bean and never have to hand-build activity stubs again.
111+
```

0 commit comments

Comments
 (0)