Skip to content

Backend-resolved GET_OH_PROFILE for connected toolkits #1266

@senamakel

Description

@senamakel

Summary

After a Composio OAuth connection completes, populate the user's PROFILE.md with identity fields (name, email, username, avatar, profile URL) for any connected toolkit — not just the three with hand-written Rust providers (gmail / notion / slack). Do this by calling a single virtual action, `GET_OH_PROFILE`, that the backend resolves to the correct upstream Composio action per toolkit.

Problem

ComposioProvider::on_connection_created (src/openhuman/composio/providers/traits.rs) already fetches the profile and runs the initial sync after a connection becomes ACTIVE. PR #1262 extends that hook to mirror the result into {workspace_dir}/PROFILE.md so the agent's prompt loader picks up identity fragments on the next turn.

Today this only works for three toolkits — gmail, notion, slack — because each has a hand-written Rust provider that hard-codes:

  1. The Composio action slug to call (GMAIL_GET_PROFILE, NOTION_GET_ABOUT_ME, SLACK_FETCH_TEAM_INFO).
  2. The JSON paths to extract display_name / email / username / avatar_url / profile_url from the response.

Every other toolkit in catalog_for_toolkit() (src/openhuman/composio/providers/mod.rs) — github, twitter, linkedin, discord, telegram, googlecalendar, googledrive, googledocs, googlesheets, outlook, microsoft_teams, linear, jira, trello, asana, dropbox, spotify, whatsapp, shopify, stripe, hubspot, salesforce, airtable, figma, youtube — has no provider, so the bus subscriber bails at get_provider(toolkit) (composio/bus.rs:317) and contributes nothing to the profile or to the facets table.

Adding a Rust provider per toolkit doesn't scale: it's ~50 LoC of pick_str paths each, gated on a core release every time Composio adds an integration.

Solution (optional)

Move the per-toolkit knowledge to the backend, where Composio's tool catalog already lives, behind one virtual action.

Backend

Expose composio.get_oh_profile { toolkit, connection_id } → ProviderUserProfile:

  • The backend owns a static map toolkit → upstream_action_slug + field_paths (e.g. twitter → TWITTER_USER_ME, github → GITHUB_GET_THE_AUTHENTICATED_USER).
  • It executes the upstream action on the user's connection, normalizes the response to the existing ProviderUserProfile shape, and returns it.
  • Adding a new integration becomes one mapping entry in the backend, no core release.

Core

In composio::bus::ComposioConnectionCreatedSubscriber::handle:

  1. Keep the get_provider(toolkit) branch — bespoke providers (gmail / notion / slack) still get richer extras for sync().
  2. Add a fallback branch: when no provider is registered, call the new RPC, build a ProviderUserProfile from the response, then run it through the existing persist_provider_profile (facets) + merge_provider_into_profile_md (PROFILE.md) path landed in feat(profile): mirror connected-account profiles into PROFILE.md #1262.

No new persistence code, no new prompt-injection plumbing — just one new fetcher and a fallback.

Optional follow-up

Even registered providers could call GET_OH_PROFILE first as a baseline and overlay their toolkit-specific enrichment. Out of scope for v1.

Acceptance criteria

  • Backend composio.get_oh_profile route — accepts { toolkit, connection_id }, returns the existing ProviderUserProfile shape (display_name, email, username, avatar_url, profile_url, extras). Resolves at least these toolkits on day one: github, twitter, linkedin, googlecalendar, discord. Other toolkits in the catalog return a structured "no profile mapping" error rather than 500ing.
  • Core fallback in the connect hookComposioConnectionCreatedSubscriber calls get_oh_profile when get_provider(toolkit) returns None, builds a ProviderUserProfile, and runs it through persist_provider_profile + merge_provider_into_profile_md. Failures are logged at warn and never abort the connect flow (matches the existing PII-discipline pattern).
  • Disconnect paritycomposio_delete_connection already cleans up facets + PROFILE.md by (toolkit, connection_id); verify it works unchanged for backend-resolved profiles.
  • Tests — at least one mock-backend integration test that connects a toolkit with no Rust provider (e.g. github), observes the bus dispatch, and asserts the bullet appears in PROFILE.md.
  • Diff coverage ≥ 80% — the implementing PR meets the changed-lines coverage gate (Vitest + cargo-llvm-cov, enforced by .github/workflows/coverage.yml).
  • Docs — short note in src/openhuman/composio/providers/README.md (or equivalent) explaining when to write a Rust provider vs. rely on the backend resolver.

Related

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions