Skip to content

Refresh orchestrator integration context after OAuth (fixes stale delegate_to_integrations_agent) #3044

@senamakel

Description

@senamakel

Related symptom report

#3030Notion 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:

  1. Per-turn tool-schema refreshrefresh_delegation_tools() rebuilds SkillDelegationTool when INTEGRATIONS_CACHE hash changes (cached_active_integrations, UI 5s poll, post-OAuth invalidation).
  2. Spawn-time refreshsubagent_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.

Metadata

Metadata

Assignees

Labels

Type

No type
No fields configured for issues without a type.

Projects

Status
Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions