composio.tools.execute("GITHUB_CREATE_ISSUE", …). It does that job well, and the API is clean.
But it is also a separate vendor account:
- Tool connections live behind their own API key, dashboard, and
user_id(entity) namespace. - That
user_idscopes connected accounts and nothing else — it knows the agent’s Slack and GitHub grants, but not its virtual card, its inbox, its vault secrets, or its KYC status. - “Who let this agent post to Slack, and what else can it touch?” is answered in Composio for tools, and somewhere else for everything else. There is no shared accountability.
/connections primitive gives the agent the same
capability — connect an app, list its tools, execute them — but rooted in one identity:
- The tenant user that holds the connections is the same user that owns its cards, its email inbox, its vault secrets, and its KYC.
- What the agent is allowed to connect and execute is decided by that user’s Account Kit at execution time — not by which tools you remembered to pass into the model.
- Every connect and execute lands in the same per-user activity log as everything else the agent does.
Composio is a trademark of its owner, used here for identification only. No endorsement or affiliation is implied.
Tested against: Composio TypeScript SDK
@composio/core v3
(API surface per docs.composio.dev, snapshot June 2026) and the
Naive Node SDK @usenaive-sdk/server against the Naive API (base
https://api.usenaive.ai/v1, docs snapshot June 2026).Version notes:- Composio v3 renamed v1/v2 concepts: entity → user, action → tool, app → toolkit, integration → auth config, connection → connected account. This guide uses v3 terms.
- For Composio-managed OAuth,
connectedAccounts.initiate()is being retired (org cutover by 2026-07-03) in favor ofconnectedAccounts.link()— same return shape andredirectUrl. This guide useslink(). - Both are evolving APIs — verify method names against your installed SDK version.
Concept map
| Composio | Naive | Notes |
|---|---|---|
new Composio({ apiKey }) | new Naive({ apiKey }) | Server-side key in both cases |
user_id (entity) — scopes connected accounts only | tenant user via naive.forUser(id) — scopes every primitive | The core consolidation win |
toolkit slug (github, gmail, slack) | Same slug convention (GET /v1/toolkits) | Naive reuses toolkit + tool naming |
tool slug (GITHUB_CREATE_ISSUE) | Same tool slug | Identical naming |
Auth config ac_… (composio.authConfigs.create / .get) | No standalone auth-config object — toolkits are connectable directly | Naive defaults to managed auth; bring-your-own OAuth is an Account-Kit setting, not a separate resource |
composio.connectedAccounts.link(userId, authConfigId, { callbackUrl }) → redirectUrl | client.connections.connect(toolkit, { callbackUrl }) → redirectUrl | Naive keys by toolkit slug, not an auth-config id |
composio.connectedAccounts.list({ userIds, statuses }) | client.connections.connected() | Naive reads a local mirror (lazy-reconciled) |
composio.toolkits.get() (full catalog) | GET /v1/toolkits / client.connections.list({ search }) | The per-user list is kit-filtered; /v1/toolkits is the full catalog |
composio.tools.get(userId, { toolkits }) | client.connections.tools(toolkit) | Naive returns raw schemas — see gaps on framework wrappers |
composio.tools.execute(slug, { userId, arguments, connectedAccountId }) | client.connections.execute(toolkit, tool, args) | The core path; see gaps for multi-account selection |
| Disconnect (revoke a connected account) | client.connections.disconnect(toolkit, { purge }) | Soft-disable by default; purge: true revokes |
| Auth-config-level tool enable/disable | Account Kit connections_config — allow/block + per-tool filter + approval gating | Execution-time policy, per user — the governance win |
Triggers (composio.triggers.*, app event subscriptions) | Partial — Webhooks | No generic per-toolkit trigger subscription — see gaps |
| Provider toolsets (OpenAI / Anthropic / LangChain / Vercel wrappers) | — | Naive exposes tools to agents via MCP / its own runtime, not per-framework wrappers |
Version pinning (version, dangerouslySkipVersionCheck) | — | Naive pins toolkit versions internally; no per-call version |
Before / after: the core path
The path that matters for almost every agent is connect an app, then execute one of its tools. Here it is on both platforms (GitHub used as the example toolkit).- No auth-config resource. Composio makes you create an auth config (
ac_…) per toolkit before linking. Naive connects by toolkit slug directly using managed auth — there is no intermediate object to create or track. White-label OAuth (your own client id/secret) is set on the Account Kit (connections_config.custom_auth_configs), not as a standalone resource. - Arguments are the last positional arg. Composio takes
{ userId, arguments }; Naive’s scoped client already knows the user, soexecute(toolkit, tool, args)passes the tool’s arguments directly. - The user id is your identity, not a separate entity. In Composio
user_idis an entity that exists only for tool auth. In Naive the same id is a tenant user that also owns the agent’s cards, inbox, vault, and KYC.
Connecting an app (OAuth)
Both platforms return a hosted redirect URL and surface a connection status you can poll.- On Naive, connecting a service is a sensitive action and may be
approval-gated by the Account Kit —
connectcan returnpending_approval(202) until a human approves. INITIATEDrows older than ~10s are reconciled from the provider before responding. See Connection status.
Minimal viable migration
The smallest swap that keeps a working agent running is just connect + execute. You do not need to recreate auth configs, tenant-user fan-out, or Account Kits to make your first call.Install the SDK and set your key
NAIVE_API_KEY (a server-side key from the dashboard).Map your user ids
Composio’s
user_id (entity) becomes a Naive tenant user. Reuse
your own database id as external_id so the mapping is 1:1, or pass it straight into
naive.forUser(id) if you already key by it.Drop the auth-config step, connect by slug
Replace
authConfigs.create + connectedAccounts.link(userId, authConfigId, …) with a
single client.connections.connect(toolkit, { callbackUrl }). Same hosted redirectUrl.Swap execute
Replace
composio.tools.execute(tool, { userId, arguments }) with
client.connections.execute(toolkit, tool, args). Tool slugs (GITHUB_CREATE_ISSUE,
GMAIL_SEND_EMAIL, …) are unchanged.Consolidate further once you’re on Naive
This is where the migration pays for itself. In Composio,user_id isolates tool connections
and nothing else. On Naive, the unit of isolation is a tenant user, and it isolates the
agent’s entire footprint.
Gain #1 — one identity across primitives
- With Composio, the agent’s connections are an island. With Naive,
naive.forUser(acme.id)is a single handle to connections and cards and email and vault and KYC. - You provision a customer’s whole agent footprint from one identity, and tear it down from one place. Sibling tenants can never read each other’s connections, cards, or secrets.
Gain #2 — execution-time permission enforcement
- Which apps an agent may connect, and which tools it may run, is policy on the Account Kit — not a check you hand-write, and not just “which tools you passed to the model.”
- The agent’s code is identical for every tier —
client.connections.execute("github", …). Whether the call runs is decided by the caller’s kit at execution time:- A
blocklist/allowlistmodegoverns which toolkits connect at all. - Per-tool
enable/disablefilters lock execution down to specific tools. approvalToolkits(orrequiresApproval) freezes a sensitive call until a human approves — the API replays it only after approval.
- A
- Move the user to a kit that drops
githuband the exact same line returnsforbidden, with no code change on your side. Composio gates auth (which accounts exist); Naive additionally gates execution per user.
Gain #3 — unified accountability
- Every connect, execute, and disconnect for a customer lands in one per-user activity log — alongside their card, email, and vault events, not in a separate tools dashboard:
- That is the question that is hard to answer when tool auth lives in Composio, cards live in Stripe, and secrets live somewhere else. Under Naive it is a single query.
What does not map yet
A migration guide that hides gaps is worse than none. None of the following block the core path (connect → list tools → execute → disconnect all map cleanly), but they are real differences. Check this list against your app before you commit.| Composio feature | Status on Naive | Workaround |
|---|---|---|
Auth configs as first-class objects (composio.authConfigs.*, multiple configs per toolkit) | No standalone object — managed auth by toolkit slug | Bring-your-own OAuth via Account Kit connections_config.custom_auth_configs (per toolkit) |
Provider toolsets (tools.get returning OpenAI / Anthropic / LangChain / Vercel-shaped tools, provider.handleToolCalls) | Not provided on /connections | connections.tools(toolkit) returns raw schemas; expose tools to agents via MCP / the Naive runtime, or wrap the schemas yourself |
Triggers (composio.triggers.* — subscribe to app events that fire tools) | No generic per-toolkit trigger | Use Webhooks for Naive-native events; poll for app-side changes |
Toolkit version pinning (version, dangerouslySkipVersionCheck) | Not exposed | Naive pins versions internally; no per-call version to set |
Custom tools / proxy execute (createCustomTool, raw HTTP passthrough) | Not on /connections | Use Naive Functions for custom server-side logic |
waitForConnection() polling helper | No promise helper | Poll connections.connected() or drive status from a connection webhook |
Multiple connected accounts per toolkit (allowMultiple, pick by connectedAccountId) | Partial — single active account per toolkit is the default path | If you need a specific account, check the execute API reference for optional connected_account_id support |
| File / attachment helpers in tool execution | Provider-dependent | Pass file references in arguments; no dedicated upload helper on /connections |
Where to go next
/connectionsprimitive — full connect/execute lifecycle- Connections API reference — exact request/response shapes
connectionsSDK sub-client — typed method signatures- Account Kits and Approvals — the policy model that makes execution-time tool governance real
- Tenant users — the identity that the consolidation hangs off