Sagacity API¶
The facade in dev.sagacity.springai.Sagacity.
Construction¶
Sagacity.create(); // in-memory journal + approval store
Sagacity.create(journal); // custom journal
Sagacity.create(journal, approvalStore); // both
With the Spring Boot starter, a Sagacity bean is auto-configured — inject it
rather than constructing one.
wrap(Object... toolBeans)¶
Builds Spring AI callbacks from @Tool methods, registers every @Compensable
declaration (throwing at startup if one is malformed), records the raw callbacks
so approved tools can be resumed later, and returns callbacks that journal.
Outside a saga scope the returned callbacks pass straight through to the delegate, so the same beans stay usable in non-saga flows.
saga(String sagaId, Supplier<T> work)¶
SagaResult<ChatResponse> result = sagacity.saga("order-123", () ->
chatClient.prompt().user("...").toolCallbacks(tools).call().chatResponse());
Runs work in a saga scope. Compensates if the supplier throws, if any wrapped
tool failed — including when Spring AI swallowed the failure and fed it back to
the model as text — and returns AWAITING_APPROVAL if an irreversible tool
suspended the run. A Runnable overload exists for work returning nothing.
approve / reject¶
ApprovalDecision d = sagacity.approve(sagaId, journalSeq, "manager@company.com");
ApprovalDecision d = sagacity.reject(sagaId, journalSeq, "manager@company.com");
approve journals the decision and does not execute — call resumeSaga.
It deliberately leaves the pending request in the store so the payload can still
be verified. reject journals, drops the request, and compensates prior steps.
resumeSaga¶
SagaResult<String> r = sagacity.resumeSaga(sagaId, journalSeq, livePayload);
SagaResult<String> r = sagacity.resumeSaga(sagaId, journalSeq, livePayload, rawCallback);
Executes an approved tool after verifying three things, failing closed on each:
- a pending approval request exists — otherwise
IllegalStateException - an
APPROVEDdecision is journaled for it — otherwise refused and compensated - the live payload's SHA-256 matches the approved one — otherwise refused and compensated
The three-argument form looks the tool up among callbacks registered by wrap().
The four-argument form takes the callback explicitly — pass the unwrapped
delegate, or the approval gate fires again.
Inspection¶
List<ApprovalRequest> pending = sagacity.pendingApprovals();
List<ApprovalRequest> pending = sagacity.pendingApprovals(sagaId);
String jsonLines = sagacity.exportAuditLog(sagaId);
VerificationResult v = sagacity.verifyJournal(sagaId);
SideEffectJournal journal = sagacity.journal();
ApprovalStore store = sagacity.approvalStore();
SagaResult<T>¶
| Accessor | Meaning |
|---|---|
status() |
COMPLETED, COMPENSATED, COMPENSATION_FAILED, AWAITING_APPROVAL |
value() |
The work's return value — only on COMPLETED |
report() |
Per-tool compensation outcomes — null unless something compensated |
failure() |
The originating Throwable |
awaitingToolName() |
Tool that triggered the gate — only on AWAITING_APPROVAL |
sagaId() |
The saga id |