feat(composio): tell the model what a toolkit's actions hand back - #132
feat(composio): tell the model what a toolkit's actions hand back#132yh928 wants to merge 1 commit into
Conversation
`toolkit_description` answers "what can this service do" — an input-side question, and so is everything else the model reads before calling: the tool catalogue, the parameter schema. Nothing tells it what comes *back*. So a list action returns records keyed by id, the model has no statement that the id is the handle for the detail it actually wanted, and it re-issues the same list call. Observed live against Gmail: a sub-agent searched with GMAIL_LIST_THREADS, got snippets rather than bodies, and reported that mail which does exist could not be found — the thread ids it needed were in the result it already had. `toolkit_result_notes` is the output-side counterpart, sitting beside `toolkit_description` because they answer the two halves of the same question. Two entries to start: gmail and slack, the toolkits whose response shape this crate's curated catalogues let us state rather than guess. Deliberately narrow: - Only action slugs `GMAIL_CURATED` / `SLACK_CURATED` actually carry. A note naming a renamed or dropped slug is worse than no note — it sends the model after a tool that is not in its list — so a test checks each toolkit's prose against its OWN catalogue. Pooling them would let a Gmail note name a Slack-only action and pass, which is the likeliest editing mistake. - No field-by-field record shapes. Composio dispatch prefers the backend's rendered `markdownFormatted` body and falls back to the JSON envelope only when that is absent, so a note reciting JSON keys is true on one of two renderings. An earlier revision made exactly that mistake. - `None` for anything unestablished. A guess about a response is worse than silence here, because the model acts on it. Tests live in a sibling `descriptions_tests.rs`, matching `mod_tests.rs`, so the crate's `expect_used` / `unwrap_used` lints stay on for real code. 307 tests pass; fmt and clippy clean.
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Team Run ID: Warning Your free Security trial is over. An organization admin can upgrade to Advanced for continuous pull request security review or dismiss this notice. Comment |
How this change flows1 changed behaviour across 5 relationships. 5 surrounding behaviours are shown (60 graph nodes walked). 20 further behaviours left out to keep the diagram readable. flowchart LR
n0["toolkit_description<br/>changed"]:::changed
n1["catalog_for_toolkit"]:::impacted
n2["is_action_visible_with_pref"]:::impacted
n3["curated_scope_for"]:::impacted
n4["...is_populated_for_every_capability_toolkit"]:::impacted
n5["ToolScope"]:::impacted
n2 -->|calls| n1
n3 -->|calls| n1
n3 -->|uses| n5
n4 -->|calls| n0
n4 -->|tests| n0
classDef changed fill:#0d4429,stroke:#238636,color:#e6edf3
classDef impacted fill:#161b22,stroke:#6e7681,color:#c9d1d9
classDef flagged fill:#5a1e02,stroke:#d93f0b,color:#ffffff
classDef blocking fill:#67060c,stroke:#f85149,color:#ffffff
Green: changed behaviour. Grey: surrounding behaviour. Arrows name the call, use, implementation, or test relationship. Orange: has findings. Red: has a finding that blocks the merge. |
Summary
toolkit_descriptionanswers "what can this service do" — an input-side question. So is everything else the model reads before calling: the tool catalogue, the parameter schema. Nothing tells it what comes back.toolkit_result_notesis the output-side counterpart, added besidetoolkit_descriptionbecause they are the two halves of one question.Problem
A list action returns records keyed by id. The model has no statement that the id is the handle for the detail it actually wanted, so it re-issues the same list call.
Observed live against Gmail: a sub-agent searched with
GMAIL_LIST_THREADS, got one-line snippets rather than message bodies, and reported that mail which does exist could not be found. The thread ids it needed were sitting in the result it already had — nothing had told it that a thread listing is a set of handles, not content.Scope
Two entries, gmail and slack: the toolkits whose response shape this crate's curated catalogues let us state rather than guess.
Three constraints, each enforced or documented:
Only slugs the catalogue carries. A note naming a renamed or dropped slug is worse than no note — it sends the model after a tool that is not in its list. A test checks each toolkit's prose against its own catalogue; pooling them would let a Gmail note name a Slack-only action and still pass, which is the likeliest mistake when editing prose that mentions both.
No field-by-field record shapes. A note may say what a result contains and what to do with it, not how it is serialized. Composio dispatch prefers the backend's rendered
markdownFormattedbody and falls back to the JSON envelope only when that is absent, so a note reciting JSON keys is true on one of two renderings. An earlier revision of this text made exactly that mistake and told the model every Gmail read action answers with a markdown body, when onlyGMAIL_FETCH_EMAILScarries one.Nonefor anything unestablished. A guess about a response is worse than silence, because the model acts on it.Tests
307 pass. Coverage for the slug-validity rule, the absent-by-default rule, and the specific Gmail failure above — that the note names both halves, that a thread listing has no body and which action produces one.
Tests are in a sibling
descriptions_tests.rswith the same#![allow(clippy::expect_used, …)]headermod_tests.rsuses, so the crate's lints stay on for real code. fmt and clippy clean.Related
The consumer side is tinyhumansai/openhuman#5322 — the orchestrator and integrations-agent prompts render these notes for connected toolkits. That PR is blocked on this one: it calls a function that has to exist here first. The catalogue half moved into this crate before it could land, which is why it is arriving as two PRs.