ai-bot: fulfill readRealmFile via Matrix attachments on the host-command path#5369
Merged
jurgenwerk merged 2 commits intoJun 30, 2026
Conversation
…and path Re-architect readRealmFile so the bot no longer runs reads in an inline, same-turn loop. Instead it surfaces each read as an `executedBy: 'ai-bot'` command request, fetches the file, uploads it to the Matrix media repo, and posts a command-result event carrying the file as `data.attachedFiles`. The existing host-command result path then reconstructs the content on the next turn (via the same attachment-download the host `readFile` uses), and the bot's own result event re-triggers generation. Because reads now resolve next-turn like host commands, the two timing models collapse into one: an answer may freely mix reads and host commands, so the mixed-round rejection, the inline `for(;;)` loop, the "thinking"-event indicator rotation, and the in-memory followup splicing are all deleted. Uploads dedupe by content hash, so the same skill read repeatedly is stored in Matrix once; keying on the hash keeps it version-correct (changed content re-uploads). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
The bot fulfills a readRealmFile read by posting a command-result event, which is meant to re-trigger the bot for the continuation turn. Two things prevented that continuation from running: - Fulfillment happened while the room lock was still held. The re-trigger has to acquire that lock, so it was dropped. Fulfill after the lock is released instead. - The re-trigger runs on the result event's local echo, before the homeserver has indexed it into /messages. getRoomEvents is a server fetch, so the continuation's history missed the just-posted result and the read looked unresolved (shouldRespond=false). Splice the in-hand result event into the history when the fetch didn't include it. Host command results arrive via sync (already server-side), so this only affects the bot's own results. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
faf693f
into
cs-11554-loadskill-tool-in-ai-bot-body-references
23 checks passed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Stacked on #5344. Base is that PR's branch, so the diff here is only the delta. Review/merge #5344 first.
What this explores
An alternative architecture for the readRealmFile feature: instead of ai-bot running reads in an inline, same-turn loop and feeding content to the model in-process, the read content lives in Matrix as an attachment, and the continuation rides the existing host-command result path. This is the "content in Matrix, attachment variant" we wanted to evaluate against the same-turn approach in #5344.
How it works now
readRealmFile. It surfaces as a normal command request on the bot's message, taggedexecutedBy: 'ai-bot'(the host records it, never runs it — those guards from ai-bot: the assistant can read a skill directly from the realm server #5344 are unchanged).data.attachedFiles(success) or aninvalidresult with a reason (failure).getShouldRespondthen waits until every request — reads and any host commands — has a result, exactly as it already does for host commands.toResultMessages→buildAttachmentsMessagePart) downloads the attachment and feeds its content to the model — the same path the hostreadFileuses.What this deletes
Because reads now resolve next-turn like host commands, the two timing models collapse into one. Gone:
for(;;)generation loop inmain.tsandmessagesOverride;readRealmFilecan now coexist with host commands in one answer);beginCommandResultIndicator/sendCommandResultIndicator/resetForNextEvent);buildReadRealmFileFollowup/ in-memory tool-result splicing.Net −90 lines, with
main.tssubstantially simpler.Storage / dedup
Uploads dedupe by content hash, so a skill read repeatedly is stored in Matrix once, and a changed file misses the cache and re-uploads (dedup without staleness). Within a room, a file already attached isn't re-read at all (the existing
isFileAttachedInRoompath). Matrix media itself is not content-addressable, so this app-level cache is what avoids re-storing identical bytes.Trade-offs to weigh vs #5344
Notes on correctness
getRoomEventsslices history at the trigger event, so for a batch of reads only the handler whose result completes the set passesgetShouldRespond; the bot's answer is posted after all results and is never in a result-handler's sliced history. Same mechanism that protects the host multi-command flow.Tests
Unit tests for the fulfillment (applied result + attachment on success, invalid + reason on failure, malformed args, and content-hash dedup → one upload), plus
classifyToolCalls(reads and host commands coexist) andfileLabelFromUrl. All green; ai-bot type-check and lint clean.🤖 Generated with Claude Code