LearnNewsExamplesServices
Frontmatter
id17403
titleFutility has no decider, and a frozen target publishes no way out
stateClosed
labels
bugaiagent-os
assigneesneo-opus-vega
createdAtAug 19, 2026, 11:28 PM
updatedAtAug 20, 2026, 1:19 PM
githubUrlhttps://github.com/neomjs/neo/issues/17403
authorneo-opus-vega
commentsCount0
parentIssue17044
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 20, 2026, 1:19 PM

Futility has no decider, and a frozen target publishes no way out

Closed Backlog/active-chunk-17 bugaiagent-os
neo-opus-vega
neo-opus-vega commented on Aug 19, 2026, 11:28 PM

Context

Split from #17044, which needs a futility breaker for the self-heal loop. This leaf is the decision, the state and the operator surface — all pure or read-only. The runtime wiring that emits freeze/unfreeze and skips evaluation while frozen stays on the parent, because it changes live actuator behaviour on a plane that is currently running.

Live ledger, external plane 2026-08-19: 5,000 heal events, byStatus declined 2,310 · recorded 2,495 · deferred 170 · actioned 15 · failed 10. currentlyFrozen: [] — the freeze has demonstrably never engaged.

The Problem

#17044's AC-1 keyed the breaker on consecutive executor failures. CONTAINER_HEALTH_ACTION_ROUTES gives throttleShed (throttle-shed-has-no-admitted-action) and record (diagnosis-record) an actuatorAction: null, so neither ever invokes an executor. A failure counter sees 10 of 5,000 rows and never engages, while the dominant terminals accrue thousands of no-effect records. AC-1 was widened by its author before this leaf was cut.

And a frozen target had nothing to publish. summarizeHealLedger folds currentlyFrozen as a bare target list, so an operator learns that a target is frozen and neither why nor what clears it.

The Architectural Reality

  • ai/services/memory-core/helpers/healActionDispatch.mjs — the pure dispatch decider. freeze is already in HEAL_ACTIONS as non-mutating containment, exempt from the rate/thrash bounds.
  • ai/services/memory-core/helpers/healSystemicCircuit.mjs — the precedent: a decider plus foldSystemicCircuitState over its own event vocabulary. Shape mirrored.
  • ai/services/memory-core/helpers/healEventLedgerStore.mjs:41HEAL_LEDGER_FROZEN_TRANSITIONS, and summarizeHealLedger's existing fold of those two types. This module owns the freeze vocabulary, which is why the state fold lands here rather than beside the decider: healActionDispatch is documented pure and this module does I/O.
  • ai/daemons/orchestrator/services/DeploymentStateBridgeService.mjs:2058collectSelfHealSnapshot, the operator-facing projection.
  • ai/daemons/orchestrator/services/ContainerHealthControllerService.mjs:90 — where throttle-shed-has-no-admitted-action is raised.

The Fix

  1. decideFutilityFreeze in the dispatch module — keys on consecutive identical verdicts with no state change, whatever the disposition. Verdict identity is {target, recoveryClass, rung, reasonCode}; a changed stateFingerprint breaks the run. Fails open on a bad clock or threshold: a breaker that engaged on malformed input would silence healing.
  2. Escalation distinguishes the two futility shapes — a failed action escalates remedy-ineffective, an unactioned verdict escalates no-admitted-remedy, which is a substrate gap for someone to close rather than a retry to abandon.
  3. foldFutilityFreezeState in the ledger store — per target: escalation, the verdict that froze it, the tier, the required quiet window, and thaw eligibility. Tier N requires baseThawQuietMs * tierMultiplier^(N-1), so a target that keeps returning is progressively harder to clear. An operator thaw does not raise the tier.
  4. collectSelfHealSnapshot publishes freezeState, declared on the disabled and degraded envelopes too so a consumer never branches on its absence.

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
decideFutilityFreeze healActionDispatch.mjs (verified pure, no I/O) freezes on N identical verdicts, any disposition bad clock / threshold ⇒ no freeze actuator docs 10 failed of 5,000 ledger rows
foldFutilityFreezeState healEventLedgerStore.mjs:41 freeze vocabulary (verified present) per-target state, evidence, tier, thaw condition no clock ⇒ never thaw-eligible actuator docs currentlyFrozen is a bare list today
selfHeal.freezeState collectSelfHealSnapshot (verified present) published on every envelope null on disabled / degraded deployment-state docs frozen target published no reason

Decision Record impact

aligned-with ADR 0026 (recovery actuator) — cooperative containment only. No hard preemption and no new admitted action; the breaker stops futile evaluation, it does not invent a remedy.

Acceptance Criteria

  • Red-proof: an arm asserting a freeze on a verdict stream containing zero failed dispositions must pass, and it must be the arm that fails if the signal reverts to executor failures.
  • Freezes at the threshold for recorded, declined, no-action, deferred and failed alike.
  • Negative control: one below the threshold does not freeze.
  • A changed verdict, and separately a changed stateFingerprint, each break the streak with a distinguishable status.
  • Escalation is remedy-ineffective for a failed action and no-admitted-remedy for an unactioned one.
  • Fails open on a missing clock, NaN clock, and non-positive threshold — asserted per case, since a breaker that engages on bad input silences healing.
  • A refrozen target requires a strictly longer quiet window than its first freeze; an operator thaw does not raise the tier.
  • Never thaw-eligible without a finite clock or with unusable bounds.
  • selfHeal.freezeState is published with escalation, evidence and thaw condition, and is declared (as null) on the disabled and degraded envelopes.

Out of Scope

  • Emitting freeze / unfreeze, and skipping evaluation while frozen. #17044 keeps it: it changes live actuator behaviour, and the decision surface should land and be reviewable first.
  • Automatic thaw execution when thawEligible turns true, and the operator thaw command surface — both need the producer above.
  • raiseCeiling reachability for the exhaustion class. CONTAINER_HEALTH_ACTION_ROUTES carries it with a container-memory-ceiling knob and exhaustion does not reach it; that is #16676 / #16695.
  • Hard preemption or any new admitted action — ADR 0026, and #17379 died proposing preemption on the sibling lane.

Avoided Traps

  • Counting executor failures. The two record-only routes never invoke one, and they are the majority of live rows.
  • Failing closed. For a dispatch gate, refusing to act is safe; for a breaker it is the opposite — engaging on a bad clock silences the immune system, so this fails open.
  • Duplicating the freeze vocabulary. HEAL_LEDGER_FROZEN_TRANSITIONS already exists and summarizeHealLedger already folds it; a second copy beside the decider would be two owners for one two-value enum. The fold moved to the owning module instead, which also keeps healActionDispatch free of the ledger store's fs import.
  • Publishing a freeze with no way out. A frozen target that states no thaw condition converts a bounded containment into an indefinite one.

Related

  • #17044 — parent; keeps the producer, the consumer gate and thaw execution.
  • #16823 — the kbSync cooperative-yield precedent for a pure decider consumed by a wired caller.
  • #16676 / #16695raiseCeiling reachability, deliberately not this leaf.
  • #17336 — if the frozen subject is OOM-killed rather than staying pinned, the resulting provider death is that ticket's scenario.
  • ADR 0026 — recovery actuator.

Origin Session ID: 8cbd588b-be06-4a56-9997-1058f2a3a07b

Retrieval Hint: query_raw_memories("futility breaker identical verdicts no state change record-only routes actuatorAction null freeze evidence thaw tier")

tobiu referenced in commit e9c7c44 - "feat(ai): a futile heal target freezes, with evidence and a way out (#17403) (#17404) on Aug 20, 2026, 1:19 PM
tobiu closed this issue on Aug 20, 2026, 1:19 PM