name: composio
description: Use Composio from Agent Swarm via the agent-swarm x composio CLI route or the swarm_x MCP tool. Trigger when a task needs connected third-party app tools such as Gmail, GitHub, Slack, Notion, or HubSpot through Composio Tool Router sessions, Connect Links, or connected accounts. This is the HUB skill — for Google apps see the sibling skills composio-gmail, composio-google-calendar, composio-google-docs.
Composio
Hub skill for Composio-managed third-party app access from the swarm.
The supported surface is the Agent Swarm x route:
- CLI:
agent-swarm x composio <METHOD> <path> [--body '<json>'] - MCP:
swarm_xwithtarget: "composio"
COMPOSIO_API_KEY is deployment-scoped and injected by the CLI/API process — you
never pass or see it. All paths are relative Composio REST paths.
Per-app playbooks (verified slugs + argument shapes + gotchas): [[composio-gmail]] · [[composio-google-calendar]] · [[composio-google-docs]]. Read the sibling skill for the app you're touching — it lists the verified tool slugs so you don't have to
/searchblind.
Core Model
user_idis the app user whose connected accounts are used. We use the person's email asuser_id(e.g.t@desplega.ai). There is no explicit "create user" call — a user is created implicitly the first time you reference itsuser_id(e.g. when you create a Connect Link). Don't look for aPOST /usersendpoint; it doesn't exist in this flow.- Auth config (
ac_…) = a project-level OAuth app config (one per toolkit, set up in the Composio dashboard). A project can have several. - Connected account (
ca_…) = a specific user's authorized connection to a toolkit. Persists across sessions under thatuser_id. - Connect Link = the short-lived URL the user clicks to authorize OAuth.
- Tool Router session = a task/conversation runtime context that auto-resolves
the right connected account for a toolkit set. Reuse its
session_id; create a new one if the user, toolkit set, auth config, or pinned account changes.
Two ways to call a tool — and when to use each
- Tool Router session (
/tool_router/session…) — best for multi-turn agent work over a toolkit set. Auto-resolves connections, supports in-session/search. But it can fail withToolRouterV2_NoActiveConnection(code- when stale/duplicate accounts shadow the good one (see Gotchas).
- Direct execute (
POST /tools/execute/<TOOL_SLUG>) — best for one-off reads, verification, or when the session reports no active connection. Pin the account explicitly withconnected_account_id. This is the reliable path when a user has exactly one good connection per toolkit.
agent-swarm x composio POST /tools/execute/GMAIL_FETCH_EMAILS \
--body '{"user_id":"t@desplega.ai","connected_account_id":"ca_xlWpkPocZSGr","arguments":{"max_results":3,"include_payload":false,"verbose":false}}'
Recipe A — Register a user + send Connect Links (one per toolkit)
- List the project's auth configs to get the
ac_…ids:agent-swarm x composio GET "/auth_configs" \ | jq -r '.items[] | "\(.toolkit.slug)\t\(.id)\t\(.name)"' - Create one Connect Link per auth config (flat payload — the user is created
implicitly here):
agent-swarm x composio POST /connected_accounts/link \ --body '{"auth_config_id":"ac_isRh2iU0Z0lM","user_id":"t@desplega.ai"}' # → returns { redirect_url / connect_url: "https://connect.composio.dev/link/lk_…" }- Use
/connected_accounts/link, NOTPOST /connected_accounts. The older path now returns 400 for Composio-managed OAuth configs. - There is no single bundled URL for multiple toolkits — Composio issues one link per toolkit. Send all of them, labelled per app.
- Use
- Links expire ~10 minutes after creation (link-start token TTL). Regenerate fresh links immediately before posting to the user, and tell them the expiry. Offer to regenerate on request.
- The user clicks each link and authorizes. Connections then show as
ACTIVE.
Recipe B — Verify connections / check status
agent-swarm x composio GET "/connected_accounts?user_id=t@desplega.ai" \
| jq -r '.items[] | "\(.toolkit.slug)\t\(.id)\t\(.status)"'
Look for status: ACTIVE. Anything INITIALIZING/FAILED/EXPIRED is not
usable. Pin the ca_… of the ACTIVE account when calling tools directly.
Recipe C — Link a Composio connection to a swarm user identity
"Add the composio connection to my user as identity" → manage-user with
action: update and the identities array.
CRITICAL:
identitiesis declarative (the desired full set). You MUST pass every existing externalId PLUS the new one, or the omitted ones get removed. First read the current identities (resolve-user/ get the user), then write the full list back.
// manage-user action:update
{
"userId": "4dacc65cdab044a6805b2aa0342331b7",
"identities": [
{ "kind": "github", "externalId": "tarasyarema" },
{ "kind": "slack", "externalId": "U08NR6QD6CS" },
{ "kind": "linear", "externalId": "…" },
{ "kind": "kapso", "externalId": "…" },
{ "kind": "composio", "externalId": "t@desplega.ai" } // ← the new one
]
}
Confirm with resolve-user(kind:composio, externalId:"t@desplega.ai").
Workflow (Tool Router session path)
- Create or reuse a Tool Router session for the user + toolkit set.
/searchbefore executing — get the current slug, schema, plan, pitfalls, and connection status. (The sibling skills list verified slugs so you can often skip the search.)- If Composio reports no active connection, run
COMPOSIO_MANAGE_CONNECTIONSfor the exact toolkit names, share the Connect Link, and pause for auth. - Retry only after the toolkit shows an ACTIVE connected account.
- Prefer metadata-first reads (
include_payload:false,verbose:false) unless the user explicitly needs bodies/attachments/full records. - Paginate on
nextPageToken/ cursors.
# Create a Gmail-scoped session
agent-swarm x composio POST /tool_router/session \
--body '{"user_id":"t@desplega.ai","toolkits":{"enable":["gmail"]},"workbench":{"enable":false}}'
# Search for the right tool
agent-swarm x composio POST /tool_router/session/$SESSION_ID/search \
--body '{"queries":[{"use_case":"Check recent emails and return metadata only."}]}'
# Connect a toolkit if needed
agent-swarm x composio POST /tool_router/session/$SESSION_ID/execute \
--body '{"tool_slug":"COMPOSIO_MANAGE_CONNECTIONS","arguments":{"toolkits":["gmail"]}}'
Discovering tools for any toolkit
The sibling skills cover the Google apps. For any other toolkit, list its tools:
agent-swarm x composio GET "/tools?toolkit_slug=<slug>&limit=100" \
| jq -r '.items[] | "\(.slug)\t\(.name)"'
Inspect a tool's arguments before calling:
agent-swarm x composio GET "/tools?toolkit_slug=<slug>&limit=200" \
| jq '.items[] | select(.slug=="<SLUG>") | {required:.input_parameters.required, props:(.input_parameters.properties|keys)}'
Gotchas
ToolRouterV2_NoActiveConnection(4302) despite an ACTIVE account. Cause: staleINITIALIZINGduplicate accounts (leftover from regenerated Connect Links) shadow the good one; the session auto-resolves the wrong account and in-session/searchreturns empty. Fix: use directPOST /tools/execute/<SLUG>with the ACTIVEconnected_account_idpinned (Recipe B → theca_…). Long-term fix is deleting the stale duplicates — but ask the user before deleting any connection.- Calendar "from a year ago" trap —
GOOGLECALENDAR_EVENTS_LISThas no defaulttimeMin, so it returns old events. Always passtimeMin(now, RFC3339),singleEvents:true,orderBy:"startTime". See [[composio-google-calendar]].
Guardrails
- Never invent tool slugs or argument shapes. Use the sibling skills,
/search, orGET /tools?toolkit_slug=…. - Never pass absolute URLs as Composio paths — relative API paths only.
- Do not expose
COMPOSIO_API_KEY; server-side code injects it. - Do not fetch email bodies/attachments or run destructive/write tools unless the task explicitly requires them.
- If multiple accounts exist for a toolkit, ask which to use or pin the specific connected account per the task. Ask before deleting any connection.