fix(company): stop failed children from wedging the tree and soften the dispatch guard
Root-cause fix for the project-4444 class of deadlocks: any FAILED/ CANCELLED work item with downstream dependents used to block its whole tree forever, because the advancement gate required all dependencies to be APPROVED and nothing ever propagated or triaged failures. Settlement mechanism (work_item_transition.py): - compute doomed set (FAILED/CANCELLED seeds contagious through hard deps); settlement-released cards are treated as alive - three-way advancement gate: all-approved (unchanged) / settled-with- failures releases the nearest decision-capable card (manager parent -> failure-triage synthesis turn, rollup delivery/aggregate -> READY) with an atomic dependency_settlement stamp; released cards never oscillate back - claim/park/resume/dispatcher-tick all honor the stamp: runnable gates admit settled failed+stuck deps, parking excludes settled deps and re-arms triage when a failure raced the park, engine resume no longer re-locks released cards, the dispatcher tick releases rollup cards created after the failure - settlement cascade: once the settled card is APPROVED, stuck children the manager did not rebuild are cancelled (transitive closure over stamped stuck seeds, retried until every cancel lands) - info-class deps never block settlement; adaptive runnable gate now shares DEPENDENCY_CLASS_DEFAULT with the release gates Dispatch guard (company_mode.py): - NO_DELEGATION_JUSTIFICATION parsing tolerates markdown decoration (bold/lists/quotes/full-width colon) and rejects placeholder echoes across all artifact/metadata/content channels - retries exhausted no longer FAILs the work item: dispatch is a soft constraint, so the turn output is accepted as normal completion and annotated via manager_dispatch_guard_unresolved; the reminder loop is unchanged, and the mutation flag is now reset per turn so one past delegation can never mute future reminders - manager board context now surfaces failed/cancelled children with their preserved output and pending-cancellation stuck list so the triage turn can rebuild, accept partial results, or escalate Self-produced delegation output goes through review (persisted fact, single predicate): - the DONE transition classifies what a dispatch/intake/plan turn actually delivered from store ground truth (live children => delegated, none => self_produced) and persists turn_output_kind/-source on the WorkItem - is_manager_reviewable_turn honors the persisted marker, so the DONE routing, report spawn, report completion and recovery scans all read the same fact — this closes a pre-existing hole where NO_DELEGATION_JUSTIFICATION output auto-approved with no review at all - escalation requires a real agent manager above; top seats reporting to the human owner keep auto-approve (covered by final delivery's human acceptance) instead of minting unclaimable review cards - dispatcher tick reconciles reviewable cards stuck in AWAITING_MANAGER_REVIEW with no live report/review card by rebuilding the report card idempotently (legacy DBs, crash windows) Verified: 4444 tasks.db replay unwedges end to end; real-store report->review chain exercised without lifecycle mocks (mutation check confirms the tests bite); full suite failure set identical to a same-session HEAD baseline run (all remaining failures pre-existing or environment flakes reproduced at HEAD). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -5520,6 +5520,10 @@ class OPCEngine:
|
||||
work_item: DelegationWorkItem,
|
||||
work_item_by_id: dict[str, DelegationWorkItem],
|
||||
) -> bool:
|
||||
from opc.layer2_organization.work_item_transition import (
|
||||
settled_failure_dependency_ids,
|
||||
)
|
||||
|
||||
metadata = dict(getattr(work_item, "metadata", {}) or {})
|
||||
dependency_ids = [
|
||||
str(item).strip()
|
||||
@@ -5529,6 +5533,7 @@ class OPCEngine:
|
||||
if not dependency_ids:
|
||||
return True
|
||||
dependency_classes = dict(metadata.get("dependency_classes", {}) or {})
|
||||
settled_failure_ids = settled_failure_dependency_ids(metadata)
|
||||
for dep_id in dependency_ids:
|
||||
dependency = work_item_by_id.get(dep_id)
|
||||
if dependency is None:
|
||||
@@ -5539,9 +5544,18 @@ class OPCEngine:
|
||||
continue
|
||||
if dep_class == "soft":
|
||||
if dep_phase not in DONE_PHASES and dep_phase not in IN_PROGRESS_PHASES:
|
||||
if dep_id in settled_failure_ids:
|
||||
continue
|
||||
return False
|
||||
continue
|
||||
if dep_phase != Phase.APPROVED:
|
||||
# Failure-triage release: the frontier pass released this
|
||||
# card over the dep (dependency_settlement stamp). Stop/
|
||||
# resume must not regress a released triage card back into
|
||||
# WAITING_* — with the failed dep already terminal, no
|
||||
# later event would ever wake it again.
|
||||
if dep_id in settled_failure_ids:
|
||||
continue
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
Reference in New Issue
Block a user