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.
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 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. Captured upstream compatibility for bounded pending admission Keep the current unquoted MessageEvent annotations in the FIFO and busy steering methods. Preserve the original boolean return values and admission behaviour, including refusal reporting and queued input.
iainlane
force-pushed
the
codex/matrix-native-pending-durability
branch
from
October 6, 2026 09:47
e08f74e to
a2eba03
Compare
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. 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/matrix-native-pending-durability
branch
5 times, most recently
from
October 9, 2026 00:41
9eef216 to
e2d2c6b
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.
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
force-pushed
the
codex/matrix-native-pending-durability
branch
from
October 9, 2026 12:25
e2d2c6b to
5b1b1b0
Compare
iainlane
added a commit
to iainlane/hermes-agent
that referenced
this pull request
Oct 9, 2026
A queued sticker or emote includes a MatrixRichContentSnapshot. Shutdown capture asks every inbound snapshot for data-only pending state, but the native-content implementation did not provide that method. Both native inputs therefore failed snapshot capture and could not be preserved. Record the nested turn context and the original event, text, attachment identity and attachment paths for each contribution. Live adapter and cache objects remain process-local. The saved identity lets restoration compare each attachment with current Matrix state before model use. The regression uses actual typed Matrix emotes and stickers through the adapter and shutdown writer. Both capture paths now succeed, and the same live dependency still withdraws text and media after redaction. The rich pending snapshot caller creates a real store through its runner helper. Apply the published NousResearch#130877 finally cleanup after its complete original exercise, preserving the private restored-event decoding and live revalidation assertions.
Exercise the real async bridge with a background task and a native default-executor thread in persistent and running-loop modes. Complement PR NousResearch#135482 transport and async-generator coverage without replacing its implementation. Prepared with OpenAI Codex assistance; no human review claimed. Use the default-executor shutdown API supported by Python 3.11, which is the minimum version for this project. The test releases its own executor work before shutdown and verifies that the worker thread exits.
Pending withdrawal changes three oversized modules: the shared adapter, turn execution and Matrix adapter. Extract their existing pending merge, post-turn selection and redaction handling into topical siblings first. The Matrix redaction and runner drain methods keep their complete bodies. The pending merge keeps its existing caption helper through a local adapter import. In-tree callers and patch targets use the defining module, with no compatibility re-export. The strict fifteen-file gate passes 631 tests before any withdrawal behaviour changes.
iainlane
force-pushed
the
codex/matrix-native-pending-durability
branch
from
October 9, 2026 16:34
5b1b1b0 to
f3e231b
Compare
iainlane
added a commit
to iainlane/hermes-agent
that referenced
this pull request
Oct 9, 2026
A queued sticker or emote includes a MatrixRichContentSnapshot. Shutdown capture asks every inbound snapshot for data-only pending state, but the native-content implementation did not provide that method. Both native inputs therefore failed snapshot capture and could not be preserved. Record the nested turn context and the original event, text, attachment identity and attachment paths for each contribution. Live adapter and cache objects remain process-local. The saved identity lets restoration compare each attachment with current Matrix state before model use. The regression uses actual typed Matrix emotes and stickers through the adapter and shutdown writer. Both capture paths now succeed, and the same live dependency still withdraws text and media after redaction. The rich pending snapshot caller creates a real store through its runner helper. Apply the published NousResearch#130877 finally cleanup after its complete original exercise, preserving the private restored-event decoding and live revalidation assertions.
iainlane
added a commit
to iainlane/hermes-agent
that referenced
this pull request
Oct 9, 2026
A queued sticker or emote includes a MatrixRichContentSnapshot. Shutdown capture asks every inbound snapshot for data-only pending state, but the native-content implementation did not provide that method. Both native inputs therefore failed snapshot capture and could not be preserved. Record the nested turn context and the original event, text, attachment identity and attachment paths for each contribution. Live adapter and cache objects remain process-local. The saved identity lets restoration compare each attachment with current Matrix state before model use. The regression uses actual typed Matrix emotes and stickers through the adapter and shutdown writer. Both capture paths now succeed, and the same live dependency still withdraws text and media after redaction. The rich pending snapshot caller creates a real store through its runner helper. Apply the published NousResearch#130877 finally cleanup after its complete original exercise, preserving the private restored-event decoding and live revalidation assertions.
iainlane
added a commit
to iainlane/hermes-agent
that referenced
this pull request
Oct 10, 2026
A queued sticker or emote includes a MatrixRichContentSnapshot. Shutdown capture asks every inbound snapshot for data-only pending state, but the native-content implementation did not provide that method. Both native inputs therefore failed snapshot capture and could not be preserved. Record the nested turn context and the original event, text, attachment identity and attachment paths for each contribution. Live adapter and cache objects remain process-local. The saved identity lets restoration compare each attachment with current Matrix state before model use. The regression uses actual typed Matrix emotes and stickers through the adapter and shutdown writer. Both capture paths now succeed, and the same live dependency still withdraws text and media after redaction. The rich pending snapshot caller creates a real store through its runner helper. Apply the published NousResearch#130877 finally cleanup after its complete original exercise, preserving the private restored-event decoding and live revalidation assertions.
iainlane
force-pushed
the
codex/matrix-native-pending-durability
branch
from
October 10, 2026 07:15
f3e231b to
dfc8a8a
Compare
iainlane
added a commit
to iainlane/hermes-agent
that referenced
this pull request
Oct 10, 2026
A queued head leaves pending storage before its handler finishes admission. A concurrent arrival could fill the vacated slot, then a deferred dispatch restored the accepted head and increased the queue beyond its bound. Cancellation during that same window could start a successor from the old task's cleanup before the caller restored the unclaimed head. Reserve a removed FIFO head while dispatch is provisional. Count the reservation only while the event is absent from the queue and buffered storage. Release it when the runner claims a real turn, restores the head, or completes the owning task. Rewritten and ID-less events use the existing task-scoped dispatch identity; cleanup cannot remove a replacement head's reservation. Expected cancellation leaves successor dispatch to the cancelling caller, including when an admission callback suppresses cancellation and completes. Record successful claim on the reservation so cancellation can check that state after awaiting the task. When pending input is retained, restore only an unclaimed head before its successors. Once a turn has claimed the event, cancellation cannot replay that input. The caller's discard and command-guard options still decide whether pending work resumes. The concurrent-arrival contract reproduces a 33rd pending event before the reservation. Lifecycle contracts verify cancellation before callback entry, during admission and after claim, including an admission callback that suppresses cancellation before claim. They also verify complete media/reply/control context. Maintenance adaptation: import the reservation helpers from their defining pending module in the platform facade. The debounce predicate wrapper remains in its existing sibling, so the facade does not import that predicate. Preserve the earlier predicate name and defining merge import in the busy-session module when applying the original reservation methods. The upstream handler and selected-owner loop remain unchanged. Maintenance-adaptation: Declare the existing session-state and optional delivery-adapter interfaces. Keep the reflective optional-method guards, make their None narrowing explicit, and use dynamic attribute lookup for both guarded calls without changing call arguments or admission and reservation implementations. A sentinel claim can precede the durable user row. Keep queued input recoverable during admission and preparation, and bind its prepared session and input owner to the captured dispatch. Check that exact owner in the source profile before omitting the reservation from recovery or queue capacity. Definite plugin consumption, lease refusal and normal handler completion still release the reservation. Preserve provisional input when a cancellation waiter times out. The task may finish later without a durable transcript owner. Reuse the accepted public cancellation completion correction: retain its reservation until the completion callback reconciles recovery once, while durable inputs remain excluded. Existing contributor source, identities and credit records remain unchanged outside these dispatch and regression scopes. Current compatibility on captured main: Keep the actual published NousResearch#130877 prerequisite and its complete current source. The first 57 copied records remain explicit contributor credits; their effects are supplied by that prerequisite. Preserve its current annotations, bounded recovery, fixture cleanup and live-test corrections, then replay the four attachment filename records at their original scope.
iainlane
added a commit
to iainlane/hermes-agent
that referenced
this pull request
Oct 11, 2026
A queued sticker or emote includes a MatrixRichContentSnapshot. Shutdown capture asks every inbound snapshot for data-only pending state, but the native-content implementation did not provide that method. Both native inputs therefore failed snapshot capture and could not be preserved. Record the nested turn context and the original event, text, attachment identity and attachment paths for each contribution. Live adapter and cache objects remain process-local. The saved identity lets restoration compare each attachment with current Matrix state before model use. The regression uses actual typed Matrix emotes and stickers through the adapter and shutdown writer. Both capture paths now succeed, and the same live dependency still withdraws text and media after redaction. The rich pending snapshot caller creates a real store through its runner helper. Apply the published NousResearch#130877 finally cleanup after its complete original exercise, preserving the private restored-event decoding and live revalidation assertions.
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; the linked descriptions list their earlier dependencies.Change
Matrix emote and sticker contributions retain their typed pending snapshot data. Native batch receipts defer checkpoint consumption until admission succeeds. The same contribution identities support later sender withdrawal. The pending native snapshot fixtures close their real session stores after the original assertions.
Current supplier and compatibility
The previous branch inherited four concrete conflicts against the fixed upstream revision
3637c512fc3211bdccb61b7140d09ac1018508ba. The independently published #128687 supplier at1005c8fe8934609b57d149b5fee40b1426551fa6resolves those conflicts and includes the three credited worker-lifetime prerequisites from #135482. All seven original Matrix contributions replay automatically on that supplier. The image-pack implementation from #126437 is already present: its four complete defining modules and complete adapter entry point match the published source, and its configuration remains available alongside the later Matrix toolsets.The inherited live fixture still used a shared release marker across two phases, removed an expected-event file while the gateway could read it, and used the default interrupt policy for input that the test expected to queue. I applied the independently published #126375 fixture corrections at the original fixture contribution. Queued preparation has separate start and release paths, the expected value is reset atomically, and the withdrawal barrier records actual redacted event IDs. The complete queued sticker expectation also includes the existing source permalink. Every existing model, pixel and prior-history assertion remains.
The branch contains 346 credited records. The first 162 signed records are unchanged. The 184 affected records are rebuilt with the original author and committer headers, dates, timezones and order. Every original message remains verbatim, with fixture explanations appended only at the two introducing contributions. All 40 unchanged-tree contributions remain separate. The production implementation is the seven original contributions on the published supplier; the additional changes affect only three native fixture files.
Review the seven original contributions. The inherited fixture corrections precede that comparison and are described above.
Validation
The exact final source passes 496 cases across nine complete canonical SDK test files, with four strict warning categories and zero retries. Eight complete files contain 411 cases and finish without unclosed resources. The byte-identical rich-content SDK file passes 85 cases but emits the same 82 database shutdown warnings as the original public source; those cases remain qualified. Seven pending-shutdown warnings disappear with the published supplier correction. Full child output includes interpreter exit, and all actual repository inputs are authenticated before and after execution.
The complete live rich-content file passes its plain and encrypted scenarios against a disposable Synapse and separate Matrix clients. Both cases finish without unclosed resources. They exercise emote attribution, sticker pixels, paused context preparation, sender withdrawal, coalesced input, reactions, explicit room reads and stable prior model history. The gateway image is built with the project's package manager from the exact final source; the actual repository inputs and complete child output are authenticated.
The paired 15-path type comparison has 241 findings on the original source and 246 on both the published supplier and final candidate. Every candidate finding matches a baseline's full lexical span, complete statement AST, relative offsets and multiplicity. No new finding is introduced. Ruff passes; the same 14 files retain inherited formatting differences. The final local join is clean against the fixed upstream revision above.
These are local source and execution results. Hosted CI execution and maintainer acceptance remain pending.