Repository navigation
P439 derive one sibling UnitState per unit - #2488
Conversation
There was a problem hiding this comment.
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
left a comment
There was a problem hiding this comment.
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.
-
src/agent_comms/ui_model/unit_state.py:285-290
commander_pings(self, todos, states)importsagent_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. -
src/agent_comms/ui_model/unit_state.py:311-313
more_urgent_thanhas no caller in src. Dead code. Delete it. -
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, andUnitState.receivingis a new property restatingThreadStatus.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: deleteUnitState.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. -
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() chainsobservedinto its output and CommanderObservation.read (commander_heartbeat.py:76) and wake (:54) strip them again withnot 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 requiredSequence[UnitState]argument (callers that have none call UnitState.current themselves), never chains them into its result, and returns only scanned rules. DeleteStandingRule.unit_statesand bothnot rule.unit_states()filters; the heartbeat already holdsstatesseparately. -
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 withrule.unit_states()to get back UnitState. HuntDue.found'sif observed:repeats the empty-means-absent sentinel from 4. Fix: annotateobserved: Sequence[UnitState], iteratestate.readable_units()directly, drop theif observed:branch (with 4 fixed, observed is always the real frame). -
src/agent_comms/ui_model/unit_state.py:263-264
current()sorts bytype(state) is memberover the member list: an exact-type switch over the family. Fix:sorted(states, key=lambda state: members.index(type(state)))withmembers = cls.worst_first()once, or derive per member and concatenate inread. Notype(x) iscomparison. -
src/agent_comms/commander_parity/deletion.py:91 still writes
(root / "commander.json"). You migrated storage.json on the line above toStorageRule.path_in(root)and declaredCommanderConfig.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
left a comment
There was a problem hiding this comment.
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:
- 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.
- IMPL-3 at
src/agent_comms/ui_model/unit_state.py:182and:190.UnitBlock.acceptsdecides the block withisinstance(generation, RetryableGeneration)andisinstance(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).
ThenBlocked.deriveasks the generation and theacceptsstaticmethods go away. - The new file imports two modules the virtual bus plan deletes:
bus_page_index(unit_state.py:19) andwire_log(:45). The PR moved them over from the old reader, which is relocation rather than new dependence, but #2549 deleteswire_log.pyright now. Whichever lands second breaks. Read unanswered asks through #2549's replacement reader once the batch lands, and do not polishUnansweredUnitAsks.indexedbefore 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.
168b269 to
95443c0
Compare
95443c0 to
2f2f025
Compare
2f2f025 to
66102aa
Compare
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>
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
P439 — shared unit frame, review repair
Head:
66102aae9da86a0e06ae2b166eb2ff5cfca8eaba, rebased onto main6b2b32ad11b1f0523b775d105da57a105d19d1c1; 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
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.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.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 withPublicationActivationBlocked: 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.063f86b9e): only two positive deltas remained: LintFindings in vibe_check +1 and Unformatted in questions +1. The other earlier positive deltas were gone.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
Deleted methods — main coordinates at 3754641
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
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:
commander/questions.pywhen adopting the new bus ABI; no old-module alias or duplicate reader.