Repository navigation
Conversation
Matrix permalinks change with each triggering event. Rendering one in the pinned session prompt made the prompt stale or changed its bytes every turn, which breaks conversation prompt caching. Put the link in the current model-facing user message and remove it from the durable transcript, including queued turns. Respect effective PII redaction when adding the note. Percent-encode room IDs, event IDs and the via server so delimiters cannot change the matrix.to URL structure. Preserve the Matrix and Discord source-note handling when integrating the upstream prefix removal change. Use removeprefix after selecting the platform-specific note, so each transport removes only its own generated prefix.
The live Matrix exchange now checks the model request for a permalink to the event sent by the independent client. It also verifies that the link is absent from the system prompt, which must remain stable across turns. Record the exchange duration separately from container setup time.
Room version 12 IDs have no server component. A permalink built only from the room ID therefore omitted the server hint, which can prevent a client outside the room from locating it. Use the joined bot account's server when the room ID has no domain. The builder still leaves via absent when neither server is known.
A room ID can identify a server that no longer participates in the room. Its server may also include a port or IPv6 address, which the last-colon parser truncated to an invalid via hint. Use the joined bot account's server for inbound source links. When only a room ID is available, parse its full server after the first colon. The same parse also corrects the room's session scope.
In a thread, every turn's source link pointed at the thread root. The root was chosen as a stable anchor while the link lived in the pinned session prompt, which is per thread. The link now goes on each user turn, so the reason no longer applies, and the model never received the ID of the message that triggered the turn. A request to cite "this message" produced a link to the root, which may be another user's message. Build the per-turn link from the triggering event in all cases. A matrix.to link to an event inside a thread opens that thread in current clients, and the session prompt already gives the thread root.
A room-ID permalink needs via servers so that a reader outside the room can join through one of them. The link carried only the bot's server. When that server was unreachable from the reader's server, the link failed. Choose up to three servers as the Matrix specification recommends for room-ID permalinks. The first is the server of the highest-power joined member at power level 50 or above; room version 12 creators count as the highest power. The rest are the servers with the most joined members, and IP literals are never chosen. The adapter reads members, power levels and the create event from the mautrix state store, which sync keeps current, so building a link makes no request. When the store has no joined members for the room, use the bot's server and then the room ID's server. Server ACLs are not applied, because the state store does not keep m.room.server_acl. Move the builder and the selection into a sibling module, and emit one via parameter per server.
The session prompt stopped rendering the Matrix source link when the link moved to the current user turn. Two prompt tests still asserted that the prompt omitted the link when it was unset or redacted. No code path produces that output any more, so neither test could fail because of this feature. The prompt-stability test covers the remaining contract, and the per-turn tests cover the note and its PII suppression. Restore the comment on the per-turn notes. strip_inbound_source_note removes a note only when it is the first thing in the message, so a later prefix added outside the notes would leave the note in the persisted transcript.
Choosing source-link via servers made every inbound message await get_power_levels and get_create on the client's state store. The project-context isolation and voice tests gave the adapter a MagicMock store with only get_members awaitable, so all six inbound tests in those files failed with "'MagicMock' object can't be awaited". Production always uses mautrix's MemoryStateStore. Give both fakes a real MemoryStateStore in which the bot and the sender have joined the test rooms. The real store implements every state query, so these tests keep working when the adapter starts reading other room state. The stores are filled with MemoryStateStore.deserialize because the helpers are synchronous and some are called inside a running event loop.
The effective-state parent added a description to the matrix_read limit argument: "Maximum number of events to read". In this layer the same argument also bounds the members kind, which returns members rather than events, and the pins kind. Say that the limit applies to events or members so the model can tell that it bounds member lists too.
The merged matrix_read description said that events are listed oldest first and that `errors` lists events that could not be decrypted. Both sentences came from the history reads. A pins read returns events in the order of the room's m.room.pinned_events list, and its errors also cover pins that could not be fetched, are not visible messages, came back as another event, or whose edit was redacted. A model that read the description could tell a user that a deleted pin was an encrypted message. Limit the ordering sentence to room and thread reads, say that pins follow the room's pinned order, and say that `errors` lists events that could not be read or decrypted, with the reason. History reads already returned non-decryption errors such as "event content changed", so the wider wording is also accurate for them.
This was referenced Oct 1, 2026
The fake model server for the live gateway listened on 0.0.0.0, so any machine on the developer's network could send it requests while a test ran. A stray request would consume the scripted reply or appear in main_requests(), and the macOS firewall can ask whether to accept incoming connections. The gateway container reaches the fake as host.docker.internal. With a daemon on this host, as on Linux, the test network's bridge gateway is an address of this host, so the fake now listens on that address and host.docker.internal resolves to it. A daemon in a VM, such as Docker Desktop or OrbStack, does not expose that address on the host, and its host-gateway forwards to the host's loopback interface, so the fake listens on 127.0.0.1 there. The fixture chooses by trying to bind the gateway address. In OrbStack's Linux VM, a server bound to a network's gateway address answered a container on that network through the host.docker.internal mapping. On macOS the bind fails with EADDRNOTAVAIL, and the plain exchange passes over 127.0.0.1.
The gateway fixture yielded as soon as the gateway logged "Matrix: joined <room>". The Matrix adapter joins the invited room shortly after it connects, while the gateway is still in startup restore. During startup restore the gateway queues inbound messages and replays them when restore finishes, so a test's first message could reach the gateway through that replay. A scratch live test that sent its message as soon as the fixture yielded was queued in two of three runs. The replay can reach the adapter while the adapter's original dispatch of the same event still holds the session guard: for Matrix, the guard stays held during the typing-stop request after the handler has queued the event. The adapter then handles the replay as a second message while the session is busy, and the bot's first reply is the "Interrupting current task" notice. One native run of the oversized-media test failed this way. The gateway logs "Press Ctrl+C to stop" after startup restore has finished. The fixture now waits for that line as well as for the join, so a test's first message is dispatched directly. With this check the same scratch test was dispatched directly in three of three runs. When a test that uses the fixture fails, a report hook adds the last 200 lines of the gateway log to the failure report. The fixture deletes the gateway home at teardown, so the log of a failed run was lost before. LiveGateway now includes the home so that the hook can find the log.
_host_user() compared hermes_platform's os_family() with "windows", but os_family() returns sys.platform, which is "win32" on Windows. The Windows branch therefore never ran, and on a Windows host with Docker the gateway fixture called os.getuid(), which Windows does not have. Compare with "win32", as the conftest already does with "darwin". The matrix-live workflow's path filter omitted code that the gateway image copies and loads at run time: providers/, which agent/ imports; plugins/ outside the Matrix adapter (the plugin loader, model providers, memory and context engines); locales/, which agent/i18n.py reads; tui_gateway/, which gateway/ imports; and skills/, which the gateway syncs into its home at start-up. A change to any of them could break the live tests without running the workflow. Add all five. Rename HERMES_TEST_MATRIX_IMAGE to HERMES_TEST_MATRIX_GATEWAY_IMAGE. The suite uses Synapse, Ryuk and gateway images, and "Matrix image" reads most naturally as the homeserver.
When an explicit reply (m.in_reply_to) had a body starting with "> ",
the adapter treated every leading "> " line as the legacy reply
fallback. It removed those lines from the message text and reported
them as the text of the replied-to message. Matrix 1.13 (MSC2781)
removed reply fallbacks, and current clients such as Element X send a
reply exactly as the user typed it. A user who began a reply with a
quotation therefore lost the quotation, and the agent saw it as the
message being replied to. Element also sets m.in_reply_to, with
is_falling_back, on ordinary thread messages, so a quote at the start
of a thread message was lost in the same way.
Treat the leading quote as a fallback only when the event has one: the
HTML body starts with an <mx-reply> element, or the plain quote block
starts with the quoted sender's pill ("> <@user:server>", or
"> * <@user:server>" for an emote) and ends with a blank line. Older
clients generate both forms. The reply-context parse and the mention
strip under require_mention use the same check, so a quote without a
fallback stays in the text with any bot mention removed, as it does in
a message that is not a reply.
The plain-body check is deliberately narrower than the spec. The
spec's stripping note still says to remove every leading "> " line of
a reply without condition, and that rule cannot tell a fallback from a
quote now that clients no longer send fallbacks. Ruma's
remove_plain_reply_fallback, which matrix-rust-sdk and Element X use,
narrows it in the same way: it strips only a body that starts with
"> <" or "> * <". The HTML check follows the spec's rule for
formatted_body, which applies only when it starts with <mx-reply>.
A hand-typed quote that starts with a pill and ends with a blank line
has the same shape as a plain fallback and is still stripped.
A reply to an m.emote message has a fallback that starts with "> * <@user:server>". The regex that parses the fallback's pill expected "<" straight after "> ", so it did not match this form. The reply lost its author (reply_to_author_id and reply_to_author_name were None), and the quoted text kept "* <@user:server>" at its start. Accept the optional "* " before the pill. The fallback detector already recognised the emote form with a second regex that described the same line, so both now use the one pattern.
Rapid Telegram messages can be combined before dispatch, but one event has only one set of reply fields. When two messages quote different messages, batching their text keeps only the first quote. The busy-text debounce also overwrote the quote ID with a later inbound message ID. Split ingress batches when their reply contexts differ. Dispatch the earlier event through Telegram's tracked inbound redispatch queue so a concurrent disconnect preserves the event for reconnect. A metadata-free continuation after a near-limit chunk inherits the first chunk's quote, which Telegram clients can omit from later parts of a split message. Compare reply context during busy-text debounce as well, and keep the quote ID distinct from the inbound message that an answer should target. (cherry picked from commit 348bef0)
Messages that arrive while a turn is running can be merged into one pending event. merge_pending_message_event joins a follow-up into the pending slot, and with busy_text_mode queue, _queue_text_debounce first joins a burst of text into one buffered event. A merged event has one set of reply_to_* fields, and neither path copied them from the incoming message. If a user sent "one" and then replied to an earlier message with "two", the next turn had no reply pointer, so the agent could not tell which message "two" referred to. The debounce also dropped a quoted image from the incoming media_urls. The previous commit, from NousResearch#65526, stops the debounce from merging messages whose reply contexts differ in any way. That keeps two reply targets apart, but it also splits a plain message from a reply that follows it, and merging those two would lose nothing. The debounce has two places where a queued turn can wait: the pending slot and its own buffer. Every split uses one of them, and a third message that fits neither was dropped. Copy the reply context instead where that loses nothing. MessageEvent.absorb_reply_context gives an event without a reply context the incoming one. Both branches of merge_pending_message_event and the debounce buffer call it, and the buffer now also takes the incoming media, as _enqueue_text_event does. MessageEvent.reply_context_conflicts is true only when both events reply to a message and their reply contexts differ. The debounce keeps those messages as separate turns, as it already does for messages from different senders. If the pending slot and the buffer both reply to other messages, a third message from the same sender joins the buffer. The buffer keeps its own reply context, so the third message's text reaches the agent but its reply context does not. merge_pending_message_event has to store every event in the single pending slot. When two reply contexts conflict there, the event keeps the first one, which is the rule that NousResearch#62088 applies to Matrix merges. NousResearch#66027 made the newest message's reply context replace the older one. That drops the older reply target, and it clears a quote when the newest message has none, so this change does not adopt it. Its coverage of both merge_pending_message_event branches and of the debounce is kept. Co-authored-by: Hilo-Hilo <57138830+Hilo-Hilo@users.noreply.github.com>
Busy-text debounce copied incoming attachment paths and MIME types but omitted media_text_inlined. A false flag means the attachment's text is not in the message. Losing that flag changes it to the legacy inlined state when the gateway builds document context. Pad legacy flag lists to the existing attachment count before appending new media. Append incoming flags and pad any legacy incoming entries as well, so each merged attachment keeps its own text-inlining contract. Consolidate the reply and media checks into two parameterized behavior contracts that compare complete events. Telegram dispatch checks await tracked tasks with zero batching delays instead of assuming that a short wall-clock sleep is enough under runner load. Three attachment cases reproduced missing flags before the repair and pass with the aligned lists. The contracts also retain the documented single-slot fallback for adapters without separate-turn storage.
Matrix thread continuations include an m.in_reply_to pointer for clients that do not render threads. The adapter treated that fallback pointer as a user reply, so it attributed the continuation to the thread root. Parse relations into explicit reply, thread fallback and edit fields. Use the parsed reply target for inbound context and the thread root for session routing. Malformed relation content now yields an empty relation instead of failing message handling.
A Matrix reply may omit the legacy inline quote. The gateway then knew only the target event ID and could not tell the agent what message the user referenced or who wrote it. Resolve reply targets from a bounded cache or the room event API. Apply edits to cached text and keep redactions as empty cache entries so a later reply cannot revive deleted content. Decrypt fetched events when the room uses E2EE, and label media bodies so filenames are not treated as spoken text. Attach the target author's name and authorisation verdict to the inbound event. The gateway marks fetched content from an unauthorised author as unverified background and renders Matrix quotes on one line. The adapter never fetches a remote target for a sender it rejects. Co-authored-by: Wessel <2385844+weebl2000@users.noreply.github.com> Co-authored-by: uuedxz <249543386+uuedxz@users.noreply.github.com> Co-authored-by: Simon Sharp <234941529+simonamsharp@users.noreply.github.com>
An explicit Matrix reply could identify its parent but gave the agent only a filename when that parent was an image. Threaded messages in a fresh gateway session lacked the earlier messages that explain them. Resolve reply targets through a bounded room-event cache, decrypting when needed. Download quoted images through the existing Matrix media path and attach their cached files to the triggering message. Read the thread root and recent relations for a new threaded session, then attribute and bound the prior messages before adding them to the user turn. Add external thread text after local @ reference expansion so it cannot trigger a file read. Use mautrix.api.Method.GET for the relations request because mautrix 0.21.1 treats a string method as a non-GET request and adds a body. Cache successful outbound messages by event ID for immediate replies, and preserve reply context when a later text chunk joins a batch. Mark inline fallback quotes as unverified because their author pills come from the sender and can claim the bot's identity. Expose a config.yaml limit for thread backfill and preserve room state context when both appear. Co-authored-by: Wessel <2385844+weebl2000@users.noreply.github.com>
The reaction follow-up layer now includes the complete action and effective-event prerequisites. Text intake and event construction pass reply_parent explicitly, while reaction tools require joined room state and an authorised requester. The old callbacks rejected the keyword, and the queued identity test failed before exercising context restoration. Accept the reply context in those callbacks and configure the queued fixture with joined membership and both intended requesters. Remove the duplicated identity argument introduced when replaying the metadata test. The admission, cursor, FIFO and A/B/A identity assertions remain intact. The adjacent background-task control also created an unawaited coroutine because its AsyncMock treated toolsets_for_source as asynchronous. Give that synchronous method an explicit MagicMock, like the existing media extractors. The warning reproduces on the original published branch under strict coroutine checks; the corrected control and the composed fixture checks pass all 103 cases.
Move reaction watch registration, delivery tracking and admission into a topical mixin before changing their interaction with effective reply contexts. The adapter exceeds the project file-size threshold; the mixin keeps the existing behaviour and gateway bindings together. The plugin loader imports each adapter under a profile-owned namespace. A class imported through the source path has a different identity, so check the shared Matrix mixin when registering a delivery. A real plugin loader invariant verifies watch registration across A/B/A profile homes.
A reaction to the last chunk of a split final reply loses the beginning of the logical answer when gateway preparation refreshes the concrete reply event. After an encrypted restart, the follow-up therefore quotes the reacted chunk instead of the saved final excerpt. Record the delivered prefix in delivery order, with digests of the final visible bodies. Persist those bounded dependencies with the watch and propagate their retained effective states through the existing turn snapshot. Reuse the exact saved excerpt while the visible prefix remains unchanged. Current edits replace the quotation, and redactions or content errors prevent stale text from entering the model input. The reacted target is checked separately even when it follows the quoted prefix. The regressions reproduce the restart loss and stale prefix quotation on the previous head. The strict seven-file gate passes 172 tests with the actual Matrix SDK, four workers and retries disabled.
A cancelled reaction handler deletes its single-use watch before the gateway admits the follow-up. Cancellation during the busy-session lookup leaves no queued input and no acknowledged sync event, so replaying the same reaction cannot resume the conversation. Reserve the watch for that reaction instead of deleting it immediately. Release the reservation when admission fails before the gateway consumes the event. Delete it after admission, including cancellation while a queued event's durable acknowledgement finishes. Persist the reaction ID so a restart can retry the same event without allowing a different reaction to consume the watch. Claim tokens make completion conditional on the reservation's current owner. The regression cancels the real busy admission path after its session lookup, then retries through the native sync dispatcher. It also checks reservation replacement after reopening the store and cancellation after FIFO admission while the durable write is pending. The strict eight-file gate passes 175 tests with the actual Matrix SDK and no retries. The reaction-admission tests created SessionStores but finished with their shared SQLite handles still open. All three cancellation cases passed, followed by six unclosed database warnings at interpreter exit. Close each test-owned store after cancellation and task gathering in the existing finally block. The cancellation and exactly-once assertions stay unchanged. The three complete child cases now exit without unclosed database warnings.
The reaction follow-up contract registered the gateway authorisation callback, then expected the adapter's direct user check to deny every caller. Matrix now delegates that check to the live callback so pairing revocations take effect immediately. Global, platform and pairing grants therefore fail the old assertion before the reaction path runs. Expect the direct check to match the configured grant. Keep the actual follow-up admission assertion and the denied case, which retains its reaction watch without dispatching a turn. Historical attribution for original source a4d3478 (audit record 314; historical approval position 315). Public PR NousResearch#126281 already includes this assertion correction at its captured head 1369cea. This empty commit records the missing original contribution credit. The source tree is unchanged.
Reaction sends retain every delivered event ID, and final edits update effective content after successful delivery. Those outcomes can still report success when the adapter exceeds Matrix's byte budget. Check the complete receipt result for multibyte chunks. Check that a final edit fits the content budget without changing its complete effective content, finalization flag or successful cache update. On the unchanged public source, these contracts reproduce a 16-byte chunk under a 12-byte limit and a 59,494-byte formatted edit under a 45,000-byte content limit. Both pass in the combined upstream and reaction implementation.
Restoring every accepted follow-up separately preserves FIFO input, but it also separates a parked photo from the compatible goal continuation that should consume it. The photo then completes with a separate turn while the continuation remains pending. At the recursion limit, merge compatible photo and text input through the existing recorded merger and attach its processing lifecycle to the consuming event. Conflicting replies, security contexts and execution owners still use accepted FIFO restoration. That path retains every previously admitted input without repeating capacity admission. All 93 original lifecycle routes now pass, including merged photos and orphan cancellation. The queued profile and reply contracts use the actual TurnContext model, so the fixture includes the receipt state without replacing its identity or profile assertions. Source-commit: a51649262e4290a32bdd2f64e29d704b7425747d Integration-note: A fresh processing task previously promoted its reservation to accepted queued input before the runner decided whether to merge it. Preserve the existing accepted bit when a queued reservation is reused, but create fresh processing reservations with accepted=False. This keeps fresh photos with compatible pending input and retains accepted FIFO restoration for previously queued records. The actual processing contract reproduces the fresh-input failure and retains the accepted-input control. The qualified 111-case gate excludes only the two independently diagnosed shutdown observer cases; the complete gate follows their owner correction.
The shared batching implementation is in the platform facade, which is already about 4,700 lines long. A new reply-boundary hook would add more behaviour to that facade. Move the existing batching methods to BaseTextBatchingMixin before changing their behaviour. Keep every method body unchanged and type the mixin receiver. Update the gateway instructions to specify the new implementation path. The fixed upstream annotation cleanup overlaps the extracted text-batch methods. Keep the complete methods in their topical module and apply the same unquoted event annotations there. Their bodies are unchanged.
Gate finding BR8 r1 2c [Medium]: the new reply-context conflict rule made the debounce's pending-slot merge reachable for one sender. one->X, two->Y, three->X left pending='one\nthree' and buffer='two', so the agent saw "three" before "two". Same-sender text now stays in the debounce buffer; only a different sender may merge into the pending slot.
The shared text batcher appends text and media without absorbing reply context. A plain message followed by a quoted message loses the quote, and two different reply targets become one turn with the first target. Use the existing boundary hook to dispatch incompatible reply contexts as separate batches. Absorb the complete reply context when one event has no quote, and pad attachment flags before extending media URLs. Keep the shared dispatch cancellation policy for completed batches. WeCom now promotes the current batch after enqueueing. Looking up the previous batch would change the attachment event that a reply boundary has already dispatched. Keep the upstream unquoted event annotations and format the extracted module consistently. Method bodies and assertions are unchanged.
The Telegram adapter contains the teardown of text, photo and album buffers in a large transport facade. Reply-boundary deliveries need the same teardown policy. Move the existing cancellation method into TelegramDelayedDeliveryMixin before extending it. Keep the method body and adapter method resolution unchanged, and type the mixin receiver.
A reply boundary starts a flush outside the ordinary delivery maps. Delivery teardown therefore misses a boundary that has already popped its event and is awaiting dispatch. Cancelling a boundary before its coroutine starts can also lose the event in its private temporary map. Keep boundary events and tasks in a dedicated buffer. Include that buffer in delivery cancellation, salvage and clearing. Cancellation requeues an in-flight event through the existing flush body; buffered boundaries are salvaged before the later ordinary text batch. The event-synchronised regression reproduces the missing earlier batch and unfinished task before the fix. It checks both complete events and that the boundary task has exited when teardown returns.
When busy_text_mode=queue and a debounce state for sender B is stuck in the store (timer fired but the pending slot belongs to sender A, a different sender), any message from a third sender C hit an early `return` without being saved to the debounce store, pending_messages, or anywhere else — permanently and silently dropped. Remove the `_can_merge(existing_pending, event)` guard from the inner conflict branch in `_queue_text_debounce` and unconditionally call `merge_pending_message_event` instead. C's text is appended to the pending slot (or placed there directly if empty), matching the pre-debounce behaviour that always queued follow-ups regardless of sender. Sender attribution is best-effort when 3+ senders collide simultaneously; losing the message entirely is worse. Adds a regression test that pins the 3-sender scenario: A in pending, B stuck in the debounce store with task=None, C from a third sender — C must survive in _pending_messages and B must remain in the store for _drain_pending_after_session_command to recover. (cherry picked from commit 2a7e3c5) Relocation onto pinned upstream 343500: The current copy inserts the pending-event merge call before the third-sender branch returns. The preceding extraction moved _queue_text_debounce to BaseTextDebounceMixin. Apply the same insertion in gateway/platforms/base_text_debounce.py and retain its typed receiver. BasePlatformAdapter inherits this method. Keep the existing Base adapter and the rest of the extracted module unchanged.
iainlane
force-pushed
the
fix/gateway-queue-debounce-handoff
branch
from
October 5, 2026 21:07
1bf60b5 to
398891c
Compare
Four routing tests pass a recording transport to RelayAdapter, whose transport parameter requires the RelayTransport protocol. The fixture only implements its inbound setter and outbound send, so the four constructor calls fail static type checking even though their exercised routing paths work. Implement the complete structural protocol in the recording fixture. Unused operations raise an assertion error so an unexpected call fails the test. Keep the recorded action, platform and send result, and narrow the captured dictionaries before the existing routing assertions. The correction changes the inherited test fixture. It preserves the existing contributor records and all original routing assertions.
The encrypted-file fixture returned a constant serialized payload, so assigning its uploaded URL did not appear in later serialization. The Matrix SDK serializes the URL stored on the encrypted-file object. The adapter currently overwrites the outgoing URL, which concealed this fixture difference in the existing upload test. Read the fixture URL when serialization is called. Add an invariant that compares the complete payload before and after URL assignment, with only the URL overwritten. Keep the existing upload and routing assertions.
Matrix receives ordinary m.reaction events, but reads and catch-up only showed message events. The bundled m.relations summary cannot provide annotation reactions. Read bounded annotation children through the live Matrix client. Decrypt encrypted reactions to verify their type, but use the outer cleartext m.relates_to for the target and key even if the decrypted payload specifies another relation. Discard redacted events and deduplicate by sender and key. Present the actor and target in read and catch-up context without starting a turn. Mark clipped keys and report incomplete reads when keys, API failures or bounded pages prevent a complete snapshot. A five-second batch deadline keeps a slow homeserver from delaying a turn for every earlier message. Completed lookups remain visible when others time out. A separate Matrix client confirms that a reaction appears in the next mentioned turn. Focused tests cover duplicate and redacted reactions, encrypted events, long keys, lookup errors, and the deadline. Bind the real room-policy predicate in the read fixtures. Read admission rechecks room policy after awaiting room identity, so the fixture must provide the same adapter protocol as the production path.
The concurrent-update fixture creates three handler coroutines before passing each to the processor. Cancelling the middle update while it waits for its predecessor leaves that handler coroutine unawaited, so the fixture fails under strict RuntimeWarning checks. Keep references to the fixture-created coroutines and close the cancelled handler after every processor task has finished. The existing assertions still require the first and third handlers to complete in order without executing the cancelled handler.
Source-permalink and reaction-context branches each test their own prompt contract. Their combined adapter adds the trigger permalink to catch-up messages, so the reaction fixtures need the SDK's asynchronous state-store methods and complete expected source notes. Merge both feature histories without changing their existing commits. Preserve reaction author trust, room and thread context, transcript roles, cache privacy and the source-permalink encoding and routing rules. Use the real MemoryStateStore in the affected fixtures and retrieve SessionSource from the final context tuple item. Check each complete catch-up prompt against its explicit room, trigger event and via server, including the first event of a batched turn. The four composed gateway files cover 343 cases. The affected two files pass all 264 cases after the fixture adaptation; the unchanged session and project-isolation files passed their 79 cases. Scoped Ruff reports no lint diagnostics. Existing formatter differences remain qualified separately. Live Docker room and thread backfill validation is still outstanding. This dependent layer preserves both parent histories. Its two live backfill fixture adaptations do not claim complete original 858 delivery. Historical composition qualification The preceding message records the original two-parent composition. This successor reapplies its fixture integration after the individually preserved prerequisite, SDK and reaction contributions on captured main 648a309. Its history has one parent. The original composition message remains complete for provenance; this commit does not create a merge or establish live Matrix acceptance.
The original feature tests predate the canonical approval lifecycle, permalink source fields, room policy owner argument and SDK send error classification. Their doubles omitted asynchronous state-store reads, exact approval request IDs and terminal card edits. Several complete expected events also omitted the newly composed source and mention metadata, so the full feature gate failed before checking its intended behaviour. Update those fixture bindings and complete expected values for the current defining owners. Keep every original parameter row, delivery attempt, payload, cancellation and authorization assertion. Thread reconnect expectations retain confirmed delivery and cache identity. Catch-up expectations include the permalink note before the new message. The complete nine-file feature boundary passes all 808 cases with the actual installed Matrix SDK, four workers and no file retries. The full Matrix file finishes in 30.7 seconds, including the event-loop lookup contract that timed out on the earlier incomplete source composition. Original858 public source qualification The preceding 808-case result records the original composition. It does not describe a new run against this public tree. The published backfill composition has 343 scoped cases, using 264 final affected cases and 79 unchanged earlier successes. Its inherited formatter and type findings remain qualified, and live Docker validation remains pending. The other feature contracts are represented by their existing public owners. Their fixture expectations follow the actual approval, notice, location, send-classification, read-policy and delivered-event APIs. Expectations from the original later composition do not establish bugs in standalone owners that use an earlier API. No combined six-module endpoint or repeated native gate is required for this source credit. This source-neutral contribution preserves the complete original message and both original contributor dates. It records the distributed source qualification without changing the published source tree. Current linear successor qualification The 343-case result above describes the earlier a76019d composition. This contribution follows the verified single-parent successor a83881c and keeps its source tree unchanged. The accepted seven-file gateway gate for that successor passes 362 cases. Live Matrix validation, private integration and whole-campaign completion remain separate.
The Matrix SDK retries rate-limit responses and connection timeouts indefinitely by default. A failed request during account or room setup can therefore stop the whole native module until the outer runner kills it, without reporting the setup error. Disable those retries and bound ordinary fixture requests to fifteen seconds. Apply the same configuration to registration, restored accounts and the encrypted observer. The fixtures now expose the SDK error so the failing operation can be diagnosed directly. A real loopback HTTP server reproduces the repeated rate-limit requests through registration and restored accounts. Both paths now make one request and return the expected error.
Matrix turns could read message history, but the agent could not inspect the current room state, joined members, power levels or pinned messages. Those details are available from the receiving bot and can affect what the agent should say or attempt. Extend the Matrix-only read tool with bounded inspection kinds. Fetch state and members through the owning adapter, apply its existing room and requester policy, and resolve pinned messages with the same decryption path as event reads. Report missing pin content per event so the other pins remain visible. Include the event type and creator override when presenting permission thresholds. A separate Matrix client verifies that the model receives the room details and pinned message from a disposable Synapse instance. Use the effective-state parent's client-events decoder and gateway scheduling path throughout inspection and its tests. Recheck admission after identity resolution, derive permission defaults from raw state, and preserve cancellation while refreshing pinned content. The feature and these interface migrations form one change so the initial commit uses the current prerequisite APIs.
The dependency-rebinding fixtures installed a fake importer but left the package-availability check unchanged. With a complete SDK installed, ensure_matrix_deps skips the importer, so these fixtures failed without exercising their intended success and refusal paths. Report a missing Matrix package in the fixture alongside the existing installation seam. The real rebind and failed-import assertions then exercise the intended fresh-install boundary.
The Matrix adapter ignores emotes and does not register native sticker events. Emotes describe the sender's action, while stickers use a separate event type with a description and an image. The existing text handler would interpret a slash-like action body as a gateway command, so native actions need attributed text before command classification. Accept both through the existing room, sender and mention gates. Prefix emotes with their actor, preserve sticker descriptions, and download sticker pixels through the bounded media path. Use the same labels in effective reads, replies and thread history. Queued stickers can be edited or redacted while media preparation is pending. Add an optional media-event snapshot capability and retain the source paths of image descriptions, so final model preparation removes both withdrawn pixels and their derived text. Existing platform snapshots remain compatible and previous conversation context remains unchanged. The two parameterised invariants reproduce six failures on the committed parents and now cover native SDK events, gates, unavailable media and withdrawal during asynchronous preparation. Matrix and adjacent gateway checks pass against the pinned SDK source.
iainlane
force-pushed
the
fix/gateway-queue-debounce-handoff
branch
2 times, most recently
from
October 8, 2026 08:20
a12940c to
fd5de45
Compare
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.
Pending-event merges and busy-text debounce share reply and attachment semantics inside the large base adapter. Telegram's ingress overrides also modify the same batching boundary. Extract those existing topics before integrating the reviewed reply-context fixes. Repoint in-tree imports and patch targets to the defining merge module. The adapter classes inherit their existing methods, so timers, tracked redispatch and delayed-delivery ownership keep their current behaviour. The fixed upstream annotation cleanup overlaps the extracted debounce state. Keep the state in its existing topical module and apply the same unquoted task annotation there. The complete state and timer behaviour remain unchanged.
iainlane
force-pushed
the
fix/gateway-queue-debounce-handoff
branch
from
October 10, 2026 03:51
fd5de45 to
c6f7ce7
Compare
13 of 14 tasks
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stack
All PRs below target
main. Nesting records merge order for the composed prerequisites and their dependents. A dependent must follow every prerequisite shown. Each linked description lists the other dependencies.Change
Queue mode preserves each sender’s input, reply target and control context in arrival order. One merge predicate checks sender identity, the complete reply context, gateway control permissions and plugin metadata. Debounced bursts use FIFO admission; independent buffered events remain separate when an adapter has no runner. Deferred dispatch returns to the front before retrying.
Only consecutive compatible photos and captions coalesce. Pending voice and document input remain independent, and an orphaned FIFO tail is promoted before new input. The third-sender repair preserves AhmetArif0’s authorship. The shared batching, delayed Telegram delivery, contributor IDs and padded-media handling from the prerequisite remain intact.
Photo, startup and grace assertions compare complete events, including contributor identities. The cancelled concurrent-processor fixture closes its retained handler coroutine after processor tasks complete.
History and review
This revision has 13 own contributions above the actual composed prerequisite. Every complete author and committer header, timestamp, timezone, message and trailer is preserved. History has one parent per commit. Defects found during integration are folded into the commits that introduced the affected code; later contributions retain their own credit. The current revision contains 285 contributions: the actual published prerequisite retains 272 signed records, followed by these 13 original contributions. The captured upstream base is
3637c512fc3211bdccb61b7140d09ac1018508ba.Review the own contribution. The comparison against
mainalso includes the composed prerequisites.Earlier validation
The composed queue has 279 distinct passing cases across 14 complete files: 250 from the unchanged files and 29 from the corrected photo file. The batching subset contains 106 passing cases. Four reaction-follow-up and deferred-admission files pass all 93 cases after the source-builder fixture repair. Three reaction and read-policy files pass all 45 cases. These are source-aware outcomes from separate canonical runs, with one worker, zero retries and strict runtime and pytest warnings. Earlier failed wrappers remain recorded; this is not a single fresh 417-case run.
Scoped Ruff checking of all 85 changed Python paths passes. The before/after type comparison records 1,562 candidate diagnostics: 1,560 match complete statements from the actual published suppliers or current media base, and two have explicit proofs for unchanged inherited contracts. It reports zero unqualified new findings. The overall type and formatter checks remain non-green because of inherited findings. No new native Matrix execution or hosted CI pass is claimed.
Current compatibility and validation
This successor uses the independently published #129160 prerequisite at
627fb2227c402f619e699eb2afd9214dd6e9180d. Seven complete cumulative snapshots compose automatically. The six later snapshots require one type-checking import block to retain both the current prerequisite comment and the originalBasePlatformAdapterimport. Only the original eighth contribution has an appendix explaining that join. Every original author and committer header, timestamp, timezone, complete message and contribution order remains intact. None of these 13 records is empty.The ten complete affected busy-followup, FIFO, batching, photo and Telegram admission files pass all 280 assertions on both the original published head and the current candidate. Both runs use the canonical runner with all four resource and thread warnings treated as errors, zero retries and no selected subsets. Complete shutdown output has zero resource warnings. All 9,229 original and 9,282 candidate tracked Python, configuration, lock and runner inputs match their Git objects before and after execution. A sandbox control stopped before any test child because the runner could not create its isolated scratch directory; that control is retained separately and contributes no passing cases.
Full typing covers 16 complete affected and caller files. The original, actual prerequisite and candidate have 281, 285 and 286 diagnostics, respectively. Every complete candidate lexical and statement operand, including its occurrence count and relative span, matches the union of the original and prerequisite operands. There are zero introduced findings. Scoped Ruff checking passes; the same 14 formatter paths remain inherited. The overall typing and formatting checks remain non-green. The exact candidate merges cleanly with both fixed upstream references, including captured
3637c512fc3211bdccb61b7140d09ac1018508ba, and its own diff passes the whitespace check. No fresh native service execution or hosted workflow pass is claimed.