Problem
The supervision restart budget is kept per parent, in one array shared by all of that parent's children, rather than per child. Five unrelated children that each crash exactly once therefore consume one budget between them: with maxRetries: 2, three survive and two are stopped — even though no individual child ever failed more than once.
This matters most where fan-out is highest and failures are most likely to be independent: a router with 50 routees, or a shard hosting N entities, loses the whole subtree after maxRetries + 1 unrelated failures in the window.
The behaviour is acknowledged in a comment at the decision site, but Actor.supervisorStrategy's JSDoc reads as per-actor, and OneForOneStrategy — "applies the directive only to the failing child" — reinforces that reading.
Evidence
One array on the cell, not one per child:
src/internal/ActorCell.ts:100
private _failureTimes: number[] = [];
The decision site, with the comment that concedes it:
src/internal/ActorCell.ts:1136-1140
// `registerRestart` stays per-parent, so siblings share one allowance.
const strategy: SupervisorStrategy =
child.blueprint.supervisorStrategy
?? this.actor?.supervisorStrategy()
?? defaultStrategy;
Reproduced: one parent with OneForOneStrategy(() => Restart, { maxRetries: 2, withinTimeRangeMs: 60_000 }) and five children, each told to crash exactly once.
kids restarted after one crash each: 3/5 -> [ "k0", "k1", "k2" ]
k3 and k4 were stopped with the threshold message, having failed once each.
A second, compounding defect lives in the same code. registerRestart prunes _failureTimes only when withinTimeRangeMs > 0, and a hand-built OneForOneStrategy defaults withinTimeRangeMs to 0 (src/Supervision.ts:37). So new OneForOneStrategy(d, { maxRetries: 5 }) — the natural way to write it — produces a budget that never resets and an array that grows for the process lifetime.
Proposal
Move _failureTimes from the parent cell to per-child state (the child's own cell, or a Map on the parent keyed by child). AllForOneStrategy legitimately wants a shared budget; OneForOneStrategy does not, and the two should not share a counter.
Separately, decide what withinTimeRangeMs: 0 means and make it consistent: either it is "no window, budget never resets" — in which case the array must still be bounded — or it should default to the same 60 s defaultStrategy uses.
Acceptance sketch
Verification status
Found in the ten-lens production-readiness review of 2026-08-05 (v0.13.0) and re-verified before filing: reproduced by execution — the 3/5 result above is from a running system; the shared-array mechanism and the withinTimeRangeMs: 0 pruning gap were read at the cited lines.
Part of the production-readiness review batch — tracked in #913.
Problem
The supervision restart budget is kept per parent, in one array shared by all of that parent's children, rather than per child. Five unrelated children that each crash exactly once therefore consume one budget between them: with
maxRetries: 2, three survive and two are stopped — even though no individual child ever failed more than once.This matters most where fan-out is highest and failures are most likely to be independent: a router with 50 routees, or a shard hosting N entities, loses the whole subtree after
maxRetries + 1unrelated failures in the window.The behaviour is acknowledged in a comment at the decision site, but
Actor.supervisorStrategy's JSDoc reads as per-actor, andOneForOneStrategy— "applies the directive only to the failing child" — reinforces that reading.Evidence
One array on the cell, not one per child:
The decision site, with the comment that concedes it:
Reproduced: one parent with
OneForOneStrategy(() => Restart, { maxRetries: 2, withinTimeRangeMs: 60_000 })and five children, each told to crash exactly once.k3andk4were stopped with the threshold message, having failed once each.A second, compounding defect lives in the same code.
registerRestartprunes_failureTimesonly whenwithinTimeRangeMs > 0, and a hand-builtOneForOneStrategydefaultswithinTimeRangeMsto0(src/Supervision.ts:37). Sonew OneForOneStrategy(d, { maxRetries: 5 })— the natural way to write it — produces a budget that never resets and an array that grows for the process lifetime.Proposal
Move
_failureTimesfrom the parent cell to per-child state (the child's own cell, or aMapon the parent keyed by child).AllForOneStrategylegitimately wants a shared budget;OneForOneStrategydoes not, and the two should not share a counter.Separately, decide what
withinTimeRangeMs: 0means and make it consistent: either it is "no window, budget never resets" — in which case the array must still be bounded — or it should default to the same 60 sdefaultStrategyuses.Acceptance sketch
maxRetries: 2all restart.maxRetries + 1times within the window is still stopped.AllForOneStrategykeeps a shared budget, deliberately and documented.withinTimeRangeMs: 0does not grow_failureTimeswithout bound.Actor.supervisorStrategy's JSDoc states which scope the budget has.Verification status
Found in the ten-lens production-readiness review of 2026-08-05 (
v0.13.0) and re-verified before filing: reproduced by execution — the 3/5 result above is from a running system; the shared-array mechanism and thewithinTimeRangeMs: 0pruning gap were read at the cited lines.Part of the production-readiness review batch — tracked in #913.