fix(macros): tool_describe must publish its descriptor for the describe fan-out#56
Conversation
There was a problem hiding this comment.
Code Review
This pull request updates the capsule_impl macro in astrid-sdk-macros/src/lib.rs to explicitly publish the tool descriptor on the tool.v1.response.describe.self topic, ensuring that tool discovery works correctly under the current ABI. It also adds corresponding regression tests. The feedback suggests logging a warning if the publish_json call fails instead of silently ignoring the error, which will help with troubleshooting IPC or host issues.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
Pull request overview
This PR updates the #[capsule] macro’s auto-generated tool_describe interceptor so it explicitly publishes the tool descriptor on the describe-response IPC topic, aligning macro-generated tool capsules with the current ABI where interceptor return values are no longer fanned out to describe callers.
Changes:
- Publish the
{tools, description}descriptor totool.v1.response.describe.selffrom the generatedtool_describearm (while still returningContinuefor interceptor-chain semantics). - Add macro regression tests to ensure describe publishing occurs for tool capsules and does not occur for capsules without tools.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…be fan-out
The #[capsule] macro's auto-generated tool_describe arm built the {tools, description} descriptor and returned it as a Continue payload, but under the current ABI an interceptor's return value is not fanned out to the caller. So no SDK-macro tool capsule (fs, http, identity, system, skills) answered the tool.v1.request.describe fan-out, and every consumer — prompt-builder, registry, and the sage-mcp MCP bridge behind 'astrid mcp serve' — collected 0 tools.
Publish the descriptor explicitly on tool.v1.response.describe.self, mirroring the hand-written LLM providers that publish llm.v1.response.describe. The topic suffix is routing-only: the route layer's trailing * needs >= 5 segments, and the responder's real source_id rides in the kernel-stamped message metadata (used by prompt-builder for per-source permission filtering). The Continue return is kept for interceptor-chain semantics.
Adds a regression guard asserting the generated tool_describe arm publishes (and that a tool-less capsule emits no stray describe publish).
9d5b1b1 to
6856cd8
Compare
Rebuilt on main post-#53/#56 squash-merge: the branch previously carried its own pre-squash copy of the feature diff; now it is the bump + CHANGELOG only. - Workspace 0.7.0 -> 0.7.1 (+ the three internal dependency pins). - CHANGELOG: [Unreleased] -> [0.7.1] - 2026-06-10, with a release synopsis (extracted into the GitHub release body by release.yml), the previously missing Fixed entry for the #56 tool_describe fan-out fix, and PR attribution on the #53 entries.
## Summary `chore: release` — bumps the workspace to **0.7.1**, the patch release that restores capsule tool discovery (#56) and ships the post-0.7 additive surfaces (#53). Per the repo rule, a version bump is its own PR. The branch was originally stacked on #53; after #53 and #56 squash-merged it has been **rebuilt on `main`** so the diff is exactly the bump + CHANGELOG. No breaking changes — `astrid-sdk = "0.7"` consumers resolve 0.7.1 automatically once published, so capsules pick up the tool_describe fix with a rebuild and **no** dep bump. ## Changes - `[workspace.package].version` `0.7.0` → `0.7.1` (+ the three internal `[workspace.dependencies]` pins: `astrid-sdk`, `astrid-sdk-macros`, `astrid-sys`). - CHANGELOG: `[Unreleased]` → `[0.7.1] - 2026-06-10` with: - a release **synopsis** paragraph (release.yml extracts the whole `## [0.7.1]` block into the GitHub release body on tag), - the previously **missing `Fixed` entry for #56** (the tool_describe describe-fan-out fix — the fleet-critical one), - PR attribution on the #53 `Added` entries (`capabilities::enumerate`, persistent-process tier). ## Release steps (maintainer) 1. Merge this PR. 2. `cargo publish` in dependency order: `astrid-sys`, `astrid-sdk-macros`, `astrid-sdk`. 3. Tag `v0.7.1` on the merge commit → release.yml creates the GitHub release from the CHANGELOG block.
What & why
The
#[capsule]macro auto-generates atool_describeinterceptor arm for any capsule with#[astrid::tool]methods. It built the{tools, description}descriptor and returned it as aCapsuleResult{action:"continue", ...}— but under the current ABI an interceptor's return value is no longer fanned out to the caller (the dispatcher drops a lone request handler's Continue payload). So no SDK-macro tool capsule (fs,http,identity,system,skills) ever answered thetool.v1.request.describefan-out, and every consumer collected 0 tools: prompt-builder, registry, and the sage-mcp MCP bridge behindastrid mcp serve(tools/listreturned{tools:[]}on a healthy daemon).The hand-written LLM providers already do this correctly —
astrid-capsule-openai::llm_describeends withipc::publish_json("llm.v1.response.describe", ...)and its doc says verbatim "the interceptor return value is no longer fanned out to the caller under the new ABI." The macro was simply never updated to match.Fix
Publish the descriptor explicitly on
tool.v1.response.describe.selfbefore returning (the Continue return is kept for interceptor-chain semantics). One-site change that fixes every SDK-macro tool capsule at once.Topic suffix is routing-only. Consumers subscribe
tool.v1.response.describe.*; the route-layer trailing*requires ≥ 5 segments (a 4-seg unsuffixed publish would not match), and the responder's realsource_idrides in the kernel-stamped message metadata (msg.source_id, which prompt-builder uses for per-source permission filtering), so a fixedselfsuffix is sufficient. Subscribers parsetoolsfrom the payload, not the topic. No self-id accessor exists in the SDK today.Verification
cargo test -p astrid-sdk-macros— 46 pass, including the newtool_describe_publishes_response_for_fanoutregression guard andcapsule_without_tools_has_no_describe_publish.[patch.crates-io]): rebuiltastrid-capsule-fsagainst this branch and reinstalled it;astrid mcp serve→tools/listwent from 0 tools to 8 — fs's full set (read_file,write_file,list_directory,grep_search,create_directory,delete_file,move_file,replace_in_file). The other tool capsules still carry the old macro and contribute nothing until rebuilt, which confirms the fan-out now works and this is the sole gating change.Landing the full surface: merge → publish
astrid-sdkto crates.io → rebuild the tool capsules.Root-cause companion: the empty
astrid mcp servetool surface traced back here; the cli-proxy daemon-down crash and MCP publish-ACL gap on the path were fixed separately in capsule-cli #25.