Files
werkregie/doc/prs/2026-10-23-PR#100-matrix-send-read.md
claude-code 228f31f2e8
werkator build succeeded after 01:06
werkator/deploy build succeeded after 00:27
werkator/vulnerability-data build succeeded after 01:55
werkator/mutation build failed after 19:27
matrix send and matrix read (#100)
Co-authored-by: claude-code <claude-code@javagil.de>
2026-10-24 22:42:04 +02:00

9.2 KiB

PR#100: matrix send and matrix read

Version: minor

WARNING: This document describes only the change applied in this PR. It may already be outdated once the next PR is merged. Historic PR documentation is not maintained along with new PRs; treat it as a snapshot.

Problem

An agent can only react to a message in a Matrix room, not write one or read the room. The rooms are end-to-end encrypted, so the client API with curl is not enough; only the verified device of matrix login has the keys.

Non-Goals

  • No Markdown: the message is plain text; the human decided against a new runtime dependency for it.
  • No replies, threads, edits, files or reactions with send.
  • No new device or login: the commands use the device of matrix login.
  • No change of matrix react.

Specification

Feature: an agent writes and reads a Matrix room with the user's device

Scenario#100.01: send a message

… so that an agent can write into an encrypted room.

  • Given the device of matrix login is verified and member of the room "HSAdmin NG"
  • When an agent runs werkregie-dashboard matrix send --room "HSAdmin NG" "Build ist grün"
  • Then the message is in the room, encrypted like the room, and the command prints SENT and its event ID, SENT alone when the sync took the message out of the outbox before the client saw its ID
Verified by

Scenario#100.02: the text from stdin

… so that a text with several lines needs no quoting in the shell.

  • Given the device is member of the room
  • When the agent runs matrix send --room "HSAdmin NG" - with the text on stdin
  • Then the message holds the text from stdin, without its final line break
Verified by

Scenario#100.03: read the newest messages

… so that an agent sees what was said in the room.

  • Given the room holds messages, some of which the device cannot decrypt
  • When the agent runs matrix read --room "HSAdmin NG"
  • Then the command prints the newest 20 messages, oldest first, each as a block of time, sender, event ID and text, and a message without key as (nicht entschlüsselt)
Verified by

Scenario#100.04: read since a time or an event

… so that an agent reads only what is new since it looked last.

  • Given the room holds messages before and after an event $e sent at 14:00
  • When the agent runs matrix read --room "HSAdmin NG" --since '$e', or --since 2026-10-23T14:00
  • Then the command prints the messages after $e, or those from 14:00 on, at most 500 or --limit N (the newest of them); an event ID that is not among the last 500 events of the room fails the command
Verified by

Scenario#100.05: the running relay sends the message

… so that the relay keeps running, as for matrix react.

  • Given the relay of the machine runs
  • When an agent runs matrix send --room "HSAdmin NG" "Build ist grün"
  • Then the message is in the room, and the relay still runs
Verified by

Scenario#100.06: a wrong call

… so that the agent tells a wrong call from a failure.

  • Given any state
  • When the agent runs matrix send without room or text, or matrix read with a limit that is no number from 1 to 500, or a time it cannot read
  • Then nothing is sent, and the command prints its usage and ends with exit code 2
Verified by

Solution

  • MatrixCommands, new: the way of matrix react for each command that needs the device's client. run asks control.sock first and takes sync.lock and a client of its own only when nobody answers; handle waits for the first sync, finds the room by name or ID and runs the command. The answer is the text the command prints, so the socket carries it as it is.
  • MatrixRequest, new: ReactionRequest, SendRequest and ReadRequest.
  • MatrixControl takes the commands react, send and read, each with its fields, and refuses any other; a relay of an older version answers send and read with an error, so it needs a restart after the update.
  • MatrixSender: m.text with body only, through the outbox as the reaction; prints SENT and the event ID, as matrix react does without the event ID when the outbox gave it up before the client saw it.
  • MatrixReader: the newest events of the room back to --since or up to the limit, at most 500; a reaction does not count, by its relation, which stays cleartext also in an encrypted room; an encrypted event of any algorithm counts until its decryption shows that it is no message. Each event gets 30 s for its key, all at once, as for react; without it the message prints as (nicht entschlüsselt). An event ID of --since that is not among the last 500 events fails the command, also with a smaller limit: the reading looks for it beyond the limit and keeps the newest messages after it.
  • With --since a time, an event before it is skipped, and the reading ends after 20 of them in a row: the room orders its events as the server got them, so an older time stamp may stand among newer ones.
  • The request carries the caller's time zone, so the relay prints the times in the zone of the command.
  • MatrixReading, free of the client library: --since as event ID ($…) or time, time without offset in local time; the blocks with the text indented by two spaces, so that a blank line in a message ends no block.
  • matrix read prints nothing for a room without messages, rather than a blank line.
  • matrix send refuses more than one text argument: an unquoted text would send only its first word.
  • The tests of settled and sentId and of the request to control.sock move from MatrixReactorTest to MatrixCommandsTest and MatrixSenderTest, with the code; MatrixReactorTest keeps those of react.
  • pitestPartial mutationCrapCheck against main: the highest Mutation-CRAP is 14, readRequest in Main.kt; MatrixReader.candidates takes the timeline as a flow, so that its tests need no client.
  • Tested on 2026-10-23 with the relay stopped, by the branch's own client: matrix read on "HSAdmin NG" decrypted 3 messages in 11 s; matrix send into the test room !B8yKiz2HJtweVdVbIhQdo2iOvHdx2L8KoeD4K7QNnW8, with the human's consent, printed SENT $DbfeJUag…, and matrix read showed the message.

Release Notes

  • matrix send --room RAUM TEXT writes a message into a room, also an end-to-end encrypted one.
  • matrix read --room RAUM [--since ZEIT|EVENT-ID] [--limit N] prints the messages of a room, decrypted.
  • Both go through the running relay or local mode, as matrix react; restart it after the update.

Open Questions

None.