Repository navigation
Conversation
The adapter could post its own processing reactions, but a standard Matrix agent could not request one. The existing send_message reaction path was outside the Matrix session's model toolset. Register matrix_reaction in the Matrix bundle and route react and unreact through the session's receiving adapter on its gateway loop. Use the inbound event ID from the turn as the default target so another turn in the same room cannot change which message receives a reaction. Track every annotation that the agent adds to a target. Unreact removes those annotations without touching processing tapbacks, and a failed redaction keeps its event ID available for a later retry. A separate Synapse client verifies that the model discovers the tool and that react and unreact create native annotation and redaction events for the intended message.
Two unreact calls could read the same annotation list before either redaction completed. Both sent a redaction, then the second call raised ValueError when the first call removed the shared event ID. Claim the tracked IDs before awaiting the server. Restore IDs that fail to redact, including IDs still pending when the call is cancelled. A concurrent call no longer redacts the same annotation. The generic send_message reaction action also ran adapter coroutines on a worker event loop. Matrix's live client belongs to the gateway loop, so dispatch that action through the existing gateway-loop path used for adapter sends.
`add_reaction` and `remove_reaction` fell back to the most recent event that had started processing anywhere in the room when the caller gave no `message_id`. Matrix threads run as separate concurrent sessions, so that event could belong to another thread or another user, and the agent would react to or unreact from the wrong message. The `matrix_reaction` tool always supplies an event ID, and no in-tree caller of `send_message(action="react")` relies on the fallback. Remove the per-room record of the latest inbound event and return an error when `message_id` is missing. `on_processing_start` goes back to its original shape. The error strings with em dashes from the salvaged commit go with the fallback.
`remove_reaction` decided success by reading `_agent_reactions` after its redactions had finished. That map is shared, so when an `add_reaction` on the same event recorded a new annotation while the redaction was in flight, the unreact returned `success: False` even though every annotation that it had claimed was redacted. When a redaction did fail, the result had no `error`, so the model received no reason. Decide the result from the list of annotations that this call failed to redact, and return an error message when that list is not empty.
`_agent_reactions` gained an entry for every event that the agent reacted to and lost it only when the agent removed the reaction. A long-running gateway therefore kept the ID of every reaction that it had ever sent. Store the records with `bounded_put`, as the adapters' other per-chat maps do, capped at 1000 events. A new reaction or a failed redaction moves the event to the newest position, so the eviction drops the events that the agent has not touched for longest. Unreact already works only for reactions recorded in this process, and an evicted event falls under the same limit. `bounded_put` was annotated for string keys only. Its implementation accepts any hashable key, so type it generically for the tuple keys used here.
`matrix_read` passes the session requester to the adapter, which checks that the room is joined and admitted by `MATRIX_ALLOWED_ROOMS` and that the requester is still authorised before it touches the client. `matrix_reaction` did neither. Its only policy check was the admission check that ran when the inbound message started the turn. If an operator revoked a user's pairing during that turn, `matrix_read` refused the user's later calls, but `matrix_reaction` still sent and redacted annotations as the bot. A session without a user ID was also refused by one tool and accepted by the other. Move the room and requester checks from `read_matrix_context` into `check_session_access`, and expose it on the adapter. The reaction tool now requires `HERMES_SESSION_USER_ID` and runs that check on the gateway loop immediately before it sends or redacts. Both Matrix session tools therefore apply the same policy when they act.
After 30 seconds `matrix_reaction` cancelled the future for the reaction, which cancelled `add_reaction` on the gateway loop. When the homeserver had already accepted the `m.reaction` event, the cancellation did not withdraw it, but `add_reaction` never reached the line that records the reaction's event ID. The reaction stayed visible, and a later unreact reported that no reaction was recorded. Wait on the dispatched future through `asyncio.shield`, as the generic send path in `send_message_tool.py` does, and stop cancelling it. The timeout now ends only the tool's wait. The reaction keeps running on the gateway loop and records its event when the send completes. The timeout message tells the model that the reaction may still complete. The test sets the timeout to zero and blocks the send on an event, so it exercises the timeout without waiting on a clock.
The reaction tool test used `AsyncMock` adapter methods, which complete on whichever event loop awaits them. If the tool awaited the adapter on the worker thread's loop instead of dispatching to the gateway loop, the test still passed, although the real Matrix client is bound to the gateway loop. Replace the mocks with a fake adapter that records, for each call, whether it ran on the loop bound to the session. The test compares the whole call list, so a call on the wrong loop fails it. With the dispatch removed from the tool, all four calls record `False`.
The Matrix guide said that the agent can remove its own reactions from a message. The adapter records agent reactions only in memory and keeps records for the 1,000 most recently used messages, so after a gateway restart, or for an older message, unreact finds no record and reports an error. State that limit, and state that each reaction or read call checks the room allowlist and the requester's authorisation again.
The read-tools parent changed set_session_vars so that it no longer records the running event loop as the session's transport loop. The gateway passes its own loop as transport_loop, and other callers bind no loop. The reaction tool tests bound only the adapter, so the tool found no loop and returned "Matrix gateway loop is unavailable" before reaching the adapter. Bind the test's running loop as transport_loop, as the gateway does for a live turn.
When the matrix_reaction handler ran on the session's gateway loop itself, it awaited the adapter call directly and never applied _REACTION_TIMEOUT_SECONDS, so a stalled send blocked the tool call indefinitely. The tool also scheduled its coroutine onto the gateway loop with its own code for closing the coroutine when scheduling failed. The read-tools parent fixed the same two problems in matrix_read. Schedule the reaction with safe_schedule_threadsafe, which closes the coroutine on every failure path, and await every call through the same shielded deadline. A reaction that outlives the deadline still completes on the gateway loop and is recorded for a later unreact. The timeout test now also runs the handler on the gateway loop. Before this change that case waited for the stalled send until the test's own 2-second guard failed it.
The `matrix_reaction` toolset is in the `hermes-matrix` composite but not in `CONFIGURABLE_TOOLSETS`. The Matrix checklist of `hermes tools` did not list it, and `hermes tools disable matrix_reaction --platform matrix` printed "Unknown toolset" and saved nothing. Tool resolution treated the non-configurable toolset as native to Matrix and added it to every Matrix toolset list, including a saved list of individual toolsets. Add `matrix_reaction` to `CONFIGURABLE_TOOLSETS` and restrict it to Matrix in `_TOOLSET_PLATFORM_RESTRICTIONS`, as the read-tools parent did for `matrix_read`. The checklist and the enable and disable commands now offer it on Matrix, and other platforms reject it. An unsaved Matrix configuration and a saved `[hermes-matrix]` list enable it through the composite. A saved list of individual toolsets enables it only when the list includes `matrix_reaction`. The parent's two toolset tests now run for both Matrix toolsets. The toolsets reference lists `matrix_reaction` and the `hermes-matrix` composite, and the Matrix guide describes turning either toolset off.
An earlier commit on this branch changed send_message's react and unreact actions to run the adapter call on the gateway loop, so that the Matrix adapter's add_reaction and remove_reaction would use the live client from its own loop. That path is not reachable by the model. send_message has not been a registered model tool since c6c8abb (NousResearch#47856), and the only in-tree callers of send_message_tool, `hermes send` and the MCP server's messages_send, always pass action="send". A process outside the gateway also has no live adapter, so it gets the "Reactions require a live adapter" error. The matrix_reaction tool is the model's route to Matrix reactions, and it applies the room and requester checks on the gateway loop. Restore send_message_tool.py and its react test to the versions on main instead of maintaining a second Matrix dispatch without those checks.
A relay-fronted conversation can retain Matrix as its session platform, but its delivery adapter has no native Matrix session-access method. The reaction tool accepted that adapter and failed with AttributeError when it tried to check room policy. The read tool already checks its native adapter capability before scheduling work. Require the reaction access-check capability at the same boundary. Relay-fronted input now receives the live-Matrix-session error without scheduling an unsupported operation. Native Matrix actions keep their room and requester policy checks on the owning gateway loop. The relay contract fails on the preceding head. The missing-adapter control and repaired relay case pass with the affected six-file gate: 299 tests passed, six skipped and no retries.
The declared filename tests do not show whether media from a separate Matrix client reaches the model after the gateway downloads and caches it. A filename-like caption also needs to remain distinct from the actual document filename across that path. Upload a document to disposable Synapse with different caption and filename values. Check the model-visible context, the gateway reply, and the exact bytes cached under the declared filename.
The gateway removes an addressed bot mention before it builds the inbound message. If a caption then matches the declared filename, the media normaliser mistakes it for a client-generated filename and drops the user's text. Compare the original Matrix body with the filename to decide whether the body is a transport fallback. Return the cleaned body for model context when the original body contains a caption. A parameterised regression covers a mentioned caption that matches the filename after mention removal.
The live caption test requested a one-second Synapse poll and used an outer loop deadline of one minute. Matrix-nio can retry a sync request beyond that deadline, so a stalled exchange could run for much longer than the diagnostic claimed. Request a 250 ms server wait and bound each sync by the remaining 15-second reply deadline. The test still observes the reply through the independent Matrix client and checks the model request and cache.
A Matrix client that replies with media and no caption can put the inline reply fallback before the filename in body, for example "> <@erin:example.org> nice photo\n\nphoto.png". The caption decision compares the original wire body with the declared filename, or applies the filename heuristic when no filename is declared. The wire body still contains the fallback, so neither check recognised the bare filename, and the model received "photo.png" as the user's text. Strip the reply fallback from the wire body before the comparison when the event replies to another event. Mentions stay in the wire body, so a caption that only matches the filename after mention removal is still kept. A parameterised regression covers the reply with and without a declared filename.
test_other_client_media_event_keeps_declared_filename_and_caption sent an m.file event with the caption "notes.pdf" and the filename "original.pdf" through _on_room_message. The legacy heuristic never drops a PDF name on m.file, the fake download returns no bytes so no cache file is named, and raw_message equals the event content by construction. The test therefore passed on the adapter before MSC2530 support. The m.file case of test_declared_filename_preserves_filename_looking_caption_and_names_cache already checks that the document cache receives the declared filename while the caption is kept, and it fails on that adapter.
The live media test sent an m.file with the caption "screenshot.png". For m.file, the legacy heuristic only treats audio and video names as bare filenames, so the adapter before MSC2530 support also forwarded that caption. The test failed on that adapter only because of the cache filename, and its caption check could not detect a lost caption. Use "clip.mp4" as the caption, which the legacy heuristic drops for m.file. Check that the paragraph after the document note is exactly the caption, before the cache checks, so a dropped caption fails on its own. The document note must also give the declared filename and the cached path. The model context previously went through json.dumps and a substring search, which the filename in the note could satisfy.
The Matrix adapter includes both transport filename classification and message dispatch. Caption changes need to distinguish those decisions, but adding more parsing to the adapter would expand an already large module. Move the existing filename classifiers into media_content.py. Keep the classification rules unchanged and update the existing tests to import the defining module. The adapter still uses the same private classifier when preparing reply media and captions.
A media reply can contain an ordinary quotation followed by a filename. The reply parser preserved that authored text, but caption classification stripped any leading quoted block and compared only the trailing filename. The media event could therefore lose the user's entire quotation. Classify captions with the existing Matrix reply-fallback detector and splitter. Strip only a recognised legacy fallback before comparing the transport filename. Keep ordinary quotations as authored caption text, with or without an explicit filename. The existing captionless-reply contract now covers both ordinary quotes and legacy fallbacks. Both ordinary-quote rows fail before the repair; legacy fallback controls still remove the transport filename.
Media events above the Matrix size limit returned before the adapter resolved room context or built an inbound message. The agent never saw the user's caption or knew that an attachment had been rejected. Resolve the usual message context, then deliver a text event with the caption and an attachment-too-large marker. Include the declared filename, or the transport filename when the body contains only that filename. Skip the download and leave media URLs empty so the rejected attachment cannot enter the model request through the media path.
The Matrix adapter compared MATRIX_MAX_MEDIA_BYTES only with the optional info.size in the event. When a client omitted the size or understated it, the adapter downloaded, decrypted and cached the whole attachment and passed it to the agent as media. mautrix's download_media reads the entire body into memory before it returns, so the configured limit did not bound memory either. When gateway.max_inbound_media_bytes was lower than the Matrix limit, the shared cache helper rejected the bytes after the download. The adapter logged a warning and delivered the event anyway: encrypted media arrived with no attachment and no marker, and plain media arrived with the homeserver download URL. The agent was not told that the attachment had been rejected. Take the smaller of the two limits and apply it to the declared size and to the download. The download now uses the mautrix client's HTTP session directly, with the same authenticated media endpoint that download_media chooses. It rejects a Content-Length above the limit and stops reading once the streamed body exceeds it. Decryption and caching happen only after the body fits. An attachment rejected at either point produces the same text event as a declared oversize: the caption and a "[matrix <kind> attachment too large: <filename>]" marker, with no media fields. The room and mention gates still run before any download. A separate client sent a document without info.size through disposable Synapse; the model request contained the caption and marker and the gateway cached no file.
The media-caption parent stopped serialising the model content in its own live test and removed the module's `json` import. The oversized test in the same module still serialises the user content with `json.dumps` before it looks for the caption and the size marker, so after the merge both of its cases failed with a NameError once the gateway replied. The project's Ruff rules do not include the pyflakes undefined-name check, so linting did not catch it. Import `json` again so the oversized test can inspect the model content.
A client can send a media reply with an inline reply fallback in the body, such as "> <@bob:example.org> hi\n\nvoice.ogg", and no filename field. The caption rule strips the fallback when the event has m.in_reply_to, sees the bare transport filename and removes it from the caption. The too-large marker still tested the raw body, which contains the fallback and so is not a bare filename. For an oversized reply of this kind the agent received only "[matrix audio attachment too large]", with neither a caption nor a filename. Strip the fallback in one helper and use it for both the caption and the marker filename. The marker now reads "[matrix audio attachment too large: voice.ogg]" for that reply.
The adapter now chooses the media download endpoint itself: the authenticated /_matrix/client/v1/media endpoint with a Bearer token when the homeserver supports Matrix v1.11, and the legacy /_matrix/media/v3 endpoint without a token otherwise. mautrix's download_media made that choice before. The unit tests' fake media download always reported v1.11 support and discarded the request headers, so dropping the Authorization header or forcing the legacy endpoint left every unit test passing. Let the fake report either level of support, and record for each request whether the adapter asked for the authenticated URL and which headers it sent. One parametrised case per endpoint asserts both, so each of those changes now fails a test.
The bounded inbound download now requires its effective size limit. The quoted-image path still omitted that argument, so the reply cache caught the resulting TypeError and the model received no image. That path also checked only the Matrix cap, despite the gateway cap sometimes being smaller. Use the same effective limit for quoted images, both for declared sizes and while streaming. Extend the existing real-cache reply contract with Matrix and gateway cap controls. Update adjacent media fixtures at the HTTP streaming boundary and retain their intake barriers. The quoted-image contract reproduces four failures and two declared-size controls before this repair. All seven adjacent files pass, 713 tests, including routed-profile image ownership and queued dependency retention.
The oversized-media cases used an integer gateway fixture parameter. The current shared fixture expects typed GatewaySettings, so those cases failed during setup before either attachment was sent. Configure the existing gateway media cap through gateway_extra_config. The production download uses the smaller gateway and Matrix limits, so both native cases still exercise the same ten-byte bound. Remove the unused integer parameter bridge without changing other fixture modes.
Speech reference tests used empty history, so they did not exercise channel-state acknowledgement while references appeared in stored transcript rows and adapter-generated context. Extend the existing A/B/A contract with empty and stored history for all five speech variants. Check the complete prepared message, unchanged transcript, lookup and adapter calls, returned channel state and single raw authored preprocessing input. Stored references and generated channel notes remain literal while caption and successful speech references expand under the owning profile. The complete file passes 21 cases with strict asynchronous warnings and zero retries. This adds coverage to the existing preparation behaviour; it does not repair a reproduced production defect.
iainlane
force-pushed
the
codex/pending-quote-speech-prerequisite
branch
from
October 5, 2026 05:15
da27edd to
9c5bfd3
Compare
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. Resolve the captured upstream changes in the original defining scopes when updating this contribution. Keep the extracted implementations and authored behaviour; apply the same current annotation, timeout or UTC spelling in their actual owners where those scopes moved.
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. Format the unquoted batch-pop signature on one line, as required by the current formatter; the complete module AST and behaviour 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.
The startup coalescing fixtures compared complete events but omitted the reaction parent's merged-message identities. The direct processor test also cancelled a same-chat waiter before its callback coroutine began, then discarded that caller-owned coroutine without closing it. Strict coroutine warnings exposed the incomplete fixture lifetime. Include the incoming identity in the complete merged-event expectation. Keep references to the direct test callbacks and close them after all processor tasks finish. The predecessor, cancellation and FIFO ordering assertions remain unchanged. Production coroutine ownership is unchanged. The original combined source run reproduced three complete-event mismatches and one unawaited-coroutine failure. All 455 cases in the 22 adjacent source files pass with strict warnings and no retries.
A queued Matrix reaction includes a refreshable context object with its live adapter and retained event cache. Shutdown snapshots copied every constructor field, so serialising this object raised TypeError and left the complete pending input unwritten. Capture public normalised event fields separately from private context objects. Quoted-media dependencies become plain identity records, and the existing inbound snapshot interface supplies its pending state. Matrix records the bounded logical excerpt dependencies and original excerpt without serialising its adapter or cached authority. This data is inspection evidence, not authorisation to replay a turn. Restoration must reconstruct context through the current owning adapter and revalidate effective state before model use. Capturing a pending record leaves the original live snapshot unchanged, so later redactions continue to replace stale quotations. The regression writes a real split-reply reaction through the production shutdown writer. It reproduced the TypeError before the repair, then checks the complete data record and a later redaction on the live input. Close the logical snapshot fixture runner after its original restore and revalidation assertions. Apply the published public fixture correction without replacing the later snapshot contract.
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.
iainlane
force-pushed
the
codex/pending-quote-speech-prerequisite
branch
from
October 5, 2026 17:43
9c5bfd3 to
c21cc3b
Compare
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.
Direct FIFO callers such as /queue, /steer fallback and Telegram grace could bypass the 32-entry bound. Normal busy admission only logged a refusal, while /queue acknowledged input even when no event was queued. Debounced text was also outside the pending count, so a later photo or command could overtake an older accepted burst. Check admission at the shared FIFO boundary and return its result to command and busy handlers. Count buffered bursts as pending input and submit older bursts before accepting a later FIFO event. Refused external input receives a thread-aware queue-full reply; internal wakes remain unaccepted so their existing producers can retry. Earlier accepted events are never replaced to admit the new input. Copy command events without reconstructing their fields. This preserves reply, media and control context together with the existing bot-loop admission flag, so replay cannot acquire permissions or spend admission twice after stripping the command prefix. Parameterised contracts reproduce bypassed capacity, false acknowledgements, missing refusal notices, context loss and overtaking of buffered input. fix(i18n): translate the pending queue refusal The bounded pending queue reports an English-only warning when admission is refused. The other sixteen bundled language catalogues omit that key, so users receive the English fallback even after selecting their language. Add the refusal message to every bundled catalogue. Verify that runtime lookup selects the requested catalogue, that non-English results differ from the English fallback, and that all catalogue keys and placeholders remain in parity. The runtime contract failed for all sixteen translated languages before the entries were added. The complete four-file locale gate now passes all 92 cases. Source-commit: cd37cd3a25ab9c157495df61ed3a8049765e90ab Protected-source-alias: b01a5942e1b077ee7dcd7ad73f3cc0002f926ae1 Maintenance-adaptation: Apply the debounce admission method change in base_text_debounce.py, preserving its typed receiver. Add the original refusal-control branch to the current platform handler without changing the upstream selected-owner loop. Keep the predicate name and defining merge import from the earlier sources in the busy-session module; its other incoming method and declaration bytes remain unchanged. Maintenance-adaptation: Declare the existing pending-refusal collaborator interface and use pytest monkeypatch for the admission fixture overrides and dynamic marker; keep the original scenario values and assertions. Resolve the captured upstream changes in the original defining scopes when updating this contribution. Keep the extracted implementations and authored behaviour; apply the same current annotation, timeout or UTC spelling in their actual owners where those scopes moved.
iainlane
force-pushed
the
codex/pending-quote-speech-prerequisite
branch
from
October 6, 2026 08:26
c21cc3b to
496d686
Compare
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. The live test removes a shared expected-event marker between phases, uses the default interrupt policy for queued input, and reuses the first phase release path. The separate gateway can observe stale mounted markers or fail between a marker existence check and its read. Reset the expected value atomically, configure queue mode through the existing fixture, and give queued preparation separate start and release paths. Record actual redacted cache entries by event ID so the second withdrawal barrier acknowledges its exact target. Preserve every model, pixel and prior-history assertion. Apply the independently published native fixture corrections from PR NousResearch#126375 at 6f027ce.
iainlane
force-pushed
the
codex/pending-quote-speech-prerequisite
branch
4 times, most recently
from
October 9, 2026 03:23
29dba1f to
9c36c9e
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. Apply these complete published fake-API and reconnect fixture scopes to the preserved supplier source. The original fixture definitions predate the campaign, and all original assertions remain unchanged.
iainlane
force-pushed
the
codex/pending-quote-speech-prerequisite
branch
from
October 9, 2026 15:23
9c36c9e to
1aeff9e
Compare
This was referenced Oct 9, 2026
iainlane
force-pushed
the
codex/pending-quote-speech-prerequisite
branch
2 times, most recently
from
October 10, 2026 05:41
5365f40 to
1c14b50
Compare
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. Each dependent follows every prerequisite shown. Each linked description lists its other dependencies.Change
Expand references in the sender's original caption and successful speech before adding generated notes, reply text or channel history. Transcript echoes retain raw speech. Merged pending input preserves each contribution's quoted and forwarded context, withdrawal provenance and data-only shutdown capture.
Pending recovery remains provisional until the session store records the input owner. Session preparation binds the same owner after the recursive input helper computes the identity. The active-turn marker, withdrawal boundary, caption and speech expansion, profile routing, stored context and leftover steer admission remain intact.
Current source and history
I resolved the concrete current-layout conflicts against the captured upstream revision
3637c512fc3211bdccb61b7140d09ac1018508ba. The branch is rebased onto that revision. Every commit has one parent. All 385 original contributions remain in order, including this PR's 37 own contributions. The existing credited Matrix SDK fixture maintenance contribution remains record 386. The 44 meaningful empty contributions remain separate. All original full message prefixes, author and committer headers, dates and timezones are preserved.The resolutions retain the authored implementations in their extracted owners and apply the current annotation, UTC and timeout spellings there. The source-note helper uses the published transport-preserving
removeprefixchange from #125738. The checkpoint fixture uses the ordering already published in #126281: finish its background tasks before awaiting the checkpoint completion. Its complete assertions remain unchanged. The existing fixture lifetime corrections and all later restoration, decoding and live-revalidation assertions remain intact.Review this layer's 37 original contributions and the credited fixture maintenance. Compare the preceding published head with this update. The comparison against
mainincludes the prerequisites.Native fixture correction and validation
The native rich-content fixture now uses queue mode, atomically clears its expected-event marker, separates active and queued pause paths, and records actual redacted cache entries by event ID. These bounded fixture mechanisms are already published in #126375. The retained queued sticker expectation includes its exact Matrix source permalink. The complete retained caption, cached image path, exact PNG, five model requests and prior-history assertions remain unchanged.
The fixture support begins at record 163. Queued-only changes apply after that phase appears at record 164; the atomic reset applies after the existing hand-off helper appears at record 172. The strict retained-input expectation is corrected at its original record 341. The first 162 signed objects remain exact, while the necessary 224-record suffix is renewed. All 386 full message prefixes, author and committer headers, dates, timezones, order and 44 empty credits remain intact. Only three native-test files change.
I ran the complete plain and encrypted native rich-content module on the exact corrected source and its current PM-built image. Both cases pass, including model input, PNG pixels, active and pending withdrawal, explicit Matrix reads, and stable prior history. The complete child exits with zero strict resource warnings and no retries. Every captured tracked Python and canonical-runner input is authenticated against Git before and after execution.
The preceding accepted supplier validation remains qualified evidence: 1,267 cases across 37 complete SDK files, plus seven optional lark-oapi dependency skips, with zero strict resource warnings. Its execution composes the three actual published #135482 commits; those records remain outside this branch's pure history. Every selected complete SDK file, ancestor fixture, production input and runner remains byte-exact. The three changed native-test files are outside that SDK scope. No fresh 37-file execution is claimed.
The preceding complete 254-path static result is reused through exact whole-source bindings to the preceding published source for the 251 unchanged paths, including the two previously accepted formatter and EOF inverses, and a fresh check of the three native-test paths. The fresh scope has one existing FakeLLMServer argument finding. Its lexical span and relative occurrence remain exact; a complete fixture inverse restores its original enclosing statement, which contains the generated provider literals. There are zero introduced type findings. Ruff passes. The same three native-test formatter paths remain inherited. The prior 3,425 total diagnostic occurrences, nine method/base qualifications and 213 inherited formatter paths remain explicitly source-qualified; this is not a fresh global static or formatter pass.
The branch joins both fixed upstream snapshots cleanly. Earlier verification against
1744a19e0df568c647e4f3ff9c37f2a284a282fbremains historical. Hosted CI and maintainer acceptance remain separate.