Skip to content

Repository files navigation

Sagacity

License Java Spring AI Build Maven Central Docs Tests

The SAGA pattern for AI agents. Declarative compensation for Spring AI tool calls, with a tamper-evident audit trail.

Your agent crashed after step 3 of 5. Sagacity undoes the mess — and produces the evidence.

Sagacity flow: execute tools, detect failure, compensate in reverse


The Problem

AI agents perform multi-step tasks with real-world side effects — reserving inventory, creating orders, sending emails. When step 4 fails:

  • ❌ Steps 1–3's side effects are live in production
  • ❌ Nothing undoes them automatically
  • ❌ There's no compliance-grade record of what happened

Every framework retries. None compensates. None produces evidence.

The Solution

@Tool(description = "Reserve inventory for a product")
@Compensable(by = "releaseInventory")
public String reserveInventory(String productId, int quantity) {
    // real side effect
}

@Compensation
public void releaseInventory(CompensationContext ctx) {
    // undoes the side effect using the original result
}

@Tool(description = "Send wire transfer")
@Compensable(reversibility = Reversibility.IRREVERSIBLE)  // requires human approval
public String sendWireTransfer(String orderId) {
    // dangerous — can't be undone
}
// Wrap any agent task in a saga scope:
SagaResult<ChatResponse> result = sagacity.saga("place-order-123",
    () -> chatClient.prompt()
        .user("Place an order for 2 units of product p-1")
        .tools(orderTools)
        .call().chatResponse());

// On failure → compensations run in reverse order
// IRREVERSIBLE tools → suspended until human approves
// Every step journaled with tamper-evident hash chain

Documentation

Full documentation: sumitvairagar.github.io/sagacity

Getting started Working example in five minutes
Approval gates Human sign-off for irreversible tools
Production checklist Read before pointing this at real money
Threat model What the audit trail does and does not defend against
REST API Endpoint reference

Quick Start

1. Add the dependency

<dependency>
    <groupId>io.github.sumitvairagar</groupId>
    <artifactId>sagacity-spring-boot-starter</artifactId>
    <version>0.1.0</version>
</dependency>

Or build from source:

git clone https://github.com/sumitvairagar/sagacity.git
cd sagacity
mvn clean install

2. Annotate your tools

@Component
public class OrderTools {

    @Tool(description = "Reserve inventory for a product")
    @Compensable(by = "releaseInventory")
    public String reserveInventory(String productId, int quantity) {
        inventoryService.reserve(productId, quantity);
        return "Reserved " + quantity + " of " + productId;
    }

    @Compensation
    public void releaseInventory(CompensationContext ctx) {
        // ctx.input() has the original JSON input
        // ctx.result() has what reserveInventory returned
        inventoryService.release(extractProductId(ctx.input()));
    }

    @Tool(description = "Charge the customer")
    @Compensable(by = "refundCustomer")
    public String chargeCustomer(String customerId, double amount) {
        return paymentService.charge(customerId, amount);
    }

    @Compensation
    public void refundCustomer(CompensationContext ctx) {
        paymentService.refund(extractChargeId(ctx.result()));
    }

    @Tool(description = "Send confirmation email")
    @Compensable(reversibility = Reversibility.IRREVERSIBLE)
    public String sendConfirmation(String orderId) {
        // Can't unsend an email — requires human approval before executing
        return emailService.send(orderId);
    }
}

3. Run your agent in a saga

@Service
public class OrderAgent {

    @Autowired private Sagacity sagacity;
    @Autowired private ChatClient chatClient;
    @Autowired private OrderTools orderTools;

    public SagaResult<ChatResponse> placeOrder(String userRequest) {
        ToolCallback[] tools = sagacity.wrap(orderTools);

        return sagacity.saga("order-" + UUID.randomUUID(), () ->
            chatClient.prompt()
                .user(userRequest)
                .toolCallbacks(tools)
                .call()
                .chatResponse()
        );
    }
}

4. Configure (application.yml)

# Sagacity auto-configures with sensible defaults. All optional:
sagacity:
  enabled: true                      # default
  schema-init: true                  # auto-create tables on startup
  approval-endpoints-enabled: true   # expose REST API

# Point to your Postgres (or any JDBC DataSource):
spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/myapp
    username: myuser
    password: mypass

No DataSource? Sagacity falls back to an in-memory journal (great for dev/testing).

How It Works

ChatClient → ToolCallingManager → ToolCallback
                                       ↑
                              SagacityToolCallback (decorator)
                                       │
                  ┌────────────────────┼────────────────────┐
                  │                    │                    │
            1. Journal           2. Execute           3. Journal
               INTENT             the tool            EXECUTED/FAILED
                  │                    │
                  │    (if IRREVERSIBLE)│   (on failure)
                  │         ↓          │        ↓
                  │  AWAITING_APPROVAL │  CompensationRunner
                  │  (saga suspends)   │  walks journal backward
                  │                    │  runs @Compensation methods
                  │                    │
                  ↓                    ↓
           Human approves       Journal records
           via REST API         every compensation

REST API (Spring Boot Starter)

The starter exposes these endpoints automatically:

Method Endpoint Description
GET /sagacity/approvals List all pending approval requests
GET /sagacity/approvals/{sagaId} Pending approvals for a saga
POST /sagacity/approve/{sagaId}/{seq} Approve (body: {"approver": "admin@co.com"})
POST /sagacity/reject/{sagaId}/{seq} Reject + trigger compensation
POST /sagacity/resume/{sagaId}/{seq} Execute an approved tool (body: {"payload": "..."})
GET /sagacity/audit/{sagaId} Export journal as JSON Lines
GET /sagacity/audit/{sagaId}/verify Verify hash chain integrity

Approving does not execute. /approve records who signed off; /resume runs the tool and is where the payload is checked against what was approved. Both steps are required — /resume refuses with 409 if no approval was recorded, and refuses again if the payload changed since.

Example: Approve, then execute

# 1. Record the human decision
curl -X POST http://localhost:8080/sagacity/approve/order-123/5 \
  -H "Content-Type: application/json" \
  -d '{"approver": "manager@company.com"}'

# 2. Execute, binding to the exact payload that was approved
curl -X POST http://localhost:8080/sagacity/resume/order-123/5 \
  -H "Content-Type: application/json" \
  -d '{"payload": "{\"amount\":100,\"to\":\"alice\"}"}'
# {"sagaId":"order-123","status":"COMPLETED","result":"\"transfer-ok\""}

# A payload that differs from the approved one is refused, not executed:
# 409 {"sagaId":"order-123","status":"COMPENSATED",
#      "reason":"Stale approval rejected: payload changed since approval was granted"}

Example: Verify audit trail

curl http://localhost:8080/sagacity/audit/order-123/verify
# {"valid":true,"entryCount":8,"breakAtIndex":-1,"message":"all 8 entries verified"}

Features

Feature Status Description
@Compensable / @Compensation Declare undo logic per tool
Reverse-order compensation On failure, undo steps in reverse
Postgres journal + SHA-256 hash chain Tamper-evident, crash-safe
Human approval gates IRREVERSIBLE tools suspend until approved
Approve/Reject REST API With approver identity in audit trail
Audit export (JSON Lines) Compliance-ready, one entry per line
Hash chain verification Detect any modification to history
Spring Boot Starter Zero-config auto-wiring
Concurrent-safe SELECT FOR UPDATE, tested with 10 threads

Why Not Just Use Temporal / DBOS / Restate?

Those solve durability (resume after crash). Sagacity solves compensation and evidence:

Temporal/DBOS/Restate Sagacity
Resume after crash 📋 (via DBOS integration)
Undo side effects on failure
Tamper-evident audit trail
EU AI Act Article 12
Human approval gates
Spring AI native
Annotation-based DX

They're complementary. Sagacity + DBOS (planned) gives you both.

Architecture

sagacity-core                    # Journal, hash chain, compensation runner, approval store
                                 # Zero framework dependencies — reusable anywhere

sagacity-spring-ai               # SagacityToolCallback, @Compensable processing, Sagacity facade

sagacity-spring-boot-starter     # Auto-config, REST endpoints, schema init

sagacity-examples                # Order-placing agent demo with induced failure

Compliance: EU AI Act Article 12

The EU AI Act (enforceable for high-risk systems from 2026-08-02) requires:

  • Automatic logging of AI system operations
  • Tamper-evident records retained 6–24 months
  • Traceable decision chains

Sagacity's hash-chained journal maps directly to Article 12:

Article 12 Requirement Sagacity Feature
Automatic logging Every tool call journaled (INTENT/EXECUTED/FAILED)
Tamper-evident SHA-256 hash chain, verifiable via REST API
Traceable decisions Saga ID links all steps; approval identity recorded
Retention Postgres persistence; retention policies (roadmap)

Roadmap

Milestone Status
M0 — Walking skeleton ✅ Done
M1 — Postgres journal + hash chain ✅ Done
M2 — Approval gates + audit export ✅ Done
M3 — Spring Boot Starter ✅ Done
M4 — Ecosystem 📋 Planned

M4 (planned):

  • DBOS integration (durable compensation runs)
  • LangChain4j adapter
  • Streaming tool-call support
  • MCP tool support
  • Approval dashboard UI
  • Maven Central publish

See docs/ROADMAP.md for details.

Contributing

Contributions welcome! See CONTRIBUTING.md for guidelines.

High-impact areas:

  • Testing: more failure scenarios, distributed tests
  • LangChain4j adapter
  • Documentation and examples
  • Approval dashboard UI (React/Vue)

License

Apache License 2.0 — see LICENSE.


AI doesn't undo its mistakes. Sagacity does.

Built by @sumitvairagar | YouTube

About

The SAGA pattern for AI agents. Declarative compensation for Spring AI tool calls with tamper-evident audit trail.

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages