Skip to content

[Feature] Persistent ask (survives caller restart) #205

Description

@pathosDev

Size / Priority

  • Size: L

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:

  1. Caller's journal: persist AskStarted(correlationId, targetPath, msg).
  2. Send msg to target, wrapped with correlationId.
  3. Target replies normally.
  4. Caller-side reply handler journals AskReplied(correlationId, reply).
  5. 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

  1. Correlation-id collision: user-supplied id must be unique per caller. Enforce uniqueness check.
  2. Reply ordering across restart: if reply arrives during downtime, it's queued (target's mailbox). After caller restart, mailbox delivers. Document.
  3. Timeout vs no-reply-ever: differentiate. Persistent timeout event.

Test plan

  1. Caller asks; restart; reply arrives; caller's onReply called.
  2. Reply arrives before restart (queued); after restart, delivered.
  3. Timeout fires; persisted.
  4. Concurrent multiple persistent-asks per caller.
  5. Cluster scenario.

Acceptance criteria

  • persistentAsk function.
  • Journal events for correlation.
  • Cross-restart correlation map.
  • Documentation.
  • Test suite.
  • CHANGELOG entry.

Pre-implementation checklist

  • Joint design review.
  • Correlation-store schema.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestpriority: lowNice-to-have / niche / demand-driven

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions