Skip to content

feat(matrix): configure receipts and processing feedback - #126283

Draft
iainlane wants to merge 450 commits into
NousResearch:mainfrom
iainlane:feat/matrix-receipts-lifecycle
Draft

iainlane wants to merge 450 commits into
NousResearch:mainfrom
iainlane:feat/matrix-receipts-lifecycle

Conversation

@iainlane

@iainlane iainlane commented Sep 28, 2026 •

Copy link
Copy Markdown

Stack

All PRs in this section target main. This diagram records logical merge
order. Every prerequisite must merge before its dependent. Each linked PR
lists its other dependencies.

In this diagram, nesting means the parent PR must merge before its child.

Summary

Configure automatic Matrix feedback per profile through YAML:

  • read_receipts: immediate keeps the current arrival-time receipt.
  • read_receipts: after_processing sends the receipt after a turn that
    succeeded or failed, and skips a turn that was stopped or cancelled.
  • read_receipts: disabled suppresses automatic receipts.
  • send_read_receipts: false, the switch that WhatsApp and BlueBubbles
    already read, also disables automatic Matrix receipts, whatever
    read_receipts says.
  • reactions: false disables processing reactions. Approval and picker
    reactions remain available.

The existing scoped MATRIX_REACTIONS override keeps precedence.
Receipt timing has no environment setting. The adapter resolves both
settings from its owning profile without a process-wide YAML bridge.

In after_processing mode, the receipt goes to the latest input that
the turn processed. Batched messages, queued photo merges, steers and
redirects that the model consumed, and a mention that claims a parked
voice note all move the receipt to that input. Original reply anchors
and ledger identities do not change. Input that the model did not
consume (a steer that arrived after the last tool batch, or input that
arrived after the model result) runs as its own turn and is
acknowledged there. /queue and accepted /steer wait for the turn
that processes them; other inline commands are acknowledged on their
own, without changing the active turn's reactions.

Current captured-source compatibility

Current head: dc2811135e5f3a3bdc670ad69b8973c412b33806. Source tree:
17cbd07a02f324f851cf134cd90b055f93ef6a03.

I rebased this branch onto the captured upstream revision
3637c512fc3211bdccb61b7140d09ac1018508ba because local composition
reproduced concrete conflicts in the inbound facade, platform extraction,
stream annotations and adapter context modules. The extracted methods keep
the complete current upstream bodies and existing message-processing
contracts. Source notes retain the platform-specific Matrix and Discord
content. Current UTC, timeout and annotation conventions remain intact.
These changes use the fixed captured revision; they do not follow an
advancing main branch.

All 450 current contributions remain in order, with their literal author
and committer identities, dates, time zones and complete original message
prefixes. Fifteen meaningful empty credit records remain. Existing
published admission, pending-snapshot, Matrix client/session and worker-loop
fixture corrections are preserved. Resolution rationale is appended only
to the contributions which introduce the relevant source.

The full execution reproduced missing global bindings after the platform
processing method moved to its sibling module. The introducing extraction
now imports the three current facade helpers at execution time. The whole
method body remains unchanged. The merged-event field also keeps its three
quoted forward references because its enclosing class precedes the event
class. Both corrections are included at their true introducing
contributions; every later complete module variant has an exact inverse.

Current validation and qualification

The initial complete 137-file execution is retained as a failing control.
After the source correction, all twenty affected complete files freshly
pass 495 cases under the canonical runner, installed SDK, isolated home,
zero retries and four strict warning categories. Those files include the
queue, processing lifecycle, startup replay, shutdown, native restoration
and Matrix feedback paths which failed in the control.

The other 117 successful complete children are reused after the exact
two-module source inverse and unchanged test-input comparison. The
source-qualified result is 4,190 passes and eighteen existing skips across
137 complete file outcomes. This is a qualified combination of executions,
not a fresh simultaneous execution on the final source. All captured
tracked Python and runner inputs are authenticated before and after each
execution, and the capture-only runner modification is restored exactly.

Ninety-seven complete children have no resource warnings. Forty expose the
same pre-existing shutdown-warning fixture scopes as the preceding
accepted public source. Most warnings concern unclosed test databases;
three unchanged complete test modules also emit an event-loop and two
socket warnings at shutdown. Those files and their full child output
remain explicitly qualified. The source comparison preserves their whole
original fixture and assertion modules, with only the recorded upstream
UTC spelling change in one file. This is not a globally resource-clean
claim, and no warning category or assertion is weakened.

The complete 306-path source-qualified static comparison reports 3,489
inherited findings. Fresh complete checks replace the two modified modules;
the other 304 checked sources remain byte-exact. Full literal diagnostic
spans, relative columns, whole statement ASTs and occurrence counts match
for 3,479 findings. Ten complete enclosing-statement findings require
explicit qualification through the unchanged defining media methods,
base contracts, pending alias or original Feishu and stream signatures.
They are not claimed as literal whole-class AST matches. No introduced
finding remains. Ruff and changed-line whitespace pass; the same 250
formatter paths retain inherited differences.

The earlier delivery evidence below keeps its historical source scope.
Hosted CI approval, maintainer acceptance and live homeserver execution
remain separate.

Previous fixed1744 source compatibility and retention

Current head: 52e9d3ada04519d4e06b1067201f83978ea39d72. Source tree:
2fce3c3ff1dd3769e9d5b24966495e5044af27e9.

The concrete catalogue and extracted turn conflicts require the captured
upstream revision 1744a19e0df568c647e4f3ff9c37f2a284a282fb.
Both that fixed revision and the earlier
99a45ecc17cc51c0488ec3b8ccf7eaf483f7203d join cleanly with this source.
These are exact local source comparisons, not a claim about an advancing
branch.

Keep the complete upstream Spotify catalogue migration, plugin opt-in
defaults and current Matrix rows in their original order. The complete
catalogue joins reuse independently reviewed historical source variants.
The two upstream nullable session-key annotations are retained when the
turn execution methods move into their defining sibling module. All other
captured upstream joins are automatic.

All 450 preceding contributions remain in order with literal author and
committer fields, dates, time zones, other headers and complete message
prefixes. All fifteen meaningful empty records remain. The original
447-contribution feature and attribution sequence, the published fixture
maintenance contribution, and both complete worker-loop lifetime
contributions remain included. The original room-store fixture still
recovers every historical module byte-for-byte when removed.

The published pending native snapshot cleanup
is included in its actual introducing contribution 265. Both complete
historical module variants preserve every assertion, including later
restored-event and decoding contracts. The published
admission fixture cleanup is included at
contribution 167. Its two existing finally blocks release the owning
SessionStore after intake finishes. Removing only each fixture correction
recovers every complete historical module byte-for-byte; complete function
AST comparisons preserve all other statements and assertions.

The separate pending Matrix caller and profile snapshot finalizers were
already present throughout this branch's complete historical variants.
The later consumer-only shutdown lease testcase is absent throughout the
branch. No duplicate finalizer or future testcase is added.

Previous fixed1744 validation and limits

A fresh canonical strict-warning run against the captured upstream
composition passes 4,190 cases across 136 complete passing files.
Fourteen existing cases are skipped within those files. The separate crypto
file skips all four cases and contributes no passing coverage. Zero retries
were used. All 8,539 actual Python and runner inputs were authenticated
before and after execution, and the canonical runner was restored exactly.
The subsequently corrected admission file was run separately: all three
cases pass, and its complete successful child output has no unclosed
resource warnings at interpreter exit. Every other authenticated input is
byte-for-byte identical to the fresh 137-file composition.

The resulting complete-file union contains 97 files with clean shutdown
output and 40 files with existing fixture resource warnings
. Of those
40 test modules, 39 are byte-for-byte identical to the preceding published
source. The heartbeat module differs only by two captured upstream unused unpacked
variable renames; their literal inverse recovers the complete original
module byte-for-byte.
The full child outputs and exact resource counts remain recorded. This is
not a claim that all 137 files release every resource.

The original 138-file gate remains non-green. The complete failures in
tests/agent/test_context_compressor.py and
tests/tui_gateway/test_tui_gateway_server.py remain excluded from the
passing scope above. Their partial passes do not count as complete passing
coverage. Other platform, live-service, encrypted Matrix and hosted evidence
remains bound to its recorded historical revision.

The full 306-path static comparison reports 3,474 existing diagnostics.
Of these, 3,470 match the maximum preceding-or-captured-upstream complete
lexical and statement counters. Four complete-source correspondences
prove the upstream Discord class annotation and unused-local changes,
and the upstream nullable session-message contract. No introduced finding
remains. The affected admission file was checked separately; both existing
full diagnostic spans and statements are unchanged. The other 305 source
paths are byte-for-byte identical to the complete static check. Ruff passes.
All 250 formatter paths remain inherited. The nonzero type and formatter
results are qualified, not claimed as passes. No new global or hosted CI
readiness claim follows from this delivery.

Previous captured delivery

The following section describes the preceding published source and its
validation. Its counts and head do not describe the current composition.

Previous source compatibility and retention

Current head: d8d4fead851db57aacdff17731b81b0b10112fd3. Source tree:
f47e5772b478b959c61252cadefc62a059f093c0.

Compose the complete captured branch with the necessary upstream revision
302c5d9f679333ae957c256d76a3dff35120f4e0. Preserve every original
feature, defining scope and contributor record through the actual module,
context, voice, reply, ownership and adapter joins. Both the fixed upstream
revision and the earlier 99a45ecc17cc51c0488ec3b8ccf7eaf483f7203d
join cleanly with this source. This records those exact operands; it does
not claim compatibility with an advancing branch.

Bounded cancellation now keeps captured provisional input until a delayed
task completes. Reconciliation restores eligible inputs oldest first and
runs once across both completion paths. Withdrawn, claimed and durably
persisted input keeps its existing exclusions. Real SQLite controls prove
that delayed non-durable input is recovered and durable input is excluded;
the chain case preserves aliases and FIFO order. The unchanged source
reproduced two lost-input assertion failures with 14 passing controls.
The corrected complete snapshot file passes all 16 cases.

The original 447 contributions remain in order with literal authors,
committers, dates, time zones, other headers and complete message prefixes.
Their twelve original meaningful empty records remain. Three additional
source-free attribution records are preserved and explicitly qualified.
Fixture and source corrections are included in their actual introducing
contributions with factual appendices. The published room-store teardown
fixture is included at original contribution 15. Both room helpers retain
their complete original ASTs; removing only that fixture restores each
complete historical module byte-for-byte. All original assertion contracts,
parameter cases and decorators are retained; the reply excerpt expectation
uses the existing JSON escaping contract.

Three credited published records follow that original sequence: the
#126281 fixture maintenance, whose fake API
and reconnect scopes predate the adopted range, and both complete
#135482 worker-loop lifetime contributions.
The maintenance changes only the applicable fixture scopes. The core
contributions apply through clean three-way patches. No unrelated provider
ancestry is copied. The final history contains 450 contributions.

Previous validation and limits

The original 138-file strict SDK run was non-green: 4,890 cases passed,
238 failed, 18 were skipped, and two files exceeded their deadlines.
Those receipts remain available. Required current API, resource ownership,
reservation persistence and mock-chain fixtures were corrected without
removing their original assertions. The two original mock-chain hangs also
reproduced on the captured original source before their fixture repairs.

Source-qualified complete-file results contain 4,190 passes across 136
complete passing files
, with 18 skipped cases under the existing guards.
A separate crypto file has four skips and contributes no passing coverage.
These results combine actual complete-file executions, exact source and
runner bindings, and explicit whole-module formatting, annotation and
existing-guard bridges. They include the fresh complete cache and pin
contracts, both complete Slack files, and the complete Matrix and reaction
files with the published core correction. A fresh complete Matrix file
passes all 264 cases after the room-store teardown addition. Its complete
successful child output contains no SQLite or unclosed-resource warnings
at interpreter exit. The installed mautrix dependency emits one existing
DeprecationWarning. The earlier summary-only output did not establish
zero warnings at interpreter exit. The other 135 passing complete files
remain source-qualified; this is not a new whole-suite run.

Two unchanged upstream resource-failure files remain non-green:
tests/agent/test_context_compressor.py and
tests/tui_gateway/test_tui_gateway_server.py. Their complete original
and current failures are recorded, and their affected original methods
are preserved. Their partial passing cases are excluded from the passing
scope above. This delivery does not claim a passing complete 138-file gate.

The full current type comparison reports 3,603 existing diagnostics.
Of those, 3,584 match the maximum original-or-upstream complete-statement
counter exactly. Nineteen finite correspondences prove complete methods,
current enum and provider bindings, class member unions, annotation
aliases, or literal fixture statements shifted by resource cleanup.
No unqualified new diagnostic remains. Ruff passes. The same 250 inherited
formatter paths remain; no new unformatted path is introduced. These
nonzero type and formatter results are qualified, not claimed as passes.

Earlier native, live-service, encrypted Matrix, platform, hosted and broad
validation evidence below remains tied to its recorded source revision.
No new live-service, hosted CI, global or review-ready
claim follows from this delivery.

Earlier recorded delivery and validation

The following evidence remains bound to its stated historical revisions.

Earlier final-delivery regression formatting and split contribution credit

Recorded head: 8eed870e00c9a40f24334aa6f41f91cef0ed26aa. Committed source tree: 559f763c380f3a683eefbb4d63030593e59cfda0.

Format the complete final-delivery regression without changing its AST,
parameters, decorators or assertions. The layout change is included at
contribution 82. Every byte outside that test function remains unchanged;
its remaining formatter differences belong to the earlier unchanged prefix.
The scoped lint check passes. Independent complete-module AST comparison
connects this layout to the previously accepted 85-case native gate and
scoped type comparison. Neither gate was rerun for formatting.

The signed prefix of 81 contributions is unchanged. Contributions 82 through
148 preserve their complete author and committer fields, dates, time zones,
encoding and messages. Their only source change is the regression layout.
Contribution 149 preserves the complete 929-byte PRIMARY message and both
literal metadata fields from 1655eb9d3aa2d693af7f8685185c80850408d82d.
Its appendix identifies the existing public source across these three owners:

  • #126283: lifecycle, deferred-input and feedback-policy
    fixtures, with the reviewed typed fixture adaptations.
  • #126312, checked at
    2f2989f133ea0b6eac50c82f1d96fcd2ee5e8bb3: edit fixtures and the real profile
    hook registry.
  • #126398, checked at
    4c04f0ae9aba6ef067a2d17537787636bf499e73: the native gateway fixture has
    exactly one auxiliary configuration argument and its fixture and consumer.

The final contribution records attribution for source already published
across those owners. All five original file changes and ten complete unit
fixture definitions were compared, including their explicit adaptations.
The original message describes historical verification. This credit update
claims no fresh native Matrix run or broader runtime
acceptance. This branch's native fixture has no auxiliary configuration
fixture or consumer, so that fixture argument remains with #126398.

All 68 new signatures were verified locally, and their exact hosted payloads,
signatures, trees and parent relationships were checked. Review the
scoped refresh and complete credit carrier.

Historical fixture contracts and primary committer credit

Recorded head: d830be3ea12d9f0b7a10d26e7c80fc1c92e314ed. Committed source tree: 4395c1464aa5d24cc8618a995ccaa127baec1f4e. This update corrects four gateway test fixtures to match the existing adapter and runner interfaces. Context fixtures use the existing monkeypatch fixture for legacy attributes; queue fixtures use the nominal runner type and its typed methods; the pending-steer adapter returns SendResult through the full adapter send contract. Production behaviour, assertions and parametrised cases are unchanged. The adapter fixture correction is also published in #131009.

The corrections are included at their original introducing contributions, positions 10, 11, 82 and 144. The signed prefix of nine contributions is unchanged. The 139 rewritten contributions preserve complete author fields, dates, time zones, encoding and messages, including meaningful empty contributions. Exactly 34 selected primary records correct their corresponding literal committer fields. Other committer fields remain unchanged. Unresolved primary-record gaps remain qualified; this comparison does not establish fidelity to every original record.

The paired native validation passed the same 85 cases before and after the fixture changes: 21 context-reference cases, 19 FIFO cases, 30 pending-admission cases, five busy-follow-up cases and ten pending-steer cases. Every setup, call and teardown passed, with zero failures, errors or skips. Both runs used the canonical runner, strict asynchronous warnings, two workers and zero retries.

The seven-file paired Ty comparison reported 55 diagnostics before the corrections and 45 afterwards. Exactly ten targeted occurrences were removed, with zero introduced diagnostics. The remaining 35 production and ten fixture diagnostics are inherited. The scoped Ruff lint check of the four changed fixtures passed. The final formatter diff contains the same 57 inherited edit blocks across three files and no introduced residuals; the formatter still exits one.

Two final context-fixture call-layout changes affect only whitespace. Independent review confirmed complete module ASTs, test populations and historical source associations. The native and type evidence applies through that equivalence; neither check was rerun after those layout changes. The historical SimpleX validation below continues to describe its own source and scope. No whole-project type gate, fresh broader test gate or live Matrix server run is claimed by this update.

All 139 new signatures were verified locally. Their exact hosted payloads, signatures, trees and parent relationships were checked before this description update. Review the fixture and credit update. The older composition and validation sections below describe their recorded historical heads.

Changes that apply to every platform

The receipts depend on when the gateway runs the processing hooks, and
those hooks belong to the shared runner. The following changes
therefore affect every adapter that implements on_processing_start
and on_processing_complete (Slack, Discord, Telegram, Signal, Feishu,
Matrix and others). Each has a test on a Telegram or Slack adapter in
tests/gateway/test_processing_lifecycle.py.

  • A turn completes after its own reply is delivered, before a queued
    follow-up starts. Previously the first message completed only after
    the whole chain, so cancelling the follow-up also suppressed the
    earlier message's success reaction and receipt.
  • A queued follow-up completes after the adapter's final delivery. A
    refused final send produces FAILURE, and a cancelled one CANCELLED.
  • Once the running turn has its model result, steer, redirect and
    interrupt input goes to the FIFO. The agent can no longer consume
    it, so the completed turn must not acknowledge it.
  • A leftover steer runs as a copy of the message that sent it, with that
    message's hooks and reply anchor. When a follow-up is already queued,
    the steer waits behind it; before, it was dropped.
  • Each message has one lifecycle. Once the adapter has reported a
    message's start, the message completes exactly once: with the outcome
    of the turn that read it, as FAILURE when that turn raises an error or
    its text is refused, or as CANCELLED when it is dropped before a turn
    runs it (/stop, /new, /reset, or a merge that replaces it in the
    pending slot).
    • Each adapter records the messages whose start it reported until
      they complete. When the adapter is torn down (gateway shutdown,
      startup abort, or removal of a served profile in a multiplex
      gateway), it first cancels its tasks, then completes everything
      still recorded as CANCELLED, wherever the message waits, and takes
      those messages out of its pending slots. The pending slots are
      flushed to disk for recovery before that. If cancelling overruns
      its time bound, the completion still runs under a bound of its own;
      a message completed this way is never started again.
    • When the adapter is idle but the runner still owns a turn, the
      adapter starts a new message itself and the runner steers,
      redirects or interrupts with it. The message completes with the
      turn that read it.
    • A started message that the priority path queues, or merges into the
      follow-up already waiting (a photo burst, or text on the
      pending-sentinel and Telegram grace paths), completes with the turn
      that later runs that follow-up, including a /goal continuation,
      heartbeat or internal notification. If /stop leaves it in the
      overflow FIFO and the next message's turn rescues it, that turn
      completes it.
    • When the model returns one or more such messages as a leftover
      steer, the leftover's own turn owns them from that point, whether it
      runs straight away, waits in the FIFO or is parked at the recursion
      cap. The original handler (for example one still sending a /steer
      acknowledgement) cannot complete them, and the leftover's turn does
      not report a second start. If that turn never runs, the gateway
      completes them as described above.
    • Where the hooks do not track the running turn (a turn without a
      platform message), a steered, redirected or interrupting message
      completes when its handler returns, as on main, unless the model
      returns it as a leftover steer while its handler is still running;
      then it completes with its leftover's turn. On an adapter without
      its own start hook (A2A, Google Chat, webhook), a started message is
      neither attached nor parked, so its handler completes it, as on
      main.
  • An interrupted result from a gateway control interrupt (/stop,
    /new, shutdown) completes as CANCELLED. A turn interrupted by new
    input still completes with the chain that answers that input, as
    before. An inactivity timeout usually returns a failed result, so the
    turn completes with the delivery outcome of its timeout notice, as on
    main.
  • Inline commands have their own hook, on_inline_processing_complete.
    It runs after the command's work, so /stop, /new and /reset
    cancel the turn before any acknowledgement.

The following are unchanged from main: a failed turn whose failure
notice was delivered completes as SUCCESS, and a plain-text approval
whose routing raises falls through to busy handling.

Contributor credit

Includes Zoltán Žiak's authored commit from #96302, with
configuration, profile scope and tests adapted on top. No comments or
reviews were posted on the contributor's PR.

Historical composition and validation

This continuation combines the published queue, deletion and cleanup
prerequisites with Matrix edit handling, processing lifecycle ownership,
pending-input restoration and profile-scoped recovery. The unchanged
historical prefix is 02e311576cc9b2294c0576814fd8ab74293a1205.

The frozen composition at
ba7dcbd69fa4a6ddf1270d3cb90979e24b13b207 passed the complete 42-file
restoration gate: 1,621 cases, two workers, zero retries, strict runtime
warnings and the original 300-second limit for each file. Its separate
speech-model boundary file passed all ten cases. The model boundary checks
that only the intended deferred inputs enter the model conversation.

The preceding public-source gate has 1,304 passing cases across 36 complete
files. This is a qualified combination of 1,091 cases from 35 unchanged
files and a fresh complete Slack file with 213 cases after a test-bootstrap
location correction. It is not a fresh run of all 36 files.

The capacity integration preserves the published steer-capacity
correction
and all eight original regression cases from
#131009. The source also keeps this layer's message copies, voice transcript
metadata and lifecycle ownership. When the pending queue is full, an
already accepted tracked steer remains queued behind the selected event;
its processing lifecycle completes when that steer runs or is cancelled.
Two added cases exercise that path through the model and adapter for normal
completion and /stop.

The lifecycle coverage also includes a tracked steer whose acknowledgement
is still in flight when its own follow-up fails. Processing feedback ends
once with that follow-up's failure after the original handler returns.

The causal gate ran all ten cases in the complete pending-steer file
against the original production source. Eight cases failed assertions and
two passed, with no errors or skips. Both added tracked cases reproduced
the accepted steer being lost after the pending queue became full; the
later completion and stop outcomes were not reached on that source.

The final source passed all 164 cases across six complete files covering
internal-event pins, pending admission, steer draining, processing
lifecycles, queue consumption and queued follow-up hooks. These include
all eight original capacity cases, both added tracked normal/stop cases
and the restored tracked acknowledgement-in-flight failure case. The
canonical runner used two workers, zero retries, strict runtime warnings
and the original 300-second limit for each file.

The accepted final unsigned source is
a708c47bc3f30b102a3374975b5038746be98935, with tree
32f758fcf234c48d8793293636be6648bb0d42e6. Signing preserves that tree;
the accepted 145-commit signed head is
961ee805ee0abbd92addea5cdc66d62fe1eb7973.

The composition adds 145 commits after the unchanged historical prefix:
142 reconstructed contributor commits, the existing speech-model boundary
commit, a copy of the published capacity correction and the tracked-steer
integration. The 142 reconstructed commits preserve the author and committer fields,
timestamps, timezones and encoding from the selected saved archival
records. This comparison does not establish fidelity to every primary
original record. The first original message is
retained in full as the exact prefix of its final message, with the existing
435-byte repair rationale appended. The other 141 messages are unchanged.
The capacity copy preserves its original author, committer and full message.

All 145 signed payloads at 961ee805ee0abbd92addea5cdc66d62fe1eb7973
preserve the selected saved records' literal author, committer, date,
timezone and full message, apart from the described existing first-message
repair note. Each signature was verified locally and its exact payload,
signature, tree and parent were checked on GitHub.

Historical validation

Earlier descriptions recorded broad unit, configuration and type checks,
and a Docker-backed 13-case Matrix receipt gate. Those receipts belong to
their recorded predecessor sources. They do not establish a new native
Synapse run for this continuation. The current checks above identify their
exact source and scope.

This PR remains a draft. Prerequisite merge order is specified in the Stack
section. No upstream merge or successful execution against current main
is claimed.

Public fixture credit follow-on

A further credit-only commit retains the complete 777-byte primary
rationale and the original author and committer identities, dates and
timezones for the queued-context fixture contribution. It appends the
public fixture adaptation rationale without changing the source tree or
rewriting the preceding 145 commits. The receipt teardown and membership
checks remain in this composition; the unread fixture and its consumer
remain in their separate prerequisite.

The public paused-context fixture selects queue input and interrupt
text. Ordinary active-session text therefore enters the runner FIFO; this
configuration does not request interruption of the active agent. Explicit
steer consumers keep their normal-mode overrides. Debounce timing, text
coalescing and pending storage differ between these paths. The current 164-case gate above applies to this unchanged source tree.
Historical results remain limited to their recorded sources. This follow-on
adds no production or test changes and requires no new native run.

That follow-on was published at
4918941e1b2562d783e6ed7824a676fd8cdcff14, with the same tree
32f758fcf234c48d8793293636be6648bb0d42e6. At that endpoint, the accepted
local and hosted proofs covered the preceding 145 commits. The additional
commit had its own local signature verification and exact hosted payload,
signature, tree and direct parent checks. These remain historical proof
scopes after the metadata correction below.

Compare the credit-only follow-on.

Primary committer metadata correction

Three selected archival records had different committer timestamps from
their primary originals. The commits at positions 122, 138 and 139 now
preserve the literal committer fields from primary originals
67031b79a519d569c02ac58cbb737f29e38d4e57,
540f50cd2dd0539ad4653d668d8bc37a3ca51997 and
447d4b0c43632afb48504ce97f9a6a4ac27e176a respectively.

The correction rewrites positions 122 through 146 and retains the signed
prefix through d129fec35638ea7bb180cfcfd1f8fd23f2ea5c81 unchanged. Every
source tree, author field, encoding and complete commit message remains
unchanged. Empty contribution commits are retained. The 25 rewritten
commits have fresh local signature verification and exact hosted payload,
signature, tree and parent checks. The preceding 121 commits retain their
existing raw objects and signatures; they were not signed again.

The metadata correction was published at 9c469978125dae8c1a928d077f4101df558aeb11, with tree
32f758fcf234c48d8793293636be6648bb0d42e6. The source validation above
continues to apply to that unchanged tree. This metadata correction makes
no source or test changes and requires no test rerun. Historical native
results remain limited to their recorded source and image scopes.

Compare the metadata correction.

Native SimpleX reply context

The SimpleX adapter now preserves native quote context in the existing
MessageEvent reply fields before shared text batching. The incoming item
ID remains separate from the quoted target. Direct and group replies use
the same normalisation path, including deferred voice delivery.

This consumer depends on #133509. That PR remains
an independent native-reply contribution with its own prerequisites. This
consumer preserves the native contribution's complete message and literal
author and committer fields. A separate fixture commit adapts the expected
merged message IDs and JSON quotation to the gateway contracts in this
composition. The fixture commit also formats the new SimpleX code.

The canonical test run passed all 155 cases: 71 SimpleX, 50 shared
text-batching and 34 active-session merge cases. There were no failures,
errors or skips. That run exercised unsigned source
9001b3bab592fa576230cb72506181707050c10f, with tree
183fd3b2bcc929296c03ea705211ef199d2eeb0d. The final source differs only in
formatting. Independent review compared both complete module syntax trees,
including all parametrisations and assertions, and confirmed equivalence.
The 155-case result is reused on that basis. The tests were not rerun after
formatting.

The independently reviewed paired Ty diagnostic comparison on the tested
source retained the same seven
adapter diagnostics and introduced none. Both Ty commands exited one.
Independent review confirmed that the formatting successor preserves the
complete diagnostic statements, scopes and types, so the Ty evidence is
reused. The fresh scoped Ruff check on the formatted source reported no diagnostics
and exited zero. The complete formatter diff retained 98 inherited groups:
69 in the adapter and 29 in its test file. The formatter exited one.
Independent review classified every group and found no introduced
formatting findings. The inherited formatting and type debt remain.

No live SimpleX daemon or native outbound quote emission was exercised.
The earlier Docker and whole-contribution qualifications remain unchanged.

The two-node SimpleX continuation is published at 83f8dec5caef2a605f8687d095e0a040d726a485,
with tree 3a0b6fcf292a2a44422720490bf7091f25fabcc9. The preceding 146
commit OIDs remain unchanged. Only the native contribution and its separate
fixture and formatting composition received new signatures. The two new
hosted payloads, signatures, trees and parents were verified before this
body update. The earlier prefix verification remains limited to its saved
publication scope. See the two-node SimpleX continuation.

Callback and turn-marker typing repair

Processing-start hooks and Telegram topic recovery can await before dispatch. A handler present on entry can disappear during that await. Both background and inline dispatch now select the current callback at their own call boundary and report an explicit error if the callback is absent. Background selection remains inside the pending reservation scope after the processing-start hook. The existing failure notification, marker reset, delivery and command-restoration paths remain intact.

The event's turn-marker handoff is now a declared private field. Its default is false, and dataclasses.replace resets it for the copied event. The field remains outside constructor, representation and equality data.

This update adds one logical contribution after the complete 149-commit prefix. All prior commit identities, literal author and committer headers, dates and complete messages remain unchanged.

The paired check of the three production modules reports 52 findings before and 48 afterwards. It removes the three assigned background findings and the related inline optional-call finding, with no introduced finding. The remaining 48 findings are inherited; this is not a claim that the modules are type-clean.

The canonical lifecycle, lease and withdrawal run passes 137 cases. A separate complete command and clarification run passes 19 cases. The two runs contain 156 distinct cases, with every setup, call and teardown passing. Focused runs on the old production code reproduce the absent-handler and marker defects. The final marker line wrap preserves the complete module AST, so the saved runtime and type results remain associated with the final source.

Saved scoped Ruff checks report no lint findings. Reconstruction of the complete saved formatter output confirms that the new declarations and tests add no formatter debt; the remaining formatter changes are inherited. No fresh checker execution after the final whitespace-only wrap is claimed.

Actual Matrix room/thread execution and the remaining campaign obligations require their own acceptance.

@alt-glitch alt-glitch added type/feature New feature or request comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins platform/matrix Matrix adapter (E2EE) P3 Low — cosmetic, nice to have sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-automation Sweeper risk: may affect CI, automerge, label sync, or maintainer automation labels Sep 28, 2026
@ZoloZiak

Copy link
Copy Markdown

Heads-up: the config slice of this PR (per-profile matrix.read_receipts with immediate / after_processing / disabled, plus the matrix.reactions toggle) overlaps almost exactly with my #96302 — same YAML keys, same MATRIX_READ_RECEIPTS / MATRIX_REACTIONS env back-compat, same matrix.md table. Yours is the superset (it also carries the lifecycle refactor + live tests), so I've closed #96302 in favour of this one.

If this draft ends up wanting to land the config part on its own first (smaller, no infra dependency), #96302 is that slice already reviewed — feel free to lift from it or ping me. Otherwise ignore and carry on here.

chelsealong and others added 16 commits October 1, 2026 10:16
matrix.process_edits was documented and tested as working via config.yaml,
but _YAML_LOWER_KEYS (the apply_yaml_config_fn hook that turns config.yaml's
matrix: block into env vars) never listed it, so setting
matrix.process_edits: true in config.yaml silently did nothing — only the
MATRIX_PROCESS_EDITS env var actually worked. Add the missing tuple entry
(mirroring process_notices) and a test that drives the real
_apply_yaml_config bridge instead of only asserting against a
hand-constructed PlatformConfig(extra=...).
The initial edit option used a global boolean and trusted the relation
inside m.new_content. An edit could therefore change another author's
input, route a correction into a different thread, or enter the active
turn through the gateway's interrupt or steering policy.

Configure corrections with exact room IDs and boolean values, defaulting
to no enabled rooms. Validate the original event's room and author with
the receiving adapter's client, then use its original thread relation.
Apply the current sender, room, notice and mention policies. Typed edits
must supply genuine replacement content because mautrix serialization
can manufacture m.new_content. Encrypted validation uses the existing
client crypto and replacement reader.

Queue corrections through the existing pending slot and FIFO, replacing
pending edits to the same original in their existing position. Recheck
both events and current authorisation in shared turn preparation, so
cold and recursive queued turns reject withdrawn corrections even after
cache eviction. Edited commands remain external conversational text.

Append the correction as a new user turn with its original event ID.
Earlier model messages and the system prompt remain unchanged. Keep the
existing opening-event reply anchor for final delivery in a queued chain.

Two parameterised invariants reproduce the original policy, routing and
active-turn failures. Six additional rows reproduce a withdrawn pending
replacement after cache eviction. All 50 rows pass with the repair, and
the adjacent gateway gate passes 766 tests across 39 files.
With busy_input_mode: queue, the base adapter debounces ordinary text
that arrives during a turn and merges the buffer into the pending slot
when the slot holds an event from the same sender. A Matrix correction
waiting in that slot is a text event from the same sender, so the
user's next message was appended to it. At turn start the adapter
compares the queued correction with the server's copy of the edit. The
merged text no longer matched, so the gateway dropped both the
correction and the new message without a reply.

Debounced text is now never merged with an event that waits for a turn
boundary. The text stays in the debounce buffer while the correction
occupies the slot, and the existing flush at the end of each turn moves
it into the slot as its own turn. When a correction arrives while text
is already buffered, the adapter flushes the buffer first, so that text
runs before the correction, in the order the user sent them.
Edit handling and turn-start validation resolved a correction's
context with the original event ID. _resolve_message_context records
that ID as the thread's latest event, which bot messages without an
explicit reply target use for their reply fallback, and it sends a read
receipt for it. Each correction therefore moved the thread's fallback
target back to the original and sent a receipt for an older event. The
edit handler did this before it checked the sender's authorisation.

_resolve_message_context now accepts record=False, which leaves the
thread trackers and the read receipt to the caller. The correction
handler resolves the context this way and checks authorisation first.
Only an accepted correction marks its thread and sends a read receipt,
and the receipt is for the edit event. Turn-start validation records
nothing.
A correction event's message ID is the ID of the m.replace event.
Clients display a replacement as the original message and do not show
the edit event itself, so the processing, success and failure
reactions that the adapter placed on the edit were invisible.
Corrections now use the original event as the reaction target.
The bridge test set MATRIX_PROCESS_EDITS before applying the YAML
configuration and then asserted that the value was unchanged.
apply_yaml_bridge never overwrites a variable that is already set, so
the test would also pass if a process_edits bridge row came back. The
test now starts with the variable unset and asserts that it stays
unset. With a bridge row restored, it fails.
The edit follow-up section did not say which message a correction's
reply refers to. A correction's reply is a Matrix reply to the original
message when the correction starts its own turn or runs before the end
of a queued chain. When the correction is the last queued message to
run after a busy turn, the gateway sends the reply as a Matrix reply to
the message that started that turn, as it does for every queued
message. Document this, and document that corrections are never merged
with other messages, that the model receives the corrected text as
typed, and that processing reactions appear on the original message.
The effective-state parent added requires_mention as the sixth field of
the tuple from _resolve_message_context. The correction handler still
unpacked six fields, so every accepted edit raised ValueError before an
event was built, and no correction reached the gateway.

Unpack the new field as well. The handler does not use requires_mention
itself: _build_inbound_event reads it from the same context and records
matrix_requires_mention on the correction as it does for other messages.
The effective-state parent removed fetch_mention_context and
take_turn_channel_context from the adapter, and the gateway now calls
use_turn_context on the snapshot from fetch_inbound_context. The
correction tests stubbed all three methods, so monkeypatch failed on the
missing attributes, and a None snapshot would fail in the core.

Drop the stubs so turn preparation uses the adapter's real snapshot and
prepare_turn_context. The hook looks up the session through the runner's
session store, so the cold runners get a SessionStore in tmp_path, and
the room identity fake has room_state instead of the removed
members_digest.
The effective-state parent moved the Matrix client helpers into
client_events.py, which provides Method with its own stand-in for
environments without mautrix. The correction module still had a private
try/except that fell back to read_context's re-export. Import Method
from client_events, as the other Matrix context modules do, so the
plugin has one fallback definition.
A correction must wait for a turn boundary and take its own turn. The
busy-session handler already queues a turn-boundary event through the
FIFO. The runner has a second route to a running session:
_hm_handle_running_session_message, reached from _handle_message when
the runner has a running turn for the session but the adapter has no
session guard. That route ignored _queue_at_turn_boundary.

The state is reachable in production. The CLI handoff runs its
synthetic turn through _handle_message directly, so the adapter has no
guard for that session for the whole turn. Auto-resume after a restart
claims the pending-agent sentinel before its task reaches
handle_message, and expects inbound messages in that window to reach
this route. A correction arriving then was merged into the pending text
while the agent was starting, and in interrupt or steer mode it was
delivered into the running turn.

Skip the pending-agent merge for turn-boundary events and send them
through the queue-mode branch, which uses the same FIFO as the busy
handler. The drain check still comes first, as in the busy handler.

The new test builds a correction with the real Matrix adapter and passes
it to the real runner while the session has a running turn and no
adapter guard. Before the change it failed in three of four modes: the
pending-agent case produced "earlier text\nlatest correction", interrupt
mode called interrupt("latest correction"), and steer mode steered the
correction into the turn. Queue mode already queued it.
The effective-state parent now contains NousResearch#125648, which quotes the
triggering event once per threaded response. A send anchored on an
event that Hermes has already answered in the thread is a
continuation: it sets is_falling_back and points at the latest event
in the thread.

A correction that waits behind a paused turn runs as the last message
of the queued chain. Its answer is anchored on the message that started
the paused turn, as for every queued message, and the first response
already answered that message. The native test still expected both
responses to be genuine replies to that message, so the plain and
encrypted enabled cases failed with is_falling_back true and
m.in_reply_to pointing at the first response.

The client now expects the first response to quote the blocked message
and each later response to fall back to the response before it, in the
expected order. The Matrix guide describes the correction's reply in
that case as a thread message after the running turn's answer.
…b it

In busy_text_mode queue, text that arrives during a turn waits in a
per-session debounce buffer. _flush_text_debounce_now moves the buffer
into the adapter's pending slot, but only when the slot is empty or its
event may absorb the text. Two senders' texts may not merge, and since
the Matrix edit follow-ups a queued correction absorbs no text at all.
When the slot could not absorb the buffer, the flush returned without
moving it and cancelled its timer. The buffer then waited for the next
flush.

_queue_text_debounce made that loss visible. A message from a
different sender first flushes the buffer. When the flush failed and
the slot could not absorb the new message either, the function returned
without storing it, and nothing logged the drop. Threads are shared
between their participants by default, so in queue mode a correction
followed by texts from two people lost the second text. On main the
same happened to a third sender's text while the slot contained text from
someone else. The unflushed buffer also broke arrival order: text sent
between two corrections ran after the second one, because the second
correction went to the runner's FIFO while the text stayed in the
buffer.

When the slot cannot absorb the buffer, hand the buffered event to the
runner's _queue_or_replace_pending_event, which appends it to the FIFO
behind the slot with the same cap and album rules as other busy
follow-ups. The adapter already reaches the runner through
gateway_runner for delivery scope and live-adapter lookups. The buffer
is then empty, so the next sender starts a fresh buffer instead of
reaching the drop. Each sender's burst still runs as one turn, in
arrival order. An adapter without a runner keeps the previous
behaviour, because it has no FIFO.

The platform-neutral test sends text from two senders while the slot
contains a third sender's text, and while it is empty. Before the change,
"three" was missing in both rows and a buffer stayed stranded with no
timer. The Matrix test covers a pending correction followed by text
from Alice and Bob, and from Bob and Carol, with and without the timer
firing between them. Before the change the second message was lost in
all four rows.
Text that the user sends after one correction and before another must
run between them. Before the previous commit, the text stayed in the
debounce buffer because the first correction in the pending slot could
not absorb it, while the second correction went to the runner's FIFO.
The observed order was $edit1, $edit2, $text.

The debounce flush now hands the text to the FIFO behind the first
correction, and the flush before the second correction's busy handling
runs first, so the order is $edit1, $text, $edit2. This test asserts
that order. It failed on the parent of the previous commit with
[$edit1, $edit2, $text].
A correction's m.new_content has no reply fallback, so a leading quote
in it is the user's own text. After the merge of the effective-state
parent, three conditions in the adapter strip a quote only when
reply_fallback is true and _has_reply_fallback recognises a legacy
fallback. The existing quote-on-reply row starts with "> quoted line",
which _has_reply_fallback rejects because it has no sender pill, so
that row passed even with reply_fallback removed from the conditions.

Add rows whose body quotes Bob with a pill and a blank line, which is
the shape of a legacy fallback, for an original that is a reply and for
one in a thread. With "reply_fallback and" removed from the three
conditions, both rows fail with "my answer" in place of the full quoted
body. The two older rows pass under that mutation.
The guide said that a correction's reply quotes the original message,
except for the last queued message after a busy turn. That is wrong in
both session shapes.

In a thread, a send anchored on a message that Hermes has already
answered is a thread continuation: it has no quote and falls back to
the latest message in the thread. People usually correct a message that
Hermes has answered, so an idle correction is normally a continuation
too. The reply quotes the original only when Hermes has not answered it
yet, for example when the edit adds a missing mention.

Outside a thread the continuation rule does not apply. The reply quotes
the original, and the last queued message after a busy turn quotes the
message that started that turn, as for every queued message.

Describe the two cases separately.
@iainlane
iainlane force-pushed the feat/matrix-receipts-lifecycle branch 2 times, most recently from e2502e7 to 1470db3 Compare October 1, 2026 12:48
Cached text can contain an opaque marker. Exercise
normal and restored queue execution with the actual model request path
so the test detects transcript substitution, role changes and profile
owner leaks before the request reaches the model.
An accepted leftover /steer can arrive while provisional pending input
has already filled the queue. The drain ignored FIFO admission failure,
so it dropped the correction while keeping the provisional input. The
branch that prioritised a steer over a queued event also restored that
event directly without releasing its provisional dispatch reservation.

Retain an unadmitted steer for the next turn. Check each pending input
through the recognised-command guard before combining the remaining
text, so a residual command is discarded without losing the correction.
Restore deferred FIFO events through the existing dispatch helper, which
releases reservations while preserving order.

Three assertion failures reproduce the lost correction with ordinary
provisional text, the residual /help safety case, and the restored FIFO
state on the parent. The FIFO contract releases each returned dispatch
before measuring queue depth, as the caller does in normal operation.
All eight contracts in the existing file pass after the fix.

Integrate the pending-admission correction with the current pinned channel-input constructor. The drain now uses the actual FIFO admission result, preserves unadmitted steering text and releases a deferred event reservation through the existing restore helper. The complete original regression contracts and assertions remain unchanged.
A steer accepted by the running agent can return unconsumed while earlier
queued input and provisional dispatches fill the pending queue. Admission
then fails when the drain moves the steer to its own turn. Dropping that
accepted input loses the correction and its processing completion.

Restore the tracked steer through the existing pending-dispatch helper and
park its lifecycle before transferring completion ownership. Restoration
preserves accepted input, while the admission threshold still rejects new
messages. The earlier queued event keeps its own dispatch and runs first;
the restored steer retains its message identity and completes with its own
turn or with cancellation if the queue is stopped.

The regression drives the real runner and adapter with a scripted model,
queued input and a full set of provisional reservations. It checks model
input, admission rejection, processing outcomes and retained reservations
for successful delivery and /stop.
The receipt teardown contract observed the former text-only flush call.
The current adapter writes a complete pending snapshot before clearing
its slots, so that observer missed a successful write. Wrap the actual
snapshot writer and retain the completion, ordering and empty-slot
assertions. The wrapper still writes the real snapshot.

Supply joined room members for the current Matrix authorization check.
Combine the native feedback settings with the existing paused-queue
configuration in one display section, and retain the additive auxiliary
configuration fixture required by the unread tool. These fixtures use
the current adapter and configuration paths without replacing their
behaviour assertions.

The fixture contribution is split between the receipt and unread layers.
Record its complete rationale in the receipt history. The current source
uses the real snapshot observer, the original membership assertions and
the consumed auxiliary fixture, with one parsed YAML display document.

The paused-context public fixture selects input mode `queue` and text mode
`interrupt`. Busy follow-ups enter the runner FIFO because interruption
requires effective input mode `interrupt`. The queued-context consumer
checks retained quoted context, the preceding prompt prefix and eventual
dispatch; its contract does not require adapter debounce or coalescing.
This accepts direct FIFO admission as the adaptation of the former
queued-text fixture. Normal-mode steer cases keep their explicit override.

This records contributor credit and the public fixture adaptation without
changing the source tree.
Native SimpleX chat items include the quoted message separately from
incoming content. The receiver retained that raw payload but omitted the
incoming item ID and quote fields when constructing MessageEvent. The
shared batcher therefore treated replies to different targets as
unquoted input, and the prompt could omit the quoted text.

Populate the existing reply fields before batching or direct dispatch.
Keep the incoming item ID separate from the quoted target. Use a shared
message context identity when the local target ID is unavailable, or an
identity specific to the incoming item when neither target ID exists.
Derive ownership and author data from the quoted direction, including an
unknown group author, and reuse the normaliser for deferred voice input.

Two parameterised native-payload invariants reproduce the missing event
metadata and batching boundaries on the exact prerequisite. They also
check complete prompt framing and deferred file replay on the repair.

Retain the captured UTC timestamp when normalising native SimpleX
reply context. Keep the dataclass import, complete quote normaliser
and incoming identity handling from this contribution.
@iainlane
iainlane force-pushed the feat/matrix-receipts-lifecycle branch from 24630f5 to 961ee80 Compare October 5, 2026 22:25
iainlane added a commit to iainlane/hermes-agent that referenced this pull request Oct 5, 2026
A started input can be deferred into the FIFO overflow and rescued by a
later turn. After the rescued turn finishes, the runner clears its durable
active-turn marker before reporting processing completion. Cancellation
during that storage await skips the completion hook, so the input remains
marked as processing even though its turn has finished.

Run rescued-input completion in the marker cleanup finally block. Keep
both awaits after the generation-scoped session and lease releases, and
retain the adapter-owned marker handoff and marker token comparison.

Extend the marker cancellation contract with the actual adapter start and
defer lifecycle followed by FIFO rescue. Both normal and rescued turns
release ownership and admit a successor; the successful rescued input
also reports exactly one successful completion after cleanup cancellation.

Historical attribution for original source 51c0ecf6197ce1489fb31bdcbddfb64c3d2ba125
(audit record 755). Public PR NousResearch#126283 already includes this cancellation
correction in 48902ee. This empty commit
records the missing original contribution credit. The source tree is
unchanged.
@iainlane
iainlane force-pushed the feat/matrix-receipts-lifecycle branch from 4918941 to 9c46997 Compare October 6, 2026 03:25
Native SimpleX reply tests compare complete events and prompts after
the common gateway batches incoming text. The gateway records the
additional message IDs in a merged event and JSON-escapes quoted
text in the prompt. The imported tests omitted those IDs and placed
literal newlines inside the quotation.

Include the second message ID in coalesced expected events and use
JSON encoding for the complete expected quote. Keep the split-event
cases and whole-event and whole-prompt comparisons unchanged.

The canonical 40-case run reproduced 24 merged-event assertion
failures and 16 prompt assertion failures on the unadapted tests.

The native reply additions introduced formatter changes in the reply
context, extended event construction and their fixtures. Format those
scopes and remove the extra blank pair at the fixture insertion boundary.
Keep unrelated inherited formatting unchanged. Complete module ASTs,
parameter populations and assertions remain identical to the accepted
155-case source, so its behaviour evidence remains applicable.
The complete feedback and edit source adds a second auxiliary-config
argument to the shared native gateway fixture. Native collection fails
before the unread controls can run. Keep the single additive argument
that the controls fixture already provides.

The incoming edit and lifecycle contracts also read optional mock-call
records and assign dynamic fixture fields as statically declared methods.
Check that the required calls occurred before reading their arguments,
use the real profile-hook registry, and bind fixture replacements through
monkeypatch. All original behaviour assertions remain in place.

The complete changed production, unit and native harness type comparison
has no new diagnostics after the defining-owner correspondence checks.
The native collection syntax error is removed; collection-only execution
is diagnostic evidence, not a test pass.

Source and contributor reconciliation for original 726
(1655eb9d3aa2d693af7f8685185c80850408d82d).

The original message above records the composed correction and its
historical checks. This contribution preserves its complete credit after
the source repairs were incorporated into their separate public owners.

The five edit-follow-up fixtures use direct awaited-call guards and the
real ProfileHookRegistries on NousResearch#126312. The lifecycle, deferred-input and
feedback fixtures use the declared typed and monkeypatch adaptations on
NousResearch#126283. These complete unit definitions are also present in its current
composed tree. The native auxiliary configuration fixture has one argument
and its consumer on NousResearch#126398. The NousResearch#126283 native harness has no auxiliary
fixture or consumer, so it requires no corresponding argument.

This is source and credit attribution across those owners. It adds no
product or fixture patch, and records no fresh test, type, SDK or native
execution. The complete original message and both literal contributor
dates are preserved.
@iainlane
iainlane force-pushed the feat/matrix-receipts-lifecycle branch from 83f8dec to d830be3 Compare October 6, 2026 08:34
iainlane added a commit to iainlane/hermes-agent that referenced this pull request Oct 6, 2026
A started input can be deferred into the FIFO overflow and rescued by a
later turn. After the rescued turn finishes, the runner clears its durable
active-turn marker before reporting processing completion. Cancellation
during that storage await skips the completion hook, so the input remains
marked as processing even though its turn has finished.

Run rescued-input completion in the marker cleanup finally block. Keep
both awaits after the generation-scoped session and lease releases, and
retain the adapter-owned marker handoff and marker token comparison.

Extend the marker cancellation contract with the actual adapter start and
defer lifecycle followed by FIFO rescue. Both normal and rescued turns
release ownership and admit a successor; the successful rescued input
also reports exactly one successful completion after cleanup cancellation.

Historical attribution for original source 51c0ecf6197ce1489fb31bdcbddfb64c3d2ba125
(audit record 755). Public PR NousResearch#126283 already includes this cancellation
correction in 48902ee. This empty commit
records the missing original contribution credit. The source tree is
unchanged.
iainlane added a commit to iainlane/hermes-agent that referenced this pull request Oct 6, 2026
A started input can be deferred into the FIFO overflow and rescued by a
later turn. After the rescued turn finishes, the runner clears its durable
active-turn marker before reporting processing completion. Cancellation
during that storage await skips the completion hook, so the input remains
marked as processing even though its turn has finished.

Run rescued-input completion in the marker cleanup finally block. Keep
both awaits after the generation-scoped session and lease releases, and
retain the adapter-owned marker handoff and marker token comparison.

Extend the marker cancellation contract with the actual adapter start and
defer lifecycle followed by FIFO rescue. Both normal and rescued turns
release ownership and admit a successor; the successful rescued input
also reports exactly one successful completion after cleanup cancellation.

Historical attribution for original source 51c0ecf6197ce1489fb31bdcbddfb64c3d2ba125
(audit record 755). Public PR NousResearch#126283 already includes this cancellation
correction in 48902ee. This empty commit
records the missing original contribution credit. The source tree is
unchanged.
@iainlane
iainlane force-pushed the feat/matrix-receipts-lifecycle branch from d830be3 to 8eed870 Compare October 6, 2026 09:55
iainlane added a commit to iainlane/hermes-agent that referenced this pull request Oct 6, 2026
The complete feedback and edit source adds a second auxiliary-config
argument to the shared native gateway fixture. Native collection fails
before the unread controls can run. Keep the single additive argument
that the controls fixture already provides.

The incoming edit and lifecycle contracts also read optional mock-call
records and assign dynamic fixture fields as statically declared methods.
Check that the required calls occurred before reading their arguments,
use the real profile-hook registry, and bind fixture replacements through
monkeypatch. All original behaviour assertions remain in place.

The complete changed production, unit and native harness type comparison
has no new diagnostics after the defining-owner correspondence checks.
The native collection syntax error is removed; collection-only execution
is diagnostic evidence, not a test pass.

Source and contributor reconciliation for original 726
(1655eb9d3aa2d693af7f8685185c80850408d82d).

The original message above records the composed correction and its
historical checks. This contribution preserves its complete credit after
the source repairs were incorporated into their separate public owners.

The five edit-follow-up fixtures use direct awaited-call guards and the
real ProfileHookRegistries on NousResearch#126312. The lifecycle, deferred-input and
feedback fixtures use the declared typed and monkeypatch adaptations on
NousResearch#126283. These complete unit definitions are also present in its current
composed tree. The native auxiliary configuration fixture has one argument
and its consumer on NousResearch#126398. The NousResearch#126283 native harness has no auxiliary
fixture or consumer, so it requires no corresponding argument.

This is source and credit attribution across those owners. It adds no
product or fixture patch, and records no fresh test, type, SDK or native
execution. The complete original message and both literal contributor
dates are preserved.
The adapter's processing-start hook and topic recovery can await before
message dispatch. A handler installed on entry can disappear during that
await, so the earlier presence check does not make either dispatch call
safe. The turn-marker handoff also had no declared state on MessageEvent.

Declare the marker as an event-local field that resets when dataclasses
replace an event. Select and validate the current handler at each dispatch
boundary. Background selection remains after the start hook and inside the
pending reservation scope. When no handler is installed, background
dispatch reports failure through the existing notification and cleanup
paths. Inline dispatch errors preserve command restoration and clarify
lifecycle handling.

The scoped type comparison removes the three background findings and the
sibling inline optional call, with 48 inherited findings unchanged and no
introduced findings. The canonical lifecycle, marker and withdrawal run
passes 137 cases. The separate command and clarify run passes 19 cases,
including handler removal during real awaited topic recovery. Scoped Ruff
reports no lint findings; unrelated inherited formatter changes remain.
The fake HTTP API discarded the client_session supplied by the Matrix
adapter, so disconnect closed a mock while the real aiohttp session was
left open. A reconnect test also replaced disconnect with a stub and
left its new connection open after checking the guard. Strict resource
warnings expose both leaks.

Preserve the supplied session in the fake API. Use a wrapping disconnect
spy in the reconnect test and disconnect the replacement client after
the existing call assertion. The fake API and reconnect fixture predate
this branch, so this focused fixture maintenance is separate from the
corrections folded into their campaign contributors. Production code
and all original assertions are unchanged.

Apply the published fake-API session and reconnect cleanup to this consumer fixture. Preserve its additional Matrix types, membership, encryption and account assertions.
Cached auxiliary-client teardown is mixed into the large routing module.
The worker-loop lifetime correction needs to await only the transports
owned by a terminating worker, without expanding that routing module.

Move the three transport-close functions into a lifecycle sibling and
import the close function at its existing callers. Their full function
ASTs and the remaining routing-module statements are unchanged.
Tool dispatch keeps one event loop per executor thread so cached async
clients can reuse their transports between calls. The thread-local
cache stored only the loop, so ending an executor left the loop and its
selector sockets open. Auxiliary clients also remained cached after
their owner thread exited. Temporary workers used inside an already
running event loop also closed their loops before finishing generators
and cached transports.

Give each worker a Runner owner with a finalizer attached to its
thread-local lifetime. Cancel remaining tasks and finish generators
before closing cached clients on that loop. Wait for their close tasks,
then let Runner shut down the executor and close the loop. Clients that
belong to other live loops remain cached and open.

The regression uses actual executor threads and SDK HTTP connections to
a local server. It checks reuse between calls, generator cleanup before
client closure, closed worker resources, and an unaffected main-thread
client in both worker modes. Timeout cancellation still runs inside the
worker loop, and executor shutdown remains nonblocking for the caller.
The existing fake timeout executor now also closes the test
coroutine which it deliberately never submits.
@iainlane
iainlane force-pushed the feat/matrix-receipts-lifecycle branch from f3fbf84 to d8d4fea Compare October 9, 2026 10:04
iainlane added a commit to iainlane/hermes-agent that referenced this pull request Oct 9, 2026
A started input can be deferred into the FIFO overflow and rescued by a
later turn. After the rescued turn finishes, the runner clears its durable
active-turn marker before reporting processing completion. Cancellation
during that storage await skips the completion hook, so the input remains
marked as processing even though its turn has finished.

Run rescued-input completion in the marker cleanup finally block. Keep
both awaits after the generation-scoped session and lease releases, and
retain the adapter-owned marker handoff and marker token comparison.

Extend the marker cancellation contract with the actual adapter start and
defer lifecycle followed by FIFO rescue. Both normal and rescued turns
release ownership and admit a successor; the successful rescued input
also reports exactly one successful completion after cleanup cancellation.

Historical attribution for original source 51c0ecf6197ce1489fb31bdcbddfb64c3d2ba125
(audit record 755). Public PR NousResearch#126283 already includes this cancellation
correction in 48902ee. This empty commit
records the missing original contribution credit. The source tree is
unchanged.
iainlane added a commit to iainlane/hermes-agent that referenced this pull request Oct 9, 2026
The complete feedback and edit source adds a second auxiliary-config
argument to the shared native gateway fixture. Native collection fails
before the unread controls can run. Keep the single additive argument
that the controls fixture already provides.

The incoming edit and lifecycle contracts also read optional mock-call
records and assign dynamic fixture fields as statically declared methods.
Check that the required calls occurred before reading their arguments,
use the real profile-hook registry, and bind fixture replacements through
monkeypatch. All original behaviour assertions remain in place.

The complete changed production, unit and native harness type comparison
has no new diagnostics after the defining-owner correspondence checks.
The native collection syntax error is removed; collection-only execution
is diagnostic evidence, not a test pass.

Source and contributor reconciliation for original 726
(1655eb9d3aa2d693af7f8685185c80850408d82d).

The original message above records the composed correction and its
historical checks. This contribution preserves its complete credit after
the source repairs were incorporated into their separate public owners.

The five edit-follow-up fixtures use direct awaited-call guards and the
real ProfileHookRegistries on NousResearch#126312. The lifecycle, deferred-input and
feedback fixtures use the declared typed and monkeypatch adaptations on
NousResearch#126283. These complete unit definitions are also present in its current
composed tree. The native auxiliary configuration fixture has one argument
and its consumer on NousResearch#126398. The NousResearch#126283 native harness has no auxiliary
fixture or consumer, so it requires no corresponding argument.

This is source and credit attribution across those owners. It adds no
product or fixture patch, and records no fresh test, type, SDK or native
execution. The complete original message and both literal contributor
dates are preserved.
@iainlane
iainlane force-pushed the feat/matrix-receipts-lifecycle branch from d8d4fea to 52e9d3a Compare October 9, 2026 15:16
iainlane added a commit to iainlane/hermes-agent that referenced this pull request Oct 9, 2026
A started input can be deferred into the FIFO overflow and rescued by a
later turn. After the rescued turn finishes, the runner clears its durable
active-turn marker before reporting processing completion. Cancellation
during that storage await skips the completion hook, so the input remains
marked as processing even though its turn has finished.

Run rescued-input completion in the marker cleanup finally block. Keep
both awaits after the generation-scoped session and lease releases, and
retain the adapter-owned marker handoff and marker token comparison.

Extend the marker cancellation contract with the actual adapter start and
defer lifecycle followed by FIFO rescue. Both normal and rescued turns
release ownership and admit a successor; the successful rescued input
also reports exactly one successful completion after cleanup cancellation.

Historical attribution for original source 51c0ecf6197ce1489fb31bdcbddfb64c3d2ba125
(audit record 755). Public PR NousResearch#126283 already includes this cancellation
correction in 48902ee. This empty commit
records the missing original contribution credit. The source tree is
unchanged.
iainlane added a commit to iainlane/hermes-agent that referenced this pull request Oct 9, 2026
The complete feedback and edit source adds a second auxiliary-config
argument to the shared native gateway fixture. Native collection fails
before the unread controls can run. Keep the single additive argument
that the controls fixture already provides.

The incoming edit and lifecycle contracts also read optional mock-call
records and assign dynamic fixture fields as statically declared methods.
Check that the required calls occurred before reading their arguments,
use the real profile-hook registry, and bind fixture replacements through
monkeypatch. All original behaviour assertions remain in place.

The complete changed production, unit and native harness type comparison
has no new diagnostics after the defining-owner correspondence checks.
The native collection syntax error is removed; collection-only execution
is diagnostic evidence, not a test pass.

Source and contributor reconciliation for original 726
(1655eb9d3aa2d693af7f8685185c80850408d82d).

The original message above records the composed correction and its
historical checks. This contribution preserves its complete credit after
the source repairs were incorporated into their separate public owners.

The five edit-follow-up fixtures use direct awaited-call guards and the
real ProfileHookRegistries on NousResearch#126312. The lifecycle, deferred-input and
feedback fixtures use the declared typed and monkeypatch adaptations on
NousResearch#126283. These complete unit definitions are also present in its current
composed tree. The native auxiliary configuration fixture has one argument
and its consumer on NousResearch#126398. The NousResearch#126283 native harness has no auxiliary
fixture or consumer, so it requires no corresponding argument.

This is source and credit attribution across those owners. It adds no
product or fixture patch, and records no fresh test, type, SDK or native
execution. The complete original message and both literal contributor
dates are preserved.
@iainlane
iainlane force-pushed the feat/matrix-receipts-lifecycle branch from 52e9d3a to dc28111 Compare October 10, 2026 00:25
iainlane added a commit to iainlane/hermes-agent that referenced this pull request Oct 10, 2026
A started input can be deferred into the FIFO overflow and rescued by a
later turn. After the rescued turn finishes, the runner clears its durable
active-turn marker before reporting processing completion. Cancellation
during that storage await skips the completion hook, so the input remains
marked as processing even though its turn has finished.

Run rescued-input completion in the marker cleanup finally block. Keep
both awaits after the generation-scoped session and lease releases, and
retain the adapter-owned marker handoff and marker token comparison.

Extend the marker cancellation contract with the actual adapter start and
defer lifecycle followed by FIFO rescue. Both normal and rescued turns
release ownership and admit a successor; the successful rescued input
also reports exactly one successful completion after cleanup cancellation.

Historical attribution for original source 51c0ecf6197ce1489fb31bdcbddfb64c3d2ba125
(audit record 755). Public PR NousResearch#126283 already includes this cancellation
correction in 48902ee. This empty commit
records the missing original contribution credit. The source tree is
unchanged.
iainlane added a commit to iainlane/hermes-agent that referenced this pull request Oct 10, 2026
The complete feedback and edit source adds a second auxiliary-config
argument to the shared native gateway fixture. Native collection fails
before the unread controls can run. Keep the single additive argument
that the controls fixture already provides.

The incoming edit and lifecycle contracts also read optional mock-call
records and assign dynamic fixture fields as statically declared methods.
Check that the required calls occurred before reading their arguments,
use the real profile-hook registry, and bind fixture replacements through
monkeypatch. All original behaviour assertions remain in place.

The complete changed production, unit and native harness type comparison
has no new diagnostics after the defining-owner correspondence checks.
The native collection syntax error is removed; collection-only execution
is diagnostic evidence, not a test pass.

Source and contributor reconciliation for original 726
(1655eb9d3aa2d693af7f8685185c80850408d82d).

The original message above records the composed correction and its
historical checks. This contribution preserves its complete credit after
the source repairs were incorporated into their separate public owners.

The five edit-follow-up fixtures use direct awaited-call guards and the
real ProfileHookRegistries on NousResearch#126312. The lifecycle, deferred-input and
feedback fixtures use the declared typed and monkeypatch adaptations on
NousResearch#126283. These complete unit definitions are also present in its current
composed tree. The native auxiliary configuration fixture has one argument
and its consumer on NousResearch#126398. The NousResearch#126283 native harness has no auxiliary
fixture or consumer, so it requires no corresponding argument.

This is source and credit attribution across those owners. It adds no
product or fixture patch, and records no fresh test, type, SDK or native
execution. The complete original message and both literal contributor
dates are preserved.
iainlane added a commit to iainlane/hermes-agent that referenced this pull request Oct 11, 2026
The complete feedback and edit source adds a second auxiliary-config
argument to the shared native gateway fixture. Native collection fails
before the unread controls can run. Keep the single additive argument
that the controls fixture already provides.

The incoming edit and lifecycle contracts also read optional mock-call
records and assign dynamic fixture fields as statically declared methods.
Check that the required calls occurred before reading their arguments,
use the real profile-hook registry, and bind fixture replacements through
monkeypatch. All original behaviour assertions remain in place.

The complete changed production, unit and native harness type comparison
has no new diagnostics after the defining-owner correspondence checks.
The native collection syntax error is removed; collection-only execution
is diagnostic evidence, not a test pass.

Source and contributor reconciliation for original 726
(1655eb9d3aa2d693af7f8685185c80850408d82d).

The original message above records the composed correction and its
historical checks. This contribution preserves its complete credit after
the source repairs were incorporated into their separate public owners.

The five edit-follow-up fixtures use direct awaited-call guards and the
real ProfileHookRegistries on NousResearch#126312. The lifecycle, deferred-input and
feedback fixtures use the declared typed and monkeypatch adaptations on
NousResearch#126283. These complete unit definitions are also present in its current
composed tree. The native auxiliary configuration fixture has one argument
and its consumer on NousResearch#126398. The NousResearch#126283 native harness has no auxiliary
fixture or consumer, so it requires no corresponding argument.

This is source and credit attribution across those owners. It adds no
product or fixture patch, and records no fresh test, type, SDK or native
execution. The complete original message and both literal contributor
dates are preserved.
iainlane added a commit to iainlane/hermes-agent that referenced this pull request Oct 11, 2026
The complete feedback and edit source adds a second auxiliary-config
argument to the shared native gateway fixture. Native collection fails
before the unread controls can run. Keep the single additive argument
that the controls fixture already provides.

The incoming edit and lifecycle contracts also read optional mock-call
records and assign dynamic fixture fields as statically declared methods.
Check that the required calls occurred before reading their arguments,
use the real profile-hook registry, and bind fixture replacements through
monkeypatch. All original behaviour assertions remain in place.

The complete changed production, unit and native harness type comparison
has no new diagnostics after the defining-owner correspondence checks.
The native collection syntax error is removed; collection-only execution
is diagnostic evidence, not a test pass.

Source and contributor reconciliation for original 726
(1655eb9d3aa2d693af7f8685185c80850408d82d).

The original message above records the composed correction and its
historical checks. This contribution preserves its complete credit after
the source repairs were incorporated into their separate public owners.

The five edit-follow-up fixtures use direct awaited-call guards and the
real ProfileHookRegistries on NousResearch#126312. The lifecycle, deferred-input and
feedback fixtures use the declared typed and monkeypatch adaptations on
NousResearch#126283. These complete unit definitions are also present in its current
composed tree. The native auxiliary configuration fixture has one argument
and its consumer on NousResearch#126398. The NousResearch#126283 native harness has no auxiliary
fixture or consumer, so it requires no corresponding argument.

This is source and credit attribution across those owners. It adds no
product or fixture patch, and records no fresh test, type, SDK or native
execution. The complete original message and both literal contributor
dates are preserved.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have platform/matrix Matrix adapter (E2EE) sweeper:risk-automation Sweeper risk: may affect CI, automerge, label sync, or maintainer automation sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants