Skip to content

P439 derive one sibling UnitState per unit - #2488

Merged
trissim merged 12 commits into
mainfrom
fix/p439-unit-state
Oct 22, 2026
Merged

trissim merged 12 commits into
mainfrom
fix/p439-unit-state

Conversation

@trissim

@trissim trissim commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

P439 — shared unit frame, review repair

Head: 66102aae9da86a0e06ae2b166eb2ff5cfca8eaba, rebased onto main 6b2b32ad11b1f0523b775d105da57a105d19d1c1; remote equality verified. Draft after review send-back; auto-merge off. New-head hosted run 39697374977 is in progress. Source delivery claims do not establish merged or live-installed acceptance.

Changes

  • One declaration-ordered UnitState family. Cards and scanned rules remain separate; changed and unchanged heartbeats render every card once.
  • Captured ThreadStatus owns receiving. Capacity iterates captured cards, not a newer registry thread list: a fork appearing during a read cannot produce a missing-key crash. Main Only working forks share the commander's load #2586 supersedes Forking relieves load: overload is judged per reader, and every live fork counts #2580: only working forks share the load. UnitObservation captures AgentActivity.is_working using the initial registry snapshot; Load consumes that captured result, not a second status/activity query. Per-reader load/share is preserved. No UnitState receiving flag or Detached probe.
  • Explicit Sequence[UnitState] on observed scans. IndependentStandingRule owns the independent adapter. StandingRule.broken returns only scanned rules. HuntDue has no missing-frame fallback. UnitState.current/observations require a PullRequests instance; register(todos) extends that same cache. The class-as-sentinel defaults and UnitState.violations are deleted; all production/test callers migrated, no shim.
  • GoalSnapshot.execution owns block/runnable decisions. GoalExecution.runnable defaults false and RunnableExecution overrides true; StalledOnNothing calls that behavior rather than testing its type. Local UnitBlock/UnresolvedUnitBlock/RetryableUnitBlock and automatic enforcement are deleted. No automatic reissue was relocated.
  • OwedAnswer owns the standalone message gate; answering messages does not classify every checkout/runtime. Heartbeat answer rules reuse the supplied frame. Main's all-owed-message gate and answered_by behavior remain.
  • UnansweredUnitAsks, SenderMessages and UnansweredBurst now live once in commander/questions.py; old definitions are deleted, not re-exported. This breaks the circular standing-rule/unit-observation import. SenderMessages/UnansweredBurst class ASTs equal the published c151fc8 versions. AnswersTo remains in standing.py for the bus integrator.
  • Parity observes the actual scan_observed return, using an explicit frame. Config paths derive from CommanderConfig.path_in. Copied-bus fixtures declare their protocol and derive their plan module from its existing owner.

Patterns: IDEN-5 (presence rereads), IMPL-3 (type probes), MEMB-4 (durable filename copy), IMPL-12 (competing classification/reading paths).

Verification and remaining work

  • Review repair before final rebase: 33 passed in 15.48s, native Blocked and runtime heartbeat cases excluded. After integrating main Only working forks share the commander's load #2586's working policy, 9 capacity cases passed in 5.32s, using real locally acquired turn leases, not a native launch or a patched method. Counts overlap; do not sum. Final-head combined confirmation: 34 executed, 34 passed, 4 deselected in 46.50s; one fixture rewrite warning. Deselected cases are the native Blocked scenario and three runtime heartbeat cases. Command used timeout300, faulthandler_timeout60, -n0 and a private persistent basetemp.

  • Six affected source files pass direct mypy, including goal_presentation. Ruff F/B018 passes. UnitState/questions format checks pass; vibe_check still has existing whole-file formatting debt (not claimed clean).

  • Earlier head 95443c0c5: 15 non-native checks passed in 17.25s (capacity, storage, parity and the preserved human-stop preflight). No native launch was requested.

  • Post-Forking relieves load: overload is judged per reader, and every live fork counts #2580 focused batch: 51 passed, 1 failed, 1 deselected. The failure was the fresh CLI fixture's missing private protocol marker.

  • After declaring that marker and migrating parity's scan contract: 15 passed in 31.11s, including that CLI case, capacity, storage and parity checks. These overlap the earlier batch; do not add the counts.

  • Earlier question/frame batch: 34 passed, 4 deselected. Copied-bus real watcher entrypoints: 1 passed in 5.87s.

  • Direct mypy check of questions, vibe_check, unit_state, standing_rules and parity measurements: 5 source files clean. The broader order.py invocation still reports existing-looking errors; no whole-project clean claim or main-control claim.

  • Ruff F/B018 checks passed. Commander clarification 17275 removes the mistaken local-format hold: a local source edit has no agent receiver. Fresh source corrections pass Ruff and the questions-module format check; its AST is unchanged. The uncertain technical DM was not replayed. After reading retained DM history and finding it absent, a fresh current-head question-reader ABI handoff was sent to claude-ai as requested in 17439 (bus17556). No native execution was run for this correction.

  • Read-only actual saved-root frame at 3240057d8: 45 distinct cards in 16.787s, then capacity 21 receiving forks, load 65, 3 per reader. Earlier full CommanderObservation read: 43 cards/78 rules in 180.671s. No heartbeat publication, enforcement, merge recording or new agent input was sent by these reads. These are source/live-data results, not installed acceptance or a full-speed claim.

  • Native qualification needs an exact-head runtime, not a source checkout. The P447 follow-up is merged in the base, but its installed-base proof is not proof of this uninstalled branch. No package guard was bypassed and no further native tests ran locally.

  • Smoke at 063f86b9e: 24 passed, 1 failed in 27.45s. The real-record Blocked case stopped at setup with PublicationActivationBlocked: private owner launch needs this installation's prepared native package. That is the runtime guard working, not a demonstrated startup regression or native qualification. Receipt: final-frame-tests.log. No source-only native reruns. Actual live-installed heartbeat/DM/UI acceptance remains outstanding. No local ratchets or full suite were run.

Evidence under /home/ts/.cache/agent-scratch/unit-unitstate/: main-2580-tests.log, protocol-scan-tests.log, scan-owner-types.log, final-names.log, main-2580-live.log, qualified-live-read.log, qualified-owners-after.json, main-method-inventory.json, current-method-inventory.json. AST inventory parsed 431 main/433 branch production modules with zero omissions; dynamic binding resolution remains OPEN. The broad c849 comparison also contains main's unrelated deletions; those are not claimed as P439 work.

Hosted debt-src before / after

Read using gh run view --log-failed, not a local ratchet.

  • Run 39653386427 (2700b72): LongBooleanChain +1; BooleanChainTerms in unit_state +5.
  • Run 39690399413 (c151fc8): FunctionLocalImport +1 each in standing/unit_state; LintFindings +1 each in vibe_check/unit_state; TypeErrors +2 order/+1 vibe_check; Unformatted +1 unit_state.
  • Run 39694694392 (063f86b9e): only two positive deltas remained: LintFindings in vibe_check +1 and Unformatted in questions +1. The other earlier positive deltas were gone.
  • Both remaining source findings are corrected in 95443c0c5, folded into the semantic checkpoint rather than a formatting-only commit. Local Ruff/format and AST equality checks pass. Hosted after: run 39695464904, debt-src PASS on 95443c0. Smoke, native-scripts, debt-tools and install-rehearsal also pass on 95443c0 (run39695464904 completed successfully). The rebased 2f2f025 also has a successful hosted run39696254133. Neither qualifies the newer 66102aa; run39697374977 is in progress. No live-installed acceptance is claimed.

Review repair evidence

  • Before/after AST uses the existing audit Package: 433 production modules, 472 test modules, zero parse omissions. Paired Toad at bd75da79a: 283 modules, zero omissions and no UnitState/PullRequests/GoalExecution consumers. Dynamic binding resolution remains OPEN.
  • Before: one consumer isinstance test on RunnableExecution; two class-as-sentinel defaults. After: execution.runnable() dispatches to the owning family; both required cache parameters have no defaults. Source calls and declarations are in review-r-ast-before.log/review-r-ast-after.log.
  • Deleted UnitState.violations (branch2f2f02508 ui_model/unit_state.py:222); every inherited test caller migrated to captured-frame reads. PullRequests.call (todos.py:557) is renamed register, not a behavioral deletion.
  • Review17559's request to remove unchanged-beat cards is declined against order90da4d304014's explicit run: “the heartbeat lists every unit's state once per pass.” The conflicting docstring is corrected, and the actual unchanged-beat test passes. Reviewer informed in17579.
  • Evidence: review-r-final-tests.log (34/0,4 excluded), review-r-final-types.log (6 source files clean), review-r-final-lint.log (F/B018 pass; UnitState/questions formatted, existing vibe_check formatting not claimed clean), review-r-ast-{before,after}.log, review-r-paired-ast.log. Basetemp directories pytest-working-frame and pytest-review-final are unit-unitstate-owned disposable synthetic test output; concise logs are retained.

Deleted methods — main coordinates at 3754641

Method Original source
CommanderConfig.root_path src/agent_comms/commander/config.py:95
HolderCannotRun.scan_owner src/agent_comms/commander/standing.py:90
HolderCannotRun.stalled src/agent_comms/commander/standing.py:95
HolderCannotRun.found src/agent_comms/commander/standing.py:99
HolderCannotRun.violations src/agent_comms/commander/standing.py:102
Detached.stalled src/agent_comms/commander/standing.py:121
Detached.found src/agent_comms/commander/standing.py:125
Detached.line src/agent_comms/commander/standing.py:128
GoalCannotRun.violations src/agent_comms/commander/standing.py:142
GoalCannotRun.line src/agent_comms/commander/standing.py:150
UnitAskUnanswered.asks src/agent_comms/commander/standing.py:327
CheckoutUnreadable.violations src/agent_comms/commander/standing.py:495
CheckoutUnreadable.line src/agent_comms/commander/standing.py:505
VibeCheck.violations src/agent_comms/commander/vibe_check.py:186
StandingRule.violations (adapter moved) src/agent_comms/standing_rules.py:36
ReasonedStandingRule.violations src/agent_comms/standing_rules.py:182
FirstCallMissing.conditions src/agent_comms/standing_rules.py:229
FirstCallMissing.line src/agent_comms/standing_rules.py:240
OwnerIdle.violations src/agent_comms/standing_rules.py:252
OwnerIdle.began src/agent_comms/standing_rules.py:265
OwnerIdle.line src/agent_comms/standing_rules.py:268

Relocated unchanged reader implementations, not behavioral deletions: UnansweredUnitAsks.indexed at standing.py:202, SenderMessages.indexed at :221, UnansweredBurst.indexed at :236.

Earlier requested deletion inventory — coordinates at 2700b72

Method Original source
StandingRule.violations (adapter moved) src/agent_comms/standing_rules.py:37
StandingRule.unit_states src/agent_comms/standing_rules.py:54
StandingRule.prepared src/agent_comms/standing_rules.py:127
ReasonedStandingRule.violations src/agent_comms/standing_rules.py:180
UnitObservation.presence (now captured field) src/agent_comms/ui_model/unit_state.py:124
UnitState.prepared src/agent_comms/ui_model/unit_state.py:237
UnitState.receiving src/agent_comms/ui_model/unit_state.py:325
UnitState.unit_states src/agent_comms/ui_model/unit_state.py:328
UnitState.more_urgent_than src/agent_comms/ui_model/unit_state.py:355

Additional obsolete local block methods removed since c84961c: UnitBlock.accepts/enforce at unit_state.py:171/:173; UnresolvedUnitBlock.accepts at :181; RetryableUnitBlock.accepts/enforce at :189/:192; Blocked.default_action/detail/enforce at :423/:426/:430. The old unit_state.UnansweredUnitAsks.for_orders/indexed at :63/:83 were replaced by the shared question owner, not retained as a second reader.

Assumed:

  • Local-source outcomes follow commander clarification 17275. The technical send remains uncertain and is not replayed.
  • Fresh pushed-head runtime installation/DM/open-tab acceptance remains with the merger and existing UI/runtime owners; local source checkouts do not satisfy the runtime-prefix contract.
  • The Virtual bus batch: W, B1, B2, C, D and E integrated #2549 integrator migrates the existing question family at commander/questions.py when adopting the new bus ABI; no old-module alias or duplicate reader.
  • Original sessions, stores and UNKNOWN inputs remain preserved. Owned scratch stays at the named persistent path; no new Python environment or installed-package change was made.

@trissim
trissim marked this pull request as ready for review October 21, 2026 17:59
Copilot AI balanced review requested due to automatic review settings October 21, 2026 17:59
@trissim
trissim enabled auto-merge October 21, 2026 17:59

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@trissim trissim left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SEND BACK (connector review, head 38f5f5d).

Ownership: this PR owns UnitState. P439's order bans "one state subclassing another state it is not" and orders flat siblings in registry order; #2469 (P433, ce22c97) chains Blocked < Detached < AwaitingCommander < ... < Idle, which is exactly that ban, and has no Unreadable. #2469 drops its unit_state.py. The flat family here is the right shape. The defects below still block it.

Hosted debt-src FAILS on this head. Fix it; a red debt gate is not mergeable.

  1. src/agent_comms/ui_model/unit_state.py:285-290 commander_pings(self, todos, states) imports agent_comms.commander_pings, a module that does not exist on this branch. Dead method, untyped parameters, ImportError the moment anything calls it. Your own P433 hand-off says the stub was to be removed here. Fix: delete the method.

  2. src/agent_comms/ui_model/unit_state.py:311-313 more_urgent_than has no caller in src. Dead code. Delete it.

  3. src/agent_comms/commander/capacity.py:28-31 plus unit_state.py:275-277. The order says Load's "receiving test at :29 goes". It did not go: it now reads states[...].receiving, and UnitState.receiving is a new property restating ThreadStatus.receiving (presence). Second reader of the registry's fact, and Load now pays a full UnitState derivation (git reads, transcripts) to get a bit the registry already had. Fix: delete UnitState.receiving; Load counts a fork as capacity when its derived state is not the Detached member (not isinstance(states[thread.incarnation], Detached)), i.e. the family decides, no receiving test in capacity.py.

  4. src/agent_comms/standing_rules.py:126-138 broken(..., observed=()): if not observed: uses the empty tuple as a "not supplied" sentinel, but an empty tuple is also the legitimate result for a commander with no units. In that case every heartbeat pass re-derives UnitState.current a second time. Also a function-local import of ui_model from standing_rules. Then broken() chains observed into its output and CommanderObservation.read (commander_heartbeat.py:76) and wake (:54) strip them again with not rule.unit_states(). That is an isinstance(rule, UnitState) test restated as a method (standing_rules.py:53, unannotated) and a pointless round trip. Fix: broken() takes the states as a required Sequence[UnitState] argument (callers that have none call UnitState.current themselves), never chains them into its result, and returns only scanned rules. Delete StandingRule.unit_states and both not rule.unit_states() filters; the heartbeat already holds states separately.

  5. src/agent_comms/standing_rules.py:39, :180, :247 and src/agent_comms/storage_rules.py:347-351: scan_observed(..., observed: Sequence[StandingRule]) erases the type and then re-probes it with rule.unit_states() to get back UnitState. HuntDue.found's if observed: repeats the empty-means-absent sentinel from 4. Fix: annotate observed: Sequence[UnitState], iterate state.readable_units() directly, drop the if observed: branch (with 4 fixed, observed is always the real frame).

  6. src/agent_comms/ui_model/unit_state.py:263-264 current() sorts by type(state) is member over the member list: an exact-type switch over the family. Fix: sorted(states, key=lambda state: members.index(type(state))) with members = cls.worst_first() once, or derive per member and concatenate in read. No type(x) is comparison.

  7. src/agent_comms/commander_parity/deletion.py:91 still writes (root / "commander.json"). You migrated storage.json on the line above to StorageRule.path_in(root) and declared CommanderConfig.filename, then left the literal beside its owner. Fix: CommanderConfig.path_in(root).write_text(...).

Not a defect, noted: unit_state.py:18,43 imports bus_page_index and wire_log, both on the virtual-bus deletion list. That is UnansweredUnitAsks moved from commander/standing.py, no new coupling; it goes when phase B/C delete those modules.


Generated by Claude Code

@trissim trissim left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head-review (review-3): sent back at c84961c.

The UnitState family is the right structure: one sibling per state, each owning its matches/derive, the order taken from the registry, and standing.py down 225 lines. Credit for that. It still does not land, for three reasons:

  1. It conflicts with main and has never run CI (mergeStateStatus DIRTY, no checks). Merge origin/main, resolve, and push. A draft that has never had a hosted run cannot be reviewed for delivery.
  2. IMPL-3 at src/agent_comms/ui_model/unit_state.py:182 and :190. UnitBlock.accepts decides the block with isinstance(generation, RetryableGeneration) and isinstance(goal.state, BlockedState), from outside the GenerationState family.
    Slop: pattern IMPL-3; fact: which UnitBlock a goal generation produces; at: unit_state.py:182; derive: a method on GenerationState (each member names its block member; RetryableGeneration names RetryableUnitBlock, the base names UnresolvedUnitBlock); by: src/agent_comms/goal_generation.py (RetryableGeneration).
    Then Blocked.derive asks the generation and the accepts staticmethods go away.
  3. The new file imports two modules the virtual bus plan deletes: bus_page_index (unit_state.py:19) and wire_log (:45). The PR moved them over from the old reader, which is relocation rather than new dependence, but #2549 deletes wire_log.py right now. Whichever lands second breaks. Read unanswered asks through #2549's replacement reader once the batch lands, and do not polish UnansweredUnitAsks.indexed before then (owner-authority: never polish what a plan deletes).

Minor: Spinning.matches filters with isinstance(verdict, SendBackVerdict) (unit_state.py:505). Ask the verdict family (verdict.sends_back) instead; the same IMPL-3 shape, so fix it in the same change.

@trissim
trissim force-pushed the fix/p439-unit-state branch 6 times, most recently from 168b269 to 95443c0 Compare October 21, 2026 23:45
@trissim
trissim marked this pull request as ready for review October 21, 2026 23:48
@trissim
trissim force-pushed the fix/p439-unit-state branch from 95443c0 to 2f2f025 Compare October 21, 2026 23:53
@trissim
trissim marked this pull request as draft October 21, 2026 23:59
@trissim
trissim force-pushed the fix/p439-unit-state branch from 2f2f025 to 66102aa Compare October 22, 2026 00:03
trissim and others added 4 commits October 21, 2026 20:09
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An unchanged frame carries no new card, so repeating every unit's card
restated the previous changed beat. The unchanged beat is now one line,
and the docstring keeps its original contract instead of being rewritten
to excuse the repetition.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The debt-src ratchet grew two lint findings in capacity.py: the todos
import out of order and a 101-character return line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@trissim
trissim marked this pull request as ready for review October 22, 2026 00:16
@trissim
trissim enabled auto-merge October 22, 2026 00:16
trissim and others added 3 commits October 21, 2026 20:28
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Detached.unread no longer takes part in equality, so a new unread
message does not make every card look changed and resend the frame.

Each order now declares its claims without reading the bus, and
UnitState.observations reads every claim original in one checked read,
opened only when a claim exists. UnitObservation.questions is that
captured tuple, so one unit's questions can no longer fail on their own,
and Unreadable keeps the owed questions instead of overriding them to
nothing. The heartbeat and the answer gate now read owed questions from
the same place.

Deleted StorageViolation.violations (no caller; parity reads
scan_observed), the parity row wording that named it, and the one-caller
UnitState.asks forwarder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…vibe check reads no unit frame

Main (#2598) made Load the debt itself: no forks field, no share. Load.from_states existed only
to count working forks from the captured unit frame, so it goes, and capacity.py is main's.
With it gone no Degradation reads the frame: OverloadDegradation reads Load.of, and
AnswersOwedDegradation reads OwedAnswer.overdue, the frame-free owed read the gate already uses.
Degradation.found drops the observed parameter and VibeCheck is an IndependentStandingRule, so
main's ChokeholdAnswer.blocking calls found as written. The capacity tests are main's; the
fork-counting tests went with the forks.

Also: ruff format unit_state.py and test_unit_state.py, wrap this PR's long test lines, and
point the two remaining CommanderConfig.root_path test callers at path_in, which replaced it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AeUaZjvChAcb6qAd6zPYmw
@trissim
trissim added this pull request to the merge queue Oct 22, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 22, 2026
@trissim
trissim merged commit 0876e54 into main Oct 22, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants