Size / Priority
Rationale
Standard ask: caller waits for reply; reply correlated by a per-process counter (askId). If caller crashes mid-ask, the correlation is lost — recovered caller has no way to receive the original reply.
Persistent ask journals the correlation id; recovered caller subscribes to its journal events; sees the reply when it arrives.
Use case: long-running task delegated to another actor; caller saga restarts; resumes the wait.
Design sketch
// src/persistence/PersistentAsk.ts (new)
export interface PersistentAskOptions {
readonly correlationId: string; // user-supplied; persisted
readonly timeoutMs: number;
}
export async function persistentAsk<R>(
callerActor: PersistentActor<...>,
target: ActorRef,
msg: unknown,
options: PersistentAskOptions,
): Promise<R>;
Mechanism:
- Caller's journal: persist
AskStarted(correlationId, targetPath, msg).
- Send msg to target, wrapped with correlationId.
- Target replies normally.
- Caller-side reply handler journals
AskReplied(correlationId, reply).
- On recovery: replay events; restore in-flight asks; subscribe to reply if not yet recorded.
Reply correlation across restart: use a PersistentAskCoordinator (per-actor) that maintains the mapping; persists in journal alongside actor events.
Integration
PersistentActor: persistentAsk works inside its onCommand callbacks.
- Reply routing: target's reply addressed to caller's path; if caller is restarted, mailbox holds reply until ready.
- Timeout: covered by a timer; on timeout, persist
AskTimedOut(correlationId).
Out of scope / non-goals
- Cross-cluster persistent ask — same shape, but reply routing across cluster needs the cluster transport's deliver-on-recovery.
- Replacement of standard ask — both coexist.
Open design questions
- Correlation-id collision: user-supplied id must be unique per caller. Enforce uniqueness check.
- Reply ordering across restart: if reply arrives during downtime, it's queued (target's mailbox). After caller restart, mailbox delivers. Document.
- Timeout vs no-reply-ever: differentiate. Persistent timeout event.
Test plan
- Caller asks; restart; reply arrives; caller's onReply called.
- Reply arrives before restart (queued); after restart, delivered.
- Timeout fires; persisted.
- Concurrent multiple persistent-asks per caller.
- Cluster scenario.
Acceptance criteria
Pre-implementation checklist
Size / Priority
Rationale
Standard
ask: caller waits for reply; reply correlated by a per-process counter (askId). If caller crashes mid-ask, the correlation is lost — recovered caller has no way to receive the original reply.Persistent ask journals the correlation id; recovered caller subscribes to its journal events; sees the reply when it arrives.
Use case: long-running task delegated to another actor; caller saga restarts; resumes the wait.
Design sketch
Mechanism:
AskStarted(correlationId, targetPath, msg).AskReplied(correlationId, reply).Reply correlation across restart: use a
PersistentAskCoordinator(per-actor) that maintains the mapping; persists in journal alongside actor events.Integration
PersistentActor: persistentAsk works inside its onCommand callbacks.AskTimedOut(correlationId).Out of scope / non-goals
Open design questions
Test plan
Acceptance criteria
persistentAskfunction.Pre-implementation checklist