Skip to content

feat(composio): teach the model to reach for Composio - #330

Open
plombeer31 wants to merge 1 commit into
feat/integrations-telegramfrom
feat/composio-seamless
Open

feat(composio): teach the model to reach for Composio#330
plombeer31 wants to merge 1 commit into
feat/integrations-telegramfrom
feat/composio-seamless

Conversation

@plombeer31

Copy link
Copy Markdown
Collaborator

Stacked on #329#328#327. Base is feat/integrations-telegram, so the diff here is just the prompt section. Retarget down the stack as each lands.

What

Mounting Composio's meta-tools isn't enough on its own. In the catalog they read as four opaque mcp.composio.COMPOSIO_* entries with nothing to say that "email this to Ivan" is now possible — so the model answers "I can't send email" while holding a tool that sends email.

This adds a four-line ### integrations section to the stable prefix that names the flow: search first, never guess an app tool's name, execute what you found, and if the account isn't connected yet put the sign-in link in a reply.

Why that last line means no new rendering code

Tool results aren't linkified in chat, but replies are — they already get OSC 8 links and clickable [link] chips. Routing the connect URL through reply reuses the surface that already exists, instead of teaching the tool renderer about links (a change to a much busier surface for a strictly worse result).

Derived, not flagged

The section keys off mcp.composio.COMPOSIO_SEARCH_TOOLS being present in the descriptor list rather than a new composioActive boolean threaded through BuildPromptInput. The descriptors already reflect exactly what mounted this boot, so the guidance cannot drift out of sync with the tools it describes — and replay gets it for free.

Why our text, not Composio's

Composio's session response ships an experimental.assistive_prompt that would drop straight in. I didn't use it:

  • it is a remote-controlled string being piped into the system prompt — a third party could silently re-steer the agent;
  • any edit on their side would invalidate the KV-cached prefix for every user at once.

A short local paragraph costs a few dozen tokens and keeps both properties.

Also deliberately omitted: the list of already-connected apps. Genuinely useful, but it changes the moment the operator authorises something — mid-session — and the stable prefix is the one part of the prompt that must not move. The model can ask Composio directly.

KV-cache placement

Between ### capabilities and ### instructions. Persona, rules, skills and the entire tools catalog stay byte-identical whether or not Composio is configured; with no key the section is absent and the prefix matches pre-integration builds. A test pins all three properties.

The approval work turned out to be unnecessary

The plan flagged a risk: if Composio didn't tag its discovery tools readOnlyHint, every search would need approval and the seamlessness would die. Reading the live endpoint settled it —

tool readOnlyHint gated
COMPOSIO_SEARCH_TOOLS true no
COMPOSIO_GET_TOOL_SCHEMAS true no
COMPOSIO_MANAGE_CONNECTIONS false, destructive yes
COMPOSIO_MULTI_EXECUTE_TOOL false, destructive yes

— which is exactly what mcp-tool-adapter.ts already gates on. Discovery flows unprompted, every write to a real account still hits the approval gate, and no per-tool ACL override was needed — the documented MCP invariants are untouched.

Testing

npm run lint and npm test green (7220 tests). 9 new tests.

Verified on a real runtime: with a key, a 4-tool Composio catalog and the section rendered after ### capabilities; with no key, no section at all.

Mounting Composio's meta-tools is not enough on its own. In the
catalog they read as four opaque mcp.composio.COMPOSIO_* entries with
nothing to say that "email this to Ivan" is now possible, so the model
answers "I can't send email" while holding a tool that sends email.

Adds a four-line `### integrations` section to the stable prefix that
names the flow: search first, never guess an app tool's name, execute
what you found, and if the account is not connected yet put the
sign-in link in a `reply` so the user can click it.

That last point is why no new rendering code is needed. Tool results
are not linkified in chat, but replies are -- they already get OSC 8
links and clickable chips -- so routing the connect URL through
`reply` reuses the surface that exists instead of teaching the tool
renderer about links.

Derived, not flagged: the section keys off
`mcp.composio.COMPOSIO_SEARCH_TOOLS` being present in the descriptor
list, so it cannot drift out of sync with what actually mounted, and
it needs no new plumbing through BuildPromptInput.

The text is ours rather than Composio's `experimental.assistive_prompt`
(which their session response offers, ready to drop in). Piping a
remote-controlled string into the system prompt would let a third
party silently re-steer the agent, and any edit on their side would
invalidate the KV-cached prefix for every user at once.

The list of already-connected apps is deliberately left out. It would
be useful, but it changes the moment the operator authorises something
-- mid-session -- and the stable prefix is the one part of the prompt
that must not move.

Placement is between `### capabilities` and `### instructions`, so
persona, rules, skills and the whole tools catalog stay byte-identical
whether or not Composio is configured; with no key the section is
absent entirely and the prefix matches pre-integration builds.

No approval work was needed after all: Composio annotates
SEARCH_TOOLS and GET_TOOL_SCHEMAS `readOnlyHint: true` and the two
mutating tools destructive, which is exactly what mcp-tool-adapter
already gates on. Discovery flows unprompted and every write to a real
account still hits the approval gate, with no per-tool ACL override
and no change to the documented MCP invariants.

Verified on a real runtime: with a key the section renders after a
4-tool Composio catalog; with no key it is absent.
@plombeer31
plombeer31 force-pushed the feat/integrations-telegram branch from de326ec to 3819f04 Compare September 4, 2026 21:56
@plombeer31
plombeer31 force-pushed the feat/composio-seamless branch from 891db54 to a0510a8 Compare September 4, 2026 21:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant