Skip to content

Combined carrier: queue unification #3566 with Toad #1319 - #3670

Merged
trissim merged 185 commits into
mainfrom
batch/integ-3566
Oct 30, 2026
Merged

trissim merged 185 commits into
mainfrom
batch/integ-3566

Conversation

@trissim

@trissim trissim commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Combined carrier for #3566 on current main, paired with OpenHCSDev/toad#1359 (which replaced toad#1319). These land together: Toad's suite needs Core's TranscriptPageRead, and debt-tools type-checks Toad main against the new Core names.

Import cycle fixed at its owner: FrozenRecipient moved to frozen_recipient.py, below the bus tables, as a real runtime import. No TYPE_CHECKING, no alias; every importer moved. Its writes fact imports the tables inside ClassFact, as channel_targets does.

Proof: agent_comms.messages and agent_comms.tools import; 309 Toad modules import against this Core with 0 failures; 37 targeted tests passed (test_audience_manifest, test_wake, test_route_counts, test_following_context_notifications).

🤖 Generated with Claude Code

https://claude.ai/code/session_01XbFMSpSd8zQxyozgEV2Re7

trissim and others added 30 commits October 27, 2026 12:43
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RnH6dS2Kmg1WxNnAc78YHA
ReadNotifications carried a caller-choosable viewer field so the MCP
outcomes tool could reuse it under the caller's authority. That made
the reader's authority an argument any caller could set, and the UI and
MCP sides each restated who reads. Now a BusRead member owns its
arguments and its read once, as read_as(comms, reader); the boundary
supplies the BusReader. UiRead derives the UI's read() as the operator,
and the MCP tool reads under its own ToolCaller, which is now a
BusReader. OperatorOutcomes moves to bus_read beside the reader it
serves.

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

ContentCache stored mypy counts under a key of committed content and checker
versions, but mypy also reads the installed packages, which no key names.
Hosted #3538 restored debt-ratchet-src-71453f18 (saved by main cache job
40654054315 under a Core-only install, Pydantic 2.14.1) and compared that
stored base count with a head count measured under the full stack
(Pydantic 2.13.5).

- CheckedRevision.findings runs the checker. TypeErrors inherits it and
  keeps mypy's own --cache-dir, which tracks dependency changes itself.
- LintFindings.findings keeps the content-keyed memo unchanged: Ruff reads
  only the tree and the head's configuration.
- Unformatted keeps its per-file blob cache unchanged.
- ClientTypeErrors.findings calls checked directly. The client commit and
  src tree it computed only for the key are gone.

Regression: one parameterized real-mypy test switches an installed PEP 561
dependency between two sys.path environments, with committed source, client
and configuration unchanged and the same count-cache directory. The count
must follow the dependency.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RnH6dS2Kmg1WxNnAc78YHA
GoalHistory (tools.py) and GoalHistoryReply (runtime_requests.py) were
empty subclasses of RecordedGoalHistory, and recorded_history took the
result class as an argument so each caller picked its own empty wrapper.
Neither added a field or a behavior.

- RecordedGoalHistory now extends ToolResult (itself a DeclaredReply), so
  the MCP tool and the owner socket return the same record.
- GoalHistory and GoalHistoryReply are deleted.
- GoalManagement.recorded_history(name, scope) returns RecordedGoalHistory;
  the reply argument, its HistoryReply TypeVar and the TypeVar import are gone.
- CommsGoalHistoryTool and GoalHistoryRuntimeRequest return it directly;
  tool name, declared runtime name, subject resolution, scope decoding and
  the worker-thread execution are unchanged.
- tests/test_goal_execution.py compares against RecordedGoalHistory.empty().

The encoded record is unchanged: one populated history/failure payload
decodes and re-encodes byte-identically through GoalHistory,
GoalHistoryReply and RecordedGoalHistory before this commit and through
RecordedGoalHistory after it; empty() encodes as
{"failures": [], "history": []} on both sides.

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

CommsInputDeliveryTool read goals.input_delivery in the connector's own
process under UNOBSERVED_DELIVERY_SCOPE: it could not see the owner's live
queue, yet returned an ordinary-looking delivery view. The UI and the owner
socket each restated the same read again (ReadDeliveryOverview,
InputDispositionsRuntimeRequest, with their own include_history).

- BusRead is now an abstract capability of the existing ToolDeclaration
  family (no DeclaredFamily root of its own); UiRead still derives from it.
- BusReader owns subject/own_thread/refuse_thread once; the copies on
  ToolCaller and NoThreadCaller are gone, refusal text and type unchanged.
- OwnerReading: one exchange with one held owner incarnation; admit()
  defaults to any owner, OwnerLiveRead admits only native owners, so an
  external bridge (live PID, no socket) is refused instead of retried forever.
- OwnerReplyConnection.held/answer: the one connection factory (exact
  incarnation, root, process; 3 s reply timeout, close in finally, no retry
  after dispatch) for OwnerRead and OwnerLiveRead.
- OwnerLiveRead: MCP apply/read_as resolves caller.subject(thread) and asks
  the owner via asyncio.run on the tool's synchronous boundary; the owner
  answers in owned(ctx) with ctx.name and its own session.
- InputDeliveryRead (comms_input_delivery, thread, include_history=True):
  the one declaration, in the ToolRequest/native catalogs by capability.
  owned() takes scope only from the owner's delivery_scope.
- OwnerReadRuntimeRequest (owner_read) and ReadOwnerOperation: one generic
  runtime envelope and one generic UI envelope carrying the operation.
- Deleted: CommsInputDeliveryTool, ReadDeliveryOverview,
  InputDispositionsRuntimeRequest, the input_dispositions request.
- The connector encodes Refused, ConnectionError and TimeoutError from an
  owner read as a failed tool call instead of dropping the HTTP call.
- tests/test_command_families.py: owner_read round trips for both history
  settings; a non-owner-live operation and a bad include_history refuse.

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

- test_observer_death: unavailable/stale owner reads are parametrized from
  ReadOwnerGoal plus ReadOwnerOperation over BusRead.members_with(OwnerLiveRead),
  not OwnerRead.__subclasses__(); refusal and observer-alive assertions kept.
- test_input_delivery_history: the CLI test that asserted a declared owner with
  no process answers delivery state is deleted (that answer was the defect).
  The replacement starts the canonical owner socket, queues a prompt, records an
  off-queue notice, and asserts MCP invoke and the UI envelope both equal the
  owner's own projection for include_history False and True, that the off-queue
  notice is current only under the old unobserved projection, and that a stopped
  owner refuses the MCP read.

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

- OwnerReading.admit is the shared native-transport admission
  (owner.execution.require_native()). OwnerLiveRead.admit and
  ReadOwnerOperation.admit were identical restatements and are deleted.
  ReadOwnerGoal now reaches it too: an external bridge has a live PID and no
  RuntimeServer socket, and _connect_current waited for that socket forever
  before dispatch. It is refused before the wait.
- OwnerReplyConnection.held compared the registry's incarnation with the
  supplied one, then OwnerConnection.__init__ read the registry again into
  _identity, so a replacement incarnation could be recaptured unchecked.
  held now constructs the connection and OwnerConnection.require_identity
  compares the captured _identity with the supplied witness before returning;
  the earlier separate comparison is deleted. No second identity field.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RnH6dS2Kmg1WxNnAc78YHA
…ation, death, external controls (#3538)

tests/owner_socket_fixture.py (registered in pytest_plugins) is the one fixture
owner: runtime_processes (moved from test_runtime.py; it now yields the spawned
children so a test can stop one through its own owner), declare_owner (native or
external registration) and silent_socket (the real Unix-socket peer from
test_dispatched_request_without_a_reply..., which receives one request and
closes without replying). The existing reply-loss test is refactored onto it.

New controls, nothing of ours patched, no fabricated reply:
- MCP InputDeliveryRead without a reply, for include_history False and True:
  OwnerReplyLostError, exactly one received request decoding to
  OwnerReadRuntimeRequest(thread, InputDeliveryRead(thread, include_history)),
  durable inputs unchanged. The peer runs no domain body.
- Root replaced under a held connection (rename to a sibling, new directory at
  the old path): OwnerIdentityChangedError, zero requests; root restored.
- Same-name recreation (unregister, begin_delete, remove, declare): the held
  read and a new held() with the old witness both refuse; incarnations differ.
- Held process death (runtime_processes child stopped through stop_sync): the
  read refuses with ConnectionError before any request; inputs unchanged.
- External owner with a live child and no socket: MCP delivery refuses with
  RelationViolationError; through the observer, ReadOwnerGoal and the
  input-delivery envelope (derived from OwnerLiveRead membership) both refuse
  and the observer stays alive.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RnH6dS2Kmg1WxNnAc78YHA
…ps importing its transport (#3538)

OwnerLiveRead extracted OwnerReadRuntimeRequest.declared_name and reached
runtime/runtime_requests through two function-local imports to escape the
cycle that extraction created, and OwnerReadRuntimeRequest.result returned Any
because OwnerLiveRead had no Result default (hosted debt-src: FunctionLocalImport
+2, FamilyFlattened +1, TypeErrors runtime_requests +1, BannedVocabulary +3).

- OwnerLiveRead[Result: ToolResult = ToolResult]; operation fields stay the bare
  family class. OwnerLiveRead no longer inherits OwnerReading and has no socket
  read: owned/decode_reply are its answer and decoder. read_as builds
  OwnerReadRuntimeRequest(thread=owner.name, operation=self), sends it through
  OwnerReplyConnection.answer and decodes the reply. asyncio.run stays at the
  synchronous tool boundary.
- OwnerReadRuntimeRequest moves from runtime_requests.py to bus_read.py as
  OwnerReading[object] + ResultRuntimeRequest; same owner_read action and
  operation field. Its read(owner) is the one socket exchange:
  owner.request(self.declared_name, operation=self.operation).
- OwnerReading and OwnerReplyConnection move into owner_connection.py beside
  OwnerConnection. OwnerReading.read takes OwnerConnection. OwnerReplyConnection
  derives OwnerConnection directly (owner_read and goal_snapshot use the base
  proxy_payload; only PromptRuntimeRequest needs the ACP controller token).
  REPLY_TIMEOUT, held/answer/close and the require_identity check are unchanged.
- The native transport check is OwnerReading.require_native_owner, called from
  held; RuntimeRequest.require_owner(entry) is untouched.
- ReadOwnerOperation.read builds the envelope with owner.session_id and decodes
  through the operation; ReadOwnerGoal.read takes OwnerConnection; OwnerRead.ask
  loses its local import. All imports are module scope.
- Old definitions deleted, no aliases. tests/test_runtime.py imports the moved
  names from bus_read/owner_connection, and the held root/recreation/death
  controls hand held() the real OwnerReadRuntimeRequest and await its read.

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

Root's selection on 5c26262 ran 60 cases: 49 passed, 11 failed. One same-name
control used remove() after begin_delete, which correctly refuses Deleting
status; ten observer fixtures used the removed synchronous observer surface
(`with`, readable()/results()/fileno(), sync close, NotificationWindow == {}).

- test_runtime same-name control: deletion completes through
  edit.document.remove_originals((old,)) in registry.store.editing(), the path
  production uses (thread_management.py:537); the captured connection, old
  witness, zero-dispatch peer and both OwnerIdentityChangedError refusals stay.
- test_observer_death: every observer is `async with`; empty-window checks
  compare .outcomes. The killed-observer test uses the real receiver: one read
  proves startup, SIGSTOP is confirmed with waitpid(WUNTRACED), the next read is
  shown pending, then SIGKILL; ObserverDied carries the child pid, -SIGKILL and
  signal text, and StopSidebar and a later read raise that same object. A stopped
  child is killed and a pending task cancelled in finally. The failed-observer
  test asks one real read on the not-a-wire route and requires exit status 1 and
  the original traceback. The readable() helper is deleted.
- test_busy_history_loading: three `async with` sites, the close-once test's
  `async with process` and awaited repeated close, .outcomes comparisons; the
  Connection.close endpoint observation is unchanged.
- test_deleted_thread_observation and test_unreadable_history_observation (the
  two readable() importers) start the receiver once with start_receiving and
  wait on tests/observed_queue.py, a test-local queue of delivered results that
  raises a receiver failure as its original exception. No manual pumping.

No production change.

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

Root's rerun on 9bb8b2e: 22 cases in 70.37 s, 20 passed, 2 failed, both the
other_root=False stale-identity cases (goal and input delivery). held looked up
the selected thread's process before any identity comparison, so a stale
incarnation was refused as "no owner process" (RelationViolationError) instead
of OwnerIdentityChangedError. a4702b1 had dropped the initial comparison when
it added the constructor check.

- OwnerReplyConnection.held: registry.require(owner.name), then
  thread.incarnation != owner -> OwnerIdentityChangedError, then
  reading.require_native_owner(thread), then require_process/socket path and
  construction, then connection.require_identity(owner). The two comparisons
  check two different registry reads (the selected thread and the
  constructor's capture); no identity field or cache is added.
- ObservedQueue.until holds one 30 s bound over the whole match; next() keeps
  its own bound for direct callers. Before, each unrelated result restarted the
  bound, so a steady stream could keep a wait running indefinitely.
- test_deleted_thread_observation restores one 30 s bound around waiting for
  both the missing-thread result and the retirement.

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

The notification read lived on the UI request: ReadNotifications held root and
request_id beside its references, ReadRecordedNotifications subclassed it for
the archive, and comms_message_outcomes built that UI envelope with
str(comms.root) only to reach read_as.

- notification_reads.py (new): NotificationRead(UiRead[NotificationWindow])
  with changed_since; frozen LiveNotificationRead(references) and
  RecordedNotificationRead(references, source). Two members: archive identity
  and the change rule differ (live activity/evidence vs history revision).
  The module sits outside notification_read.py because presentation imports
  that module and bus_read imports presentation.
- NotificationsRead and UnreadableNotificationsRead move unchanged into
  notification_read.py beside CapturedNotificationWindow.
- ReadNotifications(ReadRequest) keeps root and request_id and carries
  operation: NotificationRead; it is no longer a UiRead. read() delegates to
  the operation (OperatorReader). ReadRecordedNotifications is deleted.
  NotificationReading asks request.operation.changed_since; read states stay
  keyed by the whole request.
- CommsMessageOutcomesTool reads LiveNotificationRead((reference,)) under its
  caller; refusal, visibility, paging and its public schema are unchanged.
- Callers migrated: observer fixtures carry LiveNotificationRead(()),
  test_thread_read_results reads the operation instead of a fake UI root,
  read-state tests call the operations. New controls: family/codec/pickle and
  request identity, archive change drops state, root validation per
  envelope, capability discovery, a foreign operation refused; a 125-message
  live batch across two window chunks; the recorded operation reads the
  archive (not live evidence) and a detached key refuses.

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

NotificationsRead.activity spelled "this participant has no projection" twice:
absence from the window's watched names, and a None value. observed_activity
already omits registered humans and archived rows and answers DeletedThread for
a deleted name, so the None was a second encoding of absence.

- activity is Mapping[str, ObservedActivity | DeletedThread], keeping only the
  window's names that the projection actually returned.
- live_changed re-projects the window's full activity_names() when ActivityView
  moved and compares the sparse maps, so omitted->observed, observed->omitted and
  observed->DeletedThread still invalidate. With no ActivityView change it does
  not project at all.
- HistorySource.notification_references annotates references as
  Sequence[MessageReference] and returns NotificationWindow, the type
  MessageNotification.read_references already returns. That closes the Any that
  reached RecordedNotificationRead.read_as.
- The read-state fixture that built {absent-participant: None} now settles a real
  #team read whose participants are an agent, a human, an archived agent and a
  deleted agent. New controls cover: omitted humans and archived agents, an
  archived agent declared again, a deleted participant, and zero or one
  projection by revision kind.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RnH6dS2Kmg1WxNnAc78YHA
NotificationWindow.activity_names() comes from native-recipient outcomes, so the
#team fixture watches worker, archived and gone, never the agent sender or the
human. Two assertions expected sender and human, and failed in the root run
(29 passed, 2 failed). The window-names test now requires exactly
{worker, archived, gone} and excludes sender and human; only archived is omitted
from the sparse activity. The projection-count test requires archived as the one
watched-but-omitted name. No production change.

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

A recipient-scoped thread read selected every claim of each broadcast by
wire_seq alone, decoded them, and filtered recipients only afterwards: the saved
six-tab desktop held 17,681 claims to show 15 rows. The same acquired graph
(frozen audiences, decisions, WakeAssignment and activity rows) then travelled
inside ThreadPresentation to the UI, twice over, through notification_window and
through each notification's message, which was the backend record itself.

Viewer captured once:
- NotificationAssignmentRead carries its required OutcomeViewer. read, capture,
  missing_store and invalid_store take it, and the public missing store returns an
  empty read with that viewer; the private one still refuses.
- capture builds the visible (recipient_lookup, wire_seq, message_id) keys
  through the viewer before any SQL. Claims are selected through the
  recipient/seq index by those keys, using TypedTable.requested_keys (factored
  out of select_keys, which now consumes it). HarnessDelivery.for_recipients
  gets the same keys. Rows keep the original wire_seq DESC, recipient order
  across batches.
- delivery_window, project_delivery_window and for_sources read the captured
  viewer instead of taking a second one. for_deliveries skips invisible
  recipients before allocating an UnrecordedNotificationSource, and
  recorded_delivery_window allocates no placeholder activity for them.
- NotificationAssignment.changed groups retained reads by their own viewer and
  re-reads each group's sources as that viewer.
- SelectedParticipant captures its sent read as OwnerOutcomes of the owner, so the
  sender still sees every outcome.

Public envelopes:
- ThreadPresentation.with_sources and MessageNotification.for_sources attach
  source.envelope, the declared public Message, instead of the Delivered record.

Acquired versus published:
- ThreadPresentation keeps only the UI fields: status, binding, notifications and
  read_identity.
- AcquiredThreadPresentation adds incoming_references, sent_references and
  notification_window. It is built by with_sources, supplies revision inputs and
  sent_summaries (replacing SentMessageSummary.recent), and published() derives
  the base from the declared fields. Pickling it raises a TypeError naming
  published().
- ThreadReadResult.published() defaults to itself and ThreadPresented projects its
  presentation. ThreadReading keeps the acquired results for its next change
  check and publishes only the changed results' projections.

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

- test_outbound_transcript_source:
  - each recipient's own read decodes exactly one claim row (profiled
    AssignmentActivityEvidence._decode_row), equal to that recipient's rows and
    attempts in the operator read; the sender read equals the operator read;
  - invisible recipients allocate no UnrecordedNotificationSource, and the
    sender keeps all of them;
  - one claim transition refreshes only the retained reads whose viewer sees it:
    the recipient, sender and operator reads, not the other recipient's.
- test_recipient_activity_index:
  - a viewer follows frozen lookups through rename and never into a rebirth;
  - a public root without a coordinator returns an empty read with the same
    viewer and projects only visible recipients;
  - a private root without one still refuses.
- test_thread_read_results:
  - ThreadReading retains the AcquiredThreadPresentation and publishes its
    ThreadPresentation projection, with the same status, read_identity and
    notifications, whose messages are public envelopes;
  - the pickled observation carries no acquired evidence type, and sending the
    acquired result is refused;
  - after a second change, the UI's current and last-published rows reach no
    acquired evidence.
- test_thread_presentation: absence publishes itself and summarises no sent
  message; its notification_window assertion read a field absence no longer has.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RnH6dS2Kmg1WxNnAc78YHA
…ch and null-key controls (#3538)

presentation_fields() returned dict[str, object], so expanding it into
ThreadPresentation(...) and AcquiredThreadPresentation(...) erased every field's
type: 10 new arg-type errors at thread_presentation.py:287/311. It is deleted.

- with_sources builds shown = replace(self, notifications=..., read_identity=...)
  once and constructs AcquiredThreadPresentation from
  {declared.name: getattr(shown, declared.name) for declared in
  fields(ThreadPresentation)} plus its three acquisition fields.
- AcquiredThreadPresentation.published constructs ThreadPresentation from the
  same inline mapping over itself, the ThreadStatusDisplay.status_display shape.
  There is no cast, Any, ignore or copied field roster.
- test_outbound_transcript_source::test_a_forced_key_batch_captures_the_same_evidence_through_the_recipient_index:
  - setup: a second real #team original in the private _root fixture gives at
    least 4 distinct claim keys, plus a repeated original;
  - batching: on the fixture's own coordination connection, SQLITE_LIMIT_VARIABLE_NUMBER=6
    (restored in finally) forces several key batches, with deduplicated bindings
    equal to the unlimited batch;
  - result: the whole capture equals the unlimited read in rows, attempts,
    evidence, order and viewer, with no duplicated assignment;
  - index: the traced claim statements' EXPLAIN QUERY PLAN SEARCHes wake_claims
    through the unique recipient_lookup/wire_seq index found by PRAGMA, never a
    SCAN of it.
- round2 test_declared_table_family: select_keys selects the weight=None child
  through a None key once, even when the key is repeated, and stops selecting it
  once the weight is 3.25.

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

The fixed limit of 6 refused NativeRuntimeTable's schema catalog read inside
capture before any claim query ran (root: 1 passed, 1 failed in 3.30 s), so it
proved nothing about claim batching.

- The limit is the larger of 6 and each schema capture validates
  (privacy.coordinator_schema() and NativeRuntimeTable), counted as declared
  object names plus table names, the bindings of objects_of's catalog read. It
  must still be below the connection's own limit.
- Enough real #team originals are published and accepted in the same private
  fixture for more than limit / 3 distinct claim keys. The count is derived from
  the recipient count and must fit NotificationWindow.window_limit, and the
  repeated original stays as the duplicate control.
- The batching, equality, ordering, trace and EXPLAIN index assertions are
  unchanged.

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

The GC stage made two new source-gate regressions:
- NotificationAssignment.changed built an untyped `changed = []`.
- AcquiredThreadPresentation.sent_summaries imported SentMessageSummary and
  MessageNotification.summary back from presentation inside the method, a
  hidden runtime cycle.

Now:
- `changed: list[NotificationAssignmentRead] = []`.
- ThreadPresentation/AcquiredThreadPresentation.sent_summaries are deleted.
  sent_notifications() returns (reference, outcomes) pairs: () on the public
  presentation, the captured window's outcomes per sent reference on the
  acquired one.
- SentMessageSummary.for_sources aggregates those pairs with the same-module
  MessageNotification.summary. ThreadCaller.listing calls it.
- thread_presentation keeps only TYPE_CHECKING imports for these types;
  function-local imports 5 -> 4 (the four remaining predate this stage).

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

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 4 commits October 30, 2026 05:35
# Conflicts:
#	src/agent_comms/tool_caller.py
The first pass fixed the ten files GitHub's annotation cap showed; the gate's
full list named 32 more. Safe ruff fixes, Black on #3566's own line ranges
only, explicit strict= on batched, unused loop names, no-op hooks that say
so, wrapped strings. FirstRecipient and RecipientLimit are frozen values,
declared in ruff's immutable-calls beside the transcript page defaults.
Every changed src/tools file now has no more ruff findings than main and no
Black-clean file on main became dirty.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XbFMSoWgC3ZwKzjT4SwMDu
# Conflicts:
#	src/agent_comms/installer/runtime_resolution.py
…rders

ReadCommanderUnit was deleted by #3566; main's test kept importing it, so the
module failed at import. It now builds ReadOperation(root, CommanderUnitRead).

OperatorReader only forwarded outcome_viewer to its DisplayCut: OperatorOutcomes
is now the ReadAuthority, so the cut itself is the reader. BusRead.apply only
renamed read_as: one abstract apply remains, the name the tool boundary calls.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XbFMSoWgC3ZwKzjT4SwMDu
trissim and others added 2 commits October 30, 2026 08:11
# Conflicts:
#	src/agent_comms/phase_evidence.py
#	tools/agent_comms_project.py
…cope

open_requests sliced OwnerRequest.stream with islice and failure_evidence took
next() of FailedTurnEvidence.stream, leaving each generator suspended inside
TypedTable.iterate. The connection scope closed first; when the generator was
collected, iterate's finally (typed_table.py:610) closed its cursor on a closed
database and printed an ignored ProgrammingError traceback into the losing
command's stderr in test_two_commanders_cannot_assign_the_same_unit_concurrently.
Each stream is now closed by closing() inside the scope that owns its connection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XbFMSoWgC3ZwKzjT4SwMDu
trissim and others added 11 commits October 30, 2026 10:18
messages -> task_sources -> goals -> shell_effects -> storage_room -> activity ->
thread_presentation -> notification_evidence -> audience_manifest -> bus_tables ->
messages: bus_tables needs messages.BusTables while messages is half initialized.
Every use in notification_evidence is an annotation, as on main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XbFMSpSd8zQxyozgEV2Re7
Notification evidence records decode FrozenRecipient, so it must exist while
messages is still importing; audience_manifest cannot supply it because it
needs messages itself. The value depends on none of that. It moves to
frozen_recipient.py and every importer moves with it, with no alias. Its writes
fact imports the tables inside ClassFact, as channel_targets does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XbFMSpSd8zQxyozgEV2Re7
…from its owner

The manifest's own validators still call ParticipantIdentity. The following-context
test imported InputRequest from agent_backend, which no longer exports it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XbFMSpSd8zQxyozgEV2Re7
trissim and others added 3 commits October 30, 2026 17:23
Conflicts were import lists and one docstring: native_input_record keeps
ThreadLookup and drops main's now-unused WireName; phase_evidence takes
main's Checkout history with the carrier's MergeHeadReview.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XbFMSpSd8zQxyozgEV2Re7
The carrier grew a TYPE_CHECKING import in tool_caller and a ClassFact type error in
frozen_recipient. Both go. The local bus_tables import in writes stays, as in
channel_targets.py:129: the tables import this module.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XbFMSpSd8zQxyozgEV2Re7
…e side by side

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XbFMSpSd8zQxyozgEV2Re7
@trissim
trissim enabled auto-merge October 30, 2026 22:41
@trissim
trissim merged commit bdba5bd into main Oct 30, 2026
6 of 9 checks passed
trissim added a commit that referenced this pull request Oct 31, 2026
#3670 made review-verdict refuse every head that isn't a captured combined
carrier, so with --admin refused too, no ordinary PR could land by any route.
A source's inputs now say whether they owe a carrier's member proof: an ordinary
source owes nothing here and the next workflow step reads its head's verdict; a
carrier keeps its member and promotion checks unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XbFMSpSd8zQxyozgEV2Re7
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