|
| 1 | +# Primary-action instruction selection |
| 2 | + |
| 3 | +[中文镜像](2026-10-27-primary-action-instruction-selection.zh-CN.md) |
| 4 | + |
| 5 | +## Boundary and ownership |
| 6 | + |
| 7 | +This Stage 2B slice removes the Python instruction decision owner while keeping |
| 8 | +caller-visible prose and rendering stable. `projectInteractionExecution` in |
| 9 | +`work_items/interaction_contract.ts` selects the existing mode and its primary |
| 10 | +instruction in the same `work_item.interaction_execution.project` request. |
| 11 | +There is no new operation, handler registration, runtime, provider or store. |
| 12 | +Qualification remains incomplete until the validation holds below are resolved. |
| 13 | + |
| 14 | +Retired Python symbols in `work_items/primary_action.py`: |
| 15 | + |
| 16 | +- `protocol_first_candidate_action`: source precedence, addressed labels, |
| 17 | + capability candidate count, scoped successor and advancement fallbacks. |
| 18 | +- `protocol_replan_action_packet_instruction`: settlement-only, successor and |
| 19 | + generic typed-outcome wording. |
| 20 | +- `resolve_canonical_primary_action`: exact mode prose, fallbacks and child/peer |
| 21 | + lane counts, including the empty-child-list precedence over peer lanes. |
| 22 | + |
| 23 | +`interaction_contract.py::_project_interaction_execution` sends a single optional |
| 24 | +facts/views carrier and the existing `action_selection_context` projection. |
| 25 | +The final contract builder requests and threads the returned instruction through |
| 26 | +`_build_interaction_agent_channel` into `build_primary_action_projection`. |
| 27 | +`unadmitted_action_selection` retains its mode-only preflight and exact result |
| 28 | +shape. The TS owner composes `actionSelectionNeedsRecovery` before reading |
| 29 | +instruction facts; rejected/deferred recovery does not construct executable |
| 30 | +instructions. The existing Python recovery alternative, selection/capability |
| 31 | +channel overrides, blocked-successor override and blocked-wait override remain. |
| 32 | + |
| 33 | +Python retains `protocol_action_text` and `protocol_action_label`: Python `str` |
| 34 | +and nested repr, truthiness, whitespace, label prefixes and code-point limits |
| 35 | +are compatibility codecs, including `...` rather than the trace codec's `…`. |
| 36 | +`primary_action_instruction_facts` transports those views and existing Todo |
| 37 | +eligibility/lifecycle source facts; it does not select an instruction or retry a |
| 38 | +Python fallback. Python transports at most the first selectable row per list |
| 39 | +source, evaluated from the same coerced views; the typed owner applies the same |
| 40 | +predicates and source precedence. Request size is independent of backlog/Todo |
| 41 | +list length. Registered effective actions and agent-scope values come from |
| 42 | +their generated TS exports. Other existing local rule strings keep their exact |
| 43 | +spellings; no shared vocabulary or persisted schema is added. |
| 44 | + |
| 45 | +Python also retains `protocol_monitor_action` and the resolution trace through |
| 46 | +`state_projection.next_action_resolution_trace`. `primary_action` still precedes |
| 47 | +optional `resolution_trace`; channel insertion order and final Python JSON |
| 48 | +serialization are unchanged. Delete the trace/monitor facade only when their |
| 49 | +remaining consumers adopt a complete typed action/trace projection. Delete the |
| 50 | +scalar/label codecs and facts carrier only after the last Python text/label |
| 51 | +consumer exits and a complete typed compatibility codec proves Python coercion, |
| 52 | +Unicode, truncation and trace parity. This slice does not retire those owners. |
| 53 | + |
| 54 | +## Vocabulary budget |
| 55 | + |
| 56 | +The registry's `meaning` says "module_budget counts modules under loopx/ whose text carries the standalone field token" and "the token count stays until Q11 decides whether the syntactic metric replaces it"; this deliberately conservative lexical proxy protects against unexplained growth in potential field readers. |
| 57 | +On the same tracked-source workload, base `be1dc37a0` → `this commit` moves `goal_boundary` lexical counts from Python/TypeScript 30/2 → 29/3 (total 32 unchanged), originally failing `legacy field goal_boundary grew to 3 .ts modules; budget is 2` and retrodiction `(13, 15)` versus frozen `(12, 15)`. |
| 58 | +The moved sentence, `repair goal_boundary.write_scope projection before attempting the selected write`, is byte-identical agent-facing prose, adding no reader, writer or binder; the smoke's prose-versus-reader distinction keeps migration surfaces at Python 16 / TypeScript 1. |
| 59 | +Retaining that instruction, the registry tightens the Python token budget to its measured 29 and increases TypeScript 2 → 3, with zero lexical headroom; migration-surface budgets and their anchor remain unchanged at 16/1, while the registry and `RETIREMENT_ANCHOR` move together in this same commit, as the anchor mechanism requires; the retrodiction baseline is unchanged and the measured result returns to `(12, 15)` (`would_have_caught=12/15`, `caught_baseline=12/15`) once the budget accepts the measured counts. |
| 60 | + |
| 61 | +## Economics and parity |
| 62 | + |
| 63 | +Against the immutable base, the three retired function spans contain 267 physical |
| 64 | +lines (269 including separating lines). Production Python is 185 lines smaller; |
| 65 | +TS grows by 109; net production is 76 lines smaller. The primary-action module |
| 66 | +shrinks 347 → 152 lines. The new Python facts codec is 77 lines plus its |
| 67 | +separators; remaining Python changes are transport/result plumbing. The TS |
| 68 | +helper contains 14 lines of local view decoders (`facts`, `object`, `text`, |
| 69 | +`rows`, `count`), not another RPC surface. The bounded |
| 70 | +companion refactor reuses `action_selection_context` rather than a second |
| 71 | +admission-fact encoder. Broader admission, monitor and trace migration is deferred. |
| 72 | + |
| 73 | +One untimed base/head execution and one logical-request trace per command, |
| 74 | +with isolated synthetic SQLite fixtures and one or three delegation bindings: |
| 75 | + |
| 76 | +| Command | Bindings | Crossings base → head | Stdout bytes base → head | |
| 77 | +| --- | --- | --- | --- | |
| 78 | +| `agent-context --phase before_plan` | 1 | 1 → 1 | 1885 → 1885 | |
| 79 | +| `agent-context --phase after_delegate_result` | 1 | 3 → 3 | 1612 → 1612 | |
| 80 | +| Same result phase with a Turn instance | 1 | 3 → 3 | 2594 → 2594 | |
| 81 | +| `quota should-run` | 1 | 75 → 75 | 25520 → 25520 | |
| 82 | +| `agent-context --phase before_plan` | 3 | 1 → 1 | 2247 → 2247 | |
| 83 | +| `agent-context --phase after_delegate_result` | 3 | 3 → 3 | 1612 → 1612 | |
| 84 | +| Same result phase with a Turn instance | 3 | 3 → 3 | 2594 → 2594 | |
| 85 | +| `quota should-run` | 3 | 75 → 75 | 25904 → 25904 | |
| 86 | + |
| 87 | +All eight normalized JSON outputs match after excluding timestamp keys ending |
| 88 | +in `_at`, `context_id`, `event_id` and `staleness_seconds`. This is equal byte |
| 89 | +count and normalized-content evidence, not literal equality of volatile fields |
| 90 | +or a latency improvement claim. No persisted-format change or data migration. |
| 91 | + |
| 92 | +## Validation and remaining gates |
| 93 | + |
| 94 | +A pre-change synthetic fixture pins 182 cases: all modes, candidate-source |
| 95 | +precedence, fallback and lane-count branches, empty/invalid source shapes, |
| 96 | +booleans, numbers, nested values, whitespace, Unicode/non-BMP and truncation. |
| 97 | +Native tests check those independently pinned instructions and the recovery |
| 98 | +getter sentinel; Python checks codec views, non-JSON `str` and projection order. |
| 99 | +Existing Python CLI/Host integration assertions are retained. Retire the pinned |
| 100 | +Python payload/view fixture when its last codec consumer exits and native |
| 101 | +invariant tests independently cover the same wording/source branches. |
| 102 | + |
| 103 | +The oversized-backlog regression pins 22,000 rows through the public Python |
| 104 | +projection: the lane instruction matches BASE, and rejected selection preserves |
| 105 | +the BASE recovery command with `must_attempt` and `delivery_allowed` both false. |
| 106 | +Encoded instruction facts stay below 16 KiB. |
| 107 | + |
| 108 | +<!-- validation-results --> |
| 109 | +Independent review's rule table found EQUIVALENT selection semantics with one |
| 110 | +DIVERGENT row for oversized eager transport; the bounded first-selectable-row |
| 111 | +carrier fixes it: facts for 22,000 rows shrink 2,354,852 → 959 bytes, with lane |
| 112 | +and rejected-selection recovery outputs byte-identical to base and pinned in |
| 113 | +tests. The fix passes 38 arbitration tests, 37 native tests and 309 Python |
| 114 | +adapter tests, including 182-case typed-instruction parity. The `goal_boundary` |
| 115 | +vocabulary budget decision moves Python/TypeScript 30/2 → 29/3 together with |
| 116 | +`RETIREMENT_ANCHOR` in this commit; the smoke reports |
| 117 | +`semantic-vocabulary-drift-smoke: ok`, architecture tests pass 128/128, and |
| 118 | +retrodiction returns to the unchanged 12/15 baseline. The full TS suite passes |
| 119 | +5,528 tests with 0 failures and 75 PostgreSQL skips. The original full Python |
| 120 | +suite records 27,169 passed, 908 failed, 234 skipped and 8 errors; base replay |
| 121 | +of its 147 failing files records 2,353 passed, 905 failed, 18 skipped and |
| 122 | +8 errors, reproducing 913/916 distinct failure/error IDs under the same guard, |
| 123 | +whose restrictions induced some failures. Of the three head-only failures, |
| 124 | +the two architecture failures are repaired above; the third is |
| 125 | +environment-induced because dashboard dependencies were installed mid-validation, |
| 126 | +while base skips that test. The final-code 44-module sweep has identical sets |
| 127 | +of 84 failures on base and head, zero new failures, 1,871 → 1,880 passes and |
| 128 | +20 skips on each; these scoped runs do not establish a green full Python suite. |
| 129 | +The final canary reports status: `passed`, ok: `true`, merge_gate_passed: `true`, |
| 130 | +manual_holds: `0`, with 0 failures. The four interleaved benchmark runs |
| 131 | +(25 timed runs and 3 warmups per command) retain equal bytes on all eight |
| 132 | +commands and unchanged 1/3/3/75 crossings for both binding counts; every median |
| 133 | +regression is within the stated 5% rule, so byte, crossing and median checks |
| 134 | +pass without a latency-improvement claim. CI-only limits remain a real |
| 135 | +PostgreSQL server, Node 22.22.3, Windows and browser lanes; PostgreSQL skips |
| 136 | +are not passes. Main's inherited failures remain tracked by #7433–#7436 and |
| 137 | +#7389; this validation does not resolve them. |
| 138 | + |
| 139 | +Installed wheel, minimum Node, Windows, real PostgreSQL and full CI are not |
| 140 | +qualified locally; PostgreSQL skips are not passes. The committed-head bilingual |
| 141 | +check and premerge canary remain orchestrator/validation responsibilities. These |
| 142 | +holds do not authorize broader authority/provider changes or weaker acceptance. |
| 143 | +Rollback is a code-only revert; no state rewrite is needed. |
0 commit comments