Skip to content

Input delivery reads through Core's one owner-live operation (agent-comms#3538) - #1302

Closed
trissim wants to merge 10 commits into
mainfrom
refactor/input-delivery-owner-read
Closed

trissim wants to merge 10 commits into
mainfrom
refactor/input-delivery-owner-read

Conversation

@trissim

@trissim trissim commented Oct 28, 2026 •

Copy link
Copy Markdown

Consumer of OpenHCSDev/agent-comms#3538. Lands only together with or after it: it imports InputDeliveryRead and ReadOwnerOperation, which exist only on that branch.

Owner: claude-nra. This branch is my implementation head for P872, qualified by root on Core P883 as a matched Core/Toad pair. Frozen at ef21cbf25 until that matched cut is ready; no source edits, no rebase, not armed. ef21cbf25 is the one root-authorized config edit (bus 36182): [tool.mypy] mypy_path = ["src", "tools"], so the checker reads Toad's declared roots (tools/acceptance.py:34-36 already declares tools as a script root) instead of guessing src/.. It is owned, not an orphan branch.

Summary. 8 commits plus one merge of Toad main (435fd2e5, below): 3772cf0 input-delivery source (1 file, +7/−6), 0be58d8 native acquisition test, a9a330b notification operations (4 files), 54db5d1 shutdown and paint fixtures (2 files, +118/−63), cdd6e15 owner reads publish only the projection (1 file). Toad's input-delivery read stops restating Core's read. It now sends Core's one owner-live operation.

Before. ReadDeliveryOverview(root, thread, include_history=...) was a UI-only class that restated the delivery read. It had its own include_history and its own runtime request (InputDispositionsRuntimeRequest), and it returned a DeliveryView.

After. ReadOwnerOperation(root, thread, InputDeliveryRead(include_history=...)) is the generic UI envelope carrying the same InputDeliveryRead that MCP comms_input_delivery serves. The held owner answers with its DeliveryOverview, and Toad wraps it in ReadDeliveryView(overview).

Unchanged:

  • the no-coordination DeliveryView();
  • require_owner after the read;
  • the dismiss_historical_inputs write.

AgentController.input_delivery keeps its own default, include_history=False, and always passes it explicitly into InputDeliveryRead. The tool's default (True, kept for MCP) never reaches the UI.

Census. In Toad, ReadDeliveryOverview was used only at src/toad/acp/agent_controller.py:26,434. InputDispositionsChanged, refresh_input_dispositions and _read_input_dispositions are Toad's own event and method names, not the deleted Core request.

Native acquisition control (0be58d8). tests/native/test_native_goal_owner.py::test_input_delivery_is_read_from_the_live_native_owner_queue uses the real native session. After until_agent_ready(), it runs controller.input_delivery() and controller.input_delivery(include_history=True). Each must be a ReadDeliveryView whose overview.current_scope is OwnerQueueInputDeliveryScope. Only the live owner produces that scope; the no-coordination DeliveryView() and the unobserved projection never do. On the fresh private wire, inputs and historical inputs are empty and both counts are zero. No read, operation, owner or reply is mocked. Core's canonical queue/history journey keeps the non-empty evidence.

Notification reads (a9a330b, Core #3538 21b07552a).

  • HistoryNotifications.notification_request was a ReadNotifications class used as a root+references constructor. It becomes notification_operation, a root-free operation constructor (LiveNotificationRead by default), and notification_read builds ReadNotifications(root, operation(refs)) with the bound ProjectPathOwner root.
  • RecordedContents sets partial(RecordedNotificationRead, source=source); ReadRecordedNotifications is gone.
  • comms_root keeps its weak view map, full-request outcome keys, pruning and late-result rejection.
  • Tests: test_historical_notification_source requires the generic envelope with RecordedNotificationRead((reference,), source.key), and test_observation_shutdown builds its request with LiveNotificationRead. Assertions are unchanged.
  • Found, not changed: test_observation_shutdown also calls process.fileno(), which ObservationProcess doesn't have on Core main. That test is broken on main independently of this change.
  • Not run here.

Shutdown and paint fixtures (54db5d1).

  • Both shutdown tests unhooked the observer's readiness descriptor with process.fileno(), which ObservationProcess has not had since its single receiver. They now hold their own observer child with SIGSTOP, using a bounded WNOHANG|WUNTRACED stop check that never reaps the child.
  • keeps_reads_but_sends_no_late_interest: stop observing while the read is pending, keep every AnsweringReads identity and no-late-interest check, then SIGCONT. The same receiver settles the read, and set(result.outcomes) is the message.
  • observer_dying_during_shutdown: SIGKILL the held child with a read pending. It gets ObserverDied(pid, -SIGKILL), and StopSidebar and StopObserving raise that same object. Teardown may report only that cause, and unrelated grouped errors propagate. Afterwards the connection is closed, the child is dead and nothing is retiring.
  • The four paint-fixture request.references reads become request.operation.references, with no forwarding property.
  • Root run of this head with Core 5bcc78d9b: the shutdown, paint and archive selection, 7 passed in 76.19 s (exit 0). Source-stage OK for the notification stage. Not armed.

Owner presentation reads (cdd6e15, Core #3538 7b5c78a0e). owner_preparation.read_thread_presentation returned the acquired presentation to Conversation and ObservedSourcePublication, which carried the notification window and backend records into the UI. It now returns .published() from inside the worker; settlement still reads read_identity. Not run here.

Final read surface (Core #3538 780b9957e, 1c8b7e8eb, d92c06392). Three Toad producers built UI-only reads that Core deleted. Each now builds Core's one root-free operation inside its existing UI envelope.

  • 9b6c482e2, commander page (screens/commander.py): ReadOperation(root, CommanderUnitRead(commander=, unit=, orders=)) replaces ReadCommanderUnit. Request id, root, card/collapsed fencing, worker cancellation and the unavailable-detail display are unchanged.
  • 6f41bc6f9, goal snapshot (acp/agent.get_goal_snapshot): ReadOwnerOperation(wire_root, thread, operation=OwnerGoalRead()) replaces ReadOwnerGoal. Unchanged:
    • the no-coordination NoGoal/Idle answer;
    • ClientSessionRequest;
    • OwnerSnapshotConsumer's captured turn token;
    • require_owner after the read;
    • dispatch of every meta.batch update.
  • 9572b2f2c, transcript reads: KnownTranscript.snapshot_request/update_request and historical_sessions build ReadTranscript/ReadTranscriptUpdate(root, TranscriptPageRead(thread=, before=, after=, through=, historical_source=)) with every original cursor and the attached source.key. acp/transcript_reader's RefreshTranscript(identity.root, identity) is a retained lifecycle boundary. Root stays in the envelope and in transport identity.
  • New test: tests/app/test_recorded_transcript_reader.py::test_known_transcript_requests_carry_the_page_operation_and_keep_root_at_the_envelope asserts the exact page arguments on the operation, root and previous on the envelope, and no UI field on the operation. Not run here.
  • Census over Toad src/tests: 0 ReadCommanderUnit, ReadOwnerGoal or old-shape transcript constructors. Lint (ruff F) on the four changed files matches cdd6e15.

Main integration (435fd2e5). A normal merge of Toad main 61fddea36 into this branch. Its parents are 9572b2f2c (the accepted head) and 61fddea36. No rebase, reset, force or cherry-pick.

  • Carried from main (30 files, +621/−160): native activation, sidebar (side_bar, sidebar_tree), tabs (session_tabs), the new switch toast (widgets/switch_toast.py), history (history_anchor, transcript_history), the new large_text.py, tool_call and workspace_chrome, plus their tests (test_channel_scrolling, test_keymap_model, test_keymap_screen, test_stepped_layout_restoration).

  • Conflicts: none. Since merge-base 5fa29f377, no file is changed by both sides.

  • Source checks run:

    • 0 conflict markers;
    • all 630 Python files parse;
    • of the 5 top-level names this branch removed, 0 are referenced by main's added lines;
    • of the 8 names main removed, 0 are referenced by this branch's added lines;
    • both parents are ancestors of the pushed head, and origin/main is still 61fddea36.
  • Not run (combined risks): no tests, type checks, builds, UI or native run on the merged head. The risks a source check can't see are:

    • main's new tab, toast and history code reaching an owner read this branch changed (ReadOwnerOperation, ReadTranscript(…, TranscriptPageRead(…)), ReadOperation(…, CommanderUnitRead(…))) through a path not named at a removed symbol;
    • main's tests assuming the old read envelopes.

    Matched qualification and native companion metadata stay with root on Core P883. Draft, unarmed.

Debt note. Core #3538's hosted debt-src counts ClientTypeErrors +1 here against Toad main, which still imports the deleted ReadDeliveryOverview. This PR removes that import; the pair has to land together.

Checks. Root ran this head (0be58d8) with the paired Core: 5 passed in 67.48 s, the native acquisition test included. Core #3538 has head verdict OK on 8d21db48c for this stage. Both PRs stay unarmed; the pair has to land together, and hosted qualification of the matched Core/Toad pair is being traced by the commander.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RnH6dS2Kmg1WxNnAc78YHA

…omms#3538)

ReadDeliveryOverview restated Core's input-delivery read with its own
include_history; Core deleted it. Toad now sends ReadOwnerOperation carrying
InputDeliveryRead(include_history=...) to the held owner and wraps the
owner's DeliveryOverview in ReadDeliveryView. No-coordination DeliveryView,
require_owner and dismiss_historical_inputs are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RnH6dS2Kmg1WxNnAc78YHA
Copilot AI balanced review requested due to automatic review settings October 28, 2026 10:34

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

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

trissim and others added 7 commits October 28, 2026 07:11
…ms#3538)

tests/native/test_native_goal_owner.py: with the real native session, after
until_agent_ready(), controller.input_delivery() and
controller.input_delivery(include_history=True) are each a ReadDeliveryView
whose overview has current_scope OwnerQueueInputDeliveryScope (only the live
owner yields it; the no-coordination DeliveryView and the unobserved
projection never do), and on the fresh private wire empty inputs and
historical_inputs with zero counts. No mocked read, operation, owner or reply.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RnH6dS2Kmg1WxNnAc78YHA
…e root (agent-comms#3538)

Core moved the notification read off the UI request: LiveNotificationRead and
RecordedNotificationRead own references (and the archive), and
ReadNotifications(root, operation) is the one UI envelope.

- HistoryNotifications.notification_request (a ReadNotifications class used as
  a root+references constructor) becomes notification_operation, a root-free
  operation constructor (LiveNotificationRead by default). notification_read
  binds the ProjectPathOwner root: ReadNotifications(root, operation(refs)).
- RecordedContents sets partial(RecordedNotificationRead, source=source).
  ReadRecordedNotifications is gone.
- comms_root keeps keying views and outcomes by the full UI request.
- tests: the historical source test requires the generic envelope with
  RecordedNotificationRead((reference,), source.key); the observation shutdown
  test builds its request with LiveNotificationRead. Assertions unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RnH6dS2Kmg1WxNnAc78YHA
…operation's references (agent-comms#3538)

Both shutdown tests called process.fileno() to unhook a readiness callback that
ObservationProcess no longer has. The one receiver started at activation keeps
running after stop_observing, so the answer has to be held where it is made: in
this test's own observer child.

- test_stopping_observation_keeps_reads_but_sends_no_late_interest proves the
  child is ready with an empty-window read and SIGSTOPs it (stop confirmed by a
  bounded WNOHANG|WUNTRACED wait). It then registers the real read, stops
  observing with every AnsweringReads identity check and the no-late-interest
  profile, and SIGCONTs the child. The same receiver settles the read:
  set(result.outcomes) is the message's seq/id.
- test_an_observer_dying_during_shutdown_still_releases_its_resources holds the
  child, registers a pending empty-window read and SIGKILLs the child.
  - The read fails with ObserverDied carrying that pid and -SIGKILL.
  - StopSidebar and StopObserving each raise that same object.
  - Teardown may report it only as that same cause, and other grouped errors
    propagate.
  - The connection is closed, the child is gone and nothing is retiring.
- Every path resumes a still-stopped live child and settles any unconsumed read.
- The four paint-fixture reads of request.references now read
  request.operation.references; ReadNotifications has no references of its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RnH6dS2Kmg1WxNnAc78YHA
…on (agent-comms#3538)

read_thread_presentation returned the acquired presentation to Conversation and
ObservedSourcePublication, carrying the notification window and backend records
into the UI. It now returns .published(), the source-free ThreadPresentation;
settlement still reads its read_identity.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RnH6dS2Kmg1WxNnAc78YHA
…ion (#1302)

The commander page wraps CommanderUnitRead(commander, unit, orders) in the UI's
ReadOperation envelope instead of the deleted ReadCommanderUnit. Request id,
root, card/collapsed fencing, worker cancellation and the unavailable-detail
display are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RnH6dS2Kmg1WxNnAc78YHA
…#1302)

acp/agent.get_goal_snapshot sends ReadOwnerOperation(wire_root, thread,
operation=OwnerGoalRead()) through its existing transcript reader instead of
the deleted ReadOwnerGoal. The local NoGoal/Idle answer without coordination,
the ClientSessionRequest authority, OwnerSnapshotConsumer's captured turn
token, the post-read require_owner and dispatch of every meta.batch update are
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RnH6dS2Kmg1WxNnAc78YHA
)

KnownTranscript.snapshot_request and update_request, and the historical
sessions screen, build their UI envelope around TranscriptPageRead with every
original cursor and the attached source key; root and publication state stay
on the envelope. acp/transcript_reader's RefreshTranscript keeps its original
identity. A recorded-reader test asserts the exact page arguments on the
operation and root/previous on the envelope.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RnH6dS2Kmg1WxNnAc78YHA
Tristan Simas and others added 2 commits October 28, 2026 19:13
Config only, authorized by root (bus 36182) for the P883 matched cut. The
checker guessed the root from src/dot; tools/acceptance.py:34-36 already
declares tools as a script root for its sibling imports (performance.*), so
mypy_path names both roots instead of guessing one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RnH6dS2Kmg1WxNnAc78YHA
trissim added a commit that referenced this pull request Oct 29, 2026
historical_sessions keeps #1302's TranscriptPageRead import beside the
shared notification reads; message_notifications takes #1317's read, which
passes the captured display to ReadNotifications(display=...), the field
the Core carrier (agent-comms #3566) now declares. Loads and imports
against the carrier's Core source.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RnH6dR85kyFKcc2tQAJLhK
@trissim

trissim commented Oct 29, 2026

Copy link
Copy Markdown
Author

Batched into #1319 (batch/integ-20261029-toad), paired with agent-comms #3566. It lands with the batch; no separate runs.

@trissim

trissim commented Oct 29, 2026

Copy link
Copy Markdown
Author

Superseded by #1319 at 3a864eb. git merge-base --is-ancestor proves the entire ef21cbf head is carried (exit 0). Closing this duplicate only; root retains matched Core/Toad qualification and all original application obligations. No source approval or installed-success claim.

@trissim trissim closed this Oct 29, 2026
@trissim

trissim commented Oct 29, 2026

Copy link
Copy Markdown
Author

Correction: this is a live member of integration PR #1319, not a retired duplicate. Its author confirms the batch may be rebuilt; retain the member PR open until the carrier lands. Exact-head ancestry remains proved, but it does not establish that this PR lifecycle is finished. No source or obligations discarded.

@trissim trissim reopened this Oct 29, 2026
@trissim

trissim commented Oct 30, 2026

Copy link
Copy Markdown
Author

Superseded: all 8 unmerged patches are carried in #1319 (integration of #1302 and #1317, paired with agent-comms#3566). Nothing lost.

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.

2 participants