terministic Workflow, Non-Deterministic Activities:** The Temporal Workflow method must be 100% deterministic. It cannot call LLMs directly. All non-deterministic operations, including Spring AI calls, must be encapsulated in Temporal Activities.
2. Saga Pattern for Compensation: Instead of trying to roll back a database transaction, we implement backward recovery. Each successful step registers a compensating action. If a subsequent step fails, the Saga engine executes compensations in reverse order.
3. Immediate Compensation Registration: Compensations must be registered immediately after a step succeeds, not at the end of the workflow. This ensures that if the process crashes mid-execution, the system knows exactly what needs to be undone.
Implementation Example
The following TypeScript example demonstrates a financial transfer workflow. Note the separation of the LLM intent parsing from the execution steps, and the immediate registration of compensations.
import { ProxyWorkflow, startChild, Workflow, Saga } from '@temporalio/workflow';
import * as activities from './activities';
// Define the workflow interface
export const transferWorkflow = ProxyWorkflow<typeof import('./workflows')>();
export async function processTransfer(request: TransferRequest): Promise<TransferResult> {
// Initialize Saga with default options
const saga = new Saga({
parallelCompensation: false, // Execute compensations sequentially for safety
});
try {
// Step 1: Use Spring AI (via Activity) to parse natural language intent
// This is non-deterministic and isolated in an Activity
const intent = await activities.parseTransferIntent(request.prompt);
// Step 2: Validate funds
const balance = await activities.checkAccountBalance(intent.sourceAccountId);
if (balance < intent.amount) {
throw new Error('Insufficient funds');
}
// Step 3: Debit source account
const debitResult = await activities.debitAccount(intent.sourceAccountId, intent.amount);
// Register compensation immediately after success
saga.addCompensation(activities.creditAccount, intent.sourceAccountId, intent.amount);
// Step 4: Credit destination account
const creditResult = await activities.creditAccount(intent.destinationAccountId, intent.amount);
// Register compensation for the credit step
saga.addCompensation(activities.debitAccount, intent.destinationAccountId, intent.amount);
// Step 5: Send notification (External API call)
await activities.sendNotification(intent.userId, 'Transfer completed');
return {
status: 'COMPLETED',
transactionId: debitResult.transactionId,
};
} catch (error) {
// If any step fails, trigger compensations
if (saga.compensated) {
// Compensations already ran
} else {
await saga.compensate();
}
// Re-throw to mark workflow as failed
throw error;
}
}
Rationale
parseTransferIntent as Activity: Spring AI calls are wrapped in an Activity to ensure the workflow remains deterministic. If the workflow replays, the Activity result is retrieved from the event history, not re-executed.
saga.addCompensation Placement: Compensation is registered right after debitAccount succeeds. If the workflow crashes before creditAccount is called, the Saga ensures the debit is reversed upon recovery.
- Sequential Compensation:
parallelCompensation: false is chosen to ensure compensations execute in a predictable order, reducing the risk of race conditions during rollback.
Pitfall Guide
1. The Replay Trap
Explanation: Calling an LLM or external API directly inside the Workflow method. During replay, the call executes again, potentially returning different results or causing side effects.
Fix: Always wrap non-deterministic calls in Temporal Activities. The Workflow should only contain control flow logic.
2. Orphaned Resources
Explanation: Forgetting to register a compensation for a successful step. If a later step fails, the earlier step is not undone, leaving the system inconsistent.
Fix: Adopt a strict pattern: every state-changing activity must be immediately followed by saga.addCompensation. Use code reviews to enforce this.
3. Idempotency Blindness
Explanation: Activities are retried automatically by Temporal on failure. If an activity is not idempotent, retries can cause duplicate charges or data corruption.
Fix: Design activities to be idempotent. Use unique request IDs or check for existing state before performing actions. Temporal's built-in retry mechanism relies on idempotency.
4. Hallucination Propagation
Explanation: The LLM returns a valid structure but with invalid data (e.g., a negative amount or non-existent account ID). The workflow proceeds and fails downstream.
Fix: Validate LLM outputs within the Activity before returning. Use Spring AI's output parsers and validation annotations to catch errors early.
5. Blocking the Workflow Thread
Explanation: Using setTimeout or sleep inside the Workflow method. This blocks the event loop and prevents the workflow from processing events.
Fix: Use Workflow.sleep() or Workflow.timer() for delays. These are durable and survive workflow restarts.
6. Over-Reliance on LLM for Logic
Explanation: Asking the LLM to decide the workflow path or business logic. LLMs are not reliable for deterministic decision-making.
Fix: Use the LLM only for intent extraction and parameter mapping. Business logic, validation, and routing should be implemented in code.
7. Ignoring Compensation Failures
Explanation: Assuming compensations always succeed. If a compensation fails, the system remains in an inconsistent state.
Fix: Implement retry logic for compensations. Monitor compensation failures and alert operations teams. Consider manual intervention workflows for critical failures.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Single Database Update | @Transactional | Simple, strong consistency, low overhead. | Low |
| Multi-Service, No External APIs | 2PC / XA | Strong consistency across services. | Medium |
| AI Agent with External APIs | Temporal Saga | Handles latency, failures, and non-determinism. | Medium |
| High-Throughput, No Rollback | Fire-and-Forget | Maximum throughput, no consistency guarantees. | Low |
| Critical Financial Transaction | Temporal Saga + Manual Review | Guaranteed rollback with human oversight. | High |
Configuration Template
Temporal Worker Setup (TypeScript):
import { Worker } from '@temporalio/worker';
import * as activities from './activities';
async function run() {
const worker = await Worker.create({
workflowsPath: require.resolve('./workflows'),
activities,
taskQueue: 'ai-agent-queue',
});
await worker.run();
}
run().catch(console.error);
Spring AI Activity Implementation (Java):
@Component
public class LlmActivities {
private final ChatClient chatClient;
public LlmActivities(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
@ActivityMethod
public TransferIntent parseTransferIntent(String prompt) {
// Use Spring AI to extract structured data
return chatClient.prompt(prompt)
.call()
.entity(TransferIntent.class);
}
}
Quick Start Guide
- Add Dependencies: Include Temporal SDK and Spring AI dependencies in your project.
- Define Interfaces: Create Workflow and Activity interfaces. Mark activities with
@ActivityMethod.
- Implement Activities: Write activity implementations using Spring AI for LLM calls and external clients for API interactions.
- Implement Workflow: Write the workflow logic using the Saga pattern. Register compensations immediately.
- Run Worker: Start the Temporal Worker and submit workflow executions via the Temporal Client.
By adopting this pattern, you ensure that your AI agents can execute complex, multi-step workflows with the reliability required for production systems, while leveraging the power of Spring AI for intelligent intent processing.