You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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:
The Composio action slug to call (GMAIL_GET_PROFILE, NOTION_GET_ABOUT_ME, SLACK_FETCH_TEAM_INFO).
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.
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:
Keep the get_provider(toolkit) branch — bespoke providers (gmail / notion / slack) still get richer extras for sync().
Add a fallback branch: when no provider is registered, call the new RPC, build a ProviderUserProfile from the response, then run it through the existingpersist_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 hook — ComposioConnectionCreatedSubscriber 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 parity — composio_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.
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.mdso 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:GMAIL_GET_PROFILE,NOTION_GET_ABOUT_ME,SLACK_FETCH_TEAM_INFO).display_name/email/username/avatar_url/profile_urlfrom 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 atget_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_strpaths 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:toolkit → upstream_action_slug + field_paths(e.g.twitter → TWITTER_USER_ME,github → GITHUB_GET_THE_AUTHENTICATED_USER).ProviderUserProfileshape, and returns it.Core
In
composio::bus::ComposioConnectionCreatedSubscriber::handle:get_provider(toolkit)branch — bespoke providers (gmail / notion / slack) still get richer extras forsync().ProviderUserProfilefrom the response, then run it through the existingpersist_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_PROFILEfirst as a baseline and overlay their toolkit-specific enrichment. Out of scope for v1.Acceptance criteria
composio.get_oh_profileroute — accepts{ toolkit, connection_id }, returns the existingProviderUserProfileshape (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.ComposioConnectionCreatedSubscribercallsget_oh_profilewhenget_provider(toolkit)returnsNone, builds aProviderUserProfile, and runs it throughpersist_provider_profile+merge_provider_into_profile_md. Failures are logged atwarnand never abort the connect flow (matches the existing PII-discipline pattern).composio_delete_connectionalready cleans up facets + PROFILE.md by(toolkit, connection_id); verify it works unchanged for backend-resolved profiles.github), observes the bus dispatch, and asserts the bullet appears in PROFILE.md..github/workflows/coverage.yml).src/openhuman/composio/providers/README.md(or equivalent) explaining when to write a Rust provider vs. rely on the backend resolver.Related
src/openhuman/composio/providers/traits.rs—ComposioProvider::on_connection_createdhook.src/openhuman/composio/bus.rs:317— currentget_providershort-circuit that this issue removes.src/openhuman/composio/providers/profile_md.rs—merge_provider_into_profile_md(PR feat(profile): mirror connected-account profiles into PROFILE.md #1262).src/openhuman/composio/providers/profile.rs—persist_provider_profile(existing facet writer).