Size / Priority
Rationale
Pulling up production-debug info per actor: "what was actor X doing for the last N messages?" Today: nothing built-in. Common ops question.
A per-actor ring buffer of the last N message handlings with timing breakdown (mailbox-wait, handle-time, sender, message-type) is small but valuable.
Design sketch
// src/diagnostics/ExplainPlan.ts (new)
export interface MessageExplain {
readonly time: number;
readonly messageType: string;
readonly senderPath: string | null;
readonly mailboxWaitMs: number;
readonly handleTimeMs: number;
readonly outcome: 'ok' | 'error';
readonly errorMessage?: string;
}
export class ActorExplainPlan {
constructor(private readonly capacity: number);
record(entry: MessageExplain): void;
get last(N): ReadonlyArray<MessageExplain>;
}
// Opt-in per actor:
class MyActor extends Actor {
override async preStart() {
this.context.enableExplainPlan({ capacity: 100 });
}
}
// Management endpoint:
// GET /diagnostics/actors/{path}/explain → last 100 entries
Integration
ActorCell: hook into dispatch path (similar to existing tracing).
- Management endpoint: serves the ring buffer.
- Per-actor opt-in: default off (cost not zero).
Out of scope / non-goals
- All-actor capture — would be expensive; opt-in.
- Persistence — in-memory ring only.
Test plan
- Enable; process 100 messages; explain returns 100 entries with timing.
- Capacity respected (oldest evicted).
- Error entries include error message.
- Management endpoint serves JSON.
- Disabled → no overhead.
Acceptance criteria
Size / Priority
Rationale
Pulling up production-debug info per actor: "what was actor X doing for the last N messages?" Today: nothing built-in. Common ops question.
A per-actor ring buffer of the last N message handlings with timing breakdown (mailbox-wait, handle-time, sender, message-type) is small but valuable.
Design sketch
Integration
ActorCell: hook into dispatch path (similar to existing tracing).Out of scope / non-goals
Test plan
Acceptance criteria
ActorExplainPlan+ opt-in hook.