netresearch/matrix-communication
Agentic Skills for Matrix: client-side chat (matrix-communication), Synapse homeserver administration (matrix-administration), and structured announcement composition (matrix-announcement). Works with any Matrix/Synapse homeserver.
Changelog
All notable changes to this project are documented here. The format follows Keep a Changelog, and this project adheres to Semantic Versioning.
For the canonical narrative version of each release (rewritten after CI publishes the auto-generated notes), see the matching entry on the releases page.
Unreleased
3.1.8 - 2026-09-27
Fixed
matrix-announcement,text-templates.md: the gate-output template no longer says<details>is off the Matrix allow-list and stripped by clients.<details>/<summary>are on the Matrix spec's suggested tag set and kept by Element web's sanitizer, which renders a collapsible block, so a long output may go inside<details>below the state line. The state, impact and next step stay outside it, and the output is still trimmed, because collapsing does not shorten thebodyfallback or the notification. Mobile client behaviour was not verified
Documentation
matrix-announcement,html-subset.md:<details>and<summary>are listed as allowed tags, with the rule to put the first line in<summary>and never hide the verdict inside itmatrix-communication,SKILL.md: a new "Asking a colleague" section — mention once with--mention, then follow the room withmatrix-watch.py(which needsmatrix-watchd.pyrunning;matrix-read-e2ee.pyreads once and does not wait for a reply). No automatic repeat pings: a second ping only when the principal asks again, and any repeating pinger stops at the colleague's first reactiontroubleshooting.md: the store'scredentials.json,access_tokenandadmin_tokengo stale independently, sowhoamieach copy before concluding anything about any of them; a doctor[FAIL] tokennames the label it rejectedtroubleshooting.mdandAGENTS.md: a non-empty but deadadmin_tokenshadows a live adminaccess_token— thesynapse-*scripts takeadmin_tokenfirst and never tryaccess_token, so every admin call returns401. Remove or replace the deadadmin_tokeninstead of reporting "no admin access"
3.1.7 - 2026-09-18
Fixed
- The three
skills/*/LICENSEsymlinks are removed. They were added on 2026-01-31 pointing at the repository'sLICENSE, and the split-licensing migration (df89fde, 2026-03-10) deleted that file without them — they have resolved to nothing since. The Claude marketplace importer reports one warning per skill for them. Licence terms are the rootLICENSE-MITandLICENSE-CC-BY-SA-4.0, which is where every other Netresearch skill repository keeps them; none ships a per-skillLICENSE.skills/matrix-announcement/README.mdno longer lists the removed file in its structure tree, where it was annotated as pointing atLICENSE-MIT— which it never did
3.1.6 - 2026-09-16
Documentation
references/synapse-admin-api.md: a matrix-hookshot generic webhook's id is not in room state. Room state shows that a room has a generic webhook and what it is called, but nothing you can post to — nohookId, no URL. The id is the credential, since the posting URL is<urlPrefix>/<hookId>and room state is readable by every member. Undocumented, the missing field reads as a broken bridge, and both obvious next guesses are wrong: "no database configured" does not imply the id is therefore in room state (on a hookshot whoseconfig.ymlhad no database or storage section it was still unreadable from Matrix), and asking the bot is a write visible to every member of a shared room. The section names the three places to look, cheapest first, including the non-obvious one — trace the sender, because whatever already posts to that room holds the full URL. The webhook-minting step is attributed to hookshot's documentation and marked untried
3.1.5 - 2026-09-12
Fixed
matrix-doctor.py: a rejected config token is no longer answered with "mint a new token" when the credentials store holds a credential the homeserver just confirmed. That case is a stale config entry, and the old advice sent the reader to re-authenticate something that was not broken —troubleshooting.mdhas said so since 3.1.x while the tool said the opposite.check_e2ee_setupnow runs first and its verdict is passed tocheck_tokenasstore_ok, where it changes the remedy and never the verdict: a dead config token stays a failure, because the two credentials serve different scripts. Reported in the 2026-W36 window as "both Matrix tokens on this box are rejected" and parked as a blocker, with a working credential in the store the whole timematrix-doctor.py: the ambientmatrix-niocheck is no longer critical. All ten E2EE-capable scripts declarematrix-nio[e2e]as a PEP 723 inline dependency and run underuv run, which resolves it per script, so nio in the interpreter running the doctor is a convenience. Reporting its absence as a broken setup — and exiting non-zero — sent the reader to fix something that was not in the way
Documentation
troubleshooting.md: a dead watcher is re-armed in the same turn it is discovered, or that turn says plainly that the room is no longer being read. "I will restart it" is neither ending. One session broadcast into rooms it had stopped listening to for nine hours after its only watcher died on its first event; nothing looked wrong, because a watcher that emits nothing and a quiet room produce the same empty logmatrix-announcement,threading.md: a maintenance window gets one thread per ticket, rooted on the message that claims it, with the room's flat level carrying only what is true of the whole window. Three tickets in one flat timeline interleave, and a reader who cares about one of them has to reconstruct which line belongs to which at the moment they are least able tomatrix-announcement,threading.md: a new Timestamps section — an absolute time carries the zone it was measured in, and a message quoting a log says which zone that log used. A window spans a DST boundary twice a year, its operators sit in more than one offset, and the quoted logs are often in a third, so a bare21:20is guessed at wrongly in exactly the situation the message exists for
3.1.4 - 2026-09-11
Documentation
matrix-communication: reading a room's history without the E2EE store — the plain/messagesendpoint, which works beside a runningmatrix-watchdmatrix-communication: what a hookshot message looks like when it is parsed —formatted_bodyvs the rawbody, the name prefix, thematrix.toactor, the shared(?)help link, entity glyphs
3.1.3 - 2026-09-03
Changed
- The shipped scripts are declared in
allowed-tools, not the interpreter
Fixed
matrix-administration: corrected what the minted token is bound to
Documentation
matrix-administration: minting and diagnosing service tokens;admin_request's token resolution quoted verbatim; the invisibility claim bounded and the probe trap namedmatrix-announcement: a maintenance-progress template, from a window that had nonetroubleshooting: backfill after a watcher gap; a store that "cannot be opened" is a backend mismatch- Output escaping, and two limits on the recovery recipe
3.1.2 - 2026-08-27
Fixed
- AGENTS.md kept under the 150-line index limit
Documentation
- matrix-communication and matrix-announcement SKILL.md brought under the word cap
3.1.1 - 2026-08-17
Fixed
- The fallback install hints in
_lib/deps.pyand the macOS workaround in the setup guide now pinmatrix-nio[e2e]<0.26like the nine E2EE scripts and the doctor already did. Following the unpinned hints landed on 0.26/vodozemac, and the nextuv runscript then failed to open the store (#109). - The backend-mismatch hint names the likely store rewriter: a script copy from an older skill version whose unpinned dependency resolves the newest matrix-nio. The generic hint had invited exactly that copy as a workaround (#113).
Documentation
matrix-communicationgates multi-line sends onmatrix-announcement, so structured posts get its content rules before delivery (#110).- Device verification prefers
--listen;--requestis for the case of a single Element session (#112).
[3.1.0] - 2026-08-13
Documentation
SKILL.mdandAGENTS.mdsay how to read a room log: adjacent lines from one sender are adjacent events, and a reaction or redaction line names its target only when the daemon still holds it. Written after a summary turned four unrelated events into a story (#104).
3.0.0 - 2026-08-13
Breaking
-
synapse-migrate-room.pyasks before it changes anything (#102). It read no state and asked no question: invoking it started a pipeline whose last step, enabling Megolm encryption, cannot be undone. It now reads the room's current state first, prints the steps it would take with the irreversible one marked, and stops for confirmation before the first write.-y/--yesskips the question, mirroringsynapse-deactivate-user.py.Callers that run it from a script or a pipe now exit
2withRefusing to run non-interactively without --yesuntil they pass--yes. Aborting at the prompt exits1and leaves the room untouched.
Fixed
- The room log records what a reaction reacts to and what a redaction removed
(#104). It recorded that they happened and nothing else, so a reader saw
reacted ✅️andremoved a messagewith no way to tell which message, or whether two such events were related.matrix-watchd.pydroppedReactionEvent.reacts_to,RedactionEvent.redactsand itsreason; records now keep them, and the display line names the target when the daemon has it —reacted ✅️ to "so bin beim RA". Beyond the last few hundred messages of a room the line falls back to its previous wording rather than printing an event id.
Documentation
- README,
AGENTS.mdanddocs/ARCHITECTURE.mddescribe the watch daemon, thematrix-niopin and the store migration it forces. - The live-awareness spec describes the relation fields a record carries and the window in which a target stays nameable.
- The safety guide,
SKILL.mdandAGENTS.mddescribe the confirmation gate both destructivesynapse-*scripts now have. skills/matrix-communication/README.mdpoints at the two license files that exist; theLICENSEit linked never did.
2.0.0 - 2026-08-13
Breaking
-
matrix-niois pinned below 0.26, and the pin is tied to the E2EE store. 0.26 sends the SAS commitment as a hex digest where 0.25 sent unpadded base64, so Element rejects every verification before it renders emoji (matrix-nio#570). The two releases also use different crypto backends and write incompatible stores: opening one with the other fails asOlmAccountError: BAD_ACCOUNT_KEY, which names a key that was never the problem. An installation whose store was written by 0.26 must recreate it — the store cannot be migrated:matrix-e2ee-setup.py --logout && matrix-e2ee-setup.py matrix-key-backup.py --import-keys matrix-e2ee-verify.py --request DEVICE -
Every path that opens the store now takes an exclusive lock. Commands that previously ran beside each other, or beside the daemon, now wait and then refuse with the holder's pid. That is the point: two nio processes on one store corrupt it, and until now nothing stopped them. Commands routed through the daemon — send, react, redact, edit — keep working while it runs.
-
matrix-e2ee-setup.py --logoutdeletes only its own device's store files. It globbed*.dband*_devicesacross the shared store directory, so logging one device out destroyed the megolm history of every other. Use--purge-allfor the old behaviour.
Added
- Live room awareness.
matrix-watchd.pyholds the E2EE store, syncs, decrypts and appends every event of a watched room torooms/<slug>.jsonl;matrix-watch.pyfollows that log without touching the store, so any number of readers can run at once. Send, react, redact and edit route through the daemon's Unix socket when it is running and fall back to the direct path when it is not — no new flags, no second way to send a message. Rooms come fromwatch_roomsin the config. Design and plan indocs/specs/anddocs/exec-plans/completed/. - Real mentions.
--mention '@user:server'(repeatable) and--mention-roomsetm.mentions(MSC3952), which is what notifies a modern client; a plain@nameonly ever matched the legacy push rule on an exact localpart. The pill goes into the HTML body, the plain body keeps the bare name. - A governance section in
SKILL.mdon who turns the agent's function on, off or wider, and what a third party in a room may and may not decide. - A CI job that runs the unit tests. They existed and nothing executed them.
Fixed
matrix-key-backup.py --import-keysimported nothing. It decrypted every session, discarded it, and counted it as imported. Three further defects sat in front of that: the AES-CBC IV was read off the ciphertext instead of the HKDF output, the MAC check rejected every backup written by a libolm client, and the backup key the script itself stores "for future use" had no code path that read it back.matrix-doctor.pyverified only the first token it found and never asked the homeserver about the E2EE credential at all, so a deleted device reported[OK] e2ee_setupwhile every E2EE call failed withRoom not found.- A fresh device could not verify. nio cannot build a verification for a device it has no keys for, and the resulting error named the transaction rather than the missing device.
selfin the event log marked the account, not the device. An agent and the person it works for share one account, so the flag was true for both — useless for the one thing it exists for.- The E2EE guide recommended reusing a running client's access token. That hijacks the client's device and breaks decryption in it, silently.
Changed
- The event log renders an unknown display name as the localpart rather than the whole MXID, and the daemon remembers a name once it has seen it.
ruffin.pre-commit-config.yamlmatches the version CI runs.
1.28.0 - 2026-08-08
Fixed
-
matrix-doctor.pyreported a healthy setup for a token the homeserver rejects.check_configonly proved thatconfig.jsonexists, parses, and carrieshomeserveranduser_id— it never asked the homeserver anything, so an expired or revoked token still produced[OK] configandAll checks passed! Matrix Skill is ready to use.while every authenticated call returned HTTP 401M_UNKNOWN_TOKEN. A green doctor beside a 401 sends you looking for the problem everywhere except at the credential; worse, when the doctor's verdict stands in as evidence that something is fine, a dead token reads as "nothing to see" instead of "could not verify".A new
tokenrow asksGET /_matrix/client/v3/account/whoamiand reports three states: accepted (and belonging to the configureduser_id), rejected, or not verified. Not-verified renders as[??], neverOK— no token in the config is normal for E2EE use,--offlineskips the call, and an unreachable homeserver is a missing answer. The summary line now names what it could not verify instead of claiming everything passed.
Added
matrix-doctor.py --offlineskips the token check's homeserver call for air-gapped or CI runs; the row then reads "not verified" rather than OK.
1.27.1 - 2026-07-26
Added
- matrix-communication:
references/hookshot-integration.md— documents provisioning webhooks via the matrix-hookshot bridge bot (invite, promote to moderator,!hookshot webhook <name>command, retrieving the secret URL from the bot's admin DM), discovered via live testing (#61).
1.27.0 - 2026-07-26
Added
- matrix-communication: room management —
matrix-create-room.py(create, with optional alias/topic/initial invites),matrix-invite.py, andmatrix-power-level.py(--show/--get/--set, GET-modify-PUT againstm.room.power_levels) (#59).
[1.25.4] - 2026-07-13
Fixed
- matrix-announcement: resolve the cross-skill script path in SKILL.md (#53).
Documentation
- matrix-communication:
no-editorializing.mdnow points at the canonical copy (#54). - matrix-announcement: trimmed SKILL.md generic bloat to meet the 500-word cap (#53).
[1.25.3] - 2026-07-01
Documentation
- matrix-communication, matrix-announcement: added a "no editorializing" rule for messages and announcements — state what changed, not how good it is (#51).
[1.25.2] - 2026-06-27
Fixed
- matrix-communication: E2EE own-device delivery and verification — don't report verification success on a MAC mismatch, and fetch room keys in
--listen(#47, #48).
Documentation
- matrix-communication: gate E2EE device trust strictly on verification, with matrix-nio API notes (#49).
[1.25.1] - 2026-06-18
Fixed
- matrix-communication: corrected misleading
brew install libolmguidance in the setup guide and_lib/deps.pyruntime error —python-olmhas no macOS wheel and statically links its own bundledlibolm, so Homebrew's library is never used. Documented the macOS 26 (Tahoe) / Apple Clang 17 build failure, a community-reported build-from-source workaround (GCC +CMAKE_POLICY_VERSION_MINIMUM), and the upstream status (libolm deprecated; vodozemac migration inmatrix-nioPR #555) (#43)
Documentation
- matrix-administration: documented that room IDs may be passed without the
:serversuffix, and added the Synapse admin messages endpoint to the admin-API reference (#45).
1.25.0 - 2026-06-10
Added
- matrix-announcement: entity-linking rules (every issue key linked, versions link to their release page, MRs/PRs in
project/path!N/org/repo#Nnotation) and one-item-per-line status-update layout (#41)
Fixed
- matrix-communication:
[Unable to decrypt]guidance now recommendsmatrix-fetch-keys.pyfirst — resolves the common missing-room-keys case without a recovery key (#41)
1.24.0 - 2026-05-28
Added
Changed
Fixed
Removed
1.23.0 - 2026-05-15
Added
- Ship as npm package via
@netresearch/agent-skill-coordinator(#37)
Fixed
- Declare both matrix skills in
aiAgentSkill/extra.ai-agent-skill; include.claude-plugin/plugin.jsonin the npm tarball (#37)
1.22.0 - 2026-04-29
Added
matrix-announcementskill — third skill in the plugin, alongsidematrix-communication(transport) andmatrix-administration(Synapse ops). Content guidance for composing scannable, structured Matrix room announcements: HTML subset clients render, type-tag system (Release/Patch/Heads-up/Digest/Postmortem/RFC/New skill), glyph rules,m.textvsm.noticechoice, and when to render an HTML card to PNG. Ships seven references, three rendered HTML card templates (1200×630 / 1200×1500 / 1200×900), 12 evals, and a visual gallery. No scripts. (#31)--noticeflag onmatrix-send-e2ee.pyandmatrix-send.py— sendsm.noticeinstead ofm.text, mutually exclusive with--emote. msgtype precedence:notice > emote > text. Closes the gap the announcement skill recommended but the transport scripts didn't support. (#32)
Changed
matrix-send-e2ee.pyandmatrix-send.py:--emoteand--noticeare now grouped viaargparse.add_mutually_exclusive_group. Thesend_message_e2ee()andsend_message()functions gained anotice: bool = Falsekeyword parameter.matrix-communicationquick-reference,messaging-guide.md, and the rootAGENTS.mdcheat-sheet updated to document--notice.matrix-announcement/references/image-cards.md: corrected guidance —m.noticeis text-only; for an image announcement, send the card asm.imageand a follow-up notice-flavour text message (msgtypem.notice, sent viamatrix-send-e2ee.py … --notice) rather than trying to flag the image event itself asm.notice.
1.21.1 - 2026-04-29
Maintenance release: matrix-administration script harness improvements (10 → 18 checks), CI compatibility fixes, formatter robustness for pre-wrapped links and emphasis flanking.
1.21.0 - 2026-04-26
Quality overhaul of matrix-administration: 95% faster E2EE operations, 28 evals, expanded harness, full-text formatter improvements.
1.20.1 - 2026-04-22
Security patch: URL scheme validation before urllib.request.urlopen in matrix-administration.
1.20.0 - 2026-04-16
Added the matrix-administration skill — Synapse server operations (snapshot rooms, rate room health, render Graphviz map, force-join, promote, harden, deactivate, search history). Stdlib-only Python.
Older releases (before this changelog was introduced) are documented on the releases page.