How Apus delivers replies into Matrix rooms: streaming previews, inbound voice notes, and reactions.

Streaming previews

Matrix reply streaming is opt-in. streaming.mode controls how Apus delivers the in-flight assistant reply; streaming.block.enabled controls whether each completed block is kept as its own Matrix message.

{
  channels: {
    matrix: {
      streaming: { mode: "partial" },
    },
  },
}

To keep live answer previews but hide interim tool/progress lines:

{
  channels: {
    matrix: {
      streaming: {
        mode: "partial",
        preview: {
          toolProgress: false,
        },
      },
    },
  },
}

The full config accepts { mode, chunkMode, block, preview, progress }:

{
  channels: {
    matrix: {
      streaming: {
        mode: "progress",
        progress: {
          label: "auto", // pick from configured or built-in labels (false to hide)
          labels: ["Thinking", "Writing", "Searching"], // candidates for label: "auto"
          maxLines: 8, // max rolling progress lines (default: 8)
          maxLineChars: 120, // max chars per line before truncation (default: 120)
          toolProgress: true, // rolling tool log in the progress draft (default: false)
        },
      },
    },
  },
}
streaming.mode Behavior
"off" (default) Wait for the full reply, send once.
"partial" Edit one normal text message in place as the model writes the current block. Stock clients may notify on the first preview, not the final edit.
"quiet" Same as "partial" but the message is a non-notifying notice. Recipients are notified once a per-user push rule matches the finalized edit (see Self-hosted push rules for quiet finalized previews).
"progress" Sends individual compact progress lines using a progress draft.

streaming.block.enabled (default false) is independent of streaming.mode:

streaming.mode block.enabled: true block.enabled: false (default)
"partial" / "quiet" Live draft for the current block, completed blocks kept as messages Live draft for the current block, finalized in place
"off" One notifying Matrix message per finished block One notifying Matrix message for the full reply

Notes:

Voice messages

Inbound Matrix voice notes are transcribed before the room mention gate, so a voice note saying the bot name can trigger the agent in a requireMention: true room, and the agent gets the transcript instead of only an audio attachment placeholder.

Matrix uses the shared audio media provider under tools.media.audio, such as OpenAI gpt-4o-mini-transcribe. See Media tools overview for provider setup and limits.

Reactions

Matrix supports outbound reactions, inbound reaction notifications, and ack reactions.

Outbound reaction tooling is gated by channels.matrix.actions.reactions:

emoji-list reads MSC2545 im.ponies.room_emotes packs from the authorized current room and im.ponies.user_emotes account data. It returns up to 100 sorted entries such as { "name": "party", "identifier": "party", "url": "mxc://example.org/party" }; sticker-only entries are excluded. Pass identifier to react: it is the plain shortcode stored directly as the Matrix reaction's m.relates_to.key, not the mxc:// media URL. Custom-reaction rendering depends on the Matrix client, so url is included separately for clients or agents that need the image.

Resolution order (first defined value wins):

Setting Order
ackReaction per-account -> channel -> messages.ackReaction -> agent identity emoji fallback
ackReactionScope per-account -> channel -> messages.ackReactionScope -> default "group-mentions"
reactionNotifications per-account -> channel -> default "own"

reactionNotifications: "own" forwards added m.reaction events when they target bot-authored Matrix messages; "off" disables reaction system events. Reaction removals are not synthesized into system events - Matrix surfaces those as redactions, not as standalone m.reaction removals.