Take your LangGraph agents to production on SAP CAP. This CDS plugin adds durable, multi-tenant checkpointing — your agents survive restarts, recover from failures, and isolate state per tenant, all through the same database your CAP app already uses.
This package is a SAP CAP CDS plugin that provides LangGraph persistence (Checkpoint & Memory Saver) backed by the CAP data layer. Your agents gain durable, multi-tenant checkpointing on any database CAP supports — SQLite, SAP HANA, PostgreSQL, and more — with zero additional infrastructure.
This initial release includes the Checkpoint Saver (CdsCheckpointSaver). Memory storage (CdsMemoryStore) is planned for a future release.
Running AI agents in production introduces hard problems: what happens when a long-running workflow crashes midway? How do you pause for human approval and resume days later? How do you keep each tenant's conversations isolated?
LangGraph's persistence layer solves these by saving graph state at every step. This plugin makes that persistence layer run on CAP CDS, so your agents get production-grade durability without adding a separate database or state store.
Because CDS handles database mapping, connection pooling, and multi-tenancy natively:
- Any database. Develop on SQLite, deploy to HANA or PostgreSQL — no code changes.
- Multi-tenant by default. CAP routes DB queries to the correct tenant automatically via
@sap/cds-mtxs. - Crash recovery. Interrupted runs resume from the last saved checkpoint instead of starting over.
- Human-in-the-loop. Pause workflows indefinitely and resume exactly where they left off.
- Time travel. Every step is a snapshot — rewind and replay from any point.
- No new infrastructure. Checkpoint tables live in your CAP project's existing database.
npm install @mi8y/cds-langgraph-persistenceRequires @sap/cds >=9 as a peer dependency.
The package is a CDS plugin — cds-plugin.js auto-registers on startup and tells CDS to load index.cds. This adds two entities to your project model under the plugin.langgraph.persistence namespace:
| Entity | Purpose |
|---|---|
Checkpoints |
Graph state snapshots — keyed by (graphName, id, namespace, threadId) with serialized checkpoint and metadata payloads |
CheckpointWrites |
Pending writes linked to each checkpoint — composition child of Checkpoints |
CDS handles DDL generation for each target database, and in multi-tenant setups the tables are deployed per tenant automatically.
At runtime, the CdsCheckpointSaver writes through CAP CDS queries (SELECT, UPSERT, DELETE), which CDS routes to the correct database and tenant based on the active request context. This means tenant isolation happens transparently — you don't write multi-tenancy logic, CAP handles it.
The examples below show agents running inside CAP service handlers. This is important because CAP handlers carry a CDS context (req.user.id, req.tenant) — the checkpointer inherits the active database connection, which CDS automatically routes to the correct tenant.
Define an action on your CAP service. All three agent frameworks hook into the same pattern:
// srv/agent-service.cds
service AgentService {
action invoke(message: String) returns String;
}Build and compile a LangGraph workflow, then invoke it inside a CAP handler with a thread_id:
// srv/agent-service.ts
import cds from "@sap/cds";
import { StateGraph, Annotation } from "@langchain/langgraph";
import { CdsCheckpointSaver } from "@mi8y/cds-langgraph-persistence";
const State = Annotation.Root({
messages: Annotation<string[]>({
reducer: (a, b) => a.concat(b),
default: () => [],
}),
});
const graph = new StateGraph(State)
.addNode("agent", async (state) => ({ messages: ["Hello!"] }))
.addEdge("__start__", "agent")
.addEdge("agent", "__end__")
.compile({
checkpointer: new CdsCheckpointSaver({ name: "my-agent" }),
});
export default class AgentService extends cds.ApplicationService {
async init() {
this.on("invoke", async (req) => {
const { message } = req.data;
const threadId = `${req.user.id}`;
const result = await graph.invoke(
{ messages: [message] },
{ configurable: { thread_id: threadId } },
);
return result.messages.at(-1) ?? "";
});
return super.init();
}
}Use createAgent from the langchain package — the recommended agent API that runs on LangGraph under the hood:
// srv/agent-service.ts
import cds from "@sap/cds";
import { createAgent } from "langchain";
import { CdsCheckpointSaver } from "@mi8y/cds-langgraph-persistence";
const agent = createAgent({
model: "openai:gpt-4o",
tools: [searchTool, calculatorTool],
systemPrompt: "You are a helpful assistant.",
checkpointer: new CdsCheckpointSaver({ name: "my-agent" }),
});
export default class AgentService extends cds.ApplicationService {
async init() {
this.on("invoke", async (req) => {
const { message } = req.data;
const result = await agent.invoke(
{ messages: [{ role: "user", content: message }] },
{ configurable: { thread_id: req.user.id } },
);
return result.messages.at(-1)?.content ?? "I couldn't process that.";
});
return super.init();
}
}Deep Agents run on LangGraph and produce many sub-agents. The checkpointer ensures mid-run failures don't lose completed work:
// srv/agent-service.ts
import cds from "@sap/cds";
import { createDeepAgent } from "deepagents";
import { CdsCheckpointSaver } from "@mi8y/cds-langgraph-persistence";
const agent = createDeepAgent({
model: "claude-sonnet-4-20250514",
checkpointer: new CdsCheckpointSaver({ name: "my-agent" }),
});
export default class AgentService extends cds.ApplicationService {
async init() {
this.on("invoke", async (req) => {
const { message } = req.data;
const result = await agent.invoke(
{ messages: [{ role: "user", content: message }] },
{ configurable: { thread_id: req.user.id } },
);
return result.messages.at(-1)?.content ?? "No response generated.";
});
return super.init();
}
}The thread_id in configurable is how LangGraph separates conversations. In a CAP handler:
// Per-user conversation — one thread per user (automatically multi-tenant safe)
{
configurable: {
thread_id: req.user.id;
}
}
// Per-session — generate a new thread per request/task
{
configurable: {
thread_id: crypto.randomUUID();
}
}Pick the strategy that fits your use case. For a typical chatbot, scoping by req.user.id gives each user a persistent conversation that survives redeploys.
Creates a checkpoint saver instance. config.name is a required identifier that scopes all checkpoints to a specific graph/agent, preventing collisions when multiple graphs share the same database. Optionally accepts a SerializerProtocol for custom serialization (defaults to JsonPlusSerializer).
The saver implements the full BaseCheckpointSaver from @langchain/langgraph-checkpoint interface:
| Method | Description |
|---|---|
getTuple(config) |
Fetch a checkpoint and its pending writes by thread_id and optional checkpoint_id |
list(config, options?) |
List checkpoints for a thread with optional limit, before, and filter |
put(config, checkpoint, metadata, newVersions) |
Store a new checkpoint (idempotent upsert) |
putWrites(config, writes, taskId) |
Store pending writes for a checkpoint (idempotent upsert) |
deleteThread(threadId) |
Remove all checkpoints and writes for a given thread |
The persistence layer checkpoints state at every graph step. A run interrupted by a failure, timeout, or human-in-the-loop pause resumes from its last recorded state without reprocessing previous steps.
Checkpointing enables:
- Fault tolerance. Recover from crashes or timeouts without losing completed work.
- Indefinite interrupts. Human-in-the-loop workflows can pause for minutes or days and resume exactly where they left off.
- Time travel. Every checkpoint is a snapshot you can rewind to, letting you replay from an earlier state.
- Safe handling of sensitive operations. For workflows involving payments or irreversible actions, checkpoints provide an audit trail and a recovery point.
In CAP deployments, CDS manages the database connection pool and MTX handles tenant isolation — no additional infrastructure is needed beyond what your CAP application already provides.
Multi-tenancy is handled automatically when your CAP application uses @sap/cds-mtxs. The plugin's entities are deployed into each tenant's isolated database:
- SAP HANA — separate HDI container per tenant
- SQLite — separate database file per tenant
- PostgreSQL — schema-based isolation
At runtime, CDS routes all checkpoint queries to the correct tenant database based on the active request context. No special plugin configuration is required.
Any database with a CAP adapter:
- SQLite via
@cap-js/sqlite(development / testing) - SAP HANA via
@cap-js/hana(production) - PostgreSQL via
@cap-js/postgres(production)
*Usage requires @sap/cds under SAP license terms.