Prompt assembly merges tool definitions discovered via readRealmFile#5485
Conversation
Tools discovered by reading a skill file become available to the model on the next generation, sourced from room events like every other prompt input — replay-correct and inspectable. The readRealmFile fulfillment embeds each read skill's usable (schema- stamped) tool entries on the result event as data.discoveredTools, each tagged with the source skill URL so the execution leg can verify calls against the declaring skill's indexed frontmatter. Entries without a well-formed definition are dropped; tool-less reads publish exactly the same event shape as before, and attachedFiles is untouched so the attachment-download path and timeline rendering are unchanged. getTools gains a third source: tool-result events' discoveredTools, merged into the tool map before the enabled-skill and message-context sources so those win on functionName conflict. A skill read many times contributes only its latest read's definitions (the file may have changed between reads), and a skill disabled in room state contributes none. CS-12046 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR extends prompt assembly so that tool definitions discovered via readRealmFile (from skill markdown file-meta frontmatter) are persisted on tool-result events and then merged into getTools on subsequent turns, making the behavior fully event-sourced and replayable from room history.
Changes:
- Embed index-stamped tool definitions from
readRealmFileresults asdata.discoveredToolson tool-result events (tagged withsourceSkillUrl). - Teach
getToolsto scan tool-result events fordiscoveredToolsand merge them into the tool map ahead of uploaded skill tools and message-context tools. - Add ai-bot tests covering fulfillment embedding/dropping, precedence/wholesale replacement rules, disabled-skill filtering, and JSON encode/decode round-trip.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/runtime-common/ai/prompt.ts | Adds discoveredTools as a third getTools source and defines conflict/precedence behavior. |
| packages/base/matrix-event.gts | Introduces DiscoveredToolDefinition and adds data.discoveredTools to tool-result event payload typing. |
| packages/ai-bot/lib/read-realm-file-fulfillment.ts | Extracts usable stamped tool definitions from read results and publishes them on tool-result events (without changing attachment shape). |
| packages/ai-bot/tests/read-realm-file-fulfillment-test.ts | Tests that fulfillment embeds stamped definitions and omits the key for tool-less reads. |
| packages/ai-bot/tests/prompt-construction-test.ts | Tests getTools discovery behavior, precedence, disabled-skill behavior, and encode/decode round-trip via constructHistory. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Preview deploymentsHost Test Results 1 files ±0 1 suites ±0 2h 59m 56s ⏱️ + 1m 32s Results for commit 4db0737. ± Comparison against earlier commit 33223d5. Realm Server Test Results 1 files ±0 1 suites ±0 11m 54s ⏱️ +27s Results for commit 4db0737. ± Comparison against earlier commit 33223d5. |
Only the bot publishes readRealmFile results, so discovered definitions on anyone else's tool-result event are ignored; and an entry only counts when its definition is a well-formed function tool (type plus function.name), not just any object with a nested name. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A failed or invalid readRealmFile result is not evidence the skill was fetched, so its discoveredTools claim is ignored. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…cation When a tool call's functionName resolves in no enabled skill, message-builder falls back to the skill named by the latest readRealmFile result event's discoveredTools. That annotation is strictly a lookup hint: the codeRef the host executes is re-derived from the skill's realm-indexed frontmatter, loaded through the store with the user's own permissions, and accepted only when the indexed declaration's functionName matches the call. requiresApproval comes from the same verified declaration (absent means approval required), so auto-execute honors it identically to enabled skills. A forged annotation, a skill the user can't read, or a tool the skill no longer declares all leave the request unresolved, surfacing through the existing unrecognized-command invalid-result path. CS-12047 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The skill-URL hint comes from a bot event, so a load that throws on a malformed or unreadable id degrades to an unresolved tool instead of breaking message building. The hint scan also walks newest-first and returns on the first match rather than scanning the full timeline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…f-read-skill-tools Host executes tools from read skills after realm verification
230f62f
into
cs-12045-readrealmfile-file-meta-for-md
Linear: CS-12046
Stacked on #5484 (readRealmFile file-meta reads) — the pull model's prompt-injection leg. Tools discovered by reading a skill file become callable on the model's next generation, sourced from room events — the same event-sourcing every other prompt input uses, so it's replay-correct and inspectable.
Behavior
read-realm-file-fulfillment.ts): each read skill's usable tool entries — those with a well-formed, index-stamped LLMdefinition— are embedded on the read's command-result event asdata.discoveredTools, each tagged withsourceSkillUrl(the execution leg verifies calls against the declaring skill's indexed frontmatter). Entries without a definition (a skill indexed before schema enrichment) are dropped.attachedFilesis untouched — the attachment-download path and timeline rendering see exactly the event shape they saw before, and tool-less reads publish byte-identical data.getTools(runtime-common/ai/prompt.ts) gains a third source: it scans tool-result events fordata.discoveredToolsand merges the definitions into the tool map first, so an enabled skill's uploaded definition or a message-context tool with the same functionName wins on conflict. A skill read many times contributes only its latest read's definitions — wholesale, so a tool the skill no longer declares disappears. A skill listed indisabledSkillCardscontributes none.DiscoveredToolDefinitiononToolResultWithOutputContent.data(matrix-event.gts), riding the existingdataJSON-stringify/parse wire path (sendMatrixEvent/constructHistory).Testing
ai-bot suite (240 passing; the 2 failures are the pre-existing locking tests needing a CI-provisioned Postgres role):
discoveredToolskey.getTools: discovered tools are offered on later turns; the latest read of a skill wins wholesale; a disabled skill contributes nothing; a skill both enabled and read yields one definition with the uploaded one winning; definitions round-trip the encode/decode path viaconstructHistory.🤖 Generated with Claude Code