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
Let the module resolve a backend session, so proxied Composio can run 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.
0 commit comments