Skip to content

fix(kanban): C7 slice A durable-state fixes, 10 confirmed findings (t_60634825) - #1373

Merged
Kyzcreig merged 1 commit into
mainfrom
daedalus/t_60634825-c7-slice-a
Sep 28, 2026
Merged

Kyzcreig merged 1 commit into
mainfrom
daedalus/t_60634825-c7-slice-a

Conversation

@ang-fleet-workers

Copy link
Copy Markdown

C7 retro-backfill (durable-state loss), hermes-agent slice A. Card t_60634825, derived from t_fd9d632e.

Tally

  • 10 verified. Each has a regression test that fails on base 7a81d467 and passes on this head.
  • 3 dropped, with no code change.
  • 0 false positives beyond the drops.

Verified and fixed

k orig fix test
k103 #987 _home_session_guarded reads the home before fn() runs and passes it to record_foreign_action as home_before test_k103_session_takeover_records_displaced_home
k105 #953 the circuit-notify latch is written only after a successful send; _run_notify returns a bool test_k105_failed_circuit_notify_is_retried
k107 #1081 a request_changes refusal rolls back a coverage comment it posted on a held run test_k107_rejected_coverage_is_rolled_back
k109 #1050 spawned records boot_id; _spawn_start_token drops a token stamped under another boot test_k109_* (2)
k110 #994 end_orphaned_terminal_runs merges into the existing run metadata test_k110_orphaned_terminal_reaper_keeps_pool_metadata
k112 #980 the unknown-prefix exemption only counts a declaration on the current provider test_k112_* (2)
k114 #1254 strip_overlay restores PATH components the overlay removed test_k114_* (2)
k115 #1035 the desktop cron ticker gets _DesktopCronDispatchGate (serve process_gate admit) test_k115_desktop_cron_ticker_passes_admission_gate
k121 #1014 dashboard create sends the viewer's ?session=, and POST /tasks accepts session_id test_create_from_session_link_lands_in_viewer_home
k138 #1024 a supplied but invalid expected_sha256 is refused instead of being dropped test_attach_path_rejects_invalid_supplied_digest (4)

Dropped

Verification

  • RED: the slice tests ran against base sources via test-gate: 9 failed in test_kanban_c7_slice_a.py, 4 failed for k138, 1 failed for k121. The positive controls (k109 same boot, k112 current provider) passed on base.
  • GREEN: test-gate, 15 slice-file tests and the k121 test passed.
  • Neighbour files: home_session, orphaned_terminal_runs, desktop_cron_ticker_profiles, second_claim_class, review_sendback, termination_identity, process_env_files, review_coverage_gate, pool_ratelimit_gates, fanout_brakes, model_switch_provider_slash_form, dashboard plugin, tools/test_kanban_tools, cron/test_scheduler_provider, dashboard_admin_endpoints, kanban_db, review_lifecycle and worker_gh_lane_scrub all passed, except:
    • test_kanban_db.py::test_guard_stuck_recovery_command_runs_on_each_board fails on base as well (inherited).
    • tests/tools/test_kanban_tools.py fails locally only when run inside a dispatched worker, because the owner-pid env is inherited. With that var unset, all 82 pass.
  • I updated two exact-kwargs asserts in test_desktop_cron_ticker_profiles.py so they ignore the new can_dispatch key.

Light FleetReview only.

…_60634825)

C7 backfill (durable-state loss) slice A. Each fix has a regression test
that fails on 7a81d46 and passes here.

- k103 (#987) home-session guard: takeover/operator_override events now
  record the home read BEFORE the guarded mutation, so `update --session
  <new> --takeover` names the displaced home instead of <new>.
- k105 (#953) rate-limit circuit notify: the episode latch is written only
  after notify.py exits 0; a failed page is retried on the next tick.
  kanban_budget._run_notify now returns whether the send succeeded.
- k107 (#1081) request_changes: a coverage comment the gate refuses on an
  already-held review run is rolled back with the refusal.
- k109 (#1050) worker identity: `spawned` records the Linux boot_id next to
  the start token; a token stamped under another boot is ignored and the
  causal window decides.
- k110 (#994) end_orphaned_terminal_runs merges its payload into existing
  run metadata (keeps `pool`) instead of overwriting it.
- k112 (#980) model switch: the unknown-provider refusal is exempted only by
  a declaration on the CURRENT provider, same match as the override block.
- k114 (#1254) strip_overlay restores PATH components the overlay removed.
- k115 (#1035) desktop cron ticker passes a serve admission gate as
  can_dispatch, so a checkout hold refuses scheduled dispatch.
- k121 (#1014) dashboard create homes the card to the viewing ?session=,
  so it stays visible under the default "this" facet.
- k138 (#1024) kanban_attach refuses a supplied but invalid expected_sha256
  instead of storing the file unverified.

Dropped (no code change): k108, k111, k113. Reasons are in the PR body.

test_desktop_cron_ticker_profiles: two exact-kwargs asserts now ignore the
new can_dispatch key.
@ang-prism

ang-prism Bot commented Sep 28, 2026

Copy link
Copy Markdown

FleetReview

Review: pre-merge · head 2f97081ee29a · duration 13m 11s
Profile: light (merit: default light: lines 496<800, files 12<1000000, hunks 28<1000000, no hot path) · policy: changed-lines>400
Roster: B-assert-ctx → gpt-6-sol (openai), C-assert-xhigh → claude-code-opus-5-5 (anthropic), G → grok-4.6 (xai), L6 → gpt-6-sol (openai)

profile: light (rule: default light: lines 496<800, files 12<1000000, hunks 28<1000000, no hot path) · round 0 · members: B-assert-ctx, L6, C-assert-xhigh, G · families: anthropic,openai,xai

Confidence: 3/5

Findings

  • P1 hermes_cli/process_env_files.py:145 — PATH precedence · agreed: B-assert-ctx,L6,C-assert-xhigh (openai, anthropic)
  • P1 hermes_cli/model_switch.py:2513 — Empty URL match · agreed: L6 (openai)
  • P2 tests/plugins/test_kanban_dashboard_plugin.py:1489 — Coverage Gap · agreed: L6,C-assert-xhigh (openai, anthropic)
  • P1 plugins/kanban/dashboard/dist/index.js:996 — Stale Closure · agreed: C-assert-xhigh,G (anthropic, xai)

FleetReview provenance · models: B=gpt-6-sol, C=claude-code-opus-5-5, D=grok-4.6 · cost: $3.50 · duration: 13m 08s · rounds: 1 · files examined: 12

@Kyzcreig

Copy link
Copy Markdown
Collaborator

🤖 merged-by: apollo · lane: review · gate: ADVISORY (FleetReview not green for 2f97081): fleetreview-advisory-20260927-standing.md · why: t_60634825: CI green; Apollo review pass 17:18

@Kyzcreig
Kyzcreig merged commit 7c62220 into main Sep 28, 2026
41 checks passed
@Kyzcreig
Kyzcreig deleted the daedalus/t_60634825-c7-slice-a branch September 28, 2026 00:18
Kyzcreig pushed a commit that referenced this pull request Sep 28, 2026
…precedence (t_1b525136) (#1380)

FleetReview #1373 key 5796875694f5: restored components were appended, so
/opt/bin:/usr/bin overlaid by /usr/bin came back as /usr/bin:/opt/bin.
Each removed component now goes after its nearest retained predecessor
(or before its nearest retained successor) from the original PATH.

Verified: new k114 precedence test 3/3 red on fork/main 24526e1, 5/5
k114 green after; tests/hermes_cli/test_process_env_files.py 6/6.

Co-authored-by: ang-fleet-workers[bot] <333956806+ang-fleet-workers[bot]@users.noreply.github.com>
ang-fleet-workers Bot added a commit that referenced this pull request Oct 2, 2026
…talog, web_server facade symbol

- hermes_cli/cron.py: `cron list` asks the store for include_disabled=show_all (fork --json
  contract) and tops the human table up with paused jobs (upstream 3f399c0 contract).
- hermes_cli/models_catalog_static.py: drop the import-time sync_plugin_provider_catalog():
  list_providers() imports plugins that import hermes_cli.models -> partially initialised
  module (fork lazy auto-extend contract; the post-discovery sync hook still runs it).
- hermes_cli/web_server.py: module-level get_hermes_home facade symbol (tests patch it).
- tests: desktop cron ticker test admits the fork can_dispatch kwarg (#1373);
  gpt-6.1-sol IS -900k eligible on the fork (#1550, measured 922k) - upstream assertion flipped.

Verified: tests/hermes_cli/test_cron_list_json.py test_dashboard_state_db_log.py
test_desktop_cron_ticker_gateway_standdown.py -> 18 passed; tests/hermes_cli/test_cron.py -> 29 passed;
tests/hermes_cli/test_lazy_canonical_providers.py test_gpt6_tiers_registration.py
test_list_picker_providers.py -> 41 passed; test_api_key_providers.py test_provider_groups.py green.
Kyzcreig added a commit that referenced this pull request Oct 4, 2026
…ERGE COMMIT, never squash (#1624)

* fix(parity 2026-10-01 ci): L4-kanban reds — dispatch identity seams, creator origin, worktree teardown, review artifacts

Code (merge regressions / dropped fork behaviour):
- kanban_db_dispatch.enforce_max_runtime: drop the auto-merge duplicate timed_out event.
- kanban_db._resolve_birth_session: creator_task_id is lineage (after parents, before the worker run).
- kanban_db_workspace._has_active_children: restore the fork's conn=None / sqlite error fail-closed path
  (the extracted copy raised on the direct-cleanup callers -> worktrees never removed).
- kanban_db._recorded_worker_alive: consult upstream's spawn fingerprint before the owner window.
- kanban_db_dispatch._terminate_reclaimed_worker: UNVERIFIED + dead pid is terminated (never signalled);
  all dispatch call sites read it through the kanban_db facade (test patch seam);
  reap_terminal_workers passes the fork's required owner_window/conn/run_id (was TypeError, swallowed).
- kanban_db_dispatch.detect_crashed_workers: carry the worker's last output (#88603/#46593) on
  clean-exit/crash error text + event payload.
- kanban_db.request_review: do not re-promote prose artifacts already staged by the auto-route.
- gateway.kanban_watchers: corrupt-board guard class now read from kanban_db_connect (quarantine was
  disarmed by the facade move).
- tools/kanban_tools_schemas: request_review.reviewer description restored (argus/human/review_assignee).

Tests (upstream-only fixtures meeting fork contracts, or stale patch targets):
- terminal_worker_reaper: structured metadata (fork receipt gate).
- creator_origin: session_explicit for the named-session case.
- worker_pid_fingerprint: operator reclaim of an unverified live pid is refused (fork #921).
- unknown_arguments: act as the owning worker (live-claim guard).
- core_functionality: patch both connect spellings; dispatch_lock_observability: subprocess repoint.
- worktree_teardown retry: patch kanban_survivor._git (fork removal path).
- schedule_wake: promote_task force= retired (#106195).
- termination_identity / survivor: completions carry evidence (#117483).
- reclaim_unprovable_liveness timeout survivor: legacy row shape for a synthetic pid.

* test(parity 2026-10-01 ci): completion_delivery pins the merged bool/None delivery vocabulary

Fork tests asserted the fork's "delivered"/"temporary"/"dropped" outcome
strings on _deliver_completion_notification / _deliver_async_delegation_group;
the merged seam (F02c ledger decision) is upstream's True/False/None contract
with the admit_internal_event receipt. Three mechanical axes:
- "delivered"->True, "temporary"->False, "dropped"->None on the seam asserts
- AsyncMock handle_message doubles -> AdmittingHandler (sets _gateway_accepted)
- _runner() session_store lends a _db (upstream #98573 borrows the store handle;
  without it runner._session_db is None and every pre-flight returns retry)
drop_requires_proven_terminal_target[False-*]: an unroutable event is False
(retryable), matching the test's own "not proof no future consumer can deliver"
and test_unroutable_async_event_remains_retryable.

Verified: gated pytest tests/gateway/test_completion_delivery.py ->
101 passed, 2 failed before the [False-*] fix; those two re-run below.

* fix(parity 2026-10-01 ci): re-thread fork follow-up spooling, /footer setter, session.redo, /model read-path flags

gateway/run_turn._run_agent_drain_pending: the merge took upstream's
"Discarding pending follow-up" at the extracted site, re-creating the fork's
2026-09-23 data-loss (4 follow-ups lost). Restore the draining-gateway spool
(_preserve_followup_across_restart) at the moved site.

gateway/run_startup._drain_startup_restore_queue: use the fork seam
_adapter_for_source (intake first, then unique (platform, profile) owner).
Upstream's _intake_adapter_for fails closed on spooled restart follow-ups
(restored rows, no live provenance) under multiplexing -> retried forever.

gateway/run_notifications: reconcile the two parallel fixes for a stale
update notice. Fork policy stays (24h configured / 5min unconfigured); a
runner with NO config object gets upstream's flat 1h cap (601a8a17c22) and
upstream's "adapter never connected" wording.

tui_gateway/methods_config_set: fork /footer config.set branch
(display.runtime_footer.enabled) dropped when upstream tabled the if-chain;
restored as a _CONFIG_SETTERS entry.

tui_gateway/methods_session: fork session.redo RPC route restored beside
session.undo (core _redo_session_core already survived in server.py).

gateway/slash_commands (/model picker, fork handler kept by R09): import
list_picker_providers from model_switch_providers and pass upstream #74003
read-path flags (cache-only catalogs, probe only the current custom endpoint).

Verified (hermetic test-gate, CI-faithful harness without pre-set HERMES_HOME):
  tests/gateway/test_update_command.py + test_restart_interrupt_intent_followups.py
    (+ test_restart_cascade.py) 115 passed, 5 skipped, 3 failed (cascade: separate)
  tests/gateway/test_restart_followups_boot_replay_e2e.py 3 passed (in c1 batch 35 passed)
  tests/gateway/test_model_command_async_offload.py + test_notify_sub_chat_type_write_canonicalization.py
    (+ test_multiplex_routing_authz.py) 16 passed, 1 failed (multiplex: separate)
  tests/tui_gateway/test_desktop_runtime_footer.py 10 passed (tui2 batch)

* test(parity 2026-10-01 ci): repoint source anchors and doubles at upstream's moved seams (gateway/tui)

- test_notify_sub_chat_type_write_canonicalization: writer reads fields via
  _field(); metadata branch gained `and chat_type`. Behaviour (chat_type
  resolved before the branch, canonical on write) unchanged.
- test_restart_interrupt_intent_followups: draining branch moved to
  run_turn._run_agent_drain_pending; anchor there, end at the method return.
- test_desktop_runtime_footer: payload literal moved to
  prompt_turn._complete_turn_payload; anchor on the server.py call site that
  attaches payload["footer"].
- test_undo_redo: _start_inflight_turn double accepts upstream's
  display_kind/display_metadata kwargs; fixture clears
  server_requests.reset_for_tests() (server._pending/_answers retired).
- test_server_no_duplicate_defs: upstream #97948 replaced the fixed 120s
  compress wait with the config-derived budget (floored at 120s); assert the
  budget the real helper computes reaches the supervisor.

* fix(parity 2026-10-01 ci): restore D-6 finally-block breadcrumb consume in run_turn; cascade guards scan the split turn pipeline

gateway/run_turn.py: the handler's finally block consumes the restart-initiated breadcrumb again (fork/main run.py:28913, dropped when upstream split run.py into run_turn*.py).
tests/gateway/test_restart_cascade.py: the three source-scan guards concatenate run.py + run_turn.py + run_turn_runner.py so the relocated callback/gate/finally are still covered.

Verified: e45-pt-L3b.sh tests/gateway/test_restart_cascade.py -k 'c1_detection or single_gate or finally_consume' -> 3 passed.

* fix(parity 2026-10-01 ci): L4-kanban round 2 — reaper owner window, corrupt-board guard, review schemas; ledger

Code:
- kanban_db_dispatch._reap_terminal_worker_row: pass the fork's required owner_window/conn/run_id
  (merged call raised TypeError inside the per-row guard -> terminal workers never reaped).
- gateway.kanban_watchers._is_corrupt_board_db_error: read KanbanDbCorruptError from
  kanban_db_connect (facade no longer re-exports it; quarantine was disarmed).
- tools/kanban_tools_schemas: kanban_request_changes description restored (lens list from
  kanban_review_schema).

Tests (upstream-only fixtures -> fork contracts):
- test_kanban_db: stale-claim breaker models a dead claimer past the launch bound; infra spawn
  refusal marked platforms("linux") (restart_safe_gateway_child_argv is in_process off Linux);
  archive termination event is the fork's archive_worker_terminated.
- kanban_tools late-orphan: dead claimer + launch bound; _pid_started_in_claim seam.
- worker_authority_isolation: pin the RUN id, structured metadata for the receipt gate.
- review_coverage_gate human lane: drop worker scope, home the card on the reviewing session.
- second_claim_class [complete]: operator close under a live claim is force=True (#111764).
- schedule_wake: promote force= retired (#106195). survivor/termination_identity: #117483 evidence.
- worktree_teardown retry: patch kanban_survivor._git.

Ledger: docs/sync/review/ledger-2026-10-01/CI5-L4-kanban.md

* fix(parity 2026-10-01 ci): restore fork behaviours dropped by the merge, round 2 (L2-agent-core)

- codex_owner.refresh: a 429 refresh POST reserves the token's probe-throttle
  slot (new hermes_cli.auth_codex._reserve_codex_quota_probe_slot) so the
  mid-cooldown pre-probe refresh (#89415) does not re-POST the same single-use
  refresh token on the very next selection (test_codex_owner_selection_review
  saw two POSTs).
- agent_init._resolve_context_length: model.max_tokens config override
  (fork; dropped in the phase-helper split) re-threaded onto agent.max_tokens
  and _session_init_model_config.
- turn_usage: compressor update gated on a MEASURED prompt count read from the
  pre-fold aggregator usage (r6 findings 7 / round-4 2), plus the one-shot
  Codex 272K tier notice (#1567) — both lived in the fork's conversation_loop
  usage block.
- turn_finalizer: _diag_msg/_diag_args keep the fork source contract (end on
  task=/effective_task_id); the upstream origin= tag rides as a suffix.
- shell_hooks: spawn EACCES/ENOENT named exactly without the OSError detail
  (argv can be the credential); unparseable/empty stdout is its own fail-closed
  reason ("unparseable stdout"), distinct from an unknown directive.
- provider_seam._restore: carry forward containers registered after the
  snapshot (lazy facades such as HERMES_OVERLAYS) instead of leaving them out
  of the swapped generation (KeyError on every later read).
- honcho identity_signature: memo keyed on honcho.json bytes, not mtime
  (coarse-mtime filesystems served the stale pinPeerName; fork C7 k102).

Verified narrowly via e45-pt-L2.sh (sandboxed test-gate), each file green
after its fix: test_codex_owner_selection_review, test_credential_pool,
test_run_agent_init_memory, test_usageless_response_accounting,
test_codex_tier_notice, test_turn_ended_task_id_log, test_shell_hooks,
test_plugin_transport_api_mode, test_auth_registry_mid_discovery,
test_oauth_pkce_plugin, test_provider_registry, test_plugin_discovery,
test_pin_peer_name, test_identity_signature.

* test(parity 2026-10-01 ci): meet fork contracts in merged tests, round 2 (L2-agent-core)

- entitlement_fail_closed / non_chat_primary_restore: assert the fork's
  gated route announcement ("Model recovery (restore): ...",
  model.announce_recovery) instead of upstream's removed unconditional
  "Primary model restored" notice.
- credential_pool_codex_singleton_isolation: a manual:device_code row never
  adopts the singleton (agent/codex_owner, #673); the re-auth reaches the pool
  through the seeded device_code row.
- inline_edit_persistence / replay_cleanup: get_messages_as_conversation(
  include_timestamp=True) (fork F01 default projection is byte-stable);
  _SendAgent double carries provider for the interrupt-close filter.
- prompt_cache_ttl_propagation: #84733 restart-discipline guard rewritten for
  the phase-helper split — every _try_activate_fallback site in agent/turn_*
  must be an `if` test whose verdict is "break" (retry-loop phases, via
  _arm_fallback_restart / _fallback_break) or "continue" (outer-loop phases).
  Mutation-checked: flipping recover_empty_response to "break" fails it.
- route_id_correlation: fake Anthropic stream yields message_stop (upstream
  _drain_stream treats a stream without it as a drop).
- usage_pricing: models.dev leg exercised with an id absent from the fork's
  Grok snapshot; xai-oauth and api.x.ai-over-HTTPS price at xAI list rates by
  fork design (notional relay / host match).
- provider registry fixtures (plugin_transport_api_mode,
  auth_registry_mid_discovery, oauth_pkce_plugin): teardown via
  provider_seam._restore / _reset — the facades are additive and refuse
  pop/clear.
- run_agent_api_kwargs: the fresh-build assertion is checked before
  _build_system_prompt (upstream 921ab7a163 seeds the workspace pin from the
  session row inside the build).
- pin_peer_name: Honcho keys ride memory.<key> via identity_signature().
- plugin_paths_follow_profile: drop the mem0-qdrant case (fork retired the
  mem0 OSS backend; docs/sync/review/mem0-resolution-decision.md).
- credential_pool codex_sync_pool stub: httpx.SyncByteStream for the
  response-body cap; curator_disk_accounting hands the review a candidate
  list; system_prompt patches the prompt_builder symbols;
  pool_capacity_503_retry rig disables streaming (upstream c5b99a3ee5 keeps
  direct-call contexts on the streaming wire).

* fix(parity 2026-10-01 ci): L3a gateway a-m batch 1 — honcho memo digest, checkout gate ordering, boot preload roots, stale fixtures

Code:
- plugins/memory/honcho: identity_signature memoizes on the config's CONTENT digest and only
  stores values when the bytes it parsed still match (fork C7 k102; mtime/size key served stale
  values on coarse-mtime filesystems).
- tui_gateway/server._run_prompt_submit: checkout gate (fork C6 #1035) runs BEFORE the merged
  ownership/liveness admission so a frozen continuation is refused with no lease/inflight/agent
  side effects.
- gateway/boot_preload: root modules derived from the tree like upstream setup.py (py-modules
  list is gone); hermes_platform + pm packages preloaded; 4 import-side-effect scripts excluded.
- api_server_openai_routes: history loader passes include_timestamp=True (fork #107 shape).

Tests adapted to merged contracts (reasons inline + ledger):
auto_continue (interrupted marker last line), auto_skill_title (run generation gate),
background_process_notifications (bool delivery, suppress_completion double), c7 backfill
(renamed pid probe, honcho seam), checkout_admission (room-grant token, wire-contract params),
clarify batch (TurnRunner._clarify_callback_sync owns the per-card loop).

Verified: narrow gated pytest on the 7 files — 56 passed (auto_continue/auto_skill/bg_notif),
82 passed (c7/checkout/clarify after fix; checkout_admission 56 passed alone).

* test(parity 2026-10-01 ci): multiplex preflight unpins the sandbox DB; undo_redo restores server in place; toolsets denylist checks the uncollapsed catalog

- test_multiplex_routing_authz: the fork's hermetic conftest pins hermes_state.DEFAULT_DB_PATH (gateway.session imports hermes_state), which wins over the profile scope inside _default_db_path(); restore the import-time sentinel (test_housekeeping_profile_scope idiom).
- test_undo_redo: importlib.reload(server) re-binds the split siblings onto an already-tagged namespace and trips upstream's bind_module collision guard (change_watcher._active_pet vs methods_session._active_pet); restore _methods in place like test_undo_command.
- test_gui_surface_toolsets: upstream renamed todo -> todo_list and defers it behind the tool_search bridge; assert the denylist on the uncollapsed catalog and on the assembled schema.

Verified: e45-pt-L3b.sh on the three files -> 19 passed + 4 passed (undo_redo).

* fix(parity 2026-10-01 ci): L8 state/ci/scripts reds (batch 2) + ledger

CODE:
- gateway/session_persistence: _write_sessions_json_unlocked moved onto the
  mixin next to its only caller _save_sessions_json (merge left the mixin
  calling a SessionStore-only method; upstream's writer test instantiates the
  mixin alone -> AttributeError).
- gateway/platforms/base: upstream names _MACOS_PROXY_TTL_SECONDS /
  reset_macos_proxy_cache alias the fork's stale-while-revalidate cache.
- 17 upstream-only files: monotonic limiter seeds 0/0.0 -> float("-inf")
  (fork lint 2c2adef9ed3 now sees upstream code; 22 hits -> 0). Guarded unset
  sentinels carry `# zero-seed-ok:` naming the guard.
- publish_evidence_step.sh + its test removed: upstream 2ab7d9bfe30 deleted
  the publish-e2e-evidence pipeline the wrapper served (no caller left).

TESTS (fork contract vs upstream fixture):
- rewind_surfaces_invariant: gateway /undo N and undo_last(N>1) count
  half-turns (fork); harness passes 2*n, invariant unchanged.
- writer_conn_thread_safety: fork retries message-scoped SystemError; test
  asserts bounded replay then propagation.
- session_list_denorm_reland: AST contracts scan the hermes_state* mixin
  family + _INSERT_MESSAGE_SQL callers.
- test_hermes_state_core v9->v11 fixture session titled (v16 orphan tagging,
  v30 delegate exclusion from trigram).
- detect_changes_event_scoping: docker/nix are path-scoped lanes upstream.
- atomic_json_writers_unified: .sessions.lock is the fork writer lock, not a
  temp file.
- planned_restart_notice_multiplex: two inert tuple assertions.
- tests/agent/conftest: _block_real_claude_keychain honours
  allow_macos_keychain (upstream Keychain tests run the Darwin branch with
  subprocess.run mocked).
- snapshot_session_id_leak: HERMES_HOME is scoped in the fork (#543).

Verified (test-gate, sandboxed HOME, .venv python): every manifest file +
the 3 macOS-job files green narrowly; details per file in
docs/sync/review/ledger-2026-10-01/CI5-L8-state-ci-scripts-root.md.

* docs(parity 2026-10-01 ci): CI5 ledger for lane L2-agent-core

* fix(parity 2026-10-01 ci): hygiene keeps the fork's 400-message hard limit and repeated-failure escalation; /stop background test pins the catalog key

gateway/run_turn.py + run.py: _HygieneSettings defaults hard_msg_limit=400 (fork fleet default, config_defaults.py; upstream 5000) and carries failure_alert_after (compression.hygiene_failure_alert_after). The timeout and abort notices bump the per-session streak and, from the Nth consecutive failure, send agent.hygiene_timeout.format_repeated_failure_alert; a landed compaction clears it. Fork behaviour from run.py's pre-split hygiene block (fork/main 27499-27560, 27840).
tests/gateway/test_session_hygiene.py: user-facing wording assertions read the i18n catalog (gateway.compress.hygiene_timeout / hygiene_failed) instead of the pre-i18n literals.
tests/gateway/test_stop_ends_background_delegations.py (upstream-only): the reply is compared to t('gateway.stop.stopped'); the fork's catalog words it present-progressive (test_stop_honest_wording.py).

Verified: e45-pt-L3b.sh test_session_hygiene.py -> 45 passed; test_hygiene_turnhold_backoff.py + test_hygiene_warning_lifecycle.py + test_stop_ends_background_delegations.py -> 7 passed.

* fix(parity 2026-10-01 ci): L3a gateway a-m batch 2 — route anchors, env bridge funnel, slash echo fields, flush helpers, override persist ordering

Code (merge regressions, fork behaviour restored onto upstream's structure):
- gateway/run: _bridge_config_to_env re-wires restart_policy._bridge_agent_config_to_env (config
  wins over stale .env for resume/restart knobs); _set_session_vars_for_source binds
  parent_chat_id (upstream a247012dc11) so a stale env never shadows the live source.
- gateway/platforms/event: MessageEvent regains suppress_public_echo / deferred_reply_text
  (Discord native-slash one-delivery handoff; base.py read them via getattr).
- agent/session_persistence: the fork flush helpers live beside the flush instead of a lazy
  `from run_agent import` that fails closed when run_agent is swapped (rows silently unwritten);
  run_agent re-exports them.
- gateway/session.set_model_override: persist OUTSIDE _lock (the fork _StoreLock defers in-lock
  writes to release, inverting publish-after-persist), then chat pin, then publish.

Tests adapted (reasons inline + ledger CI5-L3a-gateway-a-m.md): display_null wiring doubles,
kanban fair_dispatch + dispatcher_standby (kanban_db_dispatch / kanban_watchers_common seams),
login_command (route identity carries provider; rehydrate resolves as-is), loop_liveness
(patch gateway.shutdown_watchdog).

Verified (narrow gated pytest): display_null+internal_row+kanban 28 passed; route_anchor+login+
loop_liveness 47 passed; login+chat_model_pins+model_override_persistence 47 passed;
session_store_lock_io/never_spans_io green; interrupt_close_flag + flush_diverts green.

* fix(parity 2026-10-01 ci): L7 tools/cron — web keyed-fallback + breaker chain, delegate timeout transport drain, durable background output, kanban grant boundary

Round-5 CI lane L7-tools-cron for #1624 (ledger docs/sync/review/ledger-2026-10-01/CI5-L7-tools-cron.md).

Code restored onto upstream's structure (fork behaviour the merge dropped):
- tools/web_tools_extract.py: _breaker_extract (402/401 dead-backend breaker, #1562), _failed_extract_batch and the
  keyed web.extract_fallbacks chain (#1516) back in _dispatch_extract; hooks read via the tools.web_tools facade.
  tools/web_tools_truncate.py: _trim_results keeps served_by/fallback_from (+ local-pdf metadata).
- tools/terminal_tool_background.py: durable_output=bool(notify_on_complete) on spawn_local (t_1191e078).
- tools/terminal_tool.py: messaging-gateway turns keep the over-cap foreground REFUSAL (#1012).
- tools/terminal_tool_guards.py: process(action="wait") guidance copy in the nohup/& recipes.
- tools/code_execution_env.py: git-lane env carry (t_45c11886 / C3 #1254) re-threaded into the extracted module.
- tools/delegate_tool.py + delegate_tool_child_run.py: upstream's abandoned-worker transport drain (#94248) split out
  as _drain_abandoned_child_transports and called from the fork supervisor's timeout path.
- cron/lifecycle_guard.py: read-only heredoc data paths are not mention candidates (#1017/#1348).
- cron/scheduler.py: never suppress the alert that enters a provider-window quota hold (#89376).
- hermes_cli/cron.py: vanished-job warning on every status path.
- hermes_cli/kanban_db_dispatch.py: pop HERMES_DELEGATED_CHILD_CONTEXT at the grant boundary; _recent_worker_exits
  read through the kanban_db facade.
- tools/skill_manager_tool.py: name validator fullmatch (AC10).
- agent/agent_init.py + run_agent.py: tool definitions read through the run_agent facade.

Tests repointed to moved symbols / fork contracts (see ledger per file); tests/tools/test_lazy_sdk_probe_importable.py
deleted (tests the retired tools.lazy_deps surface; property pinned by tests/pm/test_extras.py).

Verified (e45-pt-L7.sh, sandboxed HOME, <=3 files per call): every manifest file green —
cron: fallback_alert 2/2, store_concurrency 14/14, no_auto_sentinel 8/8, workdir 16/16, fire_fence 3/3,
selfisolation 1/1, heredoc_data 28/28, python_heredoc_parity 66/66, quota_hold 4/4;
tools: blocked_command_guidance 8/8, browser_real_profile 65/65, code_execution_git_lane_env 1/1,
cron_auto_model 93/93, cron_model_arg_coercion 13/13, gateway_foreground_deafness 32/32, launchctl_guard_diagnostic 2/2,
request_tool_approval 13/13, skill_manager_create_shared 35/35, snapshot_session_id_leak 4/4, terminal_task_cwd 7/7,
timeout_transport_drain 4/4, web_backend_breaker 19/19, web_keyed_fallbacks 6/6, windows_native_support 16/16.
tests/cron/test_cron_kanban_env_isolation.py 15/15 on ace-ai (linux-only spawn test).

* test(parity 2026-10-01 ci): model-override rehydration tests follow the fork's identity re-resolution; pre-agent fallback fixture answers the route precheck

tests/gateway/test_session_model_override_persistence.py: upstream's three new tests patch _resolve_runtime_agent_kwargs_for_provider, the legacy identity-less path. On the fork a persisted {model, provider} identity is durability truth and is re-resolved through _reresolve_model_override_credentials -> hermes_cli.model_switch.switch_model, so the doubles move there. The codex still_unavailable arm now asserts the fork contract: SessionRouteUnavailableError, preference preserved, default route never consulted (same as test_session_model_reset.py credentials-unavailable) instead of upstream's silent default-provider fallback.
tests/gateway/test_pre_agent_fallback_notice.py (upstream-only): the MagicMock runner returns PersistedSessionRouteLookup('absent') from _persisted_session_route_identity so the fork's fail-closed precheck sees no pin.

Verified: e45-pt-L3b.sh test_session_model_override_persistence.py -> 9 passed; test_pre_agent_fallback_notice.py -> 2 passed.

* fix(parity 2026-10-01 ci): restore fork auth-error markers; telegram/warning-wiring guards follow upstream's seams

gateway/run.py: _GATEWAY_AUTH_ERROR_RE regains the fork's credential-resolution / Codex-OAuth / pool-exhausted / provider-not-configured markers (fork/main run.py:754-764) so those envelopes map to the auth reply instead of the generic retry message.
tests/gateway/test_telegram_noise_filter.py: the credential-resolution test asserts upstream's plain-language auth reply (sign-in + /login), same as its sibling.
tests/gateway/test_telegram_restart_parity.py: the AST guards treat _start_polling_mode (extracted from connect) as bootstrap, accept upstream's _cold_boot_drop_pending(is_reconnect=...) gate (False on reconnect, config knob on cold boot — AC-1/AC-2 prove it), and resolve a single-assignment local alias. Mutation-checked: flipping the network-error ladder to drop_pending_updates=True still fails both guards.
tests/gateway/test_warning_wiring_conservation.py (upstream-only): the context carries a live status adapter + run predicate and the status lane is patched at safe_schedule_threadsafe, where the fork's late-resolving status callback schedules.

Verified: e45-pt-L3b.sh test_telegram_noise_filter.py -> 168 passed; test_telegram_restart_parity.py -> 6 passed; test_warning_wiring_conservation.py -> 1 passed.

* fix(parity 2026-10-01 ci): discord native slash option descriptions are catalog keys

plugins/platforms/discord/adapter.py: the fork's /new, /reset, /undo, /compress, /usage, /help option descriptions move from literals to platform.discord.command.<cmd>.arg_* keys (upstream's test_discord_native_slash_specs_hold_catalog_keys_only requires every slot to resolve).
locales/*.yaml: the 7 keys added to en.yaml and, in English, to every other locale (tests/agent/test_i18n.py::test_catalog_keys_match_english requires key parity; t() falls back to English anyway).

Verified: e45-pt-L3b.sh tests/agent/test_i18n.py tests/gateway/test_platform_adapter_i18n.py -> 67 passed. test_discord_slash_commands.py/_scope.py: 14 failed before and after this change (L3a lane, untouched).

* test(parity 2026-10-01 ci): ws orphan resume ctx carries params; manual-reset tests pin the store handle and read upstream's config warning

tests/tui_gateway/test_ws_orphan_races.py (upstream-only): the eager-resume ctx carries params={} — the fork's build reads the client's declared source from the request.
tests/gateway/test_manual_reset_sticky_route.py: the SQLite test pins its handle through store._db (the store now resolves through hermes_state_registry.acquire(), so patching hermes_state.SessionDB no longer reaches it — rows landed in a different file, FK failure); the parse-warning test reads upstream's config_read_errors warning (path + problem line, no source snippet) instead of gateway.run's removed error=<ExcType> line.

Verified: e45-pt-L3b.sh test_ws_orphan_races.py -> 29 passed; test_manual_reset_sticky_route.py -> 25 passed.

* fix(parity 2026-10-01 ci): queued follow-up re-stamps the turn clock and keeps a leftover /steer; relocated-seam test fixes

gateway/run_turn.py: fork 2026-09-29 behaviour re-threaded into the split turn pipeline — (1) a queued follow-up recursing on the parent's slot re-stamps turn.started_ts / busy_ack_ts (only for the key this run claimed); (2) a result['pending_steer'] that loses the next turn to a queued follow-up is appended to the session's /queue overflow (same slash-command guard) instead of being dropped. From fork/main run.py 39217-39260, 39547-39552.
tests/gateway/test_relay_injection_egress_priming.py: _inject_watch_notification returns upstream's True on adapter acceptance (merged contract, ledger F02c) — the fork's 'delivered' string is gone.
tests/gateway/test_resume_inflight_guidance.py: the AST wiring guard reads gateway/run_turn_runner.py, where upstream moved the dispatch site.
tests/gateway/test_rich_sent_store_off_loop.py: waits for the fork's dedicated writer thread instead of racing it (record_async only enqueues).

Verified: e45-pt-L3b.sh on the four files -> 22 passed (rich_sent_store run twice: 2 passed both).

* fix(parity 2026-10-01 ci): weixin writes ride the FIFO lane again; hard-exit funnel fences the lanes

ContextTokenStore.set is sync and dispatches through _dispatch_weixin_json_write
(single FIFO lane, ordered, fenced at disconnect/exit) instead of upstream's
per-call to_thread + asyncio.Lock; _sync_buf_path restored for _save_sync_buf.
gateway.run._exit_after_graceful_shutdown calls fence_lanes_for_hard_exit
between lock release and the lifecycle stamp (os._exit skips the atexit fences).
tests/gateway/test_weixin.py repointed at the lane contract (fence to observe).

Verified narrowly: test_weixin_state_write_off_loop (14 passed),
test_weixin + test_weixin_typing green, test_shutdown_pending_flush_off_loop
production-exit-funnel tests green.

* fix(parity 2026-10-01 ci): restore the ordered runtime-status lane behind the public writer

gateway/status: _RUNTIME_STATUS_LANE (single worker) + _fence_runtime_status_lane
back; submit_runtime_status_write queues on the lane again (fork #817 contract:
write_runtime_status fences it first so a direct terminal write can never be
overtaken by an older deferred one). write_runtime_status_locked alias restored.
The lane sits in front of upstream's _RuntimeStatusWriter, which still does the
actual persistence.

tests: test_no_sync_work realpath counter ignores the frames tests/home_io_guard.py
issues from inside Path.resolve()'s single walk (fork-only harness, 2b98a469d39);
test_runtime_status_write_off_loop spies _prepare_runtime_status_update (the
merge step every writer passes through) instead of the removed
_write_runtime_status_unlocked.

Verified narrowly: test_no_sync_work_per_inbound_message 13 passed,
test_runtime_status_write_off_loop 9 passed.

* test(parity 2026-10-01 ci): network-reachability ratchet follows upstream's module split; CI5-L3b ledger

tests/gateway/test_no_network_reachable_from_loop.py:
- validate_requested_model spy -> hermes_cli.models_validate (moved).
- _compress_context non-vacuity probe -> agent/compression_facade.py (AIAgent mixin).
- REACHABLE_BASELINE re-frozen for the run.py / run_agent.py split: the walker
  reports one sink per coroutine and now terminates the same pre-existing chains
  at requests.get (resolve_runtime_provider>_get_model_config>
  _auto_detect_local_model, on fork/main too) under the new module names.
  Measured with the test's walker: fork/main c14e059f8f2 = 17 sites, lane head =
  18, none newly reachable; /model and /reset doors still absent.

docs/sync/review/ledger-2026-10-01/CI5-L3b-gateway-n-z-tui.md: one row per red file.

Verified: test_no_network_reachable_from_loop.py 15 passed (72s).

* fix(parity 2026-10-01 ci): L5 keychain flags on dock launch, escaped resume-title hint, cli_hint/codex test seams

- tools/bot_desktop/browser.py: dock_argv carries --password-store=basic --use-mock-keychain
  (fork guard tests/cli/test_chrome_launcher_keychain_guard.py covers every detached launch).
- hermes_cli/main_tui_launch.py: the `-c "<title>"` resume hint goes through cli_hint.hint_value
  (fork contract: titles with $HOME / `id` must not expand).
- tests/hermes_cli/test_cli_hint.py: patch site moved to main_tui_launch; repair helpers
  now live in hermes_state_repair; SessionDB(read_only=True) ctor kwarg on the fake.
- tests/hermes_cli/test_auth_codex_provider.py: pool force-refresh goes through the owner
  transaction (refresh_codex_oauth_pure), not load_pool().try_refresh_matching.

Verified: tests/cli/test_chrome_launcher_keychain_guard.py tests/hermes_cli/test_cli_hint.py
tests/hermes_cli/test_auth_codex_provider.py -> 148 passed.

* parity(2026-10-01): L7 fold — re-apply the abandoned-worker transport drain call dropped by the import-conflict resolution

* fix(parity 2026-10-01 ci): restore compaction trigger attribution on the extracted turn modules

The fork's 2026-08-20 trigger-attribution audit passed trigger_reason=<label>
at every _compress_context call site (threshold / pre_api_pressure /
overflow_413 / overflow_context / tier_reduction / idle_resume /
engine_preflight_maintenance / session_hygiene / manual_compress_command).
Upstream's turn_*.py extraction dropped every label, so each compaction logged
trigger=UNATTRIBUTED and the announce rendered no reason clause.

Re-thread the labels onto turn_preflight, turn_context_compaction,
turn_overflow (compress/compress_scored_by_tokens gain a trigger_reason kwarg),
turn_recovery (tier_reduction), gateway/run_turn + gateway/run (session
hygiene), and the shared manual core conversation_compression_manual.compress_now
(the CLI, TUI and ACP surfaces now route through it; the gateway slash handler
still passes the literal).

test_compaction_trigger_coverage: the manual-surface guard now pins the label
in the shared core and accepts a surface that routes through compress_now
(cli -> hermes_cli/cli_session_mixin.py, acp -> acp_adapter/commands.py).
test_compress_context_progress_timeout: upstream #114594's exact-equal clamp
numbers replaced by the fork reconcile_timeouts invariants (idle lifted strictly
above the aux deadline, ceiling admits one fallback) already pinned by
tests/gateway/test_compress_abort_honesty.py.
test_length_continuation_thinking_exhaustion: helper imports repointed to
tests.run_agent._run_agent_helpers (upstream moved tests/agent/test_run_agent).

Verified: tests/agent/test_compaction_trigger_coverage.py
tests/agent/test_compress_context_progress_timeout.py
tests/agent/test_length_continuation_thinking_exhaustion.py
tests/gateway/test_compress_abort_honesty.py -> 72 passed, 1 pre-existing
unrelated failure (locale key; fails identically without this diff).

* fix(parity 2026-10-01 ci): reply re-anchor must not bind the engine's kept head to a tail twin

Upstream #118900 (_ensure_compressed_keeps_last_assistant_reply) locates the
folded reply's slot by scanning `compressed` for the LAST row content-equal to
the reply's original follower. When the engine keeps the head verbatim and the
trailing user turn is a content twin of a head row (fork #942 guard:
test_plugin_equal_head_cannot_match_overlapping_tail), that scan bound the kept
HEAD row as the follower and inserted the reply in front of the engine's head
(`[reply, head, notice]`): the notice placement the fork pins was pulled out of
order.

Apply the fork's head-first overlap rule (same as _reinsert_tool_notice_events):
leading `compressed` rows that align positionally with the original rows BEFORE
the reply are the kept head — excluded from the follower scan and never read as
the trailing real user turn (`_kept_head_len`, `kept_head=` on
_reply_insertion_index).

Tests (fork contracts adapted to the adopted upstream mechanisms):
- test_confab_notice_e2e::test_compaction_notice_without_successor_uses_original_boundary:
  the folded middle row now carries real weight, since #118900's reply
  re-anchor plus the user-turn anchor made the one-word candidate GROW and the
  commit-site anti-growth guard refused the whole compaction (fixture artefact).
  Assert the notice's left neighbour (surviving predecessor / summary boundary),
  not a list index.
- test_compression_concurrent_fork::test_compression_restores_user_turn_when_compressor_drops_all_users:
  the restored row now also carries upstream's durable `message_uid` +
  `timestamp` (751d8526e35); compare role/content only (contract: CONTENT survives).

Verified: tests/agent/test_confab_notice_e2e.py +
tests/agent/test_compression_concurrent_fork.py -> 129 passed.
tests/agent/test_confab_matrix_{bigargs_lcm,image_builtin,image_lcm} -> 48 passed;
tests/agent/test_confab_matrix_{merge_builtin,merge_lcm,twins_builtin} -> 48 passed.
tests/agent/test_compression_last_assistant_anchor.py: 6 reds pre-exist on the
lane base (fixture `None is not None` at L117), unchanged by this diff, not in
any round-5 manifest.

* fix(parity 2026-10-01 ci): restore the measured 872K gpt-6 Codex windows and the large-policy picker hint

agent/model_metadata.py: upstream carried the gpt-5.6 900K verdict forward
onto gpt-6-sol/luna (family prefixes) and bumped gpt-6-astra to 900K. The
fork measured all three against the codex-sub catalog
(max_context_window=872,000; astra 2026-09-04, sol/luna 2026-09-22) and
lists them EXACTLY so unprobed gpt-6 descendants never inherit a cap. The
merge took upstream's values -> every fork window test resolved 900,000
instead of 872,000. Fork table + exact-only gpt-6 eligibility restored.

hermes_cli/models_validate.py: the ineligible `-900k` rejection lost the
fork's policy-aware hint (under `large` the bare slug already carries the
window, so the picker example is `gpt-6-sol`, not `gpt-5.6-sol-900k`).

tests/agent/test_codex_context_policy.py (fork-only test, adapted to the
adopted upstream seams, no contract change):
- `patch("agent.model_metadata.requests.get")` -> `model_metadata_http.get`
  (upstream routes every metadata probe through model_metadata_http; the
  lazy `requests` shim is gone).
- `validate_requested_model` imported from hermes_cli.models_validate
  (upstream split hermes_cli.models).
- the live-catalog test hands a JWT-shaped token: upstream #121486 refuses a
  non-JWT credential aimed at chatgpt.com before probing.

Verified: tests/agent/test_codex_context_policy.py -> 64 passed (was 35 red).
tests/hermes_cli/test_model_validation.py: 6 failed + 1 error identical with
and without this diff (Anthropic warning wording / registry container),
not in any round-5 manifest.

* fix(parity 2026-10-01 ci): re-thread per-class skew calibration and idle blackbox bookkeeping onto the turn_* split

agent/turn_context_compaction.py + agent/turn_preflight.py +
agent/turn_request_assembly.py: the fork's P2 "compact on the truth" trigger
(note_rough_sent / calibrated_tokens / should_compress_request /
trigger_compare_tokens_for with `messages` threaded through so each request
is classified per content class; R05 ledger TODO `turn_preflight L3548`) was
dropped when upstream extracted the preflight into turn_context_compaction
and the pre-API gate into turn_preflight. Both gates compared the raw rough
estimate again, so the whole per-class arm was dead code. Restored on the
extracted modules: the prologue preflight uses the surviving
_preflight_request_tokens_split (rough vs anchored), the pre-API gate reads
the rough figure request assembly now stashes beside the anchored flag
(agent._request_pressure_rough). Non-bool verdicts from MagicMock doubles
fall back to should_compress, as on fork/main.

agent/turn_context_compaction.py: idle-resume compaction lost the fork's
blackbox bookkeeping (idle_compaction_fired + before/after tokens, C5 #42);
restored, estimator read through the turn_context facade, failure-tolerant.

agent/chat_completion_helpers.py: _summary_text reads tool_calls via getattr
so a minimal normalized shape (content only) is not a summary failure.

Tests:
- test_per_class_skew_calibration: the two AST guards follow the production
  sites into turn_context_compaction / turn_preflight (module getters only).
- test_iteration_limit_summary_display_fields: upstream routes the chat-mode
  summary through _build_api_kwargs -> _interruptible_api_call (keeps the
  cached prefix); the doubles now sit on those seams, contract unchanged.

Verified: tests/agent/test_per_class_skew_calibration.py 36 passed;
tests/agent/test_idle_compaction_lock_and_guards.py 5 passed;
tests/agent/test_iteration_limit_summary_display_fields.py 3 passed;
test_preflight_compression_gate + test_turn_context_compaction +
test_engine_preflight_wire 7 passed; test_preflight_lock_defer +
test_preflight_compression_cap_e2e + test_native_preflight_estimate 9 passed;
test_confab_notice_e2e + test_confab_matrix_merge_builtin +
test_confab_notice_compaction_stats 81 passed.

* fix(parity 2026-10-01 ci): price image parts under the shared estimator divisor; D-6 thrash fixture on upstream's bounded tail floor

agent/chat_completion_helpers.py: the stale-call estimator routes through the
fork's shared 3.5 chars/token divisor (886877cfbc8), but upstream's image
pricing (#63871/#76411) expressed the learned per-image TOKEN cost as
image_cost*4 chars — under the fork divisor that inflated every image by
4/3.5. Express it in chars under COMPOSITION_CHARS_PER_TOKEN so it
round-trips to image_cost tokens.

tests/agent/test_context_estimator_multimodal.py (upstream-only): the text
contract pins "no image pricing applied" against the fork divisor instead of
upstream's char/4 literal.

tests/agent/test_compaction_failed_summary_donepath.py (fork-only): upstream
fdbcdef9146 bounds the protect_last_n count floor by the tail token budget
(pinned by test_context_compressor::test_message_floor_does_not_unboundedly_override_soft_ceiling),
so the 4-pair huge tail no longer rides the floor past the ceiling and the
request genuinely shrinks (probe: fork/main 16->12 rows / 87K post; merged
16->6 / 41K). The thrash condition is rebuilt from the REQUIRED anchor pair
(last user #10896 + last assistant #29824), which both trees keep whole.
Contract (failed/placeholder summary over threshold counts ineffective on the
real done-site) unchanged; the done-site code itself survived the merge.

Verified: test_context_estimator_multimodal + test_non_stream_stale_timeout +
hermes_cli/test_load_progress -> 19 passed;
test_compaction_failed_summary_donepath -> 1 passed.

* fix(parity 2026-10-01 ci): re-thread the request-body byte budget and the image-lifecycle invariant onto the turn_* split

The fork's serialized request-body ceiling (agent/request_body_budget.py:
byte preflight after request middleware, terminal re-check once execution
middleware replaced the payload, and body_too_large 413 recovery that
remediates retained images once and otherwise fails the turn actionably,
never text compaction) survived the merge as a module nobody called:
upstream's turn_*.py extraction dropped all three loop sites. New sibling
agent/turn_body_budget.py carries them; wired at
turn_api_request.build_api_request (verdict gains action="return"+result,
honoured by _run_api_retry_loop), turn_api_call._perform_api_call (terminal
edge; raises RequestBodyBudgetExceeded) and turn_api_error.handle_api_error
(right after pool recovery, before retry accounting; ApiErrorVerdict now
carries api_messages so a remediated copy reaches the rebuild).

turn_iteration_prep: the fork's once-per-turn "image lifecycle invariant
violated" warning (image parts before the current-turn boundary) restored;
the flag is reset at the turn prologue.

tests/agent/test_413_compression.py (fork tests/run_agent file merged with
upstream's): patch seams for the mid-turn estimators follow the code into
agent.model_metadata (turn_request_assembly / turn_preflight read lazily;
turn_context keeps its own binding); the rough-growth-after-fit preflight
makes the estimate the deciding signal via note_usage_less_response(), since
the merge adopts upstream 0f4587e336f's deferral (pinned by
test_switch_waits_for_new_provider_evidence and the neighbouring
test_rough_over_threshold_waits_one_request_then_real_usage_compresses),
replacing the fork's (rough, real) growth projection. The calibrated-gate
contract is unchanged.

Verified: tests/agent/test_413_compression.py 41 passed (was 7 red);
test_413_image_payload_recovery + test_request_body_budget +
test_turn_api_error_stream_parse 19 passed; test_turn_api_call_interrupt +
test_image_shrink_recovery + test_retry_exhaustion_partial_retention 24 passed.

* docs(parity 2026-10-01 ci): CI5 ledger for lane L1-agent-confab-compaction

20 manifest files / 196 reds -> all green narrowly on the lane head
(final re-proof: 453 passed across the 20 files, 3 per call).

* fix(parity 2026-10-01 ci): L5 fast mode is route-aware at every call site; Opus 5/5.5 in the fast catalog

tests/cli/test_fast_route_capability.py::test_request_enforcement_call_sites_do_not_use_model_only_wrapper
forbids resolve_fast_mode_overrides( / model_supports_fast_mode( outside hermes_cli/models.py.
Upstream's parallel /fast (agent/fast_mode.py auto|cold windows, tui_gateway._fast_tier_applies,
methods_config_set._set_fast, slash_commands_model._handle_fast_command) called the model-only
wrapper; each now asks the fork's resolve_fast_mode_capability (model + provider + api_mode).

- hermes_cli/models.py: resolve_fast_mode_capability(base_url=) — optional live-endpoint gate
  (_fast_mode_route_supported) so a first-party provider id behind a proxy host fails closed
  the way the upstream wrapper did; threaded through ..._for_configured_route.
- hermes_cli/fast_mode_contracts.py: anthropic_fast = (opus-5-5, opus-5, opus-4-8) per the live
  fast-mode docs (same three ids as agent.model_metadata and the pricing rows);
  normalize_fast_model_id folds dotted opus spellings generally, suffixes still rejected.
- tests: catalog assertion updated; test_fast_command proxy branch pins {} (fork
  `{}`-when-tier-set contract, matches the sibling tests in the same class).

Verified: tests/cli/test_fast_route_capability.py tests/hermes_cli/test_fast_command.py
tests/tui_gateway/test_fast_session_scope.py -> 54 passed. tests/agent/test_fast_mode_auto.py
tests/cli/test_fast_mode_overrides.py tests/gateway/test_fast_command.py tests/agent/test_usage_pricing.py
tests/hermes_cli/test_oneshot_reasoning_and_tier.py: same red set before and after (not L5 files).

* fix(parity 2026-10-01 ci): L5 `config set` keeps comments + bak-configset sibling; codex owner clears cooldown on the refreshed row

- hermes_cli/config.py: restore the fork `config set` writer the merge dropped for upstream's
  full-state ruamel replace: _targeted_config_edit (one-scalar splice / block insert, re-parsed)
  -> roundtrip render -> block dump; refuse with a diff when a comment would be dropped unless
  --force; config.yaml.bak-configset-* sibling with the pre-write bytes (t_6da78aab,
  profile-config-keyguard). `unset` keeps upstream's _write_user_config. PyYAML's IndentDumper /
  yaml.compose are gone upstream (hermes_yaml is ruamel): hermes_yaml.compose() added, block
  dump = hermes_yaml.safe_dump. scripts/check_config_yaml_writers.py: OK.
- agent/codex_owner.py: after the quota probe refreshes an expired stored token (#89415) the
  pool row already holds the fresh pair; clear the cooldown on that row, not the stale
  snapshot (which wrote the expired access token back and resolved it).

Verified: tests/hermes_cli/test_config_set_preserves_comments.py test_config_yaml_comment_preservation.py
test_config_set_list_values.py -> 29 passed; test_config_set_coercion.py test_config_set_platforms_redirect.py
-> 20 passed; tests/hermes_cli/test_auth_codex_quota_probe.py test_auth_codex_provider.py -> green;
tests/agent/test_codex_owner_*: unchanged red set (needs L2's hermes_cli/auth.py _auth_lock_path).

* test(parity 2026-10-01 ci): M2-hermes-clia batch 1 — shell-hook coverage, anon auth, codex self-heal

- test_agent_host_shell_hook_coverage: patch discover_mcp_tools where main.py now imports it
  (tools.mcp_tool_discovery; the tools.mcp_tool re-export went with the compat layer in a5bd246865b)
  and map upstream's extracted gateway/run_turn*.py + slash_commands_session.py to the GATEWAY host.
- test_anon_auth_core (upstream-only): a successful _swap_credential returns the fork's
  SwapOutcome.SWAPPED, not bare True.
- test_auth_codex_self_heal #73667 tests (upstream-only): the fork's Codex owner store (#673) serves
  a singleton with an access_token before the singleton read, so exercise the CLI recovery through
  the rejected-refresh path (_refresh_codex_auth_tokens) with the same CAS / workspace assertions.

* fix(parity 2026-10-01 ci): L5 cron list default set, lazy provider catalog, web_server facade symbol

- hermes_cli/cron.py: `cron list` asks the store for include_disabled=show_all (fork --json
  contract) and tops the human table up with paused jobs (upstream 3f399c0bd4 contract).
- hermes_cli/models_catalog_static.py: drop the import-time sync_plugin_provider_catalog():
  list_providers() imports plugins that import hermes_cli.models -> partially initialised
  module (fork lazy auto-extend contract; the post-discovery sync hook still runs it).
- hermes_cli/web_server.py: module-level get_hermes_home facade symbol (tests patch it).
- tests: desktop cron ticker test admits the fork can_dispatch kwarg (#1373);
  gpt-6.1-sol IS -900k eligible on the fork (#1550, measured 922k) - upstream assertion flipped.

Verified: tests/hermes_cli/test_cron_list_json.py test_dashboard_state_db_log.py
test_desktop_cron_ticker_gateway_standdown.py -> 18 passed; tests/hermes_cli/test_cron.py -> 29 passed;
tests/hermes_cli/test_lazy_canonical_providers.py test_gpt6_tiers_registration.py
test_list_picker_providers.py -> 41 passed; test_api_key_providers.py test_provider_groups.py green.

* fix(parity 2026-10-01 ci): M1 — re-thread track_agent persist + wecom final-frame tail; F02c vocabulary on injection test

- gateway/run_turn.py _run_agent_track_agent: restore the fork self._persist_active_agents() after the sentinel->agent promotion (2026-09-19 regression guard); upstream extracted the closure and the merge kept only the upstream body.
- plugins/platforms/wecom/streaming.py _send_stream_reply: final frame keeps head AND tail (t_11223645) — upstream split the adapter, merge took head-only.
- tests: track_agent AST test follows the extraction (run_turn.py, tuple-assign form); injection ack test asserts the F02c True/False/None seam, ended-session drop is owned by test_completion_delivery.

* fix(parity 2026-10-01 ci): M5 lane — ci + lcm smoke reds

- tests/ci/test_evaluate_needs.py: upstream dropped the pyyaml dependency
  (284dbaf5370, YAML unified on ruamel); read ci.yaml via hermes_yaml.
- tests/ci/source_proxy_baseline.json: ratchet re-baselined on the merged
  test set — 48 entries whose tests the merge deleted/rewrote removed,
  the fork's relocated tests/run_agent -> tests/agent fallback-override
  key re-keyed, three upstream-authored pre-existing proxies and the L2
  AST-walk rewrite of test_every_fallback_activation_restarts_preflight
  frozen (pre-existing, not endorsed; count 114 -> 67).
- scripts/probe_hermes_lcm_isolated.py: compare live roots lexically; the
  merged tests/home_io_guard refuses realpath() under the real home, so
  resolving ~/.hermes/plugins to decide a refusal tripped the guard on
  every smoke test. Only the candidate path is resolved now.

Verified: tests/ci/test_evaluate_needs.py + test_no_new_source_proxy_asserts.py
+ tests/context_engine/test_lcm_adoption_smoke.py 32 passed under e45-pt;
smoke 6/6 under a non-temp HERMES_HOME (6/6 fail without the probe change).

* fix(parity 2026-10-01 ci): M1 — yaml shim in slash_commands, completion silence hint on the extracted formatter

- gateway/slash_commands.py: two raw `import yaml` -> `import hermes_yaml as yaml` (CI venv has no PyYAML; the fork reads YAML through hermes_yaml).
- tools/process_registry_notifications.py: upstream extracted format_process_notification and the copy dropped the fork COMPLETION_SILENCE_HINT tail (Ace contract 2026-09-27); constant now lives in the notifications module, re-exported from tools.process_registry.
- tests: yaml shim in two tests; compress locale sweep skips upstream *.tui.yaml catalogs; goal wait_on test stubs upstream _pid_alive; fast_command provider doubles accept target_model=.

* fix(parity 2026-10-01 ci): M5 lane — acp_adapter reds

- agent/conversation_compression_manual.py: compress_now passes
  trigger_reason="manual_compress_command" (same 3-line change as the
  unfolded L1 lane, cb0f5645c50; applied here so the ACP test is green on
  this branch regardless of fold order — identical bytes, folds clean).
- tests/acp_adapter/test_session.py: get_messages_as_conversation(
  include_timestamp=True) — the fork's default projection is byte-stable
  (F01), same adaptation L2 made for test_inline_edit_persistence.

Verified: tests/acp_adapter/test_server.py + test_session.py 59 passed (e45-pt).

* fix(parity 2026-10-01 ci): M2-hermes-clia batch 2 — codex owner row identity, auth refresh outcome, kimi/pool fixtures

- agent/codex_owner._current: compare the hydrated PooledCredential.source, not the raw row column:
  a row written without `source` loads as SOURCE_MANUAL on both sides and is the same generation
  (upstream's profile-shadowing test in test_auth_profile_fallback seeds such rows).
- hermes_cli/auth_commands.auth_refresh_command: the fork's Codex owner store raises AuthError on
  a failed/uncertain refresh instead of returning None; map it to the same "Could not renew" exit.
- test_auth_kimi_oauth_provider: drop the load_pool->None stub; the merged _add_kimi_oauth_credential
  (upstream shape) reads the re-seeded pool entry.
- test_auth_pool_operations (upstream-only): rows start unbenched (the owner store refuses a forced
  refresh of a cooldown row) and a non-429 failure is asserted as the owner's receipt fence
  (dead / codex_refresh_uncertain, pair untouched) rather than upstream's exhausted/dead split.

* fix(parity 2026-10-01 ci): restore the confab tool-call notice recovery on the extracted turn loop

The 2026-10-01 merge kept upstream's extracted agent/turn_*.py siblings and dropped three fork
seams (#942, agent/confab_notice.py) that lived inline in conversation_loop.run_conversation:

- turn_response_intake.normalize_model_response: announce a validated out-of-band notice once
  per turn (should_announce_notice ledger) and carry _confab_notice/_new_confab_notice on the
  verdict (+ _LoopState slots) for the final-text phase.
- turn_final_response.finish_text_response: tool_call_as_text / tool_call_unparseable notices
  re-prompt with the fixed TOOL_CALL_NOTICE_TEXT instruction BEFORE the empty-response ladder,
  persist the metadata-only system event row, share the 3-stall dropped-tool-call budget, and
  end the turn failed (tool_call_recovery_exhausted) when it is spent. Without this the merged
  loop fell into the empty-response retry (the StopIteration lead signature: one extra provider
  call per scripted notice across 137 reds).
- turn_context.build_api_messages: metadata-only notice rows never reach the provider request.

Fixture/assert updates in tests/agent/test_confab_notice_e2e.py for upstream mechanisms the
merge legitimately adopted (evidence in the CI5 ledger): 0f4587e336f first-request preflight
deferral (arm the built-in compressor with a real-usage anchor; the mock never prices the
transcript), #118900 last-assistant-reply re-anchor (pin the notice's neighbour, not list
length), 8f0322da5b8 failed_turn boundary row (exclude the Hermes-authored row).

Verified: tests/agent/test_confab_notice_compaction_stats.py 1/1; test_confab_notice_e2e.py
lcm shards + valid_empty_tool_event_is_not_replayed green after the turn_context port; full
file re-proof in progress.

(cherry picked from commit 0813ef705ecbfb71cf939685ee1dee725dd0ad19)

* fix(parity 2026-10-01 ci): M4-tools — approval bypass facade, async-delegation retirement/prune guards, formatter re-export

- tools/approval.py: every bypass site goes through is_approval_bypass_active() again (the merge
  re-introduced a hand-rolled `_yolo_active()` that dropped approvals.mode=off — the fork 2026-09-08
  incident); `_get_approval_mode` is a facade delegate so tests can patch either module.
- tools/async_delegation.py: `_dispatch_admitted` tolerates an executor double returning no future
  (fork test doubles) instead of rejecting as submission_failed; `_prune_completed_locked` no longer
  hashes a runner-supplied status (fork hostile-__hash__ guard).
- tools/process_registry.py: re-export `_format_async_delegation` (fork facade symbol upstream moved).
- tests/tools/test_async_delegation_terminal_receipts.py: dispatch helper assertion carries the payload.

Verified narrowly via scripts/test-gate: test_approval_mode_off_bypass + approval_mode_parity + test_approval
174 passed; numeric_binding_boundary + registry_boundaries + terminal_receipts 204 passed.

* fix(parity 2026-10-01 ci): M1 — discord native slash Range fallback + omitted-option defaults; reasoning descriptions name max/ultra

- plugins/platforms/discord/adapter.py _slash_proxy: resolve app_commands.Range tolerantly (test stubs lack it; bare type keeps the option) and render omitted optional options from the synthesised signature defaults (/undo with no count -> "/undo 1", the fork t_b4f07acf contract). Clears 14 reds in test_discord_slash_commands + 15 connect/liveness/event_silence reds that failed at connect() on the same AttributeError.
- locales/en.yaml platform.discord.command.reasoning: description/arg_effort name the effort levels incl. max + ultra (fork contract; the i18n key form dropped them). gateway/relay/command_manifest.py carries a hardcoded copy — out of lane, FOLLOWUP.
- tests: free_response offload class adapted to the fork sync mark_many + CoalescingJsonWriter seam (fork design; upstream-only tests); producer_matrix seeders import from cron.scheduler_delivery (upstream extraction), capture double stamps the admit_internal_event receipt, F02c True vocabulary.

* fix(parity 2026-10-01 ci): M3-agentb round 1 — re-thread stop-gate, placeholder seam, preflight announce; repoint moved patch targets

- agent/turn_stop_gates.py: kanban stop guard threads tools= into
  build_kanban_stop_nudge; persists the candidate answer (interim flush) and
  flags only the nudge synthetic (t_4eeb0202).
- agent/turn_final_response.py: classify_placeholder_final_text wired at the
  final-text seam before the empty ladder (t_887f9584); streamed buffer cleared.
- agent/turn_context_compaction.py: engine preflight maintenance announce with
  reason template + preflight_is_user_visible gate (fork 5636a8a6d5/829d4e0f3e).
- tests: iteration-limit summary drives agent._interruptible_api_call (summary
  now goes through _build_api_kwargs); model_metadata patches
  model_metadata_http.get/.stream, gpt-6 sol/luna 900K cap w/ catalog max,
  bedrock cache row via bedrock_confirmed provenance; kanban_stop source grep ->
  turn_stop_gates + complete_task result=; _FALLBACK_ANNOUNCE_LABELS rename.

Verified: scripts/test-gate narrow runs — test_iteration_limit_summary_session_user 3 passed,
test_kanban_stop 54 passed, test_model_metadata 158 passed,
test_placeholder_final_text_backstop 17 passed, test_preflight_announce_visibility +
test_pool_exhaustion_scope_label 22 passed.

* fix(parity 2026-10-01 ci): M1-gatewayb batch 1 - hermes_yaml shim, refused-followup report at adapter drop

- gateway/slash_commands.py: two lazy `import yaml` sites -> `import hermes_yaml as yaml`
  (upstream dropped pyyaml from deps; the fork shim is the loader everywhere else).
- tests: test_switch_announce, test_turn_concurrency, test_stop_during_preflight_refuses_turn
  import hermes_yaml (safe_dump, no `dump`).
- gateway/platforms/base.py::_drop_unresolved: a replayed restart follow-up refused by the
  adapter-level profile-route gate (which runs BEFORE the runner ingress gate upstream added)
  now logs PHASE=restart_followup_lost; the log body moved to
  fork_ext/restart_followups.report_refused_followup and run.py delegates to it.

Verified: test_switch_announce+test_stop_during_preflight+test_turn_concurrency 75 passed
(1 local-only home_io_guard false positive under the lane runner, CI-shaped run green);
test_restart_followups_admission_e2e 18 passed.

* fix(parity 2026-10-01 ci): M2-hermes-clia batch 3 — backup disk-image/usage-ledger excludes, CLI receipt batch

- hermes_cli/backup.py: restore the fork's staging disk-image suffix exclusion (.sparseimage /
  .sparsebundle / .dmg; 2026-08-09 42 GB subvps-staging.sparseimage) onto upstream's suffix tuple,
  and keep cache/claude-usage (usage.ace ledger) in the kept cache subdirs.
- test_backup: the root cache/*.MOVED.txt breadcrumb now falls under upstream's regenerable-cache
  rule (same verdict as cache/model_catalog.json); kept-dir assertion carried.
- test_cli_async_delegation_delivery (fork test relocated by upstream's tests/ mirror): completions
  arrive wrapped in ProcessNotificationBatch; unwrap before asserting the accepted input + sibling.

* fix(parity 2026-10-01 ci): M4-tools — HERMES_ALLOW_REBOOT downgrade, sandbox session-id bridge, lifecycle refusal markers

- tools/approval_detection.py: restore the fork HERMES_ALLOW_REBOOT opt-in (reboot/shutdown family
  downgrades from hardline to the DANGEROUS layer; every other hardline pattern untouched). Dropped when
  upstream extracted detection out of tools/approval.py.
- tools/code_execution_env.py: re-thread the fork `_inject_session_id` tail of `_scrub_child_env`
  (#636/C3: contextvar-resolved HERMES_SESSION_ID into the sandbox child; removed when unresolvable).
  Resolver stays on the facade (tools.code_execution_tool._resolved_session_id).
- tools/code_execution_tool.py: re-export `_scrub_child_env` / `_HERMES_CHILD_ALLOWED` (fork facade symbols).
- tools/terminal_tool_guards.py: every gateway-lifecycle refusal carries `blocked_by` =
  GATEWAY_LIFECYCLE_BLOCK_MARKER again (fork: execute_code surfaces blocks instead of a silent 0-exit).
  `_blocked_json` gains an optional `blocked_by`.
- tests/tools/test_execute_code_surfaces_blocks.py: source scan repointed to the extracted
  gateway_lifecycle_block (counts `_blocked_json(` vs markers). RED-proofed: unstamping one refusal fails it.

Verified via scripts/test-gate: test_hardline_blocklist 263 passed; test_execute_code_session_provenance +
test_execute_code_surfaces_blocks 18+11 passed.

* fix(parity 2026-10-01 ci): agent_init tool loader honors both patch contracts (run_agent.* and model_tools.*)

The L7 fold made _load_tools read get_tool_definitions/check_toolset_requirements
through the run_agent facade so fork tests patching run_agent.* work, which
shadowed the 142 upstream-style tests patching model_tools.* (a real catalog
loaded under the mock -> 'clarify' first instead of 'terminal'/'web_search').
_tool_catalog_fn prefers a patched facade attribute, else reads model_tools.

test_primary_runtime_restore: FailoverReason import from agent.error_classifier
(run_agent no longer re-exports it upstream; sole test importer).

Verified: test_run_agent_codex_responses + test_provider_parity +
test_primary_runtime_restore 140 passed; tests/cron/test_cron_fallback_alert_e2e
(run_agent.* patch convention) still green.

* fix(parity 2026-10-01 ci): M3-agenta — compaction attribution, codex ctx facade, credential-pool + anchor seams

agent/model_metadata.py: restore the fork's _resolve_codex_oauth_context_length
compatibility wrapper (upstream kept only the _with_source form; fork callers
and tests resolve through the bare name).

Compaction trigger attribution (cherry-pick of L1 cb0f5645c50 onto the fold
head, applies clean): trigger_reason labels re-threaded onto the extracted
turn_preflight / turn_context_compaction / turn_overflow / turn_recovery /
conversation_compression_manual / gateway run + run_turn call sites. Also
cherry-picked L1 0813ef705ec (confab tool-call notice recovery on the
extracted loop; one trivial union in turn_context.build_api_messages with the
fold's interrupt-close omission) — it clears the 32 confab-matrix
StopIteration reds in this manifest.

Tests adapted to seams the merge legitimately adopted (evidence in the CI6
ledger): compaction lint/announce follow the call sites into the turn_*.py
modules and the CompressionFacadeMixin forwarder; Platform is no longer a
prompt-identity field (#104414, agent/surface_switch.py); the feasibility
probe reuses the main window on a shared route (#89500) so the compressor
double carries context_length/threshold_tokens; model_metadata.requests ->
model_metadata_http.get; xai auth-store sync lives on the consolidated
_sync_entry_from_auth_store; Nous forced refresh adopts a peer-rotated usable
key without redeeming the grant (a6f75130386) while the fork's #670 stale
refusal still redeems the pool token; the fork gates timestamp behind
include_timestamp (#107) and rolls back compactions that INTRODUCE duplicate
active tool results (t_aace5343) — the anchor fixture opts in and persists
the tool rounds the way the loop's per-round flush does. The two real-lcm.db
replay oracles are marked allow_real_home_io (read-only, skip when absent).

Verified narrowly via scripts/test-gate (<=3 files/call):
  codex_subscription_proxy_context + gpt61_sol_900k + gpt61_sol_prestage: 31 passed
  compaction_attribution_lint + compaction_fallback_prompt_identity +
    compaction_stats_reconcile: 112 passed, 2 skipped
  credential_pool_singleton_freshness: 30 passed
  compression_last_assistant_anchor: 8 passed

* fix(parity 2026-10-01 ci): M5 lane — cron reds (11 files)

Code:
- cron/scheduler.py _resolve_job_fallback_chain: a job pinned by MODEL or
  ENDPOINT alone (no provider) never borrows the global chain (upstream
  #100437 / scoped_fallback_chain); the fork's same-provider filter st…
Kyzcreig added a commit that referenced this pull request Oct 4, 2026
… fixes main-red e2e-upgrade (t_c7d31a6a) (#1702)

* test(parity 2026-10-01 ci): completion_delivery pins the merged bool/None delivery vocabulary

Fork tests asserted the fork's "delivered"/"temporary"/"dropped" outcome
strings on _deliver_completion_notification / _deliver_async_delegation_group;
the merged seam (F02c ledger decision) is upstream's True/False/None contract
with the admit_internal_event receipt. Three mechanical axes:
- "delivered"->True, "temporary"->False, "dropped"->None on the seam asserts
- AsyncMock handle_message doubles -> AdmittingHandler (sets _gateway_accepted)
- _runner() session_store lends a _db (upstream #98573 borrows the store handle;
  without it runner._session_db is None and every pre-flight returns retry)
drop_requires_proven_terminal_target[False-*]: an unroutable event is False
(retryable), matching the test's own "not proof no future consumer can deliver"
and test_unroutable_async_event_remains_retryable.

Verified: gated pytest tests/gateway/test_completion_delivery.py ->
101 passed, 2 failed before the [False-*] fix; those two re-run below.

* fix(parity 2026-10-01 ci): re-thread fork follow-up spooling, /footer setter, session.redo, /model read-path flags

gateway/run_turn._run_agent_drain_pending: the merge took upstream's
"Discarding pending follow-up" at the extracted site, re-creating the fork's
2026-09-23 data-loss (4 follow-ups lost). Restore the draining-gateway spool
(_preserve_followup_across_restart) at the moved site.

gateway/run_startup._drain_startup_restore_queue: use the fork seam
_adapter_for_source (intake first, then unique (platform, profile) owner).
Upstream's _intake_adapter_for fails closed on spooled restart follow-ups
(restored rows, no live provenance) under multiplexing -> retried forever.

gateway/run_notifications: reconcile the two parallel fixes for a stale
update notice. Fork policy stays (24h configured / 5min unconfigured); a
runner with NO config object gets upstream's flat 1h cap (601a8a17c22) and
upstream's "adapter never connected" wording.

tui_gateway/methods_config_set: fork /footer config.set branch
(display.runtime_footer.enabled) dropped when upstream tabled the if-chain;
restored as a _CONFIG_SETTERS entry.

tui_gateway/methods_session: fork session.redo RPC route restored beside
session.undo (core _redo_session_core already survived in server.py).

gateway/slash_commands (/model picker, fork handler kept by R09): import
list_picker_providers from model_switch_providers and pass upstream #74003
read-path flags (cache-only catalogs, probe only the current custom endpoint).

Verified (hermetic test-gate, CI-faithful harness without pre-set HERMES_HOME):
  tests/gateway/test_update_command.py + test_restart_interrupt_intent_followups.py
    (+ test_restart_cascade.py) 115 passed, 5 skipped, 3 failed (cascade: separate)
  tests/gateway/test_restart_followups_boot_replay_e2e.py 3 passed (in c1 batch 35 passed)
  tests/gateway/test_model_command_async_offload.py + test_notify_sub_chat_type_write_canonicalization.py
    (+ test_multiplex_routing_authz.py) 16 passed, 1 failed (multiplex: separate)
  tests/tui_gateway/test_desktop_runtime_footer.py 10 passed (tui2 batch)

* test(parity 2026-10-01 ci): repoint source anchors and doubles at upstream's moved seams (gateway/tui)

- test_notify_sub_chat_type_write_canonicalization: writer reads fields via
  _field(); metadata branch gained `and chat_type`. Behaviour (chat_type
  resolved before the branch, canonical on write) unchanged.
- test_restart_interrupt_intent_followups: draining branch moved to
  run_turn._run_agent_drain_pending; anchor there, end at the method return.
- test_desktop_runtime_footer: payload literal moved to
  prompt_turn._complete_turn_payload; anchor on the server.py call site that
  attaches payload["footer"].
- test_undo_redo: _start_inflight_turn double accepts upstream's
  display_kind/display_metadata kwargs; fixture clears
  server_requests.reset_for_tests() (server._pending/_answers retired).
- test_server_no_duplicate_defs: upstream #97948 replaced the fixed 120s
  compress wait with the config-derived budget (floored at 120s); assert the
  budget the real helper computes reaches the supervisor.

* fix(parity 2026-10-01 ci): restore D-6 finally-block breadcrumb consume in run_turn; cascade guards scan the split turn pipeline

gateway/run_turn.py: the handler's finally block consumes the restart-initiated breadcrumb again (fork/main run.py:28913, dropped when upstream split run.py into run_turn*.py).
tests/gateway/test_restart_cascade.py: the three source-scan guards concatenate run.py + run_turn.py + run_turn_runner.py so the relocated callback/gate/finally are still covered.

Verified: e45-pt-L3b.sh tests/gateway/test_restart_cascade.py -k 'c1_detection or single_gate or finally_consume' -> 3 passed.

* fix(parity 2026-10-01 ci): L4-kanban round 2 — reaper owner window, corrupt-board guard, review schemas; ledger

Code:
- kanban_db_dispatch._reap_terminal_worker_row: pass the fork's required owner_window/conn/run_id
  (merged call raised TypeError inside the per-row guard -> terminal workers never reaped).
- gateway.kanban_watchers._is_corrupt_board_db_error: read KanbanDbCorruptError from
  kanban_db_connect (facade no longer re-exports it; quarantine was disarmed).
- tools/kanban_tools_schemas: kanban_request_changes description restored (lens list from
  kanban_review_schema).

Tests (upstream-only fixtures -> fork contracts):
- test_kanban_db: stale-claim breaker models a dead claimer past the launch bound; infra spawn
  refusal marked platforms("linux") (restart_safe_gateway_child_argv is in_process off Linux);
  archive termination event is the fork's archive_worker_terminated.
- kanban_tools late-orphan: dead claimer + launch bound; _pid_started_in_claim seam.
- worker_authority_isolation: pin the RUN id, structured metadata for the receipt gate.
- review_coverage_gate human lane: drop worker scope, home the card on the reviewing session.
- second_claim_class [complete]: operator close under a live claim is force=True (#111764).
- schedule_wake: promote force= retired (#106195). survivor/termination_identity: #117483 evidence.
- worktree_teardown retry: patch kanban_survivor._git.

Ledger: docs/sync/review/ledger-2026-10-01/CI5-L4-kanban.md

* fix(parity 2026-10-01 ci): restore fork behaviours dropped by the merge, round 2 (L2-agent-core)

- codex_owner.refresh: a 429 refresh POST reserves the token's probe-throttle
  slot (new hermes_cli.auth_codex._reserve_codex_quota_probe_slot) so the
  mid-cooldown pre-probe refresh (#89415) does not re-POST the same single-use
  refresh token on the very next selection (test_codex_owner_selection_review
  saw two POSTs).
- agent_init._resolve_context_length: model.max_tokens config override
  (fork; dropped in the phase-helper split) re-threaded onto agent.max_tokens
  and _session_init_model_config.
- turn_usage: compressor update gated on a MEASURED prompt count read from the
  pre-fold aggregator usage (r6 findings 7 / round-4 2), plus the one-shot
  Codex 272K tier notice (#1567) — both lived in the fork's conversation_loop
  usage block.
- turn_finalizer: _diag_msg/_diag_args keep the fork source contract (end on
  task=/effective_task_id); the upstream origin= tag rides as a suffix.
- shell_hooks: spawn EACCES/ENOENT named exactly without the OSError detail
  (argv can be the credential); unparseable/empty stdout is its own fail-closed
  reason ("unparseable stdout"), distinct from an unknown directive.
- provider_seam._restore: carry forward containers registered after the
  snapshot (lazy facades such as HERMES_OVERLAYS) instead of leaving them out
  of the swapped generation (KeyError on every later read).
- honcho identity_signature: memo keyed on honcho.json bytes, not mtime
  (coarse-mtime filesystems served the stale pinPeerName; fork C7 k102).

Verified narrowly via e45-pt-L2.sh (sandboxed test-gate), each file green
after its fix: test_codex_owner_selection_review, test_credential_pool,
test_run_agent_init_memory, test_usageless_response_accounting,
test_codex_tier_notice, test_turn_ended_task_id_log, test_shell_hooks,
test_plugin_transport_api_mode, test_auth_registry_mid_discovery,
test_oauth_pkce_plugin, test_provider_registry, test_plugin_discovery,
test_pin_peer_name, test_identity_signature.

* test(parity 2026-10-01 ci): meet fork contracts in merged tests, round 2 (L2-agent-core)

- entitlement_fail_closed / non_chat_primary_restore: assert the fork's
  gated route announcement ("Model recovery (restore): ...",
  model.announce_recovery) instead of upstream's removed unconditional
  "Primary model restored" notice.
- credential_pool_codex_singleton_isolation: a manual:device_code row never
  adopts the singleton (agent/codex_owner, #673); the re-auth reaches the pool
  through the seeded device_code row.
- inline_edit_persistence / replay_cleanup: get_messages_as_conversation(
  include_timestamp=True) (fork F01 default projection is byte-stable);
  _SendAgent double carries provider for the interrupt-close filter.
- prompt_cache_ttl_propagation: #84733 restart-discipline guard rewritten for
  the phase-helper split — every _try_activate_fallback site in agent/turn_*
  must be an `if` test whose verdict is "break" (retry-loop phases, via
  _arm_fallback_restart / _fallback_break) or "continue" (outer-loop phases).
  Mutation-checked: flipping recover_empty_response to "break" fails it.
- route_id_correlation: fake Anthropic stream yields message_stop (upstream
  _drain_stream treats a stream without it as a drop).
- usage_pricing: models.dev leg exercised with an id absent from the fork's
  Grok snapshot; xai-oauth and api.x.ai-over-HTTPS price at xAI list rates by
  fork design (notional relay / host match).
- provider registry fixtures (plugin_transport_api_mode,
  auth_registry_mid_discovery, oauth_pkce_plugin): teardown via
  provider_seam._restore / _reset — the facades are additive and refuse
  pop/clear.
- run_agent_api_kwargs: the fresh-build assertion is checked before
  _build_system_prompt (upstream 921ab7a163 seeds the workspace pin from the
  session row inside the build).
- pin_peer_name: Honcho keys ride memory.<key> via identity_signature().
- plugin_paths_follow_profile: drop the mem0-qdrant case (fork retired the
  mem0 OSS backend; docs/sync/review/mem0-resolution-decision.md).
- credential_pool codex_sync_pool stub: httpx.SyncByteStream for the
  response-body cap; curator_disk_accounting hands the review a candidate
  list; system_prompt patches the prompt_builder symbols;
  pool_capacity_503_retry rig disables streaming (upstream c5b99a3ee5 keeps
  direct-call contexts on the streaming wire).

* fix(parity 2026-10-01 ci): L3a gateway a-m batch 1 — honcho memo digest, checkout gate ordering, boot preload roots, stale fixtures

Code:
- plugins/memory/honcho: identity_signature memoizes on the config's CONTENT digest and only
  stores values when the bytes it parsed still match (fork C7 k102; mtime/size key served stale
  values on coarse-mtime filesystems).
- tui_gateway/server._run_prompt_submit: checkout gate (fork C6 #1035) runs BEFORE the merged
  ownership/liveness admission so a frozen continuation is refused with no lease/inflight/agent
  side effects.
- gateway/boot_preload: root modules derived from the tree like upstream setup.py (py-modules
  list is gone); hermes_platform + pm packages preloaded; 4 import-side-effect scripts excluded.
- api_server_openai_routes: history loader passes include_timestamp=True (fork #107 shape).

Tests adapted to merged contracts (reasons inline + ledger):
auto_continue (interrupted marker last line), auto_skill_title (run generation gate),
background_process_notifications (bool delivery, suppress_completion double), c7 backfill
(renamed pid probe, honcho seam), checkout_admission (room-grant token, wire-contract params),
clarify batch (TurnRunner._clarify_callback_sync owns the per-card loop).

Verified: narrow gated pytest on the 7 files — 56 passed (auto_continue/auto_skill/bg_notif),
82 passed (c7/checkout/clarify after fix; checkout_admission 56 passed alone).

* test(parity 2026-10-01 ci): multiplex preflight unpins the sandbox DB; undo_redo restores server in place; toolsets denylist checks the uncollapsed catalog

- test_multiplex_routing_authz: the fork's hermetic conftest pins hermes_state.DEFAULT_DB_PATH (gateway.session imports hermes_state), which wins over the profile scope inside _default_db_path(); restore the import-time sentinel (test_housekeeping_profile_scope idiom).
- test_undo_redo: importlib.reload(server) re-binds the split siblings onto an already-tagged namespace and trips upstream's bind_module collision guard (change_watcher._active_pet vs methods_session._active_pet); restore _methods in place like test_undo_command.
- test_gui_surface_toolsets: upstream renamed todo -> todo_list and defers it behind the tool_search bridge; assert the denylist on the uncollapsed catalog and on the assembled schema.

Verified: e45-pt-L3b.sh on the three files -> 19 passed + 4 passed (undo_redo).

* fix(parity 2026-10-01 ci): L8 state/ci/scripts reds (batch 2) + ledger

CODE:
- gateway/session_persistence: _write_sessions_json_unlocked moved onto the
  mixin next to its only caller _save_sessions_json (merge left the mixin
  calling a SessionStore-only method; upstream's writer test instantiates the
  mixin alone -> AttributeError).
- gateway/platforms/base: upstream names _MACOS_PROXY_TTL_SECONDS /
  reset_macos_proxy_cache alias the fork's stale-while-revalidate cache.
- 17 upstream-only files: monotonic limiter seeds 0/0.0 -> float("-inf")
  (fork lint 2c2adef9ed3 now sees upstream code; 22 hits -> 0). Guarded unset
  sentinels carry `# zero-seed-ok:` naming the guard.
- publish_evidence_step.sh + its test removed: upstream 2ab7d9bfe30 deleted
  the publish-e2e-evidence pipeline the wrapper served (no caller left).

TESTS (fork contract vs upstream fixture):
- rewind_surfaces_invariant: gateway /undo N and undo_last(N>1) count
  half-turns (fork); harness passes 2*n, invariant unchanged.
- writer_conn_thread_safety: fork retries message-scoped SystemError; test
  asserts bounded replay then propagation.
- session_list_denorm_reland: AST contracts scan the hermes_state* mixin
  family + _INSERT_MESSAGE_SQL callers.
- test_hermes_state_core v9->v11 fixture session titled (v16 orphan tagging,
  v30 delegate exclusion from trigram).
- detect_changes_event_scoping: docker/nix are path-scoped lanes upstream.
- atomic_json_writers_unified: .sessions.lock is the fork writer lock, not a
  temp file.
- planned_restart_notice_multiplex: two inert tuple assertions.
- tests/agent/conftest: _block_real_claude_keychain honours
  allow_macos_keychain (upstream Keychain tests run the Darwin branch with
  subprocess.run mocked).
- snapshot_session_id_leak: HERMES_HOME is scoped in the fork (#543).

Verified (test-gate, sandboxed HOME, .venv python): every manifest file +
the 3 macOS-job files green narrowly; details per file in
docs/sync/review/ledger-2026-10-01/CI5-L8-state-ci-scripts-root.md.

* docs(parity 2026-10-01 ci): CI5 ledger for lane L2-agent-core

* fix(parity 2026-10-01 ci): hygiene keeps the fork's 400-message hard limit and repeated-failure escalation; /stop background test pins the catalog key

gateway/run_turn.py + run.py: _HygieneSettings defaults hard_msg_limit=400 (fork fleet default, config_defaults.py; upstream 5000) and carries failure_alert_after (compression.hygiene_failure_alert_after). The timeout and abort notices bump the per-session streak and, from the Nth consecutive failure, send agent.hygiene_timeout.format_repeated_failure_alert; a landed compaction clears it. Fork behaviour from run.py's pre-split hygiene block (fork/main 27499-27560, 27840).
tests/gateway/test_session_hygiene.py: user-facing wording assertions read the i18n catalog (gateway.compress.hygiene_timeout / hygiene_failed) instead of the pre-i18n literals.
tests/gateway/test_stop_ends_background_delegations.py (upstream-only): the reply is compared to t('gateway.stop.stopped'); the fork's catalog words it present-progressive (test_stop_honest_wording.py).

Verified: e45-pt-L3b.sh test_session_hygiene.py -> 45 passed; test_hygiene_turnhold_backoff.py + test_hygiene_warning_lifecycle.py + test_stop_ends_background_delegations.py -> 7 passed.

* fix(parity 2026-10-01 ci): L3a gateway a-m batch 2 — route anchors, env bridge funnel, slash echo fields, flush helpers, override persist ordering

Code (merge regressions, fork behaviour restored onto upstream's structure):
- gateway/run: _bridge_config_to_env re-wires restart_policy._bridge_agent_config_to_env (config
  wins over stale .env for resume/restart knobs); _set_session_vars_for_source binds
  parent_chat_id (upstream a247012dc11) so a stale env never shadows the live source.
- gateway/platforms/event: MessageEvent regains suppress_public_echo / deferred_reply_text
  (Discord native-slash one-delivery handoff; base.py read them via getattr).
- agent/session_persistence: the fork flush helpers live beside the flush instead of a lazy
  `from run_agent import` that fails closed when run_agent is swapped (rows silently unwritten);
  run_agent re-exports them.
- gateway/session.set_model_override: persist OUTSIDE _lock (the fork _StoreLock defers in-lock
  writes to release, inverting publish-after-persist), then chat pin, then publish.

Tests adapted (reasons inline + ledger CI5-L3a-gateway-a-m.md): display_null wiring doubles,
kanban fair_dispatch + dispatcher_standby (kanban_db_dispatch / kanban_watchers_common seams),
login_command (route identity carries provider; rehydrate resolves as-is), loop_liveness
(patch gateway.shutdown_watchdog).

Verified (narrow gated pytest): display_null+internal_row+kanban 28 passed; route_anchor+login+
loop_liveness 47 passed; login+chat_model_pins+model_override_persistence 47 passed;
session_store_lock_io/never_spans_io green; interrupt_close_flag + flush_diverts green.

* fix(parity 2026-10-01 ci): L7 tools/cron — web keyed-fallback + breaker chain, delegate timeout transport drain, durable background output, kanban grant boundary

Round-5 CI lane L7-tools-cron for #1624 (ledger docs/sync/review/ledger-2026-10-01/CI5-L7-tools-cron.md).

Code restored onto upstream's structure (fork behaviour the merge dropped):
- tools/web_tools_extract.py: _breaker_extract (402/401 dead-backend breaker, #1562), _failed_extract_batch and the
  keyed web.extract_fallbacks chain (#1516) back in _dispatch_extract; hooks read via the tools.web_tools facade.
  tools/web_tools_truncate.py: _trim_results keeps served_by/fallback_from (+ local-pdf metadata).
- tools/terminal_tool_background.py: durable_output=bool(notify_on_complete) on spawn_local (t_1191e078).
- tools/terminal_tool.py: messaging-gateway turns keep the over-cap foreground REFUSAL (#1012).
- tools/terminal_tool_guards.py: process(action="wait") guidance copy in the nohup/& recipes.
- tools/code_execution_env.py: git-lane env carry (t_45c11886 / C3 #1254) re-threaded into the extracted module.
- tools/delegate_tool.py + delegate_tool_child_run.py: upstream's abandoned-worker transport drain (#94248) split out
  as _drain_abandoned_child_transports and called from the fork supervisor's timeout path.
- cron/lifecycle_guard.py: read-only heredoc data paths are not mention candidates (#1017/#1348).
- cron/scheduler.py: never suppress the alert that enters a provider-window quota hold (#89376).
- hermes_cli/cron.py: vanished-job warning on every status path.
- hermes_cli/kanban_db_dispatch.py: pop HERMES_DELEGATED_CHILD_CONTEXT at the grant boundary; _recent_worker_exits
  read through the kanban_db facade.
- tools/skill_manager_tool.py: name validator fullmatch (AC10).
- agent/agent_init.py + run_agent.py: tool definitions read through the run_agent facade.

Tests repointed to moved symbols / fork contracts (see ledger per file); tests/tools/test_lazy_sdk_probe_importable.py
deleted (tests the retired tools.lazy_deps surface; property pinned by tests/pm/test_extras.py).

Verified (e45-pt-L7.sh, sandboxed HOME, <=3 files per call): every manifest file green —
cron: fallback_alert 2/2, store_concurrency 14/14, no_auto_sentinel 8/8, workdir 16/16, fire_fence 3/3,
selfisolation 1/1, heredoc_data 28/28, python_heredoc_parity 66/66, quota_hold 4/4;
tools: blocked_command_guidance 8/8, browser_real_profile 65/65, code_execution_git_lane_env 1/1,
cron_auto_model 93/93, cron_model_arg_coercion 13/13, gateway_foreground_deafness 32/32, launchctl_guard_diagnostic 2/2,
request_tool_approval 13/13, skill_manager_create_shared 35/35, snapshot_session_id_leak 4/4, terminal_task_cwd 7/7,
timeout_transport_drain 4/4, web_backend_breaker 19/19, web_keyed_fallbacks 6/6, windows_native_support 16/16.
tests/cron/test_cron_kanban_env_isolation.py 15/15 on ace-ai (linux-only spawn test).

* test(parity 2026-10-01 ci): model-override rehydration tests follow the fork's identity re-resolution; pre-agent fallback fixture answers the route precheck

tests/gateway/test_session_model_override_persistence.py: upstream's three new tests patch _resolve_runtime_agent_kwargs_for_provider, the legacy identity-less path. On the fork a persisted {model, provider} identity is durability truth and is re-resolved through _reresolve_model_override_credentials -> hermes_cli.model_switch.switch_model, so the doubles move there. The codex still_unavailable arm now asserts the fork contract: SessionRouteUnavailableError, preference preserved, default route never consulted (same as test_session_model_reset.py credentials-unavailable) instead of upstream's silent default-provider fallback.
tests/gateway/test_pre_agent_fallback_notice.py (upstream-only): the MagicMock runner returns PersistedSessionRouteLookup('absent') from _persisted_session_route_identity so the fork's fail-closed precheck sees no pin.

Verified: e45-pt-L3b.sh test_session_model_override_persistence.py -> 9 passed; test_pre_agent_fallback_notice.py -> 2 passed.

* fix(parity 2026-10-01 ci): restore fork auth-error markers; telegram/warning-wiring guards follow upstream's seams

gateway/run.py: _GATEWAY_AUTH_ERROR_RE regains the fork's credential-resolution / Codex-OAuth / pool-exhausted / provider-not-configured markers (fork/main run.py:754-764) so those envelopes map to the auth reply instead of the generic retry message.
tests/gateway/test_telegram_noise_filter.py: the credential-resolution test asserts upstream's plain-language auth reply (sign-in + /login), same as its sibling.
tests/gateway/test_telegram_restart_parity.py: the AST guards treat _start_polling_mode (extracted from connect) as bootstrap, accept upstream's _cold_boot_drop_pending(is_reconnect=...) gate (False on reconnect, config knob on cold boot — AC-1/AC-2 prove it), and resolve a single-assignment local alias. Mutation-checked: flipping the network-error ladder to drop_pending_updates=True still fails both guards.
tests/gateway/test_warning_wiring_conservation.py (upstream-only): the context carries a live status adapter + run predicate and the status lane is patched at safe_schedule_threadsafe, where the fork's late-resolving status callback schedules.

Verified: e45-pt-L3b.sh test_telegram_noise_filter.py -> 168 passed; test_telegram_restart_parity.py -> 6 passed; test_warning_wiring_conservation.py -> 1 passed.

* fix(parity 2026-10-01 ci): discord native slash option descriptions are catalog keys

plugins/platforms/discord/adapter.py: the fork's /new, /reset, /undo, /compress, /usage, /help option descriptions move from literals to platform.discord.command.<cmd>.arg_* keys (upstream's test_discord_native_slash_specs_hold_catalog_keys_only requires every slot to resolve).
locales/*.yaml: the 7 keys added to en.yaml and, in English, to every other locale (tests/agent/test_i18n.py::test_catalog_keys_match_english requires key parity; t() falls back to English anyway).

Verified: e45-pt-L3b.sh tests/agent/test_i18n.py tests/gateway/test_platform_adapter_i18n.py -> 67 passed. test_discord_slash_commands.py/_scope.py: 14 failed before and after this change (L3a lane, untouched).

* test(parity 2026-10-01 ci): ws orphan resume ctx carries params; manual-reset tests pin the store handle and read upstream's config warning

tests/tui_gateway/test_ws_orphan_races.py (upstream-only): the eager-resume ctx carries params={} — the fork's build reads the client's declared source from the request.
tests/gateway/test_manual_reset_sticky_route.py: the SQLite test pins its handle through store._db (the store now resolves through hermes_state_registry.acquire(), so patching hermes_state.SessionDB no longer reaches it — rows landed in a different file, FK failure); the parse-warning test reads upstream's config_read_errors warning (path + problem line, no source snippet) instead of gateway.run's removed error=<ExcType> line.

Verified: e45-pt-L3b.sh test_ws_orphan_races.py -> 29 passed; test_manual_reset_sticky_route.py -> 25 passed.

* fix(parity 2026-10-01 ci): queued follow-up re-stamps the turn clock and keeps a leftover /steer; relocated-seam test fixes

gateway/run_turn.py: fork 2026-09-29 behaviour re-threaded into the split turn pipeline — (1) a queued follow-up recursing on the parent's slot re-stamps turn.started_ts / busy_ack_ts (only for the key this run claimed); (2) a result['pending_steer'] that loses the next turn to a queued follow-up is appended to the session's /queue overflow (same slash-command guard) instead of being dropped. From fork/main run.py 39217-39260, 39547-39552.
tests/gateway/test_relay_injection_egress_priming.py: _inject_watch_notification returns upstream's True on adapter acceptance (merged contract, ledger F02c) — the fork's 'delivered' string is gone.
tests/gateway/test_resume_inflight_guidance.py: the AST wiring guard reads gateway/run_turn_runner.py, where upstream moved the dispatch site.
tests/gateway/test_rich_sent_store_off_loop.py: waits for the fork's dedicated writer thread instead of racing it (record_async only enqueues).

Verified: e45-pt-L3b.sh on the four files -> 22 passed (rich_sent_store run twice: 2 passed both).

* fix(parity 2026-10-01 ci): weixin writes ride the FIFO lane again; hard-exit funnel fences the lanes

ContextTokenStore.set is sync and dispatches through _dispatch_weixin_json_write
(single FIFO lane, ordered, fenced at disconnect/exit) instead of upstream's
per-call to_thread + asyncio.Lock; _sync_buf_path restored for _save_sync_buf.
gateway.run._exit_after_graceful_shutdown calls fence_lanes_for_hard_exit
between lock release and the lifecycle stamp (os._exit skips the atexit fences).
tests/gateway/test_weixin.py repointed at the lane contract (fence to observe).

Verified narrowly: test_weixin_state_write_off_loop (14 passed),
test_weixin + test_weixin_typing green, test_shutdown_pending_flush_off_loop
production-exit-funnel tests green.

* fix(parity 2026-10-01 ci): restore the ordered runtime-status lane behind the public writer

gateway/status: _RUNTIME_STATUS_LANE (single worker) + _fence_runtime_status_lane
back; submit_runtime_status_write queues on the lane again (fork #817 contract:
write_runtime_status fences it first so a direct terminal write can never be
overtaken by an older deferred one). write_runtime_status_locked alias restored.
The lane sits in front of upstream's _RuntimeStatusWriter, which still does the
actual persistence.

tests: test_no_sync_work realpath counter ignores the frames tests/home_io_guard.py
issues from inside Path.resolve()'s single walk (fork-only harness, 2b98a469d39);
test_runtime_status_write_off_loop spies _prepare_runtime_status_update (the
merge step every writer passes through) instead of the removed
_write_runtime_status_unlocked.

Verified narrowly: test_no_sync_work_per_inbound_message 13 passed,
test_runtime_status_write_off_loop 9 passed.

* test(parity 2026-10-01 ci): network-reachability ratchet follows upstream's module split; CI5-L3b ledger

tests/gateway/test_no_network_reachable_from_loop.py:
- validate_requested_model spy -> hermes_cli.models_validate (moved).
- _compress_context non-vacuity probe -> agent/compression_facade.py (AIAgent mixin).
- REACHABLE_BASELINE re-frozen for the run.py / run_agent.py split: the walker
  reports one sink per coroutine and now terminates the same pre-existing chains
  at requests.get (resolve_runtime_provider>_get_model_config>
  _auto_detect_local_model, on fork/main too) under the new module names.
  Measured with the test's walker: fork/main c14e059f8f2 = 17 sites, lane head =
  18, none newly reachable; /model and /reset doors still absent.

docs/sync/review/ledger-2026-10-01/CI5-L3b-gateway-n-z-tui.md: one row per red file.

Verified: test_no_network_reachable_from_loop.py 15 passed (72s).

* fix(parity 2026-10-01 ci): L5 keychain flags on dock launch, escaped resume-title hint, cli_hint/codex test seams

- tools/bot_desktop/browser.py: dock_argv carries --password-store=basic --use-mock-keychain
  (fork guard tests/cli/test_chrome_launcher_keychain_guard.py covers every detached launch).
- hermes_cli/main_tui_launch.py: the `-c "<title>"` resume hint goes through cli_hint.hint_value
  (fork contract: titles with $HOME / `id` must not expand).
- tests/hermes_cli/test_cli_hint.py: patch site moved to main_tui_launch; repair helpers
  now live in hermes_state_repair; SessionDB(read_only=True) ctor kwarg on the fake.
- tests/hermes_cli/test_auth_codex_provider.py: pool force-refresh goes through the owner
  transaction (refresh_codex_oauth_pure), not load_pool().try_refresh_matching.

Verified: tests/cli/test_chrome_launcher_keychain_guard.py tests/hermes_cli/test_cli_hint.py
tests/hermes_cli/test_auth_codex_provider.py -> 148 passed.

* parity(2026-10-01): L7 fold — re-apply the abandoned-worker transport drain call dropped by the import-conflict resolution

* fix(parity 2026-10-01 ci): restore compaction trigger attribution on the extracted turn modules

The fork's 2026-08-20 trigger-attribution audit passed trigger_reason=<label>
at every _compress_context call site (threshold / pre_api_pressure /
overflow_413 / overflow_context / tier_reduction / idle_resume /
engine_preflight_maintenance / session_hygiene / manual_compress_command).
Upstream's turn_*.py extraction dropped every label, so each compaction logged
trigger=UNATTRIBUTED and the announce rendered no reason clause.

Re-thread the labels onto turn_preflight, turn_context_compaction,
turn_overflow (compress/compress_scored_by_tokens gain a trigger_reason kwarg),
turn_recovery (tier_reduction), gateway/run_turn + gateway/run (session
hygiene), and the shared manual core conversation_compression_manual.compress_now
(the CLI, TUI and ACP surfaces now route through it; the gateway slash handler
still passes the literal).

test_compaction_trigger_coverage: the manual-surface guard now pins the label
in the shared core and accepts a surface that routes through compress_now
(cli -> hermes_cli/cli_session_mixin.py, acp -> acp_adapter/commands.py).
test_compress_context_progress_timeout: upstream #114594's exact-equal clamp
numbers replaced by the fork reconcile_timeouts invariants (idle lifted strictly
above the aux deadline, ceiling admits one fallback) already pinned by
tests/gateway/test_compress_abort_honesty.py.
test_length_continuation_thinking_exhaustion: helper imports repointed to
tests.run_agent._run_agent_helpers (upstream moved tests/agent/test_run_agent).

Verified: tests/agent/test_compaction_trigger_coverage.py
tests/agent/test_compress_context_progress_timeout.py
tests/agent/test_length_continuation_thinking_exhaustion.py
tests/gateway/test_compress_abort_honesty.py -> 72 passed, 1 pre-existing
unrelated failure (locale key; fails identically without this diff).

* fix(parity 2026-10-01 ci): reply re-anchor must not bind the engine's kept head to a tail twin

Upstream #118900 (_ensure_compressed_keeps_last_assistant_reply) locates the
folded reply's slot by scanning `compressed` for the LAST row content-equal to
the reply's original follower. When the engine keeps the head verbatim and the
trailing user turn is a content twin of a head row (fork #942 guard:
test_plugin_equal_head_cannot_match_overlapping_tail), that scan bound the kept
HEAD row as the follower and inserted the reply in front of the engine's head
(`[reply, head, notice]`): the notice placement the fork pins was pulled out of
order.

Apply the fork's head-first overlap rule (same as _reinsert_tool_notice_events):
leading `compressed` rows that align positionally with the original rows BEFORE
the reply are the kept head — excluded from the follower scan and never read as
the trailing real user turn (`_kept_head_len`, `kept_head=` on
_reply_insertion_index).

Tests (fork contracts adapted to the adopted upstream mechanisms):
- test_confab_notice_e2e::test_compaction_notice_without_successor_uses_original_boundary:
  the folded middle row now carries real weight, since #118900's reply
  re-anchor plus the user-turn anchor made the one-word candidate GROW and the
  commit-site anti-growth guard refused the whole compaction (fixture artefact).
  Assert the notice's left neighbour (surviving predecessor / summary boundary),
  not a list index.
- test_compression_concurrent_fork::test_compression_restores_user_turn_when_compressor_drops_all_users:
  the restored row now also carries upstream's durable `message_uid` +
  `timestamp` (751d8526e35); compare role/content only (contract: CONTENT survives).

Verified: tests/agent/test_confab_notice_e2e.py +
tests/agent/test_compression_concurrent_fork.py -> 129 passed.
tests/agent/test_confab_matrix_{bigargs_lcm,image_builtin,image_lcm} -> 48 passed;
tests/agent/test_confab_matrix_{merge_builtin,merge_lcm,twins_builtin} -> 48 passed.
tests/agent/test_compression_last_assistant_anchor.py: 6 reds pre-exist on the
lane base (fixture `None is not None` at L117), unchanged by this diff, not in
any round-5 manifest.

* fix(parity 2026-10-01 ci): restore the measured 872K gpt-6 Codex windows and the large-policy picker hint

agent/model_metadata.py: upstream carried the gpt-5.6 900K verdict forward
onto gpt-6-sol/luna (family prefixes) and bumped gpt-6-astra to 900K. The
fork measured all three against the codex-sub catalog
(max_context_window=872,000; astra 2026-09-04, sol/luna 2026-09-22) and
lists them EXACTLY so unprobed gpt-6 descendants never inherit a cap. The
merge took upstream's values -> every fork window test resolved 900,000
instead of 872,000. Fork table + exact-only gpt-6 eligibility restored.

hermes_cli/models_validate.py: the ineligible `-900k` rejection lost the
fork's policy-aware hint (under `large` the bare slug already carries the
window, so the picker example is `gpt-6-sol`, not `gpt-5.6-sol-900k`).

tests/agent/test_codex_context_policy.py (fork-only test, adapted to the
adopted upstream seams, no contract change):
- `patch("agent.model_metadata.requests.get")` -> `model_metadata_http.get`
  (upstream routes every metadata probe through model_metadata_http; the
  lazy `requests` shim is gone).
- `validate_requested_model` imported from hermes_cli.models_validate
  (upstream split hermes_cli.models).
- the live-catalog test hands a JWT-shaped token: upstream #121486 refuses a
  non-JWT credential aimed at chatgpt.com before probing.

Verified: tests/agent/test_codex_context_policy.py -> 64 passed (was 35 red).
tests/hermes_cli/test_model_validation.py: 6 failed + 1 error identical with
and without this diff (Anthropic warning wording / registry container),
not in any round-5 manifest.

* fix(parity 2026-10-01 ci): re-thread per-class skew calibration and idle blackbox bookkeeping onto the turn_* split

agent/turn_context_compaction.py + agent/turn_preflight.py +
agent/turn_request_assembly.py: the fork's P2 "compact on the truth" trigger
(note_rough_sent / calibrated_tokens / should_compress_request /
trigger_compare_tokens_for with `messages` threaded through so each request
is classified per content class; R05 ledger TODO `turn_preflight L3548`) was
dropped when upstream extracted the preflight into turn_context_compaction
and the pre-API gate into turn_preflight. Both gates compared the raw rough
estimate again, so the whole per-class arm was dead code. Restored on the
extracted modules: the prologue preflight uses the surviving
_preflight_request_tokens_split (rough vs anchored), the pre-API gate reads
the rough figure request assembly now stashes beside the anchored flag
(agent._request_pressure_rough). Non-bool verdicts from MagicMock doubles
fall back to should_compress, as on fork/main.

agent/turn_context_compaction.py: idle-resume compaction lost the fork's
blackbox bookkeeping (idle_compaction_fired + before/after tokens, C5 #42);
restored, estimator read through the turn_context facade, failure-tolerant.

agent/chat_completion_helpers.py: _summary_text reads tool_calls via getattr
so a minimal normalized shape (content only) is not a summary failure.

Tests:
- test_per_class_skew_calibration: the two AST guards follow the production
  sites into turn_context_compaction / turn_preflight (module getters only).
- test_iteration_limit_summary_display_fields: upstream routes the chat-mode
  summary through _build_api_kwargs -> _interruptible_api_call (keeps the
  cached prefix); the doubles now sit on those seams, contract unchanged.

Verified: tests/agent/test_per_class_skew_calibration.py 36 passed;
tests/agent/test_idle_compaction_lock_and_guards.py 5 passed;
tests/agent/test_iteration_limit_summary_display_fields.py 3 passed;
test_preflight_compression_gate + test_turn_context_compaction +
test_engine_preflight_wire 7 passed; test_preflight_lock_defer +
test_preflight_compression_cap_e2e + test_native_preflight_estimate 9 passed;
test_confab_notice_e2e + test_confab_matrix_merge_builtin +
test_confab_notice_compaction_stats 81 passed.

* fix(parity 2026-10-01 ci): price image parts under the shared estimator divisor; D-6 thrash fixture on upstream's bounded tail floor

agent/chat_completion_helpers.py: the stale-call estimator routes through the
fork's shared 3.5 chars/token divisor (886877cfbc8), but upstream's image
pricing (#63871/#76411) expressed the learned per-image TOKEN cost as
image_cost*4 chars — under the fork divisor that inflated every image by
4/3.5. Express it in chars under COMPOSITION_CHARS_PER_TOKEN so it
round-trips to image_cost tokens.

tests/agent/test_context_estimator_multimodal.py (upstream-only): the text
contract pins "no image pricing applied" against the fork divisor instead of
upstream's char/4 literal.

tests/agent/test_compaction_failed_summary_donepath.py (fork-only): upstream
fdbcdef9146 bounds the protect_last_n count floor by the tail token budget
(pinned by test_context_compressor::test_message_floor_does_not_unboundedly_override_soft_ceiling),
so the 4-pair huge tail no longer rides the floor past the ceiling and the
request genuinely shrinks (probe: fork/main 16->12 rows / 87K post; merged
16->6 / 41K). The thrash condition is rebuilt from the REQUIRED anchor pair
(last user #10896 + last assistant #29824), which both trees keep whole.
Contract (failed/placeholder summary over threshold counts ineffective on the
real done-site) unchanged; the done-site code itself survived the merge.

Verified: test_context_estimator_multimodal + test_non_stream_stale_timeout +
hermes_cli/test_load_progress -> 19 passed;
test_compaction_failed_summary_donepath -> 1 passed.

* fix(parity 2026-10-01 ci): re-thread the request-body byte budget and the image-lifecycle invariant onto the turn_* split

The fork's serialized request-body ceiling (agent/request_body_budget.py:
byte preflight after request middleware, terminal re-check once execution
middleware replaced the payload, and body_too_large 413 recovery that
remediates retained images once and otherwise fails the turn actionably,
never text compaction) survived the merge as a module nobody called:
upstream's turn_*.py extraction dropped all three loop sites. New sibling
agent/turn_body_budget.py carries them; wired at
turn_api_request.build_api_request (verdict gains action="return"+result,
honoured by _run_api_retry_loop), turn_api_call._perform_api_call (terminal
edge; raises RequestBodyBudgetExceeded) and turn_api_error.handle_api_error
(right after pool recovery, before retry accounting; ApiErrorVerdict now
carries api_messages so a remediated copy reaches the rebuild).

turn_iteration_prep: the fork's once-per-turn "image lifecycle invariant
violated" warning (image parts before the current-turn boundary) restored;
the flag is reset at the turn prologue.

tests/agent/test_413_compression.py (fork tests/run_agent file merged with
upstream's): patch seams for the mid-turn estimators follow the code into
agent.model_metadata (turn_request_assembly / turn_preflight read lazily;
turn_context keeps its own binding); the rough-growth-after-fit preflight
makes the estimate the deciding signal via note_usage_less_response(), since
the merge adopts upstream 0f4587e336f's deferral (pinned by
test_switch_waits_for_new_provider_evidence and the neighbouring
test_rough_over_threshold_waits_one_request_then_real_usage_compresses),
replacing the fork's (rough, real) growth projection. The calibrated-gate
contract is unchanged.

Verified: tests/agent/test_413_compression.py 41 passed (was 7 red);
test_413_image_payload_recovery + test_request_body_budget +
test_turn_api_error_stream_parse 19 passed; test_turn_api_call_interrupt +
test_image_shrink_recovery + test_retry_exhaustion_partial_retention 24 passed.

* docs(parity 2026-10-01 ci): CI5 ledger for lane L1-agent-confab-compaction

20 manifest files / 196 reds -> all green narrowly on the lane head
(final re-proof: 453 passed across the 20 files, 3 per call).

* fix(parity 2026-10-01 ci): L5 fast mode is route-aware at every call site; Opus 5/5.5 in the fast catalog

tests/cli/test_fast_route_capability.py::test_request_enforcement_call_sites_do_not_use_model_only_wrapper
forbids resolve_fast_mode_overrides( / model_supports_fast_mode( outside hermes_cli/models.py.
Upstream's parallel /fast (agent/fast_mode.py auto|cold windows, tui_gateway._fast_tier_applies,
methods_config_set._set_fast, slash_commands_model._handle_fast_command) called the model-only
wrapper; each now asks the fork's resolve_fast_mode_capability (model + provider + api_mode).

- hermes_cli/models.py: resolve_fast_mode_capability(base_url=) — optional live-endpoint gate
  (_fast_mode_route_supported) so a first-party provider id behind a proxy host fails closed
  the way the upstream wrapper did; threaded through ..._for_configured_route.
- hermes_cli/fast_mode_contracts.py: anthropic_fast = (opus-5-5, opus-5, opus-4-8) per the live
  fast-mode docs (same three ids as agent.model_metadata and the pricing rows);
  normalize_fast_model_id folds dotted opus spellings generally, suffixes still rejected.
- tests: catalog assertion updated; test_fast_command proxy branch pins {} (fork
  `{}`-when-tier-set contract, matches the sibling tests in the same class).

Verified: tests/cli/test_fast_route_capability.py tests/hermes_cli/test_fast_command.py
tests/tui_gateway/test_fast_session_scope.py -> 54 passed. tests/agent/test_fast_mode_auto.py
tests/cli/test_fast_mode_overrides.py tests/gateway/test_fast_command.py tests/agent/test_usage_pricing.py
tests/hermes_cli/test_oneshot_reasoning_and_tier.py: same red set before and after (not L5 files).

* fix(parity 2026-10-01 ci): L5 `config set` keeps comments + bak-configset sibling; codex owner clears cooldown on the refreshed row

- hermes_cli/config.py: restore the fork `config set` writer the merge dropped for upstream's
  full-state ruamel replace: _targeted_config_edit (one-scalar splice / block insert, re-parsed)
  -> roundtrip render -> block dump; refuse with a diff when a comment would be dropped unless
  --force; config.yaml.bak-configset-* sibling with the pre-write bytes (t_6da78aab,
  profile-config-keyguard). `unset` keeps upstream's _write_user_config. PyYAML's IndentDumper /
  yaml.compose are gone upstream (hermes_yaml is ruamel): hermes_yaml.compose() added, block
  dump = hermes_yaml.safe_dump. scripts/check_config_yaml_writers.py: OK.
- agent/codex_owner.py: after the quota probe refreshes an expired stored token (#89415) the
  pool row already holds the fresh pair; clear the cooldown on that row, not the stale
  snapshot (which wrote the expired access token back and resolved it).

Verified: tests/hermes_cli/test_config_set_preserves_comments.py test_config_yaml_comment_preservation.py
test_config_set_list_values.py -> 29 passed; test_config_set_coercion.py test_config_set_platforms_redirect.py
-> 20 passed; tests/hermes_cli/test_auth_codex_quota_probe.py test_auth_codex_provider.py -> green;
tests/agent/test_codex_owner_*: unchanged red set (needs L2's hermes_cli/auth.py _auth_lock_path).

* test(parity 2026-10-01 ci): M2-hermes-clia batch 1 — shell-hook coverage, anon auth, codex self-heal

- test_agent_host_shell_hook_coverage: patch discover_mcp_tools where main.py now imports it
  (tools.mcp_tool_discovery; the tools.mcp_tool re-export went with the compat layer in a5bd246865b)
  and map upstream's extracted gateway/run_turn*.py + slash_commands_session.py to the GATEWAY host.
- test_anon_auth_core (upstream-only): a successful _swap_credential returns the fork's
  SwapOutcome.SWAPPED, not bare True.
- test_auth_codex_self_heal #73667 tests (upstream-only): the fork's Codex owner store (#673) serves
  a singleton with an access_token before the singleton read, so exercise the CLI recovery through
  the rejected-refresh path (_refresh_codex_auth_tokens) with the same CAS / workspace assertions.

* fix(parity 2026-10-01 ci): L5 cron list default set, lazy provider catalog, web_server facade symbol

- hermes_cli/cron.py: `cron list` asks the store for include_disabled=show_all (fork --json
  contract) and tops the human table up with paused jobs (upstream 3f399c0bd4 contract).
- hermes_cli/models_catalog_static.py: drop the import-time sync_plugin_provider_catalog():
  list_providers() imports plugins that import hermes_cli.models -> partially initialised
  module (fork lazy auto-extend contract; the post-discovery sync hook still runs it).
- hermes_cli/web_server.py: module-level get_hermes_home facade symbol (tests patch it).
- tests: desktop cron ticker test admits the fork can_dispatch kwarg (#1373);
  gpt-6.1-sol IS -900k eligible on the fork (#1550, measured 922k) - upstream assertion flipped.

Verified: tests/hermes_cli/test_cron_list_json.py test_dashboard_state_db_log.py
test_desktop_cron_ticker_gateway_standdown.py -> 18 passed; tests/hermes_cli/test_cron.py -> 29 passed;
tests/hermes_cli/test_lazy_canonical_providers.py test_gpt6_tiers_registration.py
test_list_picker_providers.py -> 41 passed; test_api_key_providers.py test_provider_groups.py green.

* fix(parity 2026-10-01 ci): M1 — re-thread track_agent persist + wecom final-frame tail; F02c vocabulary on injection test

- gateway/run_turn.py _run_agent_track_agent: restore the fork self._persist_active_agents() after the sentinel->agent promotion (2026-09-19 regression guard); upstream extracted the closure and the merge kept only the upstream body.
- plugins/platforms/wecom/streaming.py _send_stream_reply: final frame keeps head AND tail (t_11223645) — upstream split the adapter, merge took head-only.
- tests: track_agent AST test follows the extraction (run_turn.py, tuple-assign form); injection ack test asserts the F02c True/False/None seam, ended-session drop is owned by test_completion_delivery.

* fix(parity 2026-10-01 ci): M5 lane — ci + lcm smoke reds

- tests/ci/test_evaluate_needs.py: upstream dropped the pyyaml dependency
  (284dbaf5370, YAML unified on ruamel); read ci.yaml via hermes_yaml.
- tests/ci/source_proxy_baseline.json: ratchet re-baselined on the merged
  test set — 48 entries whose tests the merge deleted/rewrote removed,
  the fork's relocated tests/run_agent -> tests/agent fallback-override
  key re-keyed, three upstream-authored pre-existing proxies and the L2
  AST-walk rewrite of test_every_fallback_activation_restarts_preflight
  frozen (pre-existing, not endorsed; count 114 -> 67).
- scripts/probe_hermes_lcm_isolated.py: compare live roots lexically; the
  merged tests/home_io_guard refuses realpath() under the real home, so
  resolving ~/.hermes/plugins to decide a refusal tripped the guard on
  every smoke test. Only the candidate path is resolved now.

Verified: tests/ci/test_evaluate_needs.py + test_no_new_source_proxy_asserts.py
+ tests/context_engine/test_lcm_adoption_smoke.py 32 passed under e45-pt;
smoke 6/6 under a non-temp HERMES_HOME (6/6 fail without the probe change).

* fix(parity 2026-10-01 ci): M1 — yaml shim in slash_commands, completion silence hint on the extracted formatter

- gateway/slash_commands.py: two raw `import yaml` -> `import hermes_yaml as yaml` (CI venv has no PyYAML; the fork reads YAML through hermes_yaml).
- tools/process_registry_notifications.py: upstream extracted format_process_notification and the copy dropped the fork COMPLETION_SILENCE_HINT tail (Ace contract 2026-09-27); constant now lives in the notifications module, re-exported from tools.process_registry.
- tests: yaml shim in two tests; compress locale sweep skips upstream *.tui.yaml catalogs; goal wait_on test stubs upstream _pid_alive; fast_command provider doubles accept target_model=.

* fix(parity 2026-10-01 ci): M5 lane — acp_adapter reds

- agent/conversation_compression_manual.py: compress_now passes
  trigger_reason="manual_compress_command" (same 3-line change as the
  unfolded L1 lane, cb0f5645c50; applied here so the ACP test is green on
  this branch regardless of fold order — identical bytes, folds clean).
- tests/acp_adapter/test_session.py: get_messages_as_conversation(
  include_timestamp=True) — the fork's default projection is byte-stable
  (F01), same adaptation L2 made for test_inline_edit_persistence.

Verified: tests/acp_adapter/test_server.py + test_session.py 59 passed (e45-pt).

* fix(parity 2026-10-01 ci): M2-hermes-clia batch 2 — codex owner row identity, auth refresh outcome, kimi/pool fixtures

- agent/codex_owner._current: compare the hydrated PooledCredential.source, not the raw row column:
  a row written without `source` loads as SOURCE_MANUAL on both sides and is the same generation
  (upstream's profile-shadowing test in test_auth_profile_fallback seeds such rows).
- hermes_cli/auth_commands.auth_refresh_command: the fork's Codex owner store raises AuthError on
  a failed/uncertain refresh instead of returning None; map it to the same "Could not renew" exit.
- test_auth_kimi_oauth_provider: drop the load_pool->None stub; the merged _add_kimi_oauth_credential
  (upstream shape) reads the re-seeded pool entry.
- test_auth_pool_operations (upstream-only): rows start unbenched (the owner store refuses a forced
  refresh of a cooldown row) and a non-429 failure is asserted as the owner's receipt fence
  (dead / codex_refresh_uncertain, pair untouched) rather than upstream's exhausted/dead split.

* fix(parity 2026-10-01 ci): restore the confab tool-call notice recovery on the extracted turn loop

The 2026-10-01 merge kept upstream's extracted agent/turn_*.py siblings and dropped three fork
seams (#942, agent/confab_notice.py) that lived inline in conversation_loop.run_conversation:

- turn_response_intake.normalize_model_response: announce a validated out-of-band notice once
  per turn (should_announce_notice ledger) and carry _confab_notice/_new_confab_notice on the
  verdict (+ _LoopState slots) for the final-text phase.
- turn_final_response.finish_text_response: tool_call_as_text / tool_call_unparseable notices
  re-prompt with the fixed TOOL_CALL_NOTICE_TEXT instruction BEFORE the empty-response ladder,
  persist the metadata-only system event row, share the 3-stall dropped-tool-call budget, and
  end the turn failed (tool_call_recovery_exhausted) when it is spent. Without this the merged
  loop fell into the empty-response retry (the StopIteration lead signature: one extra provider
  call per scripted notice across 137 reds).
- turn_context.build_api_messages: metadata-only notice rows never reach the provider request.

Fixture/assert updates in tests/agent/test_confab_notice_e2e.py for upstream mechanisms the
merge legitimately adopted (evidence in the CI5 ledger): 0f4587e336f first-request preflight
deferral (arm the built-in compressor with a real-usage anchor; the mock never prices the
transcript), #118900 last-assistant-reply re-anchor (pin the notice's neighbour, not list
length), 8f0322da5b8 failed_turn boundary row (exclude the Hermes-authored row).

Verified: tests/agent/test_confab_notice_compaction_stats.py 1/1; test_confab_notice_e2e.py
lcm shards + valid_empty_tool_event_is_not_replayed green after the turn_context port; full
file re-proof in progress.

(cherry picked from commit 0813ef705ecbfb71cf939685ee1dee725dd0ad19)

* fix(parity 2026-10-01 ci): M4-tools — approval bypass facade, async-delegation retirement/prune guards, formatter re-export

- tools/approval.py: every bypass site goes through is_approval_bypass_active() again (the merge
  re-introduced a hand-rolled `_yolo_active()` that dropped approvals.mode=off — the fork 2026-09-08
  incident); `_get_approval_mode` is a facade delegate so tests can patch either module.
- tools/async_delegation.py: `_dispatch_admitted` tolerates an executor double returning no future
  (fork test doubles) instead of rejecting as submission_failed; `_prune_completed_locked` no longer
  hashes a runner-supplied status (fork hostile-__hash__ guard).
- tools/process_registry.py: re-export `_format_async_delegation` (fork facade symbol upstream moved).
- tests/tools/test_async_delegation_terminal_receipts.py: dispatch helper assertion carries the payload.

Verified narrowly via scripts/test-gate: test_approval_mode_off_bypass + approval_mode_parity + test_approval
174 passed; numeric_binding_boundary + registry_boundaries + terminal_receipts 204 passed.

* fix(parity 2026-10-01 ci): M1 — discord native slash Range fallback + omitted-option defaults; reasoning descriptions name max/ultra

- plugins/platforms/discord/adapter.py _slash_proxy: resolve app_commands.Range tolerantly (test stubs lack it; bare type keeps the option) and render omitted optional options from the synthesised signature defaults (/undo with no count -> "/undo 1", the fork t_b4f07acf contract). Clears 14 reds in test_discord_slash_commands + 15 connect/liveness/event_silence reds that failed at connect() on the same AttributeError.
- locales/en.yaml platform.discord.command.reasoning: description/arg_effort name the effort levels incl. max + ultra (fork contract; the i18n key form dropped them). gateway/relay/command_manifest.py carries a hardcoded copy — out of lane, FOLLOWUP.
- tests: free_response offload class adapted to the fork sync mark_many + CoalescingJsonWriter seam (fork design; upstream-only tests); producer_matrix seeders import from cron.scheduler_delivery (upstream extraction), capture double stamps the admit_internal_event receipt, F02c True vocabulary.

* fix(parity 2026-10-01 ci): M3-agentb round 1 — re-thread stop-gate, placeholder seam, preflight announce; repoint moved patch targets

- agent/turn_stop_gates.py: kanban stop guard threads tools= into
  build_kanban_stop_nudge; persists the candidate answer (interim flush) and
  flags only the nudge synthetic (t_4eeb0202).
- agent/turn_final_response.py: classify_placeholder_final_text wired at the
  final-text seam before the empty ladder (t_887f9584); streamed buffer cleared.
- agent/turn_context_compaction.py: engine preflight maintenance announce with
  reason template + preflight_is_user_visible gate (fork 5636a8a6d5/829d4e0f3e).
- tests: iteration-limit summary drives agent._interruptible_api_call (summary
  now goes through _build_api_kwargs); model_metadata patches
  model_metadata_http.get/.stream, gpt-6 sol/luna 900K cap w/ catalog max,
  bedrock cache row via bedrock_confirmed provenance; kanban_stop source grep ->
  turn_stop_gates + complete_task result=; _FALLBACK_ANNOUNCE_LABELS rename.

Verified: scripts/test-gate narrow runs — test_iteration_limit_summary_session_user 3 passed,
test_kanban_stop 54 passed, test_model_metadata 158 passed,
test_placeholder_final_text_backstop 17 passed, test_preflight_announce_visibility +
test_pool_exhaustion_scope_label 22 passed.

* fix(parity 2026-10-01 ci): M1-gatewayb batch 1 - hermes_yaml shim, refused-followup report at adapter drop

- gateway/slash_commands.py: two lazy `import yaml` sites -> `import hermes_yaml as yaml`
  (upstream dropped pyyaml from deps; the fork shim is the loader everywhere else).
- tests: test_switch_announce, test_turn_concurrency, test_stop_during_preflight_refuses_turn
  import hermes_yaml (safe_dump, no `dump`).
- gateway/platforms/base.py::_drop_unresolved: a replayed restart follow-up refused by the
  adapter-level profile-route gate (which runs BEFORE the runner ingress gate upstream added)
  now logs PHASE=restart_followup_lost; the log body moved to
  fork_ext/restart_followups.report_refused_followup and run.py delegates to it.

Verified: test_switch_announce+test_stop_during_preflight+test_turn_concurrency 75 passed
(1 local-only home_io_guard false positive under the lane runner, CI-shaped run green);
test_restart_followups_admission_e2e 18 passed.

* fix(parity 2026-10-01 ci): M2-hermes-clia batch 3 — backup disk-image/usage-ledger excludes, CLI receipt batch

- hermes_cli/backup.py: restore the fork's staging disk-image suffix exclusion (.sparseimage /
  .sparsebundle / .dmg; 2026-08-09 42 GB subvps-staging.sparseimage) onto upstream's suffix tuple,
  and keep cache/claude-usage (usage.ace ledger) in the kept cache subdirs.
- test_backup: the root cache/*.MOVED.txt breadcrumb now falls under upstream's regenerable-cache
  rule (same verdict as cache/model_catalog.json); kept-dir assertion carried.
- test_cli_async_delegation_delivery (fork test relocated by upstream's tests/ mirror): completions
  arrive wrapped in ProcessNotificationBatch; unwrap before asserting the accepted input + sibling.

* fix(parity 2026-10-01 ci): M4-tools — HERMES_ALLOW_REBOOT downgrade, sandbox session-id bridge, lifecycle refusal markers

- tools/approval_detection.py: restore the fork HERMES_ALLOW_REBOOT opt-in (reboot/shutdown family
  downgrades from hardline to the DANGEROUS layer; every other hardline pattern untouched). Dropped when
  upstream extracted detection out of tools/approval.py.
- tools/code_execution_env.py: re-thread the fork `_inject_session_id` tail of `_scrub_child_env`
  (#636/C3: contextvar-resolved HERMES_SESSION_ID into the sandbox child; removed when unresolvable).
  Resolver stays on the facade (tools.code_execution_tool._resolved_session_id).
- tools/code_execution_tool.py: re-export `_scrub_child_env` / `_HERMES_CHILD_ALLOWED` (fork facade symbols).
- tools/terminal_tool_guards.py: every gateway-lifecycle refusal carries `blocked_by` =
  GATEWAY_LIFECYCLE_BLOCK_MARKER again (fork: execute_code surfaces blocks instead of a silent 0-exit).
  `_blocked_json` gains an optional `blocked_by`.
- tests/tools/test_execute_code_surfaces_blocks.py: source scan repointed to the extracted
  gateway_lifecycle_block (counts `_blocked_json(` vs markers). RED-proofed: unstamping one refusal fails it.

Verified via scripts/test-gate: test_hardline_blocklist 263 passed; test_execute_code_session_provenance +
test_execute_code_surfaces_blocks 18+11 passed.

* fix(parity 2026-10-01 ci): agent_init tool loader honors both patch contracts (run_agent.* and model_tools.*)

The L7 fold made _load_tools read get_tool_definitions/check_toolset_requirements
through the run_agent facade so fork tests patching run_agent.* work, which
shadowed the 142 upstream-style tests patching model_tools.* (a real catalog
loaded under the mock -> 'clarify' first instead of 'terminal'/'web_search').
_tool_catalog_fn prefers a patched facade attribute, else reads model_tools.

test_primary_runtime_restore: FailoverReason import from agent.error_classifier
(run_agent no longer re-exports it upstream; sole test importer).

Verified: test_run_agent_codex_responses + test_provider_parity +
test_primary_runtime_restore 140 passed; tests/cron/test_cron_fallback_alert_e2e
(run_agent.* patch convention) still green.

* fix(parity 2026-10-01 ci): M3-agenta — compaction attribution, codex ctx facade, credential-pool + anchor seams

agent/model_metadata.py: restore the fork's _resolve_codex_oauth_context_length
compatibility wrapper (upstream kept only the _with_source form; fork callers
and tests resolve through the bare name).

Compaction trigger attribution (cherry-pick of L1 cb0f5645c50 onto the fold
head, applies clean): trigger_reason labels re-threaded onto the extracted
turn_preflight / turn_context_compaction / turn_overflow / turn_recovery /
conversation_compression_manual / gateway run + run_turn call sites. Also
cherry-picked L1 0813ef705ec (confab tool-call notice recovery on the
extracted loop; one trivial union in turn_context.build_api_messages with the
fold's interrupt-close omission) — it clears the 32 confab-matrix
StopIteration reds in this manifest.

Tests adapted to seams the merge legitimately adopted (evidence in the CI6
ledger): compaction lint/announce follow the call sites into the turn_*.py
modules and the CompressionFacadeMixin forwarder; Platform is no longer a
prompt-identity field (#104414, agent/surface_switch.py); the feasibility
probe reuses the main window on a shared route (#89500) so the compressor
double carries context_length/threshold_tokens; model_metadata.requests ->
model_metadata_http.get; xai auth-store sync lives on the consolidated
_sync_entry_from_auth_store; Nous forced refresh adopts a peer-rotated usable
key without redeeming the grant (a6f75130386) while the fork's #670 stale
refusal still redeems the pool token; the fork gates timestamp behind
include_timestamp (#107) and rolls back compactions that INTRODUCE duplicate
active tool results (t_aace5343) — the anchor fixture opts in and persists
the tool rounds the way the loop's per-round flush does. The two real-lcm.db
replay oracles are marked allow_real_home_io (read-only, skip when absent).

Verified narrowly via scripts/test-gate (<=3 files/call):
  codex_subscription_proxy_context + gpt61_sol_900k + gpt61_sol_prestage: 31 passed
  compaction_attribution_lint + compaction_fallback_prompt_identity +
    compaction_stats_reconcile: 112 passed, 2 skipped
  credential_pool_singleton_freshness: 30 passed
  compression_last_assistant_anchor: 8 passed

* fix(parity 2026-10-01 ci): M5 lane — cron reds (11 files)

Code:
- cron/scheduler.py _resolve_job_fallback_chain: a job pinned by MODEL or
  ENDPOINT alone (no provider) never borrows the global chain (upstream
  #100437 / scoped_fallback_chain); the fork's same-provider filter still
  governs provider-pinned jobs and job-declared chains; opt-in / revert env
  var unchanged (shared helper _pin_filter_bypassed).
- cron/scheduler.py _upsert_incident_for_failure(escalation=): the fork's
  one-time stuck page (t_04822736, 3rd identical no_agent failure) is an
  escalation; upstream's failure_repeat_alert_hours cooldown no longer
  swallows it (operator ack still does). Both call sites pass it.
- cron/scheduler.py: the fork's transient-failure page suppression skips
  agent-declared [CRON_FAILURE] evidence (it is the agent's diagnosis, not a
  provider blip; the test pins it verbatim).

Tests (merged contracts):
- test_init_fallback_job_chain: patch targets moved with upstream's split
  (cron.scheduler_delivery._resolve_origin, tools.mcp_tool_discovery).
- test_parallel_pool: mark_execution_running stub returns a row ({}),
  None now means lost ownership (upstream).
- test_oneshot_restart_catchup: the tick's claim_job_for_fire between scans
  — an offered-but-unclaimed slot is restored by design (#107485).
- test_cron_script_job_timeout: monitor.py reads _run_job_script from
  cron.scheduler_script; patch there.
- test_cron_shared_scripts_and_stuck_page: tick 2 sits in the reminder
  cooldown under the merged default (counts 3 -> 2 / 4 -> 3).
- test_lifecycle_guard_heredoc_walk (upstream-only): fork rule
  pc-fb1bd018 — a non-executable oversized file at command position is data;
  the oversized file is chmod +x so the walk is still proven.
- test_cron_no_agent / test_cron_transient_failure_suppression /
  test_cron_script_exit_house_page: wording from the merged copy table
  (cron/scheduler_failure_copy, pinned by test_cron_failure_notice_copy).

Verified (e45-pt, hermetic): all 11 manifest cron files + test_job_fallback_chain
+ test_cron_incidents green: 34 + 48 + 52 + 35 + 18 + 9 passed.

* fix(parity 2026-10-01 ci): M2-hermes-clia batch 4 — fallback-runtime telemetry, resize probe seam, goal barrier timer

- hermes_cli/cli_agent_setup_mixin._resolve_fallback_runtime: the fork's kanban route-ledger calls
  read requested_provider/model via getattr and never veto a resolved fallback (upstream's tests
  build t…
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.

1 participant