Repository navigation
Conversation
This was referenced Sep 28, 2026
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.
iainlane
force-pushed
the
fix/matrix-oversized-media
branch
from
October 1, 2026 09:36
dd29f97 to
4e14b04
Compare
Room classification controls the Matrix room allowlist and mention gates. The previous member-count shortcut classified a bot-only room as a DM, and an m.direct entry classified larger or unreadable rooms as DMs. Those rooms could bypass both gates. Classify a room as a private bot chat only when the bot and exactly one other user are joined. Request JOIN membership explicitly: mautrix's state store includes invited users by default. The state store may also contain only a subset of a room's members, so use its joined IDs only when has_full_member_list confirms completeness. Otherwise query the homeserver with get_joined_members and apply room rules if that fails. Keep m.direct as room identity metadata, without using it for authorisation. Document the resulting allowlist and mention policy. Tests reproduced the bot-only, bot-absent, multi-user, invited-user, partial-cache and unknown-membership cases before their fixes.
The DM tests marked m.direct account data but did not supply joined members. The two-person classifier therefore treated those fixtures as rooms, so their threading assertions tested the wrong policy. Give the fixture a full joined member list with the bot and one other user. Keep the three-member variant for room behaviour. The mention and threading tests now exercise the intended DM policy.
A room is a private bot chat only when the bot's user ID is one of its two joined members. With an access token, the adapter replaces the configured MATRIX_USER_ID with the ID that whoami returns. After a password login it kept the configured string. Synapse accepts a login identifier whose localpart differs in case from the registered user, so a configured @Hermes:example.org logs in as @Hermes:example.org. The membership check then never matched, and every one-to-one chat followed room rules: with MATRIX_ALLOWED_ROOMS set its messages were dropped, and otherwise they needed a mention. mautrix's login stores the homeserver's user ID in client.mxid. Copy it into the adapter after a successful login, as the whoami path does, so the membership check uses the ID that appears in the room's member list.
The Matrix handoff session-key test marked its DM case only through the m.direct cache. Room classification no longer reads m.direct, so the DM case was keyed as a group and the test failed. Give the fake client a joined member list: the bot and one other user for the DM case, and a third user for the room case.
The environment-variable reference still said that any room with two or fewer joined members is a DM, and told operators to add a third member to get room behaviour. A bot-only room, a room without the bot and a room whose membership cannot be read now follow room rules. Describe the current rule in the MATRIX_ALLOWED_ROOMS, MATRIX_REQUIRE_MENTION, MATRIX_FREE_RESPONSE_ROOMS and MATRIX_AUTO_THREAD rows. The comment in _schedule_pending_invite_joins said that a direct invite missing from m.direct is misclassified as a group. m.direct no longer affects classification, so drop that clause.
The E2EE crypto store keys every row (the Olm account, Olm sessions and inbound Megolm sessions) by an account ID taken from the adapter's user ID. After a password login the adapter now replaces the configured MATRIX_USER_ID with the homeserver's spelling of it, so the DM check matches the room's member list. That replacement also changed the store key. A deployment with a password login, MATRIX_USER_ID differing in case from the registered user, and a stable MATRIX_DEVICE_ID would open an empty store after upgrading. It would create a new Olm account for its existing device and could no longer decrypt history. The same happened on an in-process reconnect, because the second login read the replaced ID. Keep the configured ID in its own attribute and use it for the login identifier and, after a password login, for the store key, as before. The access-token path keys the store by the whoami answer, as it always has. The adapter's user ID still becomes the homeserver's spelling on both paths, and the DM, mention, invite and self-sender checks use it. Preserve the HTTP API supplied to the password-login fixture client and disconnect its first real connection before reconnecting. The fixture keeps every configured-account and canonical-identity assertion. The shared Client constructor preserves the supplied API for this fixture.
Matrix rooms often have no m.room.name state event. Identity resolution used the opaque room ID as the name shown to the agent even when other occupants had readable display names. Read member profiles from the state store, with the joined members API as a fallback when the store is empty. Use the other occupants to title an unnamed room. Explicit-name detection and DM classification remain unchanged.
Matrix room identity was cached without reacting to live state changes. Topic edits, renames and membership changes could leave stale room context. The agent also missed important privacy or replacement changes. Register sync-awaited state handlers that invalidate the identity cache. Queue one note per change kind for the next ordinary message. Keep notes through mention drops, commands and text batching. Quote and bound room-state values before they enter channel context.
iainlane
added a commit
to iainlane/hermes-agent
that referenced
this pull request
Oct 8, 2026
When the Matrix adapter could not download or cache an inbound attachment for a reason other than its size, such as a network error, an empty download or bytes that the image cache rejects, it gave the agent the attachment's `/_matrix/client/v1/media/download/...` URL instead of a cached file. That endpoint requires the bot's access token, so the agent's attempts to fetch the URL failed, and the model could repeat the URL in its reply. Encrypted media had no URL to fall back to: the event kept its media message type with no attachment, and nothing told the agent that the user had sent one. When the download or cache step produces no file, the adapter now sends a text event with the user's caption followed by a marker such as `[matrix image attachment could not be downloaded: photo.png]`. The marker has the same shape as the `[matrix <kind> attachment too large: <filename>]` marker proposed in NousResearch#125637, and `_inbound_media_marker` builds it so that the oversized-media path can use the same helper. The filename is the declared `filename`, or a body that contains only the transport filename once any reply fallback is removed. The event has no media URL or media type, so downstream tools do not treat the attachment as downloaded. `_mxc_to_http` has no other caller and is removed. Integration: use the shared filename rule and preserve the canonical sticker kind, retained reply context, mention claim and admission result. Oversize failures still take precedence. The current SDK never returns None, so the file failure fixture uses the subsequent source timeout correction at the streamed download boundary. The affected gate passes 319 tests; four original failure cases were reproduced before the fix. (cherry picked from commit cdc5ce6)
iainlane
added a commit
to iainlane/hermes-agent
that referenced
this pull request
Oct 8, 2026
When the Matrix adapter could not download or cache an inbound attachment for a reason other than its size, such as a network error, an empty download or bytes that the image cache rejects, it gave the agent the attachment's `/_matrix/client/v1/media/download/...` URL instead of a cached file. That endpoint requires the bot's access token, so the agent's attempts to fetch the URL failed, and the model could repeat the URL in its reply. Encrypted media had no URL to fall back to: the event kept its media message type with no attachment, and nothing told the agent that the user had sent one. When the download or cache step produces no file, the adapter now sends a text event with the user's caption followed by a marker such as `[matrix image attachment could not be downloaded: photo.png]`. The marker has the same shape as the `[matrix <kind> attachment too large: <filename>]` marker proposed in NousResearch#125637, and `_inbound_media_marker` builds it so that the oversized-media path can use the same helper. The filename is the declared `filename`, or a body that contains only the transport filename once any reply fallback is removed. The event has no media URL or media type, so downstream tools do not treat the attachment as downloaded. `_mxc_to_http` has no other caller and is removed. Integration: use the shared filename rule and preserve the canonical sticker kind, retained reply context, mention claim and admission result. Oversize failures still take precedence. The current SDK never returns None, so the file failure fixture uses the subsequent source timeout correction at the streamed download boundary. The affected gate passes 319 tests; four original failure cases were reproduced before the fix. (cherry picked from commit cdc5ce6)
iainlane
added a commit
to iainlane/hermes-agent
that referenced
this pull request
Oct 8, 2026
When the Matrix adapter could not download or cache an inbound attachment for a reason other than its size, such as a network error, an empty download or bytes that the image cache rejects, it gave the agent the attachment's `/_matrix/client/v1/media/download/...` URL instead of a cached file. That endpoint requires the bot's access token, so the agent's attempts to fetch the URL failed, and the model could repeat the URL in its reply. Encrypted media had no URL to fall back to: the event kept its media message type with no attachment, and nothing told the agent that the user had sent one. When the download or cache step produces no file, the adapter now sends a text event with the user's caption followed by a marker such as `[matrix image attachment could not be downloaded: photo.png]`. The marker has the same shape as the `[matrix <kind> attachment too large: <filename>]` marker proposed in NousResearch#125637, and `_inbound_media_marker` builds it so that the oversized-media path can use the same helper. The filename is the declared `filename`, or a body that contains only the transport filename once any reply fallback is removed. The event has no media URL or media type, so downstream tools do not treat the attachment as downloaded. `_mxc_to_http` has no other caller and is removed. Integration: use the shared filename rule and preserve the canonical sticker kind, retained reply context, mention claim and admission result. Oversize failures still take precedence. The current SDK never returns None, so the file failure fixture uses the subsequent source timeout correction at the streamed download boundary. The affected gate passes 319 tests; four original failure cases were reproduced before the fix. (cherry picked from commit cdc5ce6)
iainlane
added a commit
to iainlane/hermes-agent
that referenced
this pull request
Oct 8, 2026
When the Matrix adapter could not download or cache an inbound attachment for a reason other than its size, such as a network error, an empty download or bytes that the image cache rejects, it gave the agent the attachment's `/_matrix/client/v1/media/download/...` URL instead of a cached file. That endpoint requires the bot's access token, so the agent's attempts to fetch the URL failed, and the model could repeat the URL in its reply. Encrypted media had no URL to fall back to: the event kept its media message type with no attachment, and nothing told the agent that the user had sent one. When the download or cache step produces no file, the adapter now sends a text event with the user's caption followed by a marker such as `[matrix image attachment could not be downloaded: photo.png]`. The marker has the same shape as the `[matrix <kind> attachment too large: <filename>]` marker proposed in NousResearch#125637, and `_inbound_media_marker` builds it so that the oversized-media path can use the same helper. The filename is the declared `filename`, or a body that contains only the transport filename once any reply fallback is removed. The event has no media URL or media type, so downstream tools do not treat the attachment as downloaded. `_mxc_to_http` has no other caller and is removed. Integration: use the shared filename rule and preserve the canonical sticker kind, retained reply context, mention claim and admission result. Oversize failures still take precedence. The current SDK never returns None, so the file failure fixture uses the subsequent source timeout correction at the streamed download boundary. The affected gate passes 319 tests; four original failure cases were reproduced before the fix. (cherry picked from commit cdc5ce6)
iainlane
added a commit
to iainlane/hermes-agent
that referenced
this pull request
Oct 8, 2026
When the Matrix adapter could not download or cache an inbound attachment for a reason other than its size, such as a network error, an empty download or bytes that the image cache rejects, it gave the agent the attachment's `/_matrix/client/v1/media/download/...` URL instead of a cached file. That endpoint requires the bot's access token, so the agent's attempts to fetch the URL failed, and the model could repeat the URL in its reply. Encrypted media had no URL to fall back to: the event kept its media message type with no attachment, and nothing told the agent that the user had sent one. When the download or cache step produces no file, the adapter now sends a text event with the user's caption followed by a marker such as `[matrix image attachment could not be downloaded: photo.png]`. The marker has the same shape as the `[matrix <kind> attachment too large: <filename>]` marker proposed in NousResearch#125637, and `_inbound_media_marker` builds it so that the oversized-media path can use the same helper. The filename is the declared `filename`, or a body that contains only the transport filename once any reply fallback is removed. The event has no media URL or media type, so downstream tools do not treat the attachment as downloaded. `_mxc_to_http` has no other caller and is removed. Integration: use the shared filename rule and preserve the canonical sticker kind, retained reply context, mention claim and admission result. Oversize failures still take precedence. The current SDK never returns None, so the file failure fixture uses the subsequent source timeout correction at the streamed download boundary. The affected gate passes 319 tests; four original failure cases were reproduced before the fix. (cherry picked from commit cdc5ce6)
iainlane
added a commit
to iainlane/hermes-agent
that referenced
this pull request
Oct 8, 2026
When the Matrix adapter could not download or cache an inbound attachment for a reason other than its size, such as a network error, an empty download or bytes that the image cache rejects, it gave the agent the attachment's `/_matrix/client/v1/media/download/...` URL instead of a cached file. That endpoint requires the bot's access token, so the agent's attempts to fetch the URL failed, and the model could repeat the URL in its reply. Encrypted media had no URL to fall back to: the event kept its media message type with no attachment, and nothing told the agent that the user had sent one. When the download or cache step produces no file, the adapter now sends a text event with the user's caption followed by a marker such as `[matrix image attachment could not be downloaded: photo.png]`. The marker has the same shape as the `[matrix <kind> attachment too large: <filename>]` marker proposed in NousResearch#125637, and `_inbound_media_marker` builds it so that the oversized-media path can use the same helper. The filename is the declared `filename`, or a body that contains only the transport filename once any reply fallback is removed. The event has no media URL or media type, so downstream tools do not treat the attachment as downloaded. `_mxc_to_http` has no other caller and is removed. Integration: use the shared filename rule and preserve the canonical sticker kind, retained reply context, mention claim and admission result. Oversize failures still take precedence. The current SDK never returns None, so the file failure fixture uses the subsequent source timeout correction at the streamed download boundary. The affected gate passes 319 tests; four original failure cases were reproduced before the fix. (cherry picked from commit cdc5ce6)
iainlane
added a commit
to iainlane/hermes-agent
that referenced
this pull request
Oct 9, 2026
When the Matrix adapter could not download or cache an inbound attachment for a reason other than its size, such as a network error, an empty download or bytes that the image cache rejects, it gave the agent the attachment's `/_matrix/client/v1/media/download/...` URL instead of a cached file. That endpoint requires the bot's access token, so the agent's attempts to fetch the URL failed, and the model could repeat the URL in its reply. Encrypted media had no URL to fall back to: the event kept its media message type with no attachment, and nothing told the agent that the user had sent one. When the download or cache step produces no file, the adapter now sends a text event with the user's caption followed by a marker such as `[matrix image attachment could not be downloaded: photo.png]`. The marker has the same shape as the `[matrix <kind> attachment too large: <filename>]` marker proposed in NousResearch#125637, and `_inbound_media_marker` builds it so that the oversized-media path can use the same helper. The filename is the declared `filename`, or a body that contains only the transport filename once any reply fallback is removed. The event has no media URL or media type, so downstream tools do not treat the attachment as downloaded. `_mxc_to_http` has no other caller and is removed. Integration: use the shared filename rule and preserve the canonical sticker kind, retained reply context, mention claim and admission result. Oversize failures still take precedence. The current SDK never returns None, so the file failure fixture uses the subsequent source timeout correction at the streamed download boundary. The affected gate passes 319 tests; four original failure cases were reproduced before the fix. (cherry picked from commit cdc5ce6)
iainlane
added a commit
to iainlane/hermes-agent
that referenced
this pull request
Oct 9, 2026
When the Matrix adapter could not download or cache an inbound attachment for a reason other than its size, such as a network error, an empty download or bytes that the image cache rejects, it gave the agent the attachment's `/_matrix/client/v1/media/download/...` URL instead of a cached file. That endpoint requires the bot's access token, so the agent's attempts to fetch the URL failed, and the model could repeat the URL in its reply. Encrypted media had no URL to fall back to: the event kept its media message type with no attachment, and nothing told the agent that the user had sent one. When the download or cache step produces no file, the adapter now sends a text event with the user's caption followed by a marker such as `[matrix image attachment could not be downloaded: photo.png]`. The marker has the same shape as the `[matrix <kind> attachment too large: <filename>]` marker proposed in NousResearch#125637, and `_inbound_media_marker` builds it so that the oversized-media path can use the same helper. The filename is the declared `filename`, or a body that contains only the transport filename once any reply fallback is removed. The event has no media URL or media type, so downstream tools do not treat the attachment as downloaded. `_mxc_to_http` has no other caller and is removed. Integration: use the shared filename rule and preserve the canonical sticker kind, retained reply context, mention claim and admission result. Oversize failures still take precedence. The current SDK never returns None, so the file failure fixture uses the subsequent source timeout correction at the streamed download boundary. The affected gate passes 319 tests; four original failure cases were reproduced before the fix. (cherry picked from commit cdc5ce6)
iainlane
added a commit
to iainlane/hermes-agent
that referenced
this pull request
Oct 9, 2026
When the Matrix adapter could not download or cache an inbound attachment for a reason other than its size, such as a network error, an empty download or bytes that the image cache rejects, it gave the agent the attachment's `/_matrix/client/v1/media/download/...` URL instead of a cached file. That endpoint requires the bot's access token, so the agent's attempts to fetch the URL failed, and the model could repeat the URL in its reply. Encrypted media had no URL to fall back to: the event kept its media message type with no attachment, and nothing told the agent that the user had sent one. When the download or cache step produces no file, the adapter now sends a text event with the user's caption followed by a marker such as `[matrix image attachment could not be downloaded: photo.png]`. The marker has the same shape as the `[matrix <kind> attachment too large: <filename>]` marker proposed in NousResearch#125637, and `_inbound_media_marker` builds it so that the oversized-media path can use the same helper. The filename is the declared `filename`, or a body that contains only the transport filename once any reply fallback is removed. The event has no media URL or media type, so downstream tools do not treat the attachment as downloaded. `_mxc_to_http` has no other caller and is removed. Integration: use the shared filename rule and preserve the canonical sticker kind, retained reply context, mention claim and admission result. Oversize failures still take precedence. The current SDK never returns None, so the file failure fixture uses the subsequent source timeout correction at the streamed download boundary. The affected gate passes 319 tests; four original failure cases were reproduced before the fix. (cherry picked from commit cdc5ce6)
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. On the earlier Matrix prerequisite, prebuilt Client stubs also discarded the supplied API. Use the same scoped constructor for the inherited fixtures so disconnect closes the adapter-created session. This is the earlier fixture layout; all original assertions remain unchanged.
Matrix requests can outlive gateway teardown. A stopped but open owner loop accepts a threadsafe coroutine, yet cannot run its cancellation callback. The tool's deadline then waits indefinitely for the owner to drain. A result may also be complete before the stopped loop copies it back to the worker. Reject unavailable loops before admission and monitor loop availability while waiting for both the result and cancellation. Keep a confirmed owner-task result. Report an unknown outcome if a write was dispatched before loop loss, and return a normal unavailable-loop error otherwise. Cancellation remains queued for the owner and sets the existing interrupt flag, so an operation cannot resume into a new write after recovery. A real separate-loop contract reproduces four failures before this change and preserves the confirmed-result control. All five cases and the adjacent administration and pin tests pass, 264 tests in four files.
iainlane
added a commit
to iainlane/hermes-agent
that referenced
this pull request
Oct 9, 2026
When the Matrix adapter could not download or cache an inbound attachment for a reason other than its size, such as a network error, an empty download or bytes that the image cache rejects, it gave the agent the attachment's `/_matrix/client/v1/media/download/...` URL instead of a cached file. That endpoint requires the bot's access token, so the agent's attempts to fetch the URL failed, and the model could repeat the URL in its reply. Encrypted media had no URL to fall back to: the event kept its media message type with no attachment, and nothing told the agent that the user had sent one. When the download or cache step produces no file, the adapter now sends a text event with the user's caption followed by a marker such as `[matrix image attachment could not be downloaded: photo.png]`. The marker has the same shape as the `[matrix <kind> attachment too large: <filename>]` marker proposed in NousResearch#125637, and `_inbound_media_marker` builds it so that the oversized-media path can use the same helper. The filename is the declared `filename`, or a body that contains only the transport filename once any reply fallback is removed. The event has no media URL or media type, so downstream tools do not treat the attachment as downloaded. `_mxc_to_http` has no other caller and is removed. Integration: use the shared filename rule and preserve the canonical sticker kind, retained reply context, mention claim and admission result. Oversize failures still take precedence. The current SDK never returns None, so the file failure fixture uses the subsequent source timeout correction at the streamed download boundary. The affected gate passes 319 tests; four original failure cases were reproduced before the fix. (cherry picked from commit cdc5ce6)
iainlane
added a commit
to iainlane/hermes-agent
that referenced
this pull request
Oct 9, 2026
When the Matrix adapter could not download or cache an inbound attachment for a reason other than its size, such as a network error, an empty download or bytes that the image cache rejects, it gave the agent the attachment's `/_matrix/client/v1/media/download/...` URL instead of a cached file. That endpoint requires the bot's access token, so the agent's attempts to fetch the URL failed, and the model could repeat the URL in its reply. Encrypted media had no URL to fall back to: the event kept its media message type with no attachment, and nothing told the agent that the user had sent one. When the download or cache step produces no file, the adapter now sends a text event with the user's caption followed by a marker such as `[matrix image attachment could not be downloaded: photo.png]`. The marker has the same shape as the `[matrix <kind> attachment too large: <filename>]` marker proposed in NousResearch#125637, and `_inbound_media_marker` builds it so that the oversized-media path can use the same helper. The filename is the declared `filename`, or a body that contains only the transport filename once any reply fallback is removed. The event has no media URL or media type, so downstream tools do not treat the attachment as downloaded. `_mxc_to_http` has no other caller and is removed. Integration: use the shared filename rule and preserve the canonical sticker kind, retained reply context, mention claim and admission result. Oversize failures still take precedence. The current SDK never returns None, so the file failure fixture uses the subsequent source timeout correction at the streamed download boundary. The affected gate passes 319 tests; four original failure cases were reproduced before the fix. (cherry picked from commit cdc5ce6)
iainlane
added a commit
to iainlane/hermes-agent
that referenced
this pull request
Oct 9, 2026
When the Matrix adapter could not download or cache an inbound attachment for a reason other than its size, such as a network error, an empty download or bytes that the image cache rejects, it gave the agent the attachment's `/_matrix/client/v1/media/download/...` URL instead of a cached file. That endpoint requires the bot's access token, so the agent's attempts to fetch the URL failed, and the model could repeat the URL in its reply. Encrypted media had no URL to fall back to: the event kept its media message type with no attachment, and nothing told the agent that the user had sent one. When the download or cache step produces no file, the adapter now sends a text event with the user's caption followed by a marker such as `[matrix image attachment could not be downloaded: photo.png]`. The marker has the same shape as the `[matrix <kind> attachment too large: <filename>]` marker proposed in NousResearch#125637, and `_inbound_media_marker` builds it so that the oversized-media path can use the same helper. The filename is the declared `filename`, or a body that contains only the transport filename once any reply fallback is removed. The event has no media URL or media type, so downstream tools do not treat the attachment as downloaded. `_mxc_to_http` has no other caller and is removed. Integration: use the shared filename rule and preserve the canonical sticker kind, retained reply context, mention claim and admission result. Oversize failures still take precedence. The current SDK never returns None, so the file failure fixture uses the subsequent source timeout correction at the streamed download boundary. The affected gate passes 319 tests; four original failure cases were reproduced before the fix. (cherry picked from commit cdc5ce6)
iainlane
added a commit
to iainlane/hermes-agent
that referenced
this pull request
Oct 9, 2026
When the Matrix adapter could not download or cache an inbound attachment for a reason other than its size, such as a network error, an empty download or bytes that the image cache rejects, it gave the agent the attachment's `/_matrix/client/v1/media/download/...` URL instead of a cached file. That endpoint requires the bot's access token, so the agent's attempts to fetch the URL failed, and the model could repeat the URL in its reply. Encrypted media had no URL to fall back to: the event kept its media message type with no attachment, and nothing told the agent that the user had sent one. When the download or cache step produces no file, the adapter now sends a text event with the user's caption followed by a marker such as `[matrix image attachment could not be downloaded: photo.png]`. The marker has the same shape as the `[matrix <kind> attachment too large: <filename>]` marker proposed in NousResearch#125637, and `_inbound_media_marker` builds it so that the oversized-media path can use the same helper. The filename is the declared `filename`, or a body that contains only the transport filename once any reply fallback is removed. The event has no media URL or media type, so downstream tools do not treat the attachment as downloaded. `_mxc_to_http` has no other caller and is removed. Integration: use the shared filename rule and preserve the canonical sticker kind, retained reply context, mention claim and admission result. Oversize failures still take precedence. The current SDK never returns None, so the file failure fixture uses the subsequent source timeout correction at the streamed download boundary. The affected gate passes 319 tests; four original failure cases were reproduced before the fix. (cherry picked from commit cdc5ce6)
iainlane
added a commit
to iainlane/hermes-agent
that referenced
this pull request
Oct 9, 2026
When the Matrix adapter could not download or cache an inbound attachment for a reason other than its size, such as a network error, an empty download or bytes that the image cache rejects, it gave the agent the attachment's `/_matrix/client/v1/media/download/...` URL instead of a cached file. That endpoint requires the bot's access token, so the agent's attempts to fetch the URL failed, and the model could repeat the URL in its reply. Encrypted media had no URL to fall back to: the event kept its media message type with no attachment, and nothing told the agent that the user had sent one. When the download or cache step produces no file, the adapter now sends a text event with the user's caption followed by a marker such as `[matrix image attachment could not be downloaded: photo.png]`. The marker has the same shape as the `[matrix <kind> attachment too large: <filename>]` marker proposed in NousResearch#125637, and `_inbound_media_marker` builds it so that the oversized-media path can use the same helper. The filename is the declared `filename`, or a body that contains only the transport filename once any reply fallback is removed. The event has no media URL or media type, so downstream tools do not treat the attachment as downloaded. `_mxc_to_http` has no other caller and is removed. Integration: use the shared filename rule and preserve the canonical sticker kind, retained reply context, mention claim and admission result. Oversize failures still take precedence. The current SDK never returns None, so the file failure fixture uses the subsequent source timeout correction at the streamed download boundary. The affected gate passes 319 tests; four original failure cases were reproduced before the fix. (cherry picked from commit cdc5ce6)
iainlane
added a commit
to iainlane/hermes-agent
that referenced
this pull request
Oct 9, 2026
When the Matrix adapter could not download or cache an inbound attachment for a reason other than its size, such as a network error, an empty download or bytes that the image cache rejects, it gave the agent the attachment's `/_matrix/client/v1/media/download/...` URL instead of a cached file. That endpoint requires the bot's access token, so the agent's attempts to fetch the URL failed, and the model could repeat the URL in its reply. Encrypted media had no URL to fall back to: the event kept its media message type with no attachment, and nothing told the agent that the user had sent one. When the download or cache step produces no file, the adapter now sends a text event with the user's caption followed by a marker such as `[matrix image attachment could not be downloaded: photo.png]`. The marker has the same shape as the `[matrix <kind> attachment too large: <filename>]` marker proposed in NousResearch#125637, and `_inbound_media_marker` builds it so that the oversized-media path can use the same helper. The filename is the declared `filename`, or a body that contains only the transport filename once any reply fallback is removed. The event has no media URL or media type, so downstream tools do not treat the attachment as downloaded. `_mxc_to_http` has no other caller and is removed. Integration: use the shared filename rule and preserve the canonical sticker kind, retained reply context, mention claim and admission result. Oversize failures still take precedence. The current SDK never returns None, so the file failure fixture uses the subsequent source timeout correction at the streamed download boundary. The affected gate passes 319 tests; four original failure cases were reproduced before the fix. (cherry picked from commit cdc5ce6)
iainlane
added a commit
to iainlane/hermes-agent
that referenced
this pull request
Oct 9, 2026
When the Matrix adapter could not download or cache an inbound attachment for a reason other than its size, such as a network error, an empty download or bytes that the image cache rejects, it gave the agent the attachment's `/_matrix/client/v1/media/download/...` URL instead of a cached file. That endpoint requires the bot's access token, so the agent's attempts to fetch the URL failed, and the model could repeat the URL in its reply. Encrypted media had no URL to fall back to: the event kept its media message type with no attachment, and nothing told the agent that the user had sent one. When the download or cache step produces no file, the adapter now sends a text event with the user's caption followed by a marker such as `[matrix image attachment could not be downloaded: photo.png]`. The marker has the same shape as the `[matrix <kind> attachment too large: <filename>]` marker proposed in NousResearch#125637, and `_inbound_media_marker` builds it so that the oversized-media path can use the same helper. The filename is the declared `filename`, or a body that contains only the transport filename once any reply fallback is removed. The event has no media URL or media type, so downstream tools do not treat the attachment as downloaded. `_mxc_to_http` has no other caller and is removed. Integration: use the shared filename rule and preserve the canonical sticker kind, retained reply context, mention claim and admission result. Oversize failures still take precedence. The current SDK never returns None, so the file failure fixture uses the subsequent source timeout correction at the streamed download boundary. The affected gate passes 319 tests; four original failure cases were reproduced before the fix. (cherry picked from commit cdc5ce6)
iainlane
added a commit
to iainlane/hermes-agent
that referenced
this pull request
Oct 9, 2026
When the Matrix adapter could not download or cache an inbound attachment for a reason other than its size, such as a network error, an empty download or bytes that the image cache rejects, it gave the agent the attachment's `/_matrix/client/v1/media/download/...` URL instead of a cached file. That endpoint requires the bot's access token, so the agent's attempts to fetch the URL failed, and the model could repeat the URL in its reply. Encrypted media had no URL to fall back to: the event kept its media message type with no attachment, and nothing told the agent that the user had sent one. When the download or cache step produces no file, the adapter now sends a text event with the user's caption followed by a marker such as `[matrix image attachment could not be downloaded: photo.png]`. The marker has the same shape as the `[matrix <kind> attachment too large: <filename>]` marker proposed in NousResearch#125637, and `_inbound_media_marker` builds it so that the oversized-media path can use the same helper. The filename is the declared `filename`, or a body that contains only the transport filename once any reply fallback is removed. The event has no media URL or media type, so downstream tools do not treat the attachment as downloaded. `_mxc_to_http` has no other caller and is removed. Integration: use the shared filename rule and preserve the canonical sticker kind, retained reply context, mention claim and admission result. Oversize failures still take precedence. The current SDK never returns None, so the file failure fixture uses the subsequent source timeout correction at the streamed download boundary. The affected gate passes 319 tests; four original failure cases were reproduced before the fix. (cherry picked from commit cdc5ce6)
iainlane
added a commit
to iainlane/hermes-agent
that referenced
this pull request
Oct 9, 2026
When the Matrix adapter could not download or cache an inbound attachment for a reason other than its size, such as a network error, an empty download or bytes that the image cache rejects, it gave the agent the attachment's `/_matrix/client/v1/media/download/...` URL instead of a cached file. That endpoint requires the bot's access token, so the agent's attempts to fetch the URL failed, and the model could repeat the URL in its reply. Encrypted media had no URL to fall back to: the event kept its media message type with no attachment, and nothing told the agent that the user had sent one. When the download or cache step produces no file, the adapter now sends a text event with the user's caption followed by a marker such as `[matrix image attachment could not be downloaded: photo.png]`. The marker has the same shape as the `[matrix <kind> attachment too large: <filename>]` marker proposed in NousResearch#125637, and `_inbound_media_marker` builds it so that the oversized-media path can use the same helper. The filename is the declared `filename`, or a body that contains only the transport filename once any reply fallback is removed. The event has no media URL or media type, so downstream tools do not treat the attachment as downloaded. `_mxc_to_http` has no other caller and is removed. Integration: use the shared filename rule and preserve the canonical sticker kind, retained reply context, mention claim and admission result. Oversize failures still take precedence. The current SDK never returns None, so the file failure fixture uses the subsequent source timeout correction at the streamed download boundary. The affected gate passes 319 tests; four original failure cases were reproduced before the fix. (cherry picked from commit cdc5ce6)
iainlane
added a commit
to iainlane/hermes-agent
that referenced
this pull request
Oct 10, 2026
When the Matrix adapter could not download or cache an inbound attachment for a reason other than its size, such as a network error, an empty download or bytes that the image cache rejects, it gave the agent the attachment's `/_matrix/client/v1/media/download/...` URL instead of a cached file. That endpoint requires the bot's access token, so the agent's attempts to fetch the URL failed, and the model could repeat the URL in its reply. Encrypted media had no URL to fall back to: the event kept its media message type with no attachment, and nothing told the agent that the user had sent one. When the download or cache step produces no file, the adapter now sends a text event with the user's caption followed by a marker such as `[matrix image attachment could not be downloaded: photo.png]`. The marker has the same shape as the `[matrix <kind> attachment too large: <filename>]` marker proposed in NousResearch#125637, and `_inbound_media_marker` builds it so that the oversized-media path can use the same helper. The filename is the declared `filename`, or a body that contains only the transport filename once any reply fallback is removed. The event has no media URL or media type, so downstream tools do not treat the attachment as downloaded. `_mxc_to_http` has no other caller and is removed. Integration: use the shared filename rule and preserve the canonical sticker kind, retained reply context, mention claim and admission result. Oversize failures still take precedence. The current SDK never returns None, so the file failure fixture uses the subsequent source timeout correction at the streamed download boundary. The affected gate passes 319 tests; four original failure cases were reproduced before the fix. (cherry picked from commit cdc5ce6)
iainlane
force-pushed
the
fix/matrix-oversized-media
branch
from
October 10, 2026 03:18
5ed5beb to
46c24f0
Compare
iainlane
added a commit
to iainlane/hermes-agent
that referenced
this pull request
Oct 10, 2026
When the Matrix adapter could not download or cache an inbound attachment for a reason other than its size, such as a network error, an empty download or bytes that the image cache rejects, it gave the agent the attachment's `/_matrix/client/v1/media/download/...` URL instead of a cached file. That endpoint requires the bot's access token, so the agent's attempts to fetch the URL failed, and the model could repeat the URL in its reply. Encrypted media had no URL to fall back to: the event kept its media message type with no attachment, and nothing told the agent that the user had sent one. When the download or cache step produces no file, the adapter now sends a text event with the user's caption followed by a marker such as `[matrix image attachment could not be downloaded: photo.png]`. The marker has the same shape as the `[matrix <kind> attachment too large: <filename>]` marker proposed in NousResearch#125637, and `_inbound_media_marker` builds it so that the oversized-media path can use the same helper. The filename is the declared `filename`, or a body that contains only the transport filename once any reply fallback is removed. The event has no media URL or media type, so downstream tools do not treat the attachment as downloaded. `_mxc_to_http` has no other caller and is removed. Integration: use the shared filename rule and preserve the canonical sticker kind, retained reply context, mention claim and admission result. Oversize failures still take precedence. The current SDK never returns None, so the file failure fixture uses the subsequent source timeout correction at the streamed download boundary. The affected gate passes 319 tests; four original failure cases were reproduced before the fix. (cherry picked from commit cdc5ce6)
iainlane
added a commit
to iainlane/hermes-agent
that referenced
this pull request
Oct 10, 2026
When the Matrix adapter could not download or cache an inbound attachment for a reason other than its size, such as a network error, an empty download or bytes that the image cache rejects, it gave the agent the attachment's `/_matrix/client/v1/media/download/...` URL instead of a cached file. That endpoint requires the bot's access token, so the agent's attempts to fetch the URL failed, and the model could repeat the URL in its reply. Encrypted media had no URL to fall back to: the event kept its media message type with no attachment, and nothing told the agent that the user had sent one. When the download or cache step produces no file, the adapter now sends a text event with the user's caption followed by a marker such as `[matrix image attachment could not be downloaded: photo.png]`. The marker has the same shape as the `[matrix <kind> attachment too large: <filename>]` marker proposed in NousResearch#125637, and `_inbound_media_marker` builds it so that the oversized-media path can use the same helper. The filename is the declared `filename`, or a body that contains only the transport filename once any reply fallback is removed. The event has no media URL or media type, so downstream tools do not treat the attachment as downloaded. `_mxc_to_http` has no other caller and is removed. Integration: use the shared filename rule and preserve the canonical sticker kind, retained reply context, mention claim and admission result. Oversize failures still take precedence. The current SDK never returns None, so the file failure fixture uses the subsequent source timeout correction at the streamed download boundary. The affected gate passes 319 tests; four original failure cases were reproduced before the fix. (cherry picked from commit cdc5ce6)
iainlane
added a commit
to iainlane/hermes-agent
that referenced
this pull request
Oct 10, 2026
When the Matrix adapter could not download or cache an inbound attachment for a reason other than its size, such as a network error, an empty download or bytes that the image cache rejects, it gave the agent the attachment's `/_matrix/client/v1/media/download/...` URL instead of a cached file. That endpoint requires the bot's access token, so the agent's attempts to fetch the URL failed, and the model could repeat the URL in its reply. Encrypted media had no URL to fall back to: the event kept its media message type with no attachment, and nothing told the agent that the user had sent one. When the download or cache step produces no file, the adapter now sends a text event with the user's caption followed by a marker such as `[matrix image attachment could not be downloaded: photo.png]`. The marker has the same shape as the `[matrix <kind> attachment too large: <filename>]` marker proposed in NousResearch#125637, and `_inbound_media_marker` builds it so that the oversized-media path can use the same helper. The filename is the declared `filename`, or a body that contains only the transport filename once any reply fallback is removed. The event has no media URL or media type, so downstream tools do not treat the attachment as downloaded. `_mxc_to_http` has no other caller and is removed. Integration: use the shared filename rule and preserve the canonical sticker kind, retained reply context, mention claim and admission result. Oversize failures still take precedence. The current SDK never returns None, so the file failure fixture uses the subsequent source timeout correction at the streamed download boundary. The affected gate passes 319 tests; four original failure cases were reproduced before the fix. (cherry picked from commit cdc5ce6)
iainlane
added a commit
to iainlane/hermes-agent
that referenced
this pull request
Oct 10, 2026
When the Matrix adapter could not download or cache an inbound attachment for a reason other than its size, such as a network error, an empty download or bytes that the image cache rejects, it gave the agent the attachment's `/_matrix/client/v1/media/download/...` URL instead of a cached file. That endpoint requires the bot's access token, so the agent's attempts to fetch the URL failed, and the model could repeat the URL in its reply. Encrypted media had no URL to fall back to: the event kept its media message type with no attachment, and nothing told the agent that the user had sent one. When the download or cache step produces no file, the adapter now sends a text event with the user's caption followed by a marker such as `[matrix image attachment could not be downloaded: photo.png]`. The marker has the same shape as the `[matrix <kind> attachment too large: <filename>]` marker proposed in NousResearch#125637, and `_inbound_media_marker` builds it so that the oversized-media path can use the same helper. The filename is the declared `filename`, or a body that contains only the transport filename once any reply fallback is removed. The event has no media URL or media type, so downstream tools do not treat the attachment as downloaded. `_mxc_to_http` has no other caller and is removed. Integration: use the shared filename rule and preserve the canonical sticker kind, retained reply context, mention claim and admission result. Oversize failures still take precedence. The current SDK never returns None, so the file failure fixture uses the subsequent source timeout correction at the streamed download boundary. The affected gate passes 319 tests; four original failure cases were reproduced before the fix. (cherry picked from commit cdc5ce6)
iainlane
added a commit
to iainlane/hermes-agent
that referenced
this pull request
Oct 11, 2026
When the Matrix adapter could not download or cache an inbound attachment for a reason other than its size, such as a network error, an empty download or bytes that the image cache rejects, it gave the agent the attachment's `/_matrix/client/v1/media/download/...` URL instead of a cached file. That endpoint requires the bot's access token, so the agent's attempts to fetch the URL failed, and the model could repeat the URL in its reply. Encrypted media had no URL to fall back to: the event kept its media message type with no attachment, and nothing told the agent that the user had sent one. When the download or cache step produces no file, the adapter now sends a text event with the user's caption followed by a marker such as `[matrix image attachment could not be downloaded: photo.png]`. The marker has the same shape as the `[matrix <kind> attachment too large: <filename>]` marker proposed in NousResearch#125637, and `_inbound_media_marker` builds it so that the oversized-media path can use the same helper. The filename is the declared `filename`, or a body that contains only the transport filename once any reply fallback is removed. The event has no media URL or media type, so downstream tools do not treat the attachment as downloaded. `_mxc_to_http` has no other caller and is removed. Integration: use the shared filename rule and preserve the canonical sticker kind, retained reply context, mention claim and admission result. Oversize failures still take precedence. The current SDK never returns None, so the file failure fixture uses the subsequent source timeout correction at the streamed download boundary. The affected gate passes 319 tests; four original failure cases were reproduced before the fix. (cherry picked from commit cdc5ce6)
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 in this section target
main. Nesting records logical merge order. A parent must merge before its child. Linked descriptions list any additional prerequisites.Oversized Matrix attachments reach the conversation as a text marker with their caption and filename. The adapter uses the smaller Matrix and gateway limit. It stops streamed downloads when that limit is exceeded, including media with omitted or inaccurate size metadata.
Quoted images use the same bounded download path. The quoted-image consumer passes the required limit, so accepted images remain available in reply context.
Luyifan authored the original feature. All nine original feature and follow-up commits preserve the full author and committer identities, dates, timezones, messages and order. This update composes those records on the verified media prerequisite without changing their source or assertions.
Review scope
Merge the prerequisites in the Stack section first. Review this nine-record layer with the scoped comparison. The 154 signed prerequisite records remain unchanged. Only the nine commits in this layer have new signatures.
Verification
The canonical runner passes 702 cases across five complete gateway files, with four workers, no retries and strict resource, runtime, unraisable-exception and unhandled-thread warnings. All 9237 tracked Python, configuration, lock and runner inputs match the exact candidate before and after execution.
Three complete files with 288 cases are resource-clean. The complete effective-event-state file (180 cases) and turn-context file (234 cases) emit 28 and 254 database finalization warnings after passing, respectively. Both complete files reproduce exactly the same warnings on the original published PR head. Those results are qualified; the 702-case result is not resource-clean.
A fresh native run passes all three cases in the complete media-caption module, including declared oversized metadata and an omitted-size stream against a ten-byte gateway cap. A separate Matrix client sends each event through a disposable homeserver and the real gateway. The model receives the caption and filename marker without a cached oversized attachment. The accepted caption and cached-file path also pass on this candidate. All setup, call and teardown phases pass without resource warnings, retries or skips. The native run binds 8427 actual Python and runner inputs, with package and lock inputs bound separately to its newly built package-managed image.
Ruff passes across all nine Python paths in this layer. The full type comparison reports 92 diagnostics on the original, 90 on the prerequisite and 91 on the candidate. Every candidate diagnostic matches a complete original or prerequisite occurrence; there are no introduced diagnostics. The same nine inherited formatter paths remain. Committed whitespace checks pass.
The exact candidate merges cleanly with the fixed campaign validation base,
3637c512fc3211bdccb61b7140d09ac1018508ba. These are local results; hosted CI execution and maintainer approval remain separate.