readRealmFile: fetch file-meta for .md files, raw source for everything else#5484
Conversation
Preview deploymentsHost Test Results 1 files ± 0 1 suites ±0 3h 0m 38s ⏱️ - 1m 18s Results for commit 230f62f. ± Comparison against earlier commit 80149da. Realm Server Test Results 1 files ±0 1 suites ±0 11m 30s ⏱️ -20s Results for commit 230f62f. ± Comparison against earlier commit 80149da. |
…ng else A markdown file read now requests the indexed file-meta document, yielding the frontmatter-stripped body as the attached content plus the skill's frontmatter tools (with index-stamped definition, resolved codeRef, functionName, requiresApproval) returned structurally for the fulfillment layer. When the index can't serve a usable document — no row yet, an error-state row, or a document without content — the read degrades to raw source and returns instructions-only, so index lag never fails a read. Auth and network failures skip the fallback since raw source would fail identically. Non-markdown files keep the raw-source path byte-identical. The markdown predicate is hoisted to runtime-common (isMarkdownFile) so the host's isMarkdownSkillId, prompt assembly's isMarkdownSkillFile, and the ai-bot read path share one definition. The tool description now tells the model that reading a skill file unlocks its tools on the next turn. CS-12045 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
e4c1d56 to
e782e42
Compare
Observability diff (vs staging)Show diffdiff --git a/tmp/remote-canon.v6M2Wx/dashboards/boxel-status/indexing.json b/tmp/committed-canon.qhiVzR/dashboards/boxel-status/indexing.json
index fd785f2..d7bf03e 100644
--- a/tmp/remote-canon.v6M2Wx/dashboards/boxel-status/indexing.json
+++ b/tmp/committed-canon.qhiVzR/dashboards/boxel-status/indexing.json
@@ -80,6 +80,10 @@
"uid": "cef5v5sl9k7i8f"
},
"description": "System-wide operator action: queue a full reindex across every realm. The button disables itself while a `full-reindex` orchestration job is already pending or running. Per-realm reindex moved to the Realms dashboard. Click POSTs with `Authorization: Bearer ${grafana_secret}` (substituted from SSM at apply time, CS-10929).",
+ "fieldConfig": {
+ "defaults": {},
+ "overrides": []
+ },
"gridPos": {
"h": 8,
"w": 24,
(Run: https://github.com/cardstack/boxel/actions/runs/29280170242) |
Grafana previewPreview deployed for 1 dashboard in the staging Grafana.
Dashboards: Preview is torn down automatically when this PR is closed or merged. (Run: https://github.com/cardstack/boxel/actions/runs/29280170231) |
There was a problem hiding this comment.
Pull request overview
This PR updates executeReadRealmFile (ai-bot) to treat markdown URLs specially: markdown reads prefer the indexed file-meta document (to return frontmatter-stripped body plus stamped tool metadata), while all other files continue to fetch raw source byte-identically to the prior behavior. It also centralizes “is this markdown?” detection into a shared isMarkdownFile helper in runtime-common, and wires callers to use it.
Changes:
- Add
isMarkdownFile()inpackages/runtime-common/paths.tsand switch prompt/host markdown detection to use it. - Update ai-bot
executeReadRealmFileto request file-meta for markdown, parseattributes.content+attributes.frontmatter.tools, and fall back to raw source on HTTP/unusable file-meta bodies. - Add/adjust ai-bot tests to cover markdown file-meta behavior (happy path, gating/auth flow, and fallback scenarios) and to pin the raw-source path for non-markdown URLs.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| packages/runtime-common/paths.ts | Introduces shared isMarkdownFile() predicate for consistent markdown detection across packages. |
| packages/runtime-common/ai/prompt.ts | Switches markdown-skill-file detection to delegate to isMarkdownFile(). |
| packages/host/app/lib/skill-tools.ts | Removes local markdown predicate and uses isMarkdownFile() for markdown skill IDs. |
| packages/ai-bot/lib/read-realm-file.ts | Implements markdown=file-meta branching + parsing + fallback behavior; extends result shape with optional tools. |
| packages/ai-bot/tests/read-realm-file-test.ts | Adds a dedicated markdown file-meta test module and updates existing tests to use a non-markdown URL for raw-source transport/auth invariants. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
typeof [] === 'object', so a malformed tools array whose entries are themselves arrays would have passed the object filter and reached the fulfillment layer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
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
…ered-tool-definitions Prompt assembly merges tool definitions discovered via readRealmFile
There was a problem hiding this comment.
I tested this by using a new system card and attaching the https://github.com/cardstack/boxel-skills/blob/main/index.md to it and was able to observe the bot is able to dig to get the correct skill that has the needed tool, and executes it:
Another case:
However in the case above, there were 2 same executions of Check if holiday-postcard.gts exists - I'll file a ticket to check that.
Linear: CS-12045
The pull model's second leg (independent of the index-time schema stamping — it reads whatever
frontmatter.toolsthe index serves, and behaves as today until that enrichment lands).executeReadRealmFilenow branches per URL on file type: markdown fetches the indexed file-meta document; every other file keeps requesting raw source, byte-identical to before. The auth/realm-discovery flow (header probe, delegated token, retry-on-revoke) is shared — only the Accept header and result handling differ.Behavior
Accept: application/vnd.card.file-meta+json. From the document,attributes.content(the frontmatter-stripped body) becomes the attached content — the downstream prompt-inlining path is unchanged — andattributes.frontmatter.tools(index-stampeddefinition, resolved codeRef, functionName, requiresApproval) are returned structurally on the result astoolsfor the fulfillment layer. Relative links keep absolutizing against the attachment'ssourceUrl, which remains the file URL (indexed content is as-authored).contentstring — the read degrades to the raw-source fetch and returns instructions-only. Index lag never fails a read. Auth and network failures skip the fallback: raw source would fail identically, so the error surfaces directly.isMarkdownFilelives in runtime-common (paths.ts); the host'sisMarkdownSkillId, prompt assembly'sisMarkdownSkillFile, and this read path all delegate to it.Landable before the indexer enrichment serves stamped schemas: tools simply come back absent and reads behave as today.
Testing
pnpm testin ai-bot: the newexecuteReadRealmFile markdown file-metamodule covers the happy path (body + stamped tools, single fetch), a plain markdown file (body, no tools), fallback on 404 / contentless document / non-JSON body, the gated-skill token flow on the file-meta Accept type, fallback re-running the auth flow, and no-fallback on access failure. The transport/auth-flow tests now exercise a non-markdown URL, pinning the raw-source path unchanged. 232/234 pass locally — the two failures are the pre-existing locking tests needing a CI-provisioned Postgres role, untouched by this change.🤖 Generated with Claude Code