Related symptom report
#3030 — Notion connection active but write/editing not supported: UI shows Notion ACTIVE, memory sync/read works, but delegate_to_integrations_agent rejects notion with "toolkit not connected" / the tool’s toolkit enum does not list notion.
This is tracked here as a prompt + tool-schema staleness problem, not missing Notion write support in Composio.
Summary
When a user connects a new Composio integration mid-conversation (e.g. Notion OAuth completes while a chat thread is open), the orchestrator can keep operating on a stale view of connected toolkits. The collapsed delegate_to_integrations_agent tool and the frozen system-prompt Connected Integrations block may not include the new slug even though Settings / Composio report ACTIVE.
Result: reads via memory sync succeed; routed writes through the integrations agent fail because the orchestrator cannot select the toolkit.
Expected vs actual
| Layer |
Expected after ACTIVE |
Often actual mid-thread |
| Composio / Settings |
notion ACTIVE |
✅ ACTIVE |
| Memory sync (notion provider) |
Pages ingest |
✅ Works (#3030) |
Orchestrator tool schema (delegate_to_integrations_agent toolkit enum) |
Includes notion |
❌ Stale list from session start |
Orchestrator system prompt (## Connected Integrations) |
Mentions notion |
❌ Frozen for KV cache (documented) |
integrations_agent spawn gate |
Allows notion |
❌ May reject if spawn uses parent’s frozen list |
Root cause (architecture)
OpenHuman intentionally freezes the system prompt after turn 1 to preserve the inference KV-cache prefix (session/turn.rs). Dynamic integration discovery is supposed to ride on:
- Per-turn tool-schema refresh —
refresh_delegation_tools() rebuilds SkillDelegationTool when INTEGRATIONS_CACHE hash changes (cached_active_integrations, UI 5s poll, post-OAuth invalidation).
- Spawn-time refresh —
subagent_runner / spawn_subagent re-fetch fetch_connected_integrations_status before integrations_agent pre-flight.
Gaps that explain #3030:
- Timing:
ComposioConnectionCreatedSubscriber invalidates + warms cache only after wait_for_connection_active (async poll). A user message in the same turn as connect can run before cache warm + refresh_delegation_tools on the next turn.
- Signal path: Mid-session refresh reads
cached_active_integrations only — it does not force a backend fetch. If invalidation happened but warm failed / UI poll lagged, hash may not advance and schema stays stale.
refresh_delegation_tools failure mode: On shared Arc for tools/tool_specs, refresh aborts and rolls back (turn.rs) — connection set updates but delegation enum does not.
- Prompt / schema mismatch: System prompt
## Connected Integrations can list toolkits from turn 1 while the tool enum was refreshed (or vice versa), confusing the model and support debugging.
- No user-visible “context refreshed” — unlike starting a new thread (re-fetches integrations on
history.is_empty()).
Notion-specific writes do exist on integrations_agent (composio_execute, etc.) once the toolkit is allowlisted at spawn time — the failure in #3030 is routing, not absent Notion actions.
Proposed fix
1. Eager orchestrator refresh on connection ACTIVE (core)
When ComposioConnectionCreated finishes wait_for_connection_active:
- After
invalidate_connected_integrations_cache() + eager warm, publish a DomainEvent (e.g. ComposioIntegrationsChanged { toolkits }) consumed by the active chat session (if any) to call refresh_delegation_tools() immediately — not only on the next user turn.
Alternative: expose a socket event integrations_changed so ChatRuntimeProvider triggers refresh without waiting for turn boundary.
2. Harden refresh_delegation_tools
- Log + metric when refresh fails due to shared
Arc (today warn-only).
- Consider clone-on-write for orchestrator tool lists so mid-session refresh cannot silently no-op.
- Unit test: simulate hash change →
delegate_to_integrations_agent enum includes new toolkit.
3. Prompt / context hygiene (choose one)
- A (minimal): Inject a one-line user-role system notice on refresh: “Integration connected: notion — use delegate_to_integrations_agent with toolkit notion.” (does not bust KV prefix).
- B: Append a short Connected Integrations delta block to the user message on turns after hash change (already partially described in
turn.rs comments).
- C: Document “start a new chat after connecting” until A/B ship (interim UX copy in Integrations settings).
4. delegate_to_integrations_agent.execute safety net
Before rejecting unknown toolkit, optionally re-check fetch_connected_integrations_status once (same pattern as spawn_subagent pre-flight) so a stale SkillDelegationTool cannot block an ACTIVE connection.
5. App / QA
Acceptance criteria
Code references
- Collapsed delegation tool:
src/openhuman/agent_orchestration/tools/skill_delegation.rs (connected_toolkits, enum gate)
- Tool synthesis:
src/openhuman/tools/orchestrator_tools.rs (collect_orchestrator_tools)
- Mid-session refresh:
src/openhuman/agent/harness/session/turn.rs (refresh_delegation_tools, cached_active_integrations)
- OAuth → cache:
src/openhuman/memory_sync/composio/bus.rs (ComposioConnectionCreatedSubscriber)
- Spawn-time refresh:
src/openhuman/agent/harness/subagent_runner/ops.rs, spawn_subagent.rs
- Integrations agent:
src/openhuman/agent_registry/agents/integrations_agent/
- Notion sync (works):
src/openhuman/memory_sync/composio/providers/notion/
Interim workaround (document in #3030)
Until fixed: start a new chat thread (or send a message on a fresh thread) after connecting an integration so history.is_empty() re-runs fetch_connected_integrations + initial refresh_delegation_tools.
Related symptom report
#3030 — Notion connection active but write/editing not supported: UI shows Notion ACTIVE, memory sync/read works, but
delegate_to_integrations_agentrejectsnotionwith "toolkit not connected" / the tool’stoolkitenum does not listnotion.This is tracked here as a prompt + tool-schema staleness problem, not missing Notion write support in Composio.
Summary
When a user connects a new Composio integration mid-conversation (e.g. Notion OAuth completes while a chat thread is open), the orchestrator can keep operating on a stale view of connected toolkits. The collapsed
delegate_to_integrations_agenttool and the frozen system-prompt Connected Integrations block may not include the new slug even though Settings / Composio report ACTIVE.Result: reads via memory sync succeed; routed writes through the integrations agent fail because the orchestrator cannot select the toolkit.
Expected vs actual
notionACTIVEdelegate_to_integrations_agenttoolkitenum)notion## Connected Integrations)notionintegrations_agentspawn gatenotionRoot cause (architecture)
OpenHuman intentionally freezes the system prompt after turn 1 to preserve the inference KV-cache prefix (
session/turn.rs). Dynamic integration discovery is supposed to ride on:refresh_delegation_tools()rebuildsSkillDelegationToolwhenINTEGRATIONS_CACHEhash changes (cached_active_integrations, UI 5s poll, post-OAuth invalidation).subagent_runner/spawn_subagentre-fetchfetch_connected_integrations_statusbefore integrations_agent pre-flight.Gaps that explain #3030:
ComposioConnectionCreatedSubscriberinvalidates + warms cache only afterwait_for_connection_active(async poll). A user message in the same turn as connect can run before cache warm +refresh_delegation_toolson the next turn.cached_active_integrationsonly — it does not force a backend fetch. If invalidation happened but warm failed / UI poll lagged, hash may not advance and schema stays stale.refresh_delegation_toolsfailure mode: On sharedArcfortools/tool_specs, refresh aborts and rolls back (turn.rs) — connection set updates but delegation enum does not.## Connected Integrationscan list toolkits from turn 1 while the tool enum was refreshed (or vice versa), confusing the model and support debugging.history.is_empty()).Notion-specific writes do exist on
integrations_agent(composio_execute, etc.) once the toolkit is allowlisted at spawn time — the failure in #3030 is routing, not absent Notion actions.Proposed fix
1. Eager orchestrator refresh on connection ACTIVE (core)
When
ComposioConnectionCreatedfinisheswait_for_connection_active:invalidate_connected_integrations_cache()+ eager warm, publish aDomainEvent(e.g.ComposioIntegrationsChanged { toolkits }) consumed by the active chat session (if any) to callrefresh_delegation_tools()immediately — not only on the next user turn.Alternative: expose a socket event
integrations_changedsoChatRuntimeProvidertriggers refresh without waiting for turn boundary.2. Harden
refresh_delegation_toolsArc(today warn-only).delegate_to_integrations_agentenum includes new toolkit.3. Prompt / context hygiene (choose one)
turn.rscomments).4.
delegate_to_integrations_agent.executesafety netBefore rejecting unknown
toolkit, optionally re-checkfetch_connected_integrations_statusonce (same pattern asspawn_subagentpre-flight) so a staleSkillDelegationToolcannot block an ACTIVE connection.5. App / QA
Acceptance criteria
delegate_to_integrations_agentincludes that slug in thetoolkitenum without requiring a new chat thread or app restart (Notion connection active but write/editing not supported #3030)integrations_agentspawn accepts the toolkit when connection is ACTIVE[agent_loop] connection set changed+[skill-delegation]show refresh path when connect completesCode references
src/openhuman/agent_orchestration/tools/skill_delegation.rs(connected_toolkits, enum gate)src/openhuman/tools/orchestrator_tools.rs(collect_orchestrator_tools)src/openhuman/agent/harness/session/turn.rs(refresh_delegation_tools,cached_active_integrations)src/openhuman/memory_sync/composio/bus.rs(ComposioConnectionCreatedSubscriber)src/openhuman/agent/harness/subagent_runner/ops.rs,spawn_subagent.rssrc/openhuman/agent_registry/agents/integrations_agent/src/openhuman/memory_sync/composio/providers/notion/Interim workaround (document in #3030)
Until fixed: start a new chat thread (or send a message on a fresh thread) after connecting an integration so
history.is_empty()re-runsfetch_connected_integrations+ initialrefresh_delegation_tools.