Skip to content

feat(matrix): expose source permalinks per turn - #125738

Draft
iainlane wants to merge 27 commits into
NousResearch:mainfrom
iainlane:feat/matrix-source-permalinks
Draft

iainlane wants to merge 27 commits into
NousResearch:mainfrom
iainlane:feat/matrix-source-permalinks

Conversation

@iainlane

@iainlane iainlane commented Sep 27, 2026 •

Copy link
Copy Markdown

Stack

Nesting specifies logical merge order. All PRs target main, and every prerequisite must merge before its dependent.

Summary

Give the agent a canonical matrix.to link to the Matrix event that triggered its current turn. In a thread, the link points at the triggering reply; the session prompt already gives the thread root. The link reaches the model as a [Matrix source: <link>] note at the start of the current user turn. It is left out of the persisted user text and out of the pinned system prompt, so the cached prompt prefix does not change from turn to turn.

Room and event IDs are percent-encoded, so URL delimiters cannot change the link. Each link has up to three via servers, chosen as the Matrix specification recommends for room-ID permalinks. The first server is that of the joined member with the highest power level, if that level is at least 50; room version 12 creators count as the highest power. The other servers are those with the most joined members. 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, the link uses the bot's server and then the server of the room ID, including any port.

Other platforms

  • SessionSource gains an optional source_permalink field. It is serialized only when set.
  • strip_discord_triggering_note becomes strip_inbound_source_note, which removes either the Discord triggering-message note or the Matrix source note from the persisted user text. The Discord note and its handling are unchanged, and the Discord note-persistence tests pass.
  • _prepend_inbound_reply_context in the gateway core gains a Matrix branch next to the existing Discord branch. It adds the note only when the event has a message ID and a permalink.

Configuration and documentation

No configuration or documentation changes.

Redactions

The note is left out under effective PII redaction. Matrix is not a PII-safe platform, so that condition is never true for Matrix today, and the system prompt already shows raw Matrix room IDs.

This PR does not handle Matrix event redactions. #126282 records redactions, so reads, replies and catch-up show a redacted event as [redacted], which covers the event that a link points to. Links in earlier turns are not rewritten, because rewriting them would change replayed history and invalidate the prompt cache.

Contributor credit

This PR preserves @pretyflaco's authored commit (f47cb46dd7) from #119208, which identified the missing source link. The follow-up commits move the changing link out of the cached system prompt, link the triggering event in threads, correct the URL encoding and choose via servers from room state.

Current verification

I resolved the concrete source-note conflict against the captured upstream revision 3637c512fc3211bdccb61b7140d09ac1018508ba and rebased the current contribution history onto that revision. The complete platform-specific note removal remains intact; removeprefix incorporates the upstream equivalent prefix-removal cleanup. The fourteen existing live-test foundation contributions and twelve current source-permalink contributions preserve every author, committer, date, full original message prefix and order. The final formatting contribution remains as a credited empty record because upstream already supplies its formatting.

The earlier Matrix test fixture discarded the real HTTP session supplied by the adapter. The existing published session-maintenance correction is propagated as a separate credited commit. It preserves that session, binds the existing thirteen prebuilt fake clients to their supplied HTTP API, and closes the replacement reconnect client. All original assertions and production behaviour remain unchanged. The later fourteenth constructor testcase is absent from this source and is not added.

The focused SDK validation contains 177 passes across eight complete files with all four strict warning categories and retries disabled. Seven unchanged files contribute 54 resource-clean cases from the first exact-source run. The final complete Matrix file contributes 123 resource-clean cases after the narrow fixture correction; its preceding ten lifecycle failures are retained as the control. All 8,374 captured Python and runner inputs match their actual recorded trees, and only the replaced Matrix test module differs between those runs. This is a source-qualified union, not a repeated whole-eight invocation on the final tree.

The full scoped Python comparison contains 506 complete lexical and defining-statement diagnostics, including relative spans, columns and occurrence counts. Every finding matches the original public scope or the captured upstream scope. Scoped Ruff and changed-line whitespace checks pass. The formatter reports 18 existing paths; every path also occurs in the original or captured upstream scope. These checks do not claim a globally clean type check. Historical native-service and hosted CI evidence remains associated with its original revisions.

Review scope and merge order

Merge both listed prerequisites first. The following comparison isolates the twelve source-permalink contributions and the credited fixture-maintenance commit above the rebased fourteen-record live-test foundation.

Review this layer

Known limitations and follow-ups

  • The via selection does not apply the room's server ACL, although the specification says that servers denied by m.room.server_acl should never be chosen. The mautrix state store does not keep m.room.server_acl, and an ACL does not remove existing memberships, so a denied server can still be chosen while its users remain joined. A reader's server tries the via servers in order, so it can usually join through a later one. Filtering the candidates against the ACL is a follow-up.
  • A Matrix message that is redacted while its turn is queued still runs with its text and link. Neither this PR nor fix(matrix): expose effective edits and redactions #126282 handles that case.

Historical verification

The test results below were recorded during the earlier campaign at the revisions described here. They have not been rerun for this history and description cleanup.

All runs used scripts/run_tests.sh with mautrix 0.21.1 importable and no retries.

  • Ten of the sixteen permalink cases failed on the previous implementation and pass now. The failures included a list emitted as a single via value, a thread reply that linked the root, a fallback with only one via server, an IP-literal bot server, and four routing cases that ignored room state.
  • At 72e5e34f94, the last non-merge commit of this layer, 39 files pass 648 tests with 1 skipped: every file that constructs the Matrix adapter, mentions source permalinks or imports the Matrix plugin, plus the session, prompt-freeze and Discord note-persistence files.
  • At 08afe32e63, the 25 test files that construct the Matrix adapter pass 422 tests with 1 skipped.
  • A separate matrix-nio client sends a real Matrix event through disposable Synapse (test_plain_exchange.py). The fake model receives the exact permalink, with via=matrix.test from the state store, in the current turn, and no system message contains the link. The test passed at 08afe32e63 in 124.7 seconds, including the image build.
  • Ruff and git diff --check pass.

@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins platform/matrix Matrix adapter (E2EE) sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Sep 27, 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.

Keep the platform-specific transport note removal when integrating the
required upstream prefix-removal cleanup. The current-turn Matrix source
link remains outside the authored text in the durable user transcript.
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.
Matrix changes lacked a live exchange test, so mocked adapter tests
could miss failures between Synapse, gateway setup, and model-visible
context.

Start disposable Synapse and a checkout-built Linux gateway through
Testcontainers. Drive events with a separate matrix-nio account and a
fake model under an isolated HERMES_HOME. Provision a Linux nio client
with its own encrypted device store so later encrypted cases can also
run from macOS hosts.

Run the suite in a dedicated CI job through scripts/run_tests.sh. The
plain-room test confirms both the reply and the inbound model context.
Synapse changes the owner of /data to UID 991 when it generates its
configuration. On a Linux runner, the host test process cannot append
to the resulting homeserver.yaml. Store Synapse data in a Docker volume
and edit the configuration from a one-shot container.

The test runner can kill a timed-out file worker before pytest tears
down its fixtures. Enable Testcontainers' Ryuk reaper, pin its image by
digest, and start it before creating the volume. Label the Synapse
volume with the session ID; Testcontainers labels its network and
containers. Ryuk can then remove these resources when the worker exits.
Parallel Matrix live tests built different checkouts under the same
hermes-matrix-live:checkout tag. A second build could replace the tag
between the first build and its container start, so a green test might
exercise the wrong code.

Give each fixture invocation a UUID image tag and remove that exact tag
after its containers stop. Concurrent branches can now run the live
suite without changing which checkout an existing fixture starts.
A file timeout kills the pytest worker and its build process without
running fixture teardown. The unique image tag from the live test can
then remain on the Docker host indefinitely.

Start Ryuk before building and label the checkout image with the same
Testcontainers session ID as its containers, network and volume. Ryuk
can remove the image after the worker dies. A SIGKILL probe confirmed
that the pinned Ryuk image removes a labelled Docker image.
Matrix live tests built the production image for each checkout. That
build prepares SQLite, Chromium, Node and the UI even though the tests
start only a gateway. The separate Linux observer also built a second
Python environment at test setup time to obtain matrix-nio crypto.

Build a small Debian image with the PM-pinned Python and the locked
Matrix and test dependencies before copying the checkout source. The
gateway and observer use the same prepared interpreter in separate
containers, while the gateway still runs against an isolated home and
the host fake model. Keep gateway logs in timeout failures so startup
problems remain diagnosable.

The plain-room test passed with a 1.545-second test body and 26.8
seconds for the complete test file. On this host, an uncached image
build took 128.1 seconds and a cached rebuild took 4.67 seconds.
The live plain-room test requested a one-second Synapse poll and
allowed a minute before reporting a missing reply. Matrix-nio can
retry a sync request beyond that loop deadline, so a stalled
exchange could take even longer to fail.

Request a 250 ms server wait and bound each sync by the remaining
15-second deadline. Successful replies still arrive through the
real Matrix event stream. The separate-client Synapse test passed.
The approval client sends cards, seed reactions and requester controls in
quick succession. Synapse's default account event limit exhausts its
burst and rejects the controls before the short client deadline.

Configure the disposable server for 100 events per second with a burst
of 100. The limit remains finite, and tests can exercise protocol events
without waiting for the production rate limiter to refill. Client waits,
retry rules and assertions are unchanged.
The Matrix live fixtures skipped with "require a running Docker
daemon" on macOS installations whose daemon is reachable only through
a Docker CLI context, such as Docker Desktop or Colima without the
/var/run/docker.sock symlink. The fixture replaced DOCKER_CONFIG with
an empty configuration to keep registry credentials away from the
tests. That also discarded currentContext and the context metadata, so
the Docker SDK, Testcontainers and the docker CLI all fell back to
/var/run/docker.sock. scripts/run_tests.sh removed DOCKER_HOST,
DOCKER_CONTEXT and DOCKER_CONFIG from the test environment, so a user
could not select the socket explicitly either.

The fixture now resolves the endpoint the same way the docker CLI
does (DOCKER_HOST, then DOCKER_CONTEXT, then currentContext) before it
isolates the configuration. It exports the result as DOCKER_HOST and
removes DOCKER_CONTEXT, so the SDK probe, Testcontainers and the
buildx build all connect to that endpoint. The isolated configuration
keeps the CLI plugin directories, because Docker Desktop and Homebrew
install buildx outside the default search path. It still contains no
registry credentials or credential helpers.

On macOS the daemon runs in a Linux VM and resolves bind-mount sources
there. Testcontainers mounts the client-side socket path into Ryuk by
default, which fails when that path is a host socket such as a
context's. The fixture therefore mounts /var/run/docker.sock, the
socket inside the VM, into Ryuk on macOS.

The runner forwards DOCKER_HOST, DOCKER_CONTEXT and DOCKER_CONFIG. It
still drops DOCKER_AUTH_CONFIG, DOCKER_CERT_PATH and DOCKER_TLS_VERIFY.

With a scratch context whose socket forwards to OrbStack and counts
connections, the plain exchange passed with 70 API connections through
that socket. The previous fixture passed the same invocation with no
connections through it, because it used /var/run/docker.sock instead.
A context pointing at a missing socket now skips with a message that
includes the socket path.
The last dependency step of the test Dockerfile imports olm,
mautrix.crypto and nio, so an image without Matrix crypto support
already fails to build, and the fixture reports the build log. The
fixture then started a container from the finished image only to run
the same imports again. That check could not fail, and it added a
container start to every test module.
The gateway_image fixture built the Linux gateway image inside the
pytest worker. run_tests_parallel.py kills a worker when the per-file
timeout expires, so the cold build, the Synapse start-up and the
exchange all had to fit in the 300-second per-file timeout that the
workflow sets. The 45-minute job limit and the build's own
1800-second timeout could never take effect.
A cold build of test_plain_exchange.py on OrbStack took 242 seconds of
that budget.

Move the build command to tests/integration/matrix_live/image_build.py.
The fixture still uses it, so a local run builds a fresh image and
removes it afterwards as before. The workflow runs the same module in
a step before the tests and passes the tag in
HERMES_TEST_MATRIX_IMAGE. When that variable is set, the fixture uses
the image, checks that the daemon has it, and leaves it in place for
the next test file. run_tests.sh forwards the variable, in the same way
that it forwards HERMES_TEST_IMAGE for tests/docker. The build no
longer has its own timeout: in the fixture the per-file timeout limits
it, and in CI the job limit does.

Drop the Buildx setup step. The fixture replaces DOCKER_CONFIG, where
Buildx records the builder that the step creates, so every build used
the daemon's default builder anyway.

The pull_request path filter listed the production Dockerfile, which
the live tests no longer use, and omitted most of the code that runs
in the gateway. Filter on the code that the image copies and runs
(the root modules, agent, cron, gateway, hermes_cli, hermes_platform,
pm and tools), .dockerignore, the test runner and its setup action.

With a prebuilt image, test_plain_exchange.py passed in 70 seconds.
The gateway container ran as UID 10000 and wrote into a bind-mounted
home that the fixture made world-writable. With rootful Docker on
Linux, the directories that the gateway creates, such as logs/, belong
to UID 10000 with mode 0755, so the runner's user cannot delete the
files in them. run_tests_parallel.py ignores errors when it removes its
temporary root, so each test left a tree under
/var/tmp/hermes-pytest-<uid>/ that only root could remove. macOS was
not affected, because OrbStack and Docker Desktop map bind-mount
ownership to the host user.

Run the gateway as the host UID and GID and keep the home's default
mode. The image has no passwd entry for that UID, so set HOME to the
home that the hermes user had. At teardown the fixture removes the home
itself, so a file that the runner cannot delete fails the test instead
of accumulating on the host. Windows has no host UID; Docker Desktop
does not enforce ownership on bind mounts there, so the gateway keeps
running as UID 10000.

On OrbStack's Linux kernel, a home written by UID 10000 could not be
removed by UID 501 ("Permission denied" on logs/gateway.log), and a
home written as 501:20 could. The plain exchange passes as the host
user on macOS.
The fake model server for the live gateway listened on 0.0.0.0, so any
machine on the developer's network could send it requests while a test
ran. A stray request would consume the scripted reply or appear in
main_requests(), and the macOS firewall can ask whether to accept
incoming connections.

The gateway container reaches the fake as host.docker.internal. With a
daemon on this host, as on Linux, the test network's bridge gateway is
an address of this host, so the fake now listens on that address and
host.docker.internal resolves to it. A daemon in a VM, such as Docker
Desktop or OrbStack, does not expose that address on the host, and its
host-gateway forwards to the host's loopback interface, so the fake
listens on 127.0.0.1 there. The fixture chooses by trying to bind the
gateway address.

In OrbStack's Linux VM, a server bound to a network's gateway address
answered a container on that network through the host.docker.internal
mapping. On macOS the bind fails with EADDRNOTAVAIL, and the plain
exchange passes over 127.0.0.1.
iainlane and others added 3 commits October 1, 2026 12:20
The gateway fixture yielded as soon as the gateway logged
"Matrix: joined <room>". The Matrix adapter joins the invited room
shortly after it connects, while the gateway is still in startup
restore. During startup restore the gateway queues inbound messages and
replays them when restore finishes, so a test's first message could
reach the gateway through that replay. A scratch live test that sent
its message as soon as the fixture yielded was queued in two of three
runs.

The replay can reach the adapter while the adapter's original dispatch
of the same event still holds the session guard: for Matrix, the guard
stays held during the typing-stop request after the handler has queued
the event. The adapter then handles the replay as a second message
while the session is busy, and the bot's first reply is the "Interrupting
current task" notice. One native run of the oversized-media test failed
this way.

The gateway logs "Press Ctrl+C to stop" after startup restore has
finished. The fixture now waits for that line as well as for the join,
so a test's first message is dispatched directly. With this check the
same scratch test was dispatched directly in three of three runs.

When a test that uses the fixture fails, a report hook adds the last 200
lines of the gateway log to the failure report. The fixture deletes the
gateway home at teardown, so the log of a failed run was lost before.
LiveGateway now includes the home so that the hook can find the log.
_host_user() compared hermes_platform's os_family() with "windows", but
os_family() returns sys.platform, which is "win32" on Windows. The
Windows branch therefore never ran, and on a Windows host with Docker
the gateway fixture called os.getuid(), which Windows does not have.
Compare with "win32", as the conftest already does with "darwin".

The matrix-live workflow's path filter omitted code that the gateway
image copies and loads at run time: providers/, which agent/ imports;
plugins/ outside the Matrix adapter (the plugin loader, model
providers, memory and context engines); locales/, which agent/i18n.py
reads; tui_gateway/, which gateway/ imports; and skills/, which the
gateway syncs into its home at start-up. A change to any of them could
break the live tests without running the workflow. Add all five.

Rename HERMES_TEST_MATRIX_IMAGE to HERMES_TEST_MATRIX_GATEWAY_IMAGE.
The suite uses Synapse, Ryuk and gateway images, and "Matrix image"
reads most naturally as the homeserver.
…context

Agents acting on Matrix conversations (filing GitHub issues, writing docs,
opening tickets) had no way to cite the origin: the session-context prompt
exposed only the raw room ID and thread-root event ID, with no server name
and no permalink — and no matrix.to room/event permalink builder existed
anywhere in the tree. Agents correctly refused to invent links and reported
the exact permalink as unavailable.

- SessionSource: new optional source_permalink, wire-serialized via the
  _OPTIONAL_POST_SCOPE field list (truthy-only, backward compatible)
- build_source(): pass-through kwarg so adapters can populate it
- MatrixAdapter._build_source_permalink(): canonical
  https://matrix.to/#/<room>/<thread-root-or-event>?via=<server> — thread
  root preferred as the stable anchor (same convention as the thread
  targeting form in NousResearch#19900), triggering event fallback, via derived from
  the room ID when the identity lacks a server name
- build_session_context_prompt(): emit 'Matrix Source' in the Matrix block,
  suppressed under effective PII redaction (the permalink embeds the raw
  room/event IDs that mode hashes)

The field is platform-agnostic so Slack permalinks / Discord jump links can
follow the same path later.

Tests: permalink builder (thread root, event fallback, via derivation,
no-event), adapter wiring via _resolve_message_context, prompt
emission/omission/redaction, SessionSource roundtrip.

(cherry picked from commit fc4f021)
Four routing tests pass a recording transport to RelayAdapter, whose
transport parameter requires the RelayTransport protocol. The fixture
only implements its inbound setter and outbound send, so the four
constructor calls fail static type checking even though their exercised
routing paths work.

Implement the complete structural protocol in the recording fixture.
Unused operations raise an assertion error so an unexpected call fails
the test. Keep the recorded action, platform and send result, and narrow
the captured dictionaries before the existing routing assertions.

The correction changes the inherited test fixture. It preserves the
existing contributor records and all original routing assertions.

Rebase the recording transport fixture onto the required upstream revision.
Keep its inbound handler and its refusal of unexpected connection calls.
The encrypted-file fixture returned a constant serialized payload, so
assigning its uploaded URL did not appear in later serialization. The
Matrix SDK serializes the URL stored on the encrypted-file object. The
adapter currently overwrites the outgoing URL, which concealed this
fixture difference in the existing upload test.

Read the fixture URL when serialization is called. Add an invariant
that compares the complete payload before and after URL assignment,
with only the URL overwritten. Keep the existing upload and routing
assertions.
The complete feature union uses the current SDK state-store types,
runner mixins and native admission signatures. Older fixtures used loose
module objects, incomplete protocol implementations and obsolete runner
fields, which obscured the intended behaviour during type checking.

Use the concrete SDK identifiers and serialized state types. Mock the
relay transport interface while preserving its advertised identities,
and keep the encrypted-file fixture's URL serialization consistent with
the actual SDK. Narrow optional lookup results before accessing them.

The retained quote contract now agrees with the existing reply-quote
exemption: mentions inside a leading reply quote remain literal. Include
the permalink transport note in the complete rich-content expectation.
All original payload, outcome and routing assertions remain present.

The affected union passes 1,437 cases with six skips. The final three
changed files pass 445 cases without retries; the other 22 files retain
identical test bytes from the wider run. All 77 changed Python paths pass
Ruff, and qualified type diagnostics introduce no new rule or message.
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.

Reuse the already published Client factory from a58c137 for the thirteen
existing fake constructors in this earlier producer. The factory binds
the supplied HTTP API to the prebuilt client so disconnect closes the
session created by the adapter. The later fourteenth constructor test is
absent here; no testcase or production code is added.

This branch has not been deployed

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

Labels

comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have platform/matrix Matrix adapter (E2EE) sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants