Most agent frameworks treat sessions as ephemeral. You refresh the page, the conversation dies. You restart the process, the context vanishes. DASP (Durable Actor Session Protocol) addresses this by defining five operations that let clients reconnect to the same actor session, replay missed updates, and verify command outcomes across disconnects.
The protocol separates command admission from execution outcomes. The server saves acceptance before replying, then records the final result (completed, failed, cancelled, or uncertain) separately. This two-phase approach means you know what the server committed even if the network drops mid-execution.
Protocol Primitives
DASP defines five client operations:
- session.open: Open or return to a named session
- command: Submit work with a stable ID for retries
- view.read: Read current saved state
- updates.read: Fetch missed events since a cursor position
- outcome.read: Check a command's final result
Each session maintains an ordered history of updates. Clients track a cursor (the last update they applied). On reconnect, they read after that cursor to catch up without replaying the entire history.
Command Admission vs Outcome
When you submit a command, the server saves it with a stable ID before replying with an accepted receipt. If you retry with the same ID, the server recognizes it as a duplicate and returns the existing receipt.
The outcome is saved separately once execution finishes. Four possible outcomes:
- completed: Effects established
- failed: Known error, no effects
- cancelled: Explicitly stopped
- uncertain: Effects cannot be confirmed (network partition, crash during side effects)
This split matters for idempotency. You can retry command submission safely because the ID prevents double execution. You can poll for the outcome without re-triggering work.
Session Recovery Flow
// Client reconnects after disconnect
const session = await client.sessionOpen({ sessionId: "agent-42" });
// Client knows it applied updates through cursor 2
const updates = await client.updatesRead({
sessionId: "agent-42",
afterCursor: 2
});
// Server returns updates 3, 4, 5 in order
for (const update of updates) {
applyToLocalState(update);
currentCursor = update.cursor;
}
// Client is now caught up
The cursor is per-client. Multiple clients can share the same session with independent cursors. A web app at cursor 5, a CLI at cursor 3, and a background service at cursor 4 all read the same ordered history but track their own positions.
Shared Sessions Across Clients
DASP allows multiple clients to connect to the same actor session. The server enforces access control and delivers the same ordered update stream to each client. This enables:
- Web UI and CLI tools operating on the same agent
- Background services monitoring agent progress
- Handoff between different execution contexts (local dev to cloud deployment)
Each client maintains its own cursor. The server does not garbage-collect updates until all authorized clients have advanced past them.
State Serialization and Transport
DASP is transport-agnostic. The specification defines message shapes but not the wire protocol. Reference implementations exist for Elixir and TypeScript, both using the same message contract.
Your application provides:
- Transport layer: WebSocket, HTTP long-polling, gRPC, or custom
- Storage backend: Postgres, DynamoDB, Redis, or in-memory for testing
- Access control: Session ownership, command authorization
The protocol does not prescribe serialization format. JSON works for human-readable debugging. Protobuf or MessagePack work for bandwidth efficiency. The only requirement is that command IDs remain stable across retries.
Comparison to Other Durability Patterns
| Pattern | State Location | Recovery Mechanism | Multi-Client | Outcome Tracking |
|---|---|---|---|---|
| DASP | Server-side session | Cursor-based replay | Yes, per-cursor | Separate admission/outcome |
| Event Sourcing | Append-only log | Replay from position | Depends on implementation | Event log is source of truth |
| Database Transactions | RDBMS | Transaction log | Via pessimistic locking | ACID guarantees |
| Actor Snapshots (Orleans) | Grain storage | Snapshot + event replay | No, single activation | Activation-local |
| Workflow Engines (Temporal) | Workflow history | Deterministic replay | Via workflow ID | Activity completion tracking |
DASP sits between event sourcing and actor snapshots. It provides ordered replay like event sourcing but scopes it to a session rather than a global log. It tracks command outcomes like workflow engines but without requiring deterministic replay of the entire execution.
Failure Modes
Network partition during command execution: The server saves the command admission but cannot confirm effects. Outcome is marked uncertain. Client must decide whether to retry (risking duplicate effects) or query external systems to determine actual state.
Server crash after admission, before outcome: On restart, the server sees an admitted command with no outcome. It can either re-execute (if idempotent) or mark it uncertain and let the client decide.
Client crash before applying updates: Client reconnects with stale cursor. Server replays updates from that position. If updates are not idempotent (e.g., "increment counter"), the client must track which updates it actually applied before the crash.
Cursor divergence in shared sessions: If two clients submit commands concurrently, the server serializes them in update order. Each client sees the same order but may apply updates at different times. Application logic must handle eventual consistency.
Implementation Shape
A minimal DASP server needs:
- Session store: Map session ID to ordered update list and current state
- Command store: Map command ID to admission timestamp and outcome
- Cursor tracking: Map (session ID, client ID) to last applied update
- Transport handler: Accept connections, route messages, enforce access control
For production:
- Persist sessions to durable storage (Postgres, DynamoDB)
- Use optimistic locking or compare-and-swap for concurrent command admission
- Implement update retention policies (delete after all clients advance past)
- Add observability: command latency, outcome distribution, cursor lag per client
When DASP Fits
Good fit:
- Long-running agent conversations that must survive restarts
- Multi-client scenarios (web UI + CLI + background service)
- Systems where command outcomes matter more than real-time streaming
- Environments with unreliable networks (mobile, edge deployments)
Poor fit:
- High-throughput event streams (use Kafka or Pulsar)
- Real-time collaboration with sub-second latency requirements (use CRDTs or operational transform)
- Stateless request-response APIs (use HTTP)
- Systems where command idempotency is impossible (use distributed transactions)
Technical Verdict
DASP solves the session durability problem by separating command admission from outcome tracking and providing cursor-based replay. It works well for agent systems where conversations span hours or days, where multiple clients need access to the same session, and where you need to verify that commands actually completed.
The protocol is transport-agnostic and storage-agnostic, which means you can adapt it to your existing infrastructure. The cost is complexity: you must implement cursor tracking, handle uncertain outcomes, and reason about eventual consistency in shared sessions.
Use DASP when you need durable agent sessions with verifiable outcomes. Avoid it if you need real-time streaming, high-throughput event processing, or if your commands are not idempotent. The protocol assumes you can retry commands safely and that clients can apply updates in order.
Top comments (0)