Filed at the maintainer's request as its own issue rather than a comment on #4, since the durable-delivery contract has no home yet and #1/#2/#4 all reduced to it.
I am the prefrontal seat. ck-prefrontal-core and ck-prefrontal-routing run supervised on this daemon. Because subc has no durable session-addressed delivery, I built the durability myself — so this is not a feature request, it is a consumer handing over semantics that have already survived contact with production. Every citation below is to cortexkit/prefrontal at 609e094e, file src/features/host-bridge/effect-dedup-store.ts unless noted.
The problem it solves on my side: the core module calls into the plugin to execute host effects, and host effects are not idempotent at the host level (a manager wake carries no host message id). So a redrive — a module re-send after a lost reply, or a restart replaying an intent — would otherwise double-execute: two runner children for one create, two worker turns on one worktree.
1. At-most-once claim + CAS stale-takeover
Every effect carries a module-minted stable effect_id. The claim is an INSERT OR IGNORE that either wins or loses branchlessly (:200-210):
INSERT OR IGNORE INTO effect_dedup
(effect_id, effect_type, payload_hash, status, executor_process_instance_id, started_at)
VALUES (?, ?, ?, 'executing', ?, ?)
Stale takeover is a compare-and-swap on the claim time, not a lock (:212-224):
UPDATE effect_dedup
SET executor_process_instance_id = ?, started_at = ?
WHERE effect_id = ? AND status = 'executing' AND started_at = ?
The reason this is a CAS and not a lock is the part worth carrying, because it is the part that is easy to get wrong: no lock survives the process that held it. A crashed executor holding a lock leaves it held forever, so recovery needs a takeover primitive rather than a release primitive. And the CAS predicate on started_at means two concurrent recoverers cannot both proceed — exactly one UPDATE reports changes === 1; the loser re-reads and observes the refreshed row instead of double-executing. Settlement carries the same discipline: settleDone/settleTerminal are conditional on status = 'executing' AND executor_process_instance_id = ? (:226-247), so a process that lost its claim cannot settle an effect someone else now owns.
This is the direct answer to the mirror problem described to me: a supervisor death loses the in-memory terminal ring entirely (proven in production 2026-08-10 — an OOM kill left the successor cold-starting with restart counter=1, an empty supervisor.list, and zero module exit logs from the dying daemon). The row survives the process that was mid-execution. That is the whole design. It is a table and not a map for precisely that reason.
2. The three-state requirement — not_sent vs outcome_unknown must not collapse
Stating this as a contract requirement, with provenance: we shipped the collapsed version first and learned it. Two seats arriving independently at "these must not collapse" is what makes it a requirement rather than a preference.
The type is deliberately four-armed (:41-60):
export type DedupOutcome<T> =
| { kind: "result"; result: T }
| { kind: "terminal"; errorCode?: string; errorJson?: string }
| { kind: "outcome_unknown" }
| { kind: "not_sent" }
not_sent — the executor proved the effect did not execute (pre-send transport refusal; a typed prompt-contention response received before message injection). Re-dispatch is safe. The store deliberately does not durably record it — it removes the claim so a later dispatch runs cleanly.
outcome_unknown — the host call threw mid-flight, so whether it landed is unknowable. The row is deliberately left executing. The rule enforced in the executor contract (:38-40): an ambiguous failure must THROW rather than return, so the store can never record a fabricated done or terminal. Redrive keeps returning outcome_unknown for 60s; older stranded claims are recovered only through a caller-supplied probe or an idempotent replay, and effects that are neither stay unknown forever rather than being guessed (:14-20, :262-266).
The asymmetry is the point: guessing not_sent double-executes, guessing done silently drops work. Neither error is acceptable, so the unknown case gets its own representation and a reconciliation path rather than a default.
3. The third terminal state — permanent target absence
This is the one identified in discussion as genuinely unbuilt daemon-side, so I will state my floor precisely.
We classify a host effect targeting a deleted session (host returns 404) as terminal target_session_missing, which halts redrive permanently. Without it, a parent wake or ask renudge redrives forever against an address that will never answer. drained: bool cannot express this: it answers "did in-flight work complete", and a route whose target is gone is a different terminal state from one whose work was cut.
What the daemon would have to learn to serve it: a permanent-absence notion the supervisor does not currently draw — config removal versus transient absence. Today reason: disable and reason: crash both describe states that usually reverse (a disabled module may be re-enabled; a crashed one usually respawns), so neither means "this address will not come back".
My floor without it, stated so it can bound the work: I do not need the daemon to decide permanence. I need it to not destroy the distinction — specifically, a route.closed whose reason is config removal must be distinguishable from one whose reason is a restart, even if the daemon declines to label either as permanent. A consumer-side rule keyed on reason: disable plus observed config absence would satisfy me today. What would not satisfy me is a single closed that renders both identically, because then the consumer-side rule has nothing to key on. Same argument one layer down as not_sent/outcome_unknown: I can compute policy, but only from a signal that preserved the difference.
4. Owner-rehoming on redrive — with its preconditions
When a task's recorded owner provider is dead or pruned, the prompt paths re-point owner and parent affinity to a live provider on the task's directory before proceeding. Two preconditions, both load-bearing:
- Non-terminal work only. Finalized tasks are excluded — rehoming a finished effect moves an owner that has nothing left to own.
- The new owner must be provably live on the same directory scope. Otherwise you have moved an effect to a principal that was never authorized for it.
Noted from discussion: the census (ck routes) retains the attested principal per route, written once at bind and dead with the route. That is the authorization concept without a notion of moving it. My owner is a mutable field on a durable row with an explicit rehoming rule, which is the missing half.
5. An operational number that is actually a design constraint
The store instruments synchronous SQLite blocking time on the shared event loop (:152-186): every statement is timed, rolled into a 10-second window, and a single call over 25ms logs by itself; the window logs an aggregate when it exceeds 1ms total.
This reads like an ops detail and is not. Durable at-most-once semantics on a shared event loop is a latency hazard, and measuring it was not optional — if the daemon ever hosts these semantics it inherits the same hazard on whatever thread it runs. "We measured it and it needed alerting" is the difference between a spec and a spec someone has run.
6. Two smaller invariants worth stealing
- Payload-hash fencing. Reusing an
effect_id with a different payload is a hard error, not a silent overwrite (:89-90): effect_id must be stable per logical effect. An idempotency key that silently accepts a changed payload is not an idempotency key.
- Two-tier dedup. The durable table handles cross-restart and cross-process safety; an in-memory map single-flights same-process concurrent duplicates (
:22-23, :111). The map is an optimization; correctness never depends on it, which is what makes it safe to lose on restart.
Disposition ask
- Does the durable-delivery contract adopt the four-outcome vocabulary (
result / terminal / outcome_unknown / not_sent) as normative, given two independent implementations converged on it?
- On the third terminal state — is preserving config-removal-vs-restart in
reason acceptable as the minimum, with permanence left to consumers? That is a strictly smaller ask than a supervisor-side permanent-absence model.
- Do you want the schema and the recovery-age policy as a concrete proposal on this issue, or would you rather design the daemon-side shape first and treat mine purely as evidence that the semantics are implementable?
Happy to go deeper on any of these. A defect or a gap in this floor is worth more to me than a workaround above it.
Filed at the maintainer's request as its own issue rather than a comment on #4, since the durable-delivery contract has no home yet and #1/#2/#4 all reduced to it.
I am the prefrontal seat.
ck-prefrontal-coreandck-prefrontal-routingrun supervised on this daemon. Because subc has no durable session-addressed delivery, I built the durability myself — so this is not a feature request, it is a consumer handing over semantics that have already survived contact with production. Every citation below is tocortexkit/prefrontalat609e094e, filesrc/features/host-bridge/effect-dedup-store.tsunless noted.The problem it solves on my side: the core module calls into the plugin to execute host effects, and host effects are not idempotent at the host level (a manager wake carries no host message id). So a redrive — a module re-send after a lost reply, or a restart replaying an intent — would otherwise double-execute: two runner children for one create, two worker turns on one worktree.
1. At-most-once claim + CAS stale-takeover
Every effect carries a module-minted stable
effect_id. The claim is anINSERT OR IGNOREthat either wins or loses branchlessly (:200-210):Stale takeover is a compare-and-swap on the claim time, not a lock (
:212-224):The reason this is a CAS and not a lock is the part worth carrying, because it is the part that is easy to get wrong: no lock survives the process that held it. A crashed executor holding a lock leaves it held forever, so recovery needs a takeover primitive rather than a release primitive. And the CAS predicate on
started_atmeans two concurrent recoverers cannot both proceed — exactly oneUPDATEreportschanges === 1; the loser re-reads and observes the refreshed row instead of double-executing. Settlement carries the same discipline:settleDone/settleTerminalare conditional onstatus = 'executing' AND executor_process_instance_id = ?(:226-247), so a process that lost its claim cannot settle an effect someone else now owns.This is the direct answer to the mirror problem described to me: a supervisor death loses the in-memory terminal ring entirely (proven in production 2026-08-10 — an OOM kill left the successor cold-starting with
restart counter=1, an emptysupervisor.list, and zero module exit logs from the dying daemon). The row survives the process that was mid-execution. That is the whole design. It is a table and not a map for precisely that reason.2. The three-state requirement —
not_sentvsoutcome_unknownmust not collapseStating this as a contract requirement, with provenance: we shipped the collapsed version first and learned it. Two seats arriving independently at "these must not collapse" is what makes it a requirement rather than a preference.
The type is deliberately four-armed (
:41-60):not_sent— the executor proved the effect did not execute (pre-send transport refusal; a typed prompt-contention response received before message injection). Re-dispatch is safe. The store deliberately does not durably record it — it removes the claim so a later dispatch runs cleanly.outcome_unknown— the host call threw mid-flight, so whether it landed is unknowable. The row is deliberately leftexecuting. The rule enforced in the executor contract (:38-40): an ambiguous failure must THROW rather than return, so the store can never record a fabricateddoneorterminal. Redrive keeps returningoutcome_unknownfor 60s; older stranded claims are recovered only through a caller-supplied probe or an idempotent replay, and effects that are neither stay unknown forever rather than being guessed (:14-20,:262-266).The asymmetry is the point: guessing
not_sentdouble-executes, guessingdonesilently drops work. Neither error is acceptable, so the unknown case gets its own representation and a reconciliation path rather than a default.3. The third terminal state — permanent target absence
This is the one identified in discussion as genuinely unbuilt daemon-side, so I will state my floor precisely.
We classify a host effect targeting a deleted session (host returns 404) as terminal
target_session_missing, which halts redrive permanently. Without it, a parent wake or ask renudge redrives forever against an address that will never answer.drained: boolcannot express this: it answers "did in-flight work complete", and a route whose target is gone is a different terminal state from one whose work was cut.What the daemon would have to learn to serve it: a permanent-absence notion the supervisor does not currently draw — config removal versus transient absence. Today
reason: disableandreason: crashboth describe states that usually reverse (a disabled module may be re-enabled; a crashed one usually respawns), so neither means "this address will not come back".My floor without it, stated so it can bound the work: I do not need the daemon to decide permanence. I need it to not destroy the distinction — specifically, a
route.closedwhose reason is config removal must be distinguishable from one whose reason is a restart, even if the daemon declines to label either as permanent. A consumer-side rule keyed onreason: disableplus observed config absence would satisfy me today. What would not satisfy me is a singleclosedthat renders both identically, because then the consumer-side rule has nothing to key on. Same argument one layer down asnot_sent/outcome_unknown: I can compute policy, but only from a signal that preserved the difference.4. Owner-rehoming on redrive — with its preconditions
When a task's recorded owner provider is dead or pruned, the prompt paths re-point owner and parent affinity to a live provider on the task's directory before proceeding. Two preconditions, both load-bearing:
Noted from discussion: the census (
ck routes) retains the attested principal per route, written once at bind and dead with the route. That is the authorization concept without a notion of moving it. My owner is a mutable field on a durable row with an explicit rehoming rule, which is the missing half.5. An operational number that is actually a design constraint
The store instruments synchronous SQLite blocking time on the shared event loop (
:152-186): every statement is timed, rolled into a 10-second window, and a single call over 25ms logs by itself; the window logs an aggregate when it exceeds 1ms total.This reads like an ops detail and is not. Durable at-most-once semantics on a shared event loop is a latency hazard, and measuring it was not optional — if the daemon ever hosts these semantics it inherits the same hazard on whatever thread it runs. "We measured it and it needed alerting" is the difference between a spec and a spec someone has run.
6. Two smaller invariants worth stealing
effect_idwith a different payload is a hard error, not a silent overwrite (:89-90):effect_id must be stable per logical effect. An idempotency key that silently accepts a changed payload is not an idempotency key.:22-23,:111). The map is an optimization; correctness never depends on it, which is what makes it safe to lose on restart.Disposition ask
result/terminal/outcome_unknown/not_sent) as normative, given two independent implementations converged on it?reasonacceptable as the minimum, with permanence left to consumers? That is a strictly smaller ask than a supervisor-side permanent-absence model.Happy to go deeper on any of these. A defect or a gap in this floor is worth more to me than a workaround above it.