Let the module resolve a backend session, so proxied Composio can run in it - #102
Conversation
… in it Composio sync has two credential paths and only one of them worked inside a loaded module. The direct branch reads its API key through `ComposioHost::api_key`, a seam the host answers per call. The proxied branch read `Config::session_token`, which inside a module is a load-time snapshot carrying no bearer — so `EngineRuntimeConfig` refused it by design and every proxied user fell out of the branch. The consequence was quiet and large. A host whose Composio mode is backend — which is OpenHuman's default — got a periodic sync loop that never started, because `composio_sync_can_run` gated on direct mode, and neither the host nor the module reported it, since neither believed it was responsible. `ComposioHost` gains `session_bearer`, beside the `api_key` that already works. `composio_config`'s proxied branch consults the seam first and falls back to the config exactly as before, so a host running the engine in-process behaves identically — no seam is installed there, the accessor answers `None`, and the old config read still happens. ## Why a seam rather than a field on ModuleConfig The bearer is an app-session JWT the host refreshes. A value captured at module load works until it expires and then makes every sync fail with an auth error that reads as the user being signed out — the silent-staleness failure this whole migration keeps having to design against. Asking per call means the answer is always the one that is valid now. It is the same reasoning `api_key` already carries, and the reason that member is fetched per call too. ## What the gate now excludes Both modes qualify: direct resolves a key, backend resolves a bearer. What is still refused is a host that resolved to *neither* — an empty or unrecognised mode string, which has no credential path at all, so starting the loop would fail on every tick and append a failed audit row each time. The trait member is defaulted to `None`, so a host that predates it compiles unchanged and falls back to the config read it always did. `session_bearer` deliberately does NOT copy `is_available`'s optimism. That probe answers `true` when it cannot reach the host, because a wrong `false` there reads as "not signed in" and hides a broken sync. A credential is not something to be optimistic about: an unreachable host yields `None`, which lets `composio_config` refuse by name rather than send an empty bearer at the backend.
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: Warning Your free Security trial is over. An organization admin can activate billing to continue. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
How this change flows1 changed behaviour across 5 relationships. 3 surrounding behaviours are shown (60 graph nodes walked). 50 further behaviours left out to keep the diagram readable. flowchart LR
n0["composio_config<br/>changed"]:::changed
n1["run_composio_connection_with_caps"]:::impacted
n2["Result"]:::impacted
n3["run_gmail_backfill"]:::impacted
n0 -->|uses| n2
n1 -->|calls| n0
n1 -->|uses| n2
n3 -->|calls| n0
n3 -->|uses| n2
classDef changed fill:#0d4429,stroke:#238636,color:#e6edf3
classDef impacted fill:#161b22,stroke:#6e7681,color:#c9d1d9
classDef flagged fill:#5a1e02,stroke:#d93f0b,color:#ffffff
classDef blocking fill:#67060c,stroke:#f85149,color:#ffffff
Green: changed behaviour. Grey: surrounding behaviour. Arrows name the call, use, implementation, or test relationship. Orange: has findings. Red: has a finding that blocks the merge. |
Unblocks the last of openhuman#5560. Without this the host cannot delete its in-process engine without breaking Composio sync for its default configuration.
The problem
Composio sync has two credential paths and only one worked inside a loaded module:
Inside a module,
ConfigisEngineRuntimeConfig— a load-time snapshot with no bearer in it — sosession_tokenrefuses by design and every proxied user falls out of the branch.The consequence was quiet and large:
composio_sync_can_rungated the periodic loop on direct mode, so a host whose Composio mode is backend — OpenHuman's default — got a loop that never started. Neither side reported it, because neither believed it was responsible.The fix
ComposioHostgainssession_bearer, sitting beside theapi_keythat already works.composio_config's proxied branch consults the seam first and falls back to the config exactly as before, so a host running the engine in-process is unaffected — no seam installed there, the accessor answersNone, the old config read still happens.Why a seam and not a
ModuleConfigfieldThe bearer is an app-session JWT the host refreshes. A value captured at module load works until it expires and then makes every sync fail with an auth error that reads as the user being signed out — the silent-staleness failure this migration keeps having to design against. Asking per call means the answer is always the one valid now. Same reasoning
api_keyalready carries.What the gate still excludes
Both modes qualify now: direct resolves a key, backend resolves a bearer. Still refused is a host that resolved to neither — an empty or unrecognised mode string has no credential path, so starting the loop would fail every tick and append a failed audit row each time.
Two deliberate asymmetries
The trait member is defaulted to
None, so a host predating it compiles unchanged and falls back to the config read.session_bearerdoes not copyis_available's optimism. That probe answerstruewhen it cannot reach the host, because a wrongfalsereads as "not signed in" and hides a broken sync. A credential is not something to be optimistic about: an unreachable host yieldsNone, which letscomposio_configrefuse by name rather than send an empty bearer at the backend. The test pins both halves.Not a registered bus method
ComposioHostis served by the host and consumed by the module, likeChatHost,EmbeddingHostandRuntimeHost— so it appears in none ofMETHODS, the module manifest orEXPECTED_METHODS, and the drift assertion is untouched.Validation
cargo check --workspace --all-targetsclean in both workspaces;cargo test --workspace2034 passing, module lane 70 passing; clippy-D warningsclean on root and module;cargo fmtclean on both;cargo doc --no-deps --all-featuresclean under-D warnings.One existing test changed meaning rather than being deleted:
composio_periodic_sync_starts_only_when_the_host_resolved_direct_modeasserted the constraint this PR removes. It is now..._starts_for_any_mode_that_can_resolve_a_credential, with the reason for the change written into its doc.