# 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. ## Related Links - [werkregie PR#91](https://git.javagil.de/mi/werkregie/pulls/91), `matrix react`. - [werkregie PR#93](https://git.javagil.de/mi/werkregie/pulls/93), the running relay sends the reaction through `control.sock`. ## 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 - [MatrixSenderTest "a sent message gives its event ID, a failed one ends the command"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixSenderTest.kt) - [MainTest "matrix send takes room and text, the text also from stdin"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/MainTest.kt) #### 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 - [MainTest "matrix send takes room and text, the text also from stdin"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/MainTest.kt) #### 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 - [MatrixReaderTest "the reading takes the messages back to where it starts, at most the limit"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReaderTest.kt) - [MatrixReaderTest "a message gives its text, an undecryptable one none, other events nothing"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReaderTest.kt) - [MatrixReadingTest "each message is a block of time, sender, event ID and its indented text, the oldest first"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReadingTest.kt) #### 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 - [MatrixReaderTest "the reading takes the messages back to where it starts, at most the limit"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReaderTest.kt) - [MatrixReaderTest "an event to start after that the room does not show ends the command, also beyond the limit"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReaderTest.kt) - [MatrixReaderTest "an event before the time to start from is skipped, and enough of them in a row end the reading"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReaderTest.kt) - [MainTest "matrix read takes room, since and limit"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/MainTest.kt) #### 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 - [MatrixControlTest "the socket takes a reaction, a message or a reading, each with its fields"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixControlTest.kt) - [MatrixCommandsTest "while another process syncs, the command asks it"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixCommandsTest.kt) #### 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 - [MainTest "matrix send needs room and text, none of them blank"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/MainTest.kt) - [MainTest "matrix read needs a room, a limit from 1 to 500 and a readable since"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/MainTest.kt) ## 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.