Skip to content

feat(composio): tell the model what a toolkit's actions hand back - #132

Open
yh928 wants to merge 1 commit into
tinyhumansai:mainfrom
yh928:feat/toolkit-result-notes
Open

feat(composio): tell the model what a toolkit's actions hand back#132
yh928 wants to merge 1 commit into
tinyhumansai:mainfrom
yh928:feat/toolkit-result-notes

Conversation

@yh928

@yh928 yh928 commented Sep 2, 2026

Copy link
Copy Markdown

Summary

toolkit_description answers "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_notes is the output-side counterpart, added beside toolkit_description because 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 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 of this text made exactly that mistake and told the model every Gmail read action answers with a markdown body, when only GMAIL_FETCH_EMAILS carries one.

None for 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.rs with the same #![allow(clippy::expect_used, …)] header mod_tests.rs uses, 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.

`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.
@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 7b1501ce-9111-4cb6-8835-05ac593187e8

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 @coderabbitai help to get the list of available commands.

@tinysweeper tinysweeper Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tinysweeper found nothing blocking. Approving.

$0.0000 · 0 in / 0 out

@tinysweeper

tinysweeper Bot commented Sep 2, 2026

Copy link
Copy Markdown

How this change flows

1 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
Loading

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.

tinysweeper 0.1.0

@tinysweeper tinysweeper Bot added the priority: p3 Whenever. Cosmetic, a nicety, or a cleanup with no user visible effect. label Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority: p3 Whenever. Cosmetic, a nicety, or a cleanup with no user visible effect.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant