Skip to content

Use native Codex v2 plaintext assignments for smart routing - #1052

Merged
lilly-luo merged 40 commits into
mainfrom
JJ27/codex-v2-readable-routing
Nov 3, 2026
Merged

lilly-luo merged 40 commits into
mainfrom
JJ27/codex-v2-readable-routing

Conversation

@JJ27

@JJ27 JJ27 commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

What did you change, and why?

Native Codex v2 can encrypt delegated assignments before PreToolUse, leaving Smart Router with only a task label. Smart-routed launches now enable readable native v2 assignments and matching root/child guidance through session-only -c overrides. The router and child receive the complete assignment. This adds no gateway proxy; subagent-only routing launches the native TUI, and first-prompt routing uses the existing app-server interposer.

Compatibility change for existing managed workspaces: Codex 0.145–0.163 users with managed smart routing enabled must upgrade to 0.164.0 or newer, or explicitly run ug codex --disable-smart-routing. Known-old versions get the existing upgrade offer and are checked again before launch. Declined, failed, or insufficient upgrades keep routing blocked; UG does not silently disable it. The README highlights this migration in the launch instructions and compatibility note. Ordinary launches retain the 0.145.0 minimum; 0.154.0 remains the general CI baseline, with 0.165.0 limited to native routing cases and CUJ4. Polly uses 0.165.0.

An inconclusive version probe gets one longer attempt. If the version remains unknown, UG reports codex --version diagnostics without offering or running an update. The native-v2 overrides intentionally take precedence over saved values for the session; tests preserve conflicting saved settings byte-for-byte.

Denied session-control reads and writes report native-approval guidance without changing controls. Other write errors retain their cause. Codex decision records store an assignment hash and character count, with each rationale capped at 1,000 characters. Router/hook text stays complete. Rationale text may still quote sensitive content; Claude's audit format is unchanged. Hook input preserves UTF-8, the integration driver approves only exact local toggle commands, and persistent-state comparisons exclude disposable Codex executable links.

The Codex integration driver waits for an initialized session before typing. The native startup draft can appear before hook review; the driver handles visible review and requires the model/directory footer, preserving the task and plaintext assertions.

Independent Claude recovery fixes remain in #1397. Land that companion first to retain its first-prompt recovery in the combined rollout.

How do you know it works?

  • 1,538 local component and contract checks passed with zero failures, errors, or skips. This covers managed Codex 0.145/0.163 upgrade, decline and explicit-disable paths; unknown-version behavior; raw and wrapped write failures; audit bounds and correlation; native override precedence; and CLI/integration contracts. Repository Ruff, format, and Ty gates passed.
  • Regression checks reproduced the write-error, unknown-version update, and plaintext-audit issues before the fixes. Updated integration assertions correlate digests with exact native assignments and real router requests; full hook/child plaintext and completed-child assertions remain required.
  • Prior native verification exercised Codex 0.164, 0.165, and 0.166 on macOS and Windows. Existing Linux evidence verifies readable assignments at the hook, router, and native child. These runs predate the review fixes.
  • The startup repair passed 355 component/contract/runner checks with zero failures, errors, or skips, plus repository Ruff, format, and Ty. Offline PTY regressions reproduce premature readiness before the repair and cover delayed hook review and the 0.154/0.165 status layouts. The original CI failure occurred before task submission; all 18 other Linux routing cases passed.
  • New-head CI remains required. No live evaluation or workflow retry was launched for this repair. The additional check over unchanged scripts/run_integration.py retains two pre-existing TID251 findings outside the repository's just lint gate.

@JJ27
JJ27 force-pushed the JJ27/codex-v2-readable-routing branch from f73a23a to 3968193 Compare October 8, 2026 20:48
arnavsinghvi11 and others added 4 commits October 8, 2026 21:34
Adapt the existing collaboration namespace on the Responses wire so native
v2 tools can supply readable assignments to the existing routing hook.
Restore native response namespaces and plaintext markers, preserve replay,
and reject encrypted or malformed collaboration payloads.

Keep the transport and provider URL session-local. Add protocol, lifecycle,
streaming and installed-agent coverage without changing the native spawning
workflow or introducing another routing tool or model turn.
@JJ27
JJ27 force-pushed the JJ27/codex-v2-readable-routing branch from 042c486 to b91d692 Compare October 8, 2026 21:38
@JJ27 JJ27 changed the title fix: preserve readable assignments in native Codex v2 routing Preserve readable assignments in native Codex v2 routing Oct 12, 2026
@JJ27
JJ27 marked this pull request as ready for review October 12, 2026 17:54
Copilot AI balanced review requested due to automatic review settings October 12, 2026 17:54

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.

Copilot AI balanced review requested due to automatic review settings October 13, 2026 16:26

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.

Copilot AI balanced review requested due to automatic review settings October 19, 2026 04:02

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.

Copilot AI balanced review requested due to automatic review settings October 19, 2026 17:48

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.

Copilot AI balanced review requested due to automatic review settings November 2, 2026 19:23

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 insufficient remaining quota. Try requesting a review at a lower effort level.

Comment thread .github/workflows/integration.yml Resolved
Comment thread src/ucode/agents/codex.py Resolved
Comment thread src/ucode/agents/codex.py Resolved
Comment thread src/ucode/smart_routing/claude_pty.py Resolved
Comment thread src/ucode/smart_routing/claude_pty.py Resolved
Comment thread src/ucode/smart_routing/v2.py Resolved
Copilot AI balanced review requested due to automatic review settings November 2, 2026 19:28

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 insufficient remaining quota. Try requesting a review at a lower effort level.

@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

Polly AI Review

Blocking issues

  • Unrelated Claude recovery fix — src/ucode/smart_routing/claude_pty.py:513: can we split the Claude /model Escape-race fix and its tests/docs? It repairs an existing first-prompt flow; the toggle journeys bypass this PTY, and Codex launches separately.

Security vulnerabilities

None demonstrated in the changed code. The session-control read failure aborts before writing, and native approval remains required. Native security behavior was not independently exercised.

Non-blocking notes

  • tests/test_codex_smart_routing_v2.py:268: can we extend the existing launch assertions to check all three v2 overrides, and add a case proving Codex 0.163 is rejected before execution?
  • src/ucode/smart_routing/session_env.py:78: can we add a permission-denied read case to the session-control tests and assert that controls remain unchanged?
  • tests/integration/test_ug_smart_routing_hooks.py:162: can we test wrong commands, non-local environments, and incomplete approval dialogs offline in _codex_command_approval_pending?
  • src/ucode/agents/codex.py:1610: can we reuse the install-time upgrade prompt for the routing minimum? Codex 0.145–0.163 passes installation checks, then routed launch fails without that offer.

Approach

The Codex approach fits the existing launch overlay, native hooks, and direct-TUI/interposer split. No new proxy is needed. Reusing the existing upgrade flow would make version handling more consistent.

Summary

Diff size (computed): 24 files; +689 / -70 lines (759 changed text lines); 0 binary files.

Category Files Added Deleted Share of changed text lines
Tests and test support 13 597 51 85.4%
Documentation 3 17 1 2.4%
Dependencies and lockfiles 0 0 0 0.0%
Implementation / other 8 75 18 12.3%

Shares use additions + deletions, including generated text and lockfiles. Binary files have no line count. Tests include fixtures/helpers under test paths and colocated test/spec files; categories are path-based, not coverage measurements.

Changes

The intended outcome is readable native Codex v2 assignments for smart routing while preserving user settings and native permissions.

The diff includes launch configuration and guidance, a routing-specific version floor, UTF-8 hook input, approval guidance, workflow pins, test support, regression coverage, and documentation. It introduces no dependency or lockfile changes.

The Codex changes are proportional to that outcome. Separating the independent Claude recovery production/test/documentation hunks is the concrete reduction; the test-to-code ratio alone does not justify trimming coverage.

Tests

Test-by-test assessment

All changed test/support files are assessed below. No test function is deleted; the local snapshot helper is moved into shared support.

Test / case Behavior protected Layer Needed? Action / rationale
tests/README.md — changed coverage descriptions Documents assignments, snapshots, approvals, and recovery coverage Documentation keep Retain descriptions for the final scope; move the Claude recovery text with its separate fix.
tests/e2e_cuj/test_cuj4_smart_routing.py::_task_inference_request Matches exact plaintext NEW_TASK envelopes while preserving encrypted-child and user-prompt matching CUJ evidence helper keep Required format adaptation. Negative envelope cases belong in component coverage, not additional live journeys.
tests/integration/README.md — changed coverage/run guidance Documents native journey selection, approval handling, snapshot exclusions, and execution limits Documentation keep Retain accurate run guidance; move the independent Claude recovery paragraph.
tests/integration/test_ug_codex_managed_model_discovery.py::_codex_state_and_agent_files and _assert_rejected_before_codex_started Uses one persistent-state snapshot policy Integration support keep The removed local helper is replaced by the shared implementation; rejection assertions remain.
tests/integration/test_ug_codex_managed_model_discovery.py::test_case_04_managed_codex_rejects_provider_override_after_configure Rejected provider overrides leave persistent settings unchanged CLI/configuration integration keep Existing assertions remain meaningful after helper extraction.
tests/integration/test_ug_codex_managed_model_discovery.py::test_case_06_managed_codex_rejects_model_location_override_after_configure Rejected model-location overrides leave persistent settings unchanged CLI/configuration integration keep Protects a distinct public override input without duplicating setup.
tests/integration/test_ug_codex_v2_routing.py::_seed_user_owned_native_codex_settings Supplies a real v2 catalog and user-owned observer hook Integration setup keep Original configuration bytes support preservation assertions across the native launch.
tests/integration/test_ug_codex_v2_routing.py::test_ug_codex_native_v2_plain_assignment_routing Unicode assignment reaches hook, router, and native child; verifies completion, models, and preserved settings Native TUI integration keep Lower-layer hook tests cannot establish native assignment transport or coexistence with a user hook. Reuses existing journey primitives.
tests/integration/test_ug_smart_routing_hooks.py::_codex_command_approval_pending Recognizes only the expected local command in a complete approval dialog Integration parser support keep Retain the helper; add offline rejection and incomplete-dialog cases identified above.
tests/integration/test_ug_smart_routing_hooks.py::_toggle_with_skill and test_smart_router_skill_turns_codex_subagent_routing_off — routing-only, orchestration, managed-default, subagent_only_v0, subagent_only_v1, subagent_orch_v0 Approved off toggle changes controls and produces an unrouted completed child Native TUI integration keep Exact-command approval replaces broad approval; subsequent routing assertions remain. Approval denial is not exercised.
tests/integration/test_ug_smart_routing_hooks.py::test_smart_router_skill_turns_codex_subagent_routing_back_on — the same six variants Routed → off/unrouted → on/routed transition within one session Native TUI integration keep This stateful native skill interaction is distinct from launch-only and session-file checks.
tests/integration/utils/managed.py::codex_state_and_agent_files Excludes disposable executables and expected cache before reading bytes, while retaining persistent files Shared filesystem helper keep Centralizes existing policy and extends the exact directory exclusion to arg0-linux.
tests/integration/utils/terminal.py::AgentTerminal.boot — enter continue handling Dismisses the native update notice without updating the pinned executable TUI support keep Narrow extension of the existing notice handler; actual rendering was not exercised here.
tests/test_agent_codex.py::TestMinimumVersion.test_supported_version — 0.145.0, 0.148.0, 0.163.0 Ordinary launches remain supported below the routing floor; routing is rejected Unit keep Preserves compatibility assertions and adds the adjacent lower routing boundary.
tests/test_agent_codex.py::TestMinimumVersion.test_supported_version — 0.164.0, 0.165.0, 1.0.0 Routing accepts its minimum, integration pin, and later major versions Unit keep Small, reliable checks of the version predicate.
tests/test_agent_codex.py::TestMinimumVersion.test_unknown_version_does_not_block Unknown versions permit ordinary launches but reject routing Unit keep Protects the deliberate difference between ordinary and routed launches.
tests/test_claude_pty_replay.py — module coverage notes Describes settling and bounded recovery Test documentation keep Move with the independent Claude recovery outcome.
tests/test_claude_pty_replay.py::TestSwitchTimeoutFailOpen.test_late_model_completion_cannot_discard_the_replayed_prompt[persisted], [pending-write] Late /model completion cannot discard replay or leave the routed default persisted PTY/process component keep Both persistence orderings assert single submission, restoration, and unrelated-setting preservation; move with the Claude fix.
tests/test_claude_pty_replay.py::TestSwitchTimeoutFailOpen.test_unresponsive_switch_stops_automatic_input[silent], [busy] Silent and continuously rendering commands stop automatic input and defer restoration PTY/process component keep Distinct readiness failures at the appropriate process boundary; move with the Claude fix.
tests/test_claude_pty_replay.py::TestSwitchTimeoutFailOpen.test_switch_timeout_sends_esc_then_pastes_and_submits Cancellation redraw precedes replay and submission PTY/process component keep Modified fake response supports the changed readiness contract; move the modification with the Claude fix.
tests/test_claude_pty_replay.py::_INPUT_BOX_TUI, via _run_input_box_tui Distinguishes standalone Escape from bracketed paste and bounds the fake input loop PTY test support keep Extends existing setup rather than adding another framework. Existing exact-replay, unmangled-timeout, and single-submission consumers remain relevant; move support changes with the Claude fix.
tests/test_codex_smart_routing_v2.py::TestLaunchCodex.test_codex_smart_routing_launch_dispatches_to_v2 — empty and populated tool_args Eligible launches dispatch with the expected model and renderer Component keep Supported-version fixture preserves existing dispatch coverage. Add the overlay and rejection assertions identified above.
tests/test_codex_smart_routing_v2.py::TestLaunchCodex.test_codex_launch_normalizes_cached_bootstrap_model Normalizes cached model IDs for native startup Component keep Version fixture lets the existing assertion reach its intended path.
tests/test_codex_smart_routing_v2.py::TestLaunchCodex.test_startup_config_precedence — existing managed/profile/user, malformed-input, fallback, and custom-home matrix Preserves startup precedence, fallback behavior, and source settings Filesystem/launch component keep Only the supported-version fixture changes; existing distinct precedence cases remain useful.
tests/test_codex_smart_routing_v2.py::TestCustomCatalogModels.test_start_model_comes_from_custom_catalog Custom catalog outranks cached startup models Component keep Supported-version fixture preserves the existing test purpose.
tests/test_cuj_evidence.py::test_cuj_codex_plaintext_child_inference_matches_routed_task[top-level-tools], [responses-lite] Accepts exact assignments and rejects mismatched metadata, missing fields, malformed envelopes, and invalid tools Unit/component keep Appropriate lowest layer for false-positive coverage. Existing tests retain user-prompt and encrypted-child coverage.
tests/test_integration_managed.py::test_codex_snapshot_never_reads_disposable_executables[arg0], [arg0-linux] Disposable executable contents are never buffered Filesystem component keep Read interception catches read-then-filter regressions that result-only assertions would miss.
tests/test_integration_managed.py::test_codex_snapshot_detects_persistent_file_changes — .ucode/state.json, .codex/config.toml, .codex/auth.json, .codex/tmp/keep.txt, .codex/tmp/arg0-linux-other/keep.txt Persistent additions, modifications, and removals remain observable Filesystem component keep Covers both roots, persistent state, ordinary temporary files, and a similarly prefixed directory outside the exclusion.
tests/test_integration_managed.py::test_codex_snapshot_excludes_only_expected_control_plane_cache Excludes expected cache files without excluding persistent state Filesystem component keep Protects the narrow cache exception after helper extraction without another live configure journey.

Coverage limits: existing coverage supports version predicates, startup precedence, request matching, and persistent-state comparisons. Concrete gaps are the exact launch overlay, launch-boundary rejection, permission-denied control reads, and approval-parser rejection cases noted above.

The frozen checksum and both trusted policies were verified. Offline, in-memory checks exercised version predicates, approval parsing, evidence matching, UTF-8 decoding, and toggle configuration. These are not pytest or native-agent passes. PR-head pytest, lint, typecheck, and live runs remain independently unverified; the reported CI results are author-provided evidence. The named revision objects were unavailable locally, although relevant trusted-context blobs matched the diff’s old-side IDs.

For verification in a provisioned PR-head checkout, run:

uv run --no-sync pytest tests/test_agent_codex.py tests/test_codex_smart_routing_v2.py tests/test_claude_pty_replay.py tests/test_cuj_evidence.py tests/test_integration_managed.py

For human verification, run the documented test_ug_codex_native_v2_plain_assignment_routing journey. Separately decline approval for $smart-router off; confirm routing stays enabled and permissions/settings remain unchanged.

Scope

The Claude first-prompt Escape recovery is an independent, pre-existing outcome: src/ucode/smart_routing/claude_pty.py:513 and corresponding changes in tests/test_claude_pty_replay.py:259 and tests/test_claude_pty_replay.py:353. Caller analysis and offline configuration checks confirm the toggle journeys do not enter that wrapper.

The remaining Codex configuration, version pins, UTF-8 input, approval guidance, and integration support fit the stated outcome. No other clearly unrelated changes were found.


Automated review by Polly · workflow run

Copilot AI balanced review requested due to automatic review settings November 2, 2026 20:10

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 insufficient remaining quota. Try requesting a review at a lower effort level.

Comment thread .github/workflows/integration.yml Resolved
Copilot AI balanced review requested due to automatic review settings November 2, 2026 21:16

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 insufficient remaining quota. Try requesting a review at a lower effort level.

@lilly-luo lilly-luo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed at 160d46ac. Lint, ty, and the focused unit suites (test_agent_codex, test_codex_smart_routing_v2, test_smart_router, the integration-evidence tests) pass locally. I didn't run live Codex 0.164+ or the integration/e2e_cuj journeys.

The main open question is the fail-closed rollout for managed smart routing on Codex <0.164 (inline at codex.py:1616). The rest are non-blocking.

Outside the diff: cli.py:2944 (claude_router_hook_cmd) and cli.py:2592 (cowork_hook_cmd) still decode stdin with the locale encoding, which is the same bug this PR fixes for the Codex hook at cli.py:2871. On Windows, a non-ASCII prompt gets swallowed by except ValueError: return. Fine as a follow-up.

Before:

payload = json.loads(sys.stdin.read() or "{}")

After:

payload = json.loads(sys.stdin.buffer.read() or b"{}")

Comment thread src/ucode/agents/codex.py Resolved
Comment thread src/ucode/smart_routing/session_env.py Resolved
Comment thread src/ucode/smart_routing/routing.py Resolved
Comment thread src/ucode/agents/codex.py Resolved
Comment thread src/ucode/smart_routing/v2.py Resolved
Comment thread AGENTS.md Resolved

@lilly-luo lilly-luo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

mainly care about this 1: #1052

Copilot AI balanced review requested due to automatic review settings November 2, 2026 21:31

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 insufficient remaining quota. Try requesting a review at a lower effort level.

Copilot AI balanced review requested due to automatic review settings November 2, 2026 21: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 insufficient remaining quota. Try requesting a review at a lower effort level.

@lilly-luo
lilly-luo added this pull request to the merge queue Nov 3, 2026
Merged via the queue into main with commit 433f553 Nov 3, 2026
233 of 242 checks passed
@lilly-luo
lilly-luo deleted the JJ27/codex-v2-readable-routing branch November 3, 2026 02:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ug-review Run the automated UG review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants