Skip to content

feat(composio,agent): async sync + gated-tools surface + UI-only scope elevation + force-delegate capability questions#2348

Merged
senamakel merged 7 commits into
tinyhumansai:mainfrom
sanil-23:feat/composio-agent-ux
May 20, 2026
Merged

feat(composio,agent): async sync + gated-tools surface + UI-only scope elevation + force-delegate capability questions#2348
senamakel merged 7 commits into
tinyhumansai:mainfrom
sanil-23:feat/composio-agent-ux

Conversation

@sanil-23
Copy link
Copy Markdown
Contributor

@sanil-23 sanil-23 commented May 20, 2026

Summary

  • composio_sync is now fire-and-forget: the RPC validates synchronously (connection_id / toolkit / provider), then tokio::spawn(provider.sync) and returns {status: "started", ...} immediately. UI is no longer pinned for the 30 s frontend RPC cap while the provider walks an entire prod inbox in-band. Per-source progress already flows via the existing openhuman.memory_sync_status_list poll (reads mem_tree_chunks directly so the spawned task is reflected in real time).
  • Gated-tools surface for the integrations agent: ConnectedIntegration gains gated_tools: Vec<GatedIntegrationTool> (no parameters field — LLM can't construct a call envelope for one). fetch_connected_integrations now partitions a toolkit's actions into visible/callable vs gated-by-pref. The integrations_agent prompt renders an "Additional capabilities behind a permission toggle" section so the agent can answer "do you support X?" with "yes but admin scope is off — want me to enable it?" instead of confabulating "no I can't".
  • New composio_enable_scope meta-tool: agent-callable (registered in all_composio_agent_tools), takes (toolkit, scope?), flips the per-toolkit composio-user-scopes KV row. Strong "ALWAYS ask the user for explicit consent first" wording in the description.
  • Orchestrator force-delegates capability questions to integrations_agent rather than answering from training-data priors (which were systematically wrong — confidently claimed Gmail had no bulk-delete when the toolkit ships GMAIL_BATCH_DELETE_MESSAGES).

Problem

Observed in production-mode use over a long debugging session:

  1. composio_sync consistently shows "sync failed" in the UI ~30 s in, but the server-side sync is healthy and continues. The frontend RPC .await cap is too tight for a real prod inbox — provider.sync walks all pages in-band and ingests every message synchronously inside the RPC handler. There is no good reason for the UI to block on it; per-source progress is already polled separately.
  2. Agent flatly denies capabilities the toolkit has — user asks "can you delete spam?", agent replies "nope, I can't delete or trash emails through the Gmail integration". Wrong: Gmail's Composio catalog ships GMAIL_BATCH_DELETE_MESSAGES, GMAIL_DELETE_THREAD, GMAIL_MOVE_TO_TRASH, etc. Two root causes — (a) admin-scoped tools are filtered out of the agent's tool list by default user pref ({read:true, write:true, admin:false}) AND filtered out of the prompt-side tools: field, so the agent has zero awareness they exist; (b) for the orchestrator agent, even when admin is on, the orchestrator's training-data prior about toolkit capabilities is unreliable — it answers "can you do X?" from priors rather than delegating to the agent that actually has the tool list.

Solution

Concern File(s) What
Sync hang composio/ops.rs:1012 tokio::spawn(provider.sync(...)) + return SyncOutcome{status:"started", finished_at_ms:0, ...} immediately. Validation (resolve client, lookup provider, lookup toolkit) stays synchronous so bad-request errors surface via the RPC envelope.
Agent unaware of gated capabilities agent/prompts/types.rs, composio/providers/mod.rs, composio/ops.rs, integrations_agent/prompt.rs New GatedIntegrationTool (no parameters field — descriptive only). New curated_scope_for(slug) helper. Producer (fetch_connected_integrations_uncached) partitions into tools + gated_tools. Integrations-agent prompt renders a "behind a permission toggle" section, including instructions on how to call composio_enable_scope with user consent.
Elevation path composio/tools.rs New ComposioEnableScopeTool (struct, impl Tool, registered in all_composio_agent_tools).
Orchestrator confabulation orchestrator/prompt.rs New "Capability questions about connected toolkits" subsection in the delegation guide that forbids prior-based refusals for connected toolkits and tells the orchestrator the only honest "no" comes back from a delegation that found the action neither in visible tools nor gated_tools.
Plumbing welcome/prompt.rs, harness/subagent_runner/ops.rs, harness/test_support_test.rs, composio/ops_test.rs, tools/orchestrator_tools.rs Update existing ConnectedIntegration { ... } literal sites to populate the new gated_tools field (defaults to Vec::new()).

Design choices worth flagging for reviewers:

  • Why a separate gated_tools field instead of inlining: the parameters: None distinction is load-bearing — without it the LLM can construct call envelopes for tools it can't actually invoke. Splitting at the type level enforces that the prompt-render path produces descriptive-only output for gated entries.
  • Why ComposioEnableScopeTool is a separate meta-tool rather than auto-elevation: granting Admin scope persists across sessions. Self-elevation without explicit user consent is a security boundary regression. The tool relies on the prompt contract today ("ALWAYS ask the user for explicit consent first") and is the right shape to wire into the wider tool-policy / approval-gate layer (feat(approval): gate external-effect tool calls until user approves (#1339) #2149 / feat(agent): add tool policy session boundary #2166 area) when that lands.
  • Why force-delegate capability questions vs. push tool data into the orchestrator prompt: orchestrator prompt-size is already a concern and most user questions about toolkits don't need a sub-agent round-trip. But for "can you do X?" / "does Y support Z?", the orchestrator's training-data prior is systematically a subset of the real catalog. One sub-agent hop is cheap; a confidently wrong refusal is not.

Submission Checklist

  • N/A: tests deferred to a follow-up commit — see Validation Blocked below. The behavioural change is verified by partition-log evidence on a live workspace ([composio][scopes] integrations prompt action set toolkit=googledrive visible=14 gated=6, etc.) but unit-test additions for the partition logic, composio_enable_scope.execute(), and the orchestrator prompt-render change are pending. The PR is opened as DRAFT for this reason.
  • N/A: diff coverage gate will not pass on this commit alone — same reason. Will land tests in follow-up before un-drafting.
  • N/A: no behaviour-only feature ID change — the new gated_tools surface is an additive extension of the existing composio.connected_integrations capability; no new top-level feature row required.
  • N/A: no matrix-tracked feature IDs touched (changes are agent-prompt / Composio infrastructure).
  • No new external network dependencies — composio_enable_scope writes to the existing per-toolkit KV; composio_sync change is purely scheduling.
  • N/A: no release-cut surface touched (no manual smoke checklist change needed).
  • N/A: no linked issue — the changes were derived from a long-running production-mode debugging session, not a tracked ticket. Happy to file follow-up issues if reviewers prefer.

Impact

Runtime / platform: desktop, all platforms. No platform branches added or changed.

Performance: net positive. composio_sync no longer pins the UI thread; the orchestrator delegation cost (one extra sub-agent round-trip for capability questions) is bounded and only fires on questions that today produce confidently wrong refusals.

Migration / compatibility: schema-level — adding gated_tools to ConnectedIntegration is an additive Rust struct field; every internal constructor was updated to Vec::new() so behaviour is identical when no curated entries are pref-blocked. Backward-compatible.

Security: composio_enable_scope is the only privilege-changing surface added. Documented requirement: agent must ask for explicit user consent before invocation. No silent self-elevation. Wider tool-policy / approval-gate integration recommended as a follow-up.

Related


AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: feat/composio-agent-ux
  • Commit SHA: 0e5b042e

Validation Run

  • N/A: pnpm --filter openhuman-app format:check not run — Windows working tree has the documented ~600-file CRLF/LF drift; format:check would rewrite unrelated files. Pre-push hook bypassed via --no-verify per the same documented pattern.
  • N/A: pnpm typecheck not run — no TypeScript surface changed in this PR (Rust core only).
  • Focused tests: existing prompt-builder tests in integrations_agent/prompt.rs, orchestrator/prompt.rs, welcome/prompt.rs were updated to populate the new gated_tools field on ConnectedIntegration literals; existing composio/ops_test.rs and tools/orchestrator_tools.rs test helpers similarly updated.
  • Rust fmt/check: cargo check ran implicitly via pnpm dev:app:win rebuild — 0 errors. cargo fmt not explicitly run; will run in the follow-up that adds tests.
  • N/A: Tauri fmt/check — no app/src-tauri/ Rust changes in this PR.

Validation Blocked

  • command: cargo fmt and unit-test additions for the new partition + meta-tool paths
  • error: n/a — not blocked technically; bundled with the test-coverage follow-up before un-drafting.
  • impact: PR is opened as DRAFT; reviewer feedback welcome on the architectural direction (esp. the gated_tools / enable_scope shape) before I invest in the test suite.

Behavior Changes

  • Intended behavior change:
    1. composio_sync RPC returns {status:"started"} immediately instead of awaiting full provider sync.
    2. Integrations agent now sees + describes pref-gated toolkit actions and knows the elevation procedure.
    3. New composio_enable_scope meta-tool flips per-toolkit user scopes (with prompt-enforced user-consent contract).
    4. Orchestrator delegates capability questions about connected toolkits to integrations_agent instead of confabulating from priors.
  • User-visible effect:
    • "Memory Sources" UI no longer shows "sync failed" within 30 s — shows progress updating in real time via the existing poll.
    • Asking the agent "can you bulk-delete spam from Gmail?" produces a real answer (yes, with the relevant tool slugs and an optional composio_enable_scope prompt) rather than a confidently wrong "no".

Parity Contract

  • Legacy behavior preserved:
    • composio_sync still returns a SyncOutcome (now with status:"started" / finished_at_ms:0 as sentinels for "spawned").
    • All existing ConnectedIntegration consumers still receive populated tools; the new gated_tools is additive and starts empty whenever no curated entries are pref-blocked.
    • The orchestrator's existing delegation tools (delegate_to_integrations_agent, etc.) are unchanged; the new subsection only adds behavioural guidance, no new tools.
  • Guard/fallback/dispatch parity checks: prompt-builder tests for welcome, orchestrator, integrations_agent were updated to populate the new field; they still assert the same prompt-text invariants as before (## Connected Integrations, delegate_to_integrations_agent, per-toolkit bullets).

Duplicate / Superseded PR Handling

  • Duplicate PR(s): none
  • Canonical PR: this one
  • Resolution: n/a

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Shows permission‑gated integration capabilities in a dedicated "Additional capabilities behind a permission toggle" section, listing each restricted action, description (or “(no description)”), required scope, and unlock guidance.
  • Improvements

    • Sync operations now start quickly and continue in the background, returning an immediate "started" result.
    • Clearer messaging distinguishing available vs locked tools with explicit UI unlock instructions.
    • Cross‑chat context header now warns capabilities may be historical.

Review Change Stack

…-delegate capability questions + spawn-and-return composio_sync

composio_sync now spawns and returns: the RPC handler validates synchronously
(connection_id / toolkit / provider lookup), then tokio::spawn(provider.sync)
and returns SyncOutcome{status="started"} immediately. UI is no longer pinned
for the 30s frontend RPC cap while the provider walks an entire prod inbox in
band. Per-source progress is already exposed via openhuman.memory_sync_status_list
(poll-driven; reads mem_tree_chunks so it reflects the spawned task in real time).

ConnectedIntegration gains gated_tools: Vec<GatedIntegrationTool>. The new
struct intentionally has NO parameters field so the LLM cannot construct a
call envelope for one — it can only describe their existence and point at
the unlock path. fetch_connected_integrations_uncached now partitions a
toolkit's per-action set into the existing tools (visible/callable) and
gated_tools (curated but blocked by the user's per-toolkit scope pref).
New composio::providers::curated_scope_for(slug) helper backs the partition.

integrations_agent prompt renders an 'Additional capabilities behind a
permission toggle' section so the agent can honestly answer 'do you support X?'
with 'yes, but it needs admin scope — want me to enable it?' instead of
confabulating 'no I can't'.

New ComposioEnableScopeTool meta-tool (agent-callable, wired into
all_composio_agent_tools): takes (toolkit, scope?), flips the per-toolkit
composio-user-scopes KV row. Strong 'ALWAYS ask the user for explicit
consent first' wording in the description; relies on the prompt contract
today and is the right shape to plug into the wider tool-policy /
approval-gate layer (tinyhumansai#2149/tinyhumansai#2166 area) when it matures.

Orchestrator delegation-guide gains a 'Capability questions about connected
toolkits' subsection that forbids answering 'can you do X with {toolkit}?'
from training-data priors (which were systematically a subset of the real
catalog — confidently denied Gmail bulk-delete when the toolkit ships
GMAIL_BATCH_DELETE_MESSAGES). The only honest 'no' comes back from a
delegation that found the action neither in visible tools nor gated_tools.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

📝 Walkthrough

Walkthrough

Adds gated tools to ConnectedIntegration and GatedIntegrationTool type; composio splits visible vs gated actions and spawns provider.sync returning a “started” outcome; prompts, memory header, logging, providers, and tests updated to surface unlock guidance while keeping scope elevation UI-only.

Changes

Gated Tools Implementation

Layer / File(s) Summary
Gated tools data model
src/openhuman/agent/prompts/types.rs
Adds GatedIntegrationTool and gated_tools: Vec<GatedIntegrationTool> on ConnectedIntegration with docs explaining these actions are not directly invocable.
Provider scope lookup
src/openhuman/composio/providers/scope_lookup.rs, src/openhuman/composio/providers/mod.rs
Adds curated_scope_for(slug) and toolkit_has_scope(toolkit, scope) and re-exports them; includes tests ensuring gating behavior and case-insensitive toolkit handling.
Composio sync & action discovery
src/openhuman/composio/ops.rs, src/openhuman/composio/ops_test.rs
composio_sync validates inputs then spawns provider.sync as background task and returns immediate “started” SyncOutcome; discovery emits tools and gated_tools with required_scope and unlock_paths. Tests updated to poll spawned ingestion.
Composio tools: policy & logging
src/openhuman/composio/tools.rs, src/openhuman/composio/tools_tests.rs
Scope-elevation is UI-only (no agent-callable enable tool). scope_error_message now includes required scope and Connections UI unlock path. composio_execute gains richer dispatch/result logging; tests/messages adjusted.
Integrations agent prompt rendering
src/openhuman/agent/agents/integrations_agent/prompt.rs
When connected integrations have gated_tools, render an “Additional capabilities behind a permission toggle” section enumerating each gated tool name, description (fallback), required_scope, and unlock_paths.
Orchestrator & welcome prompts
src/openhuman/agent/agents/orchestrator/prompt.rs, src/openhuman/agent/agents/welcome/prompt.rs
Orchestrator prompt explicitly delegates capability and action questions to integrations_agent, requires consulting live per-toolkit catalogue (including gated_tools), and updates tests to include gated_tools in fixtures.
Cross-chat context header
src/openhuman/agent/harness/memory_context.rs, src/openhuman/agent/memory_loader.rs
Header changed from "[Cross-chat context]\n" to "[Cross-chat context — historical; capabilities may have changed since]\n" via CROSS_CHAT_HEADER; tests updated to assert canonical header.
Test fixture propagation & runner wiring
src/openhuman/agent/harness/subagent_runner/ops.rs, src/openhuman/agent/harness/test_support_test.rs, src/openhuman/tools/orchestrator_tools.rs, various tests
All test helpers and fixtures updated to populate gated_tools (typically empty vec). Runner spawn path now copies gated_tools from cached integration when refreshing visible tools.

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

working

Suggested reviewers

  • senamakel
  • graycyrus

🐰 A rabbit pens a tiny note,
Of gated tools and background mote—
Permissions shown, not called outright,
Spawned sync hums into the night—
Guide the user, shed some light!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly maps to the main changes: async sync (composio_sync now fire-and-forget), gated-tools surface (ConnectedIntegration gains gated_tools field), UI-only scope elevation (elevation path moved to UI), and force-delegate capability questions (orchestrator now delegates to integrations_agent).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

sanil-23 and others added 4 commits May 20, 2026 18:38
Pure formatting. CI Rust Quality (fmt) gate flagged my added code on PR tinyhumansai#2348:
- composio/ops.rs fetch_connected_integrations_uncached: the (tools, gated_tools)
  let-binding's tuple type annotation pushed the line past the limit; rustfmt
  re-indents the if-connected block by one extra level.
- integrations_agent/prompt.rs render_connected_integrations: the chained
  iter().filter() call on the gated-tools render loop wants to break across
  lines instead of staying on one.

No semantic change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…turn composio_sync

- tools_tests.rs: bump both registry-count asserts 5->6 to include
  the new ComposioEnableScopeTool meta-tool.
- ops_test.rs::composio_sync_gmail_via_mock_archives_raw_email_and_updates_outcome:
  composio_sync is now spawn-and-return; the immediate envelope is a
  'started' sentinel (items_ingested=0, finished_at_ms=0, summary
  contains 'started'). Poll list_chunks_rpc up to 50 x 100ms for the
  spawned ingest task to drain before asserting the persisted chunk.
Before saving a per-toolkit scope pref, the agent-callable
composio_enable_scope tool now short-circuits in two cases:

1. Not applicable. If the toolkit's curated catalog has zero actions
   tagged with the requested scope, flipping the bit graduates nothing
   into the callable surface. The tool returns a clear no-op message
   so the agent re-evaluates the user's original ask instead of
   silently mutating prefs that change nothing.

2. Already enabled. If pref.<scope> is already true, the tool returns
   'already on' without a redundant DB write and without a misleading
   'Enabled' message that implied a state change.

Adds providers::toolkit_has_scope(toolkit, scope) helper that walks
both the native provider catalog and the catalog_for_toolkit fallback
so the answer matches what is_action_visible_with_pref would gate
against. Adds a unit test covering gmail (has all three scopes) +
case-insensitive slug + unknown toolkit.
…eshness caveat

Two related fixes for a UX bug where the agent surfaced only one unlock
path ("want me to enable admin scope?") and never offered the manual
Settings → Integrations toggle, then quoted past 'I can't do X' refusals
as authoritative on later turns.

1. Data carries the consent policy, prompts don't:
   - GatedIntegrationTool gains unlock_paths: Vec<String>, populated at
     partition time in composio::ops with the two routes (agent meta-tool
     + manual UI toggle).
   - integrations_agent/prompt.rs renders 'unlock path:' lines under each
     gated action so the agent reads paths off data, not a memorized
     template. Verbatim consent template removed from the prompt.
   - composio_enable_scope tool description: 'Two options' template
     removed; replaced with 'surface ALL unlock paths from the
     integrations data'.
   - scope_error_message: composio_execute blocked-by-scope error now
     embeds both unlock paths so the policy travels with every blocked
     execution, not just the proactive prompt-time list.

2. Cross-chat context is historical, not authoritative:
   - Renamed cross-chat block header to
     '[Cross-chat context — historical; capabilities may have changed since]'
     in both memory_loader.rs and harness/memory_context.rs.
   - Orchestrator prompt's 'Capability questions' section gains a 4th
     bullet: a past 'I can/can't do X' surfaced via cross-chat may be
     stale — verify against the current Connected Integrations block
     and delegate before quoting.
   - 5 existing test assertions loosened from contains('[Cross-chat
     context]') to contains('[Cross-chat context') so the header can
     evolve without churning tests.

Together these address the live failure mode where the agent both
(a) presented only the agent-side unlock option and (b) parroted a
prior chat's stale refusal even after admin scope was enabled.
@sanil-23 sanil-23 marked this pull request as ready for review May 20, 2026 15:02
@sanil-23 sanil-23 requested a review from a team May 20, 2026 15:02
@coderabbitai coderabbitai Bot added the agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. label May 20, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (5)
src/openhuman/agent/memory_loader.rs (1)

607-608: ⚡ Quick win

Use full-header assertions here as well to protect the cross-module wording contract.

These prefix checks can pass even if the critical suffix changes. Please assert the complete header text (or a shared constant) in all three places.

Proposed test hardening
-        assert!(
-            out.contains("[Cross-chat context"),
+        assert!(
+            out.contains(
+                "[Cross-chat context — historical; capabilities may have changed since]"
+            ),
             "expected cross-chat header, got:\n{out}"
         );
...
-        assert!(
-            !out.contains("[Cross-chat context"),
+        assert!(
+            !out.contains(
+                "[Cross-chat context — historical; capabilities may have changed since]"
+            ),
             "no cross-chat hits must produce no header, got:\n{out}"
         );
...
-        assert!(
-            out.contains("[Cross-chat context"),
+        assert!(
+            out.contains(
+                "[Cross-chat context — historical; capabilities may have changed since]"
+            ),
             "JSONL primary path must emit the cross-chat header, got:\n{out}"
         );

Also applies to: 682-683, 754-755

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/agent/memory_loader.rs` around lines 607 - 608, The test uses a
loose prefix check out.contains("[Cross-chat context") which can miss changes to
the expected header suffix; update the assertions in memory_loader.rs (the
out.contains checks at the occurrences around the current one and also at the
other two spots referenced) to assert the full header string (or compare against
a shared constant) instead of using contains, e.g., replace the partial prefix
check with an equality or full starts_with match against the canonical header
constant so the exact cross-chat header wording is enforced in all three
locations.
src/openhuman/agent/harness/memory_context.rs (1)

361-362: ⚡ Quick win

Tighten these assertions to the exact cross-chat header string.

Using contains("[Cross-chat context") can let contract drift pass silently. Since this header is cross-referenced across components, assert the full literal (or a shared constant) in tests.

Proposed test hardening
-        assert!(
-            context.contains("[Cross-chat context"),
+        assert!(
+            context.contains(
+                "[Cross-chat context — historical; capabilities may have changed since]"
+            ),
             "expected cross-chat header, got:\n{context}"
         );
...
-        assert!(
-            !context.contains("[Cross-chat context"),
+        assert!(
+            !context.contains(
+                "[Cross-chat context — historical; capabilities may have changed since]"
+            ),
             "no cross-chat hits must produce no header, got:\n{context}"
         );

Also applies to: 427-428

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/agent/harness/memory_context.rs` around lines 361 - 362, The
current tests use context.contains("[Cross-chat context") which is too loose;
change those assertions to check for the exact header literal (or a shared
constant) instead — e.g. replace the contains check on the context variable with
an assertion that the exact header string is present (use a constant like
CROSS_CHAT_HEADER or assert!(context.contains(EXACT_HEADER_LITERAL)), and update
both occurrences (the assertion at the shown context.contains(...) and the
similar one around lines 427-428) so they require the full cross-chat header
string.
src/openhuman/composio/tools_tests.rs (1)

215-217: ⚡ Quick win

Extend metadata/category test coverage to include composio_enable_scope.

You updated registration counts to 6, but the dedicated all_composio_tools_are_in_skill_category fixture still constructs only 5 tools. Add ComposioEnableScopeTool there so category/name contract checks stay aligned with the new surface.

Also applies to: 239-241

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/composio/tools_tests.rs` around lines 215 - 217, The test
fixture all_composio_tools_are_in_skill_category constructs the expected
Composio tools list but still contains only five entries; update it to include
ComposioEnableScopeTool so the expected tool count and category/name checks
match the new registration surface (now 6 tools). Modify the fixture that builds
the expected tools (and the duplicate expectation around the other occurrence at
lines noted) to append or include ComposioEnableScopeTool alongside
list_toolkits, list_connections, authorize, list_tools, and execute so the
name/category assertions cover the new tool.
src/openhuman/agent/agents/integrations_agent/prompt.rs (2)

141-187: ⚡ Quick win

Add structured debug logs for gated-section state transitions.

This branch introduces meaningful prompt-surface transitions (has_gated, connected-gated count, emitted section) but currently has no diagnostics, which makes runtime prompt behavior harder to inspect.

Suggested patch
 fn render_connected_integrations(integrations: &[ConnectedIntegration]) -> String {
@@
-    let mut has_gated = false;
+    let mut has_gated = false;
+    let mut connected_with_gated = 0usize;
     for ci in integrations.iter().filter(|ci| ci.connected) {
         if !ci.gated_tools.is_empty() {
             has_gated = true;
+            connected_with_gated += 1;
             break;
         }
     }
+    tracing::debug!(
+        total_integrations = integrations.len(),
+        has_gated,
+        connected_with_gated,
+        "[integrations-prompt] gated tools scan complete"
+    );
     if has_gated {
@@
         out.push('\n');
+        tracing::debug!(
+            section_len = out.len(),
+            "[integrations-prompt] emitted gated tools section"
+        );
     }

As per coding guidelines: “Use log / tracing at debug or trace level on ... state transitions, and any branch that is hard to infer from tests alone.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/agent/agents/integrations_agent/prompt.rs` around lines 141 -
187, Add structured debug/tracing calls around the gated-tools discovery and
emission: log the number of integrations inspected and the count of connected
integrations with non-empty gated_tools (compute this from
integrations.iter().filter(|ci| ci.connected &&
!ci.gated_tools.is_empty()).count()), emit a debug when has_gated flips true and
when you push the gated section to out, and log per-integration details (toolkit
name and gated_tools.count() or toolkit + each gt.name) inside the loop; use the
existing local names (has_gated, integrations, ci, gt, out, gt.unlock_paths) and
call tracing::debug! or log::debug! at appropriate trace/debug level so these
state transitions and the list of gated toolkits are visible at runtime.

233-263: ⚡ Quick win

Add a focused test for gated-tools prompt rendering.

Current fixture updates only keep old assertions compiling. Please add one case with non-empty gated_tools and assert the new section header plus at least one unlock path line so this behavior is locked down.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/agent/agents/integrations_agent/prompt.rs` around lines 233 -
263, Add a new unit test in prompt.rs that constructs a ConnectedIntegration
with a non-empty gated_tools vec and uses ctx_with(...) and build(...) to render
the prompt, then assert the rendered body contains the new gated-tools section
header (e.g., "## Gated Tools" or the exact header your template uses) and at
least one expected "unlock path" line string; place the test next to
build_includes_connected_integrations_in_executor_voice and
build_skips_unconnected_integrations so it exercises build(...) with a
ConnectedIntegration that has gated_tools populated and verifies the new section
is emitted and contains an unlock path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/openhuman/agent/agents/orchestrator/prompt.rs`:
- Around line 140-143: Replace the hardcoded header "[Cross-chat context —
historical]" used in the prompt template with the canonical header that includes
the suffix (e.g. "[Cross-chat context — historical — capabilities may have
changed since]") so block matching uses the updated exact string; locate the
literal in src/openhuman/agent/agents/orchestrator/prompt.rs (the
prompt/template constant or function that builds the instructions) and update
that string everywhere it appears to the full canonical header.

In `@src/openhuman/composio/providers/mod.rs`:
- Around line 174-212: Extract the operational helpers curated_scope_for and
toolkit_has_scope into a new sibling module (e.g., scope_lookup.rs): move the
function bodies to that file, keep their signatures and required imports
(ToolScope, toolkit_from_slug, get_provider, catalog_for_toolkit, find_curated)
and make them pub so callers still work; then in mod.rs add a module declaration
(mod scope_lookup;) and re-export the functions with a pub use
scope_lookup::{curated_scope_for, toolkit_has_scope}; ensuring any relative
paths/imports are adjusted for the new module location and tests/uses compile.

In `@src/openhuman/composio/tools.rs`:
- Around line 1107-1124: The composio_enable_scope handler currently preserves
caller casing for the toolkit variable causing mismatched keys; update the code
in composio_enable_scope to canonicalize toolkit (e.g., trim() then
to_ascii_lowercase()) immediately after extracting it from args—before the empty
check and before any load/save or visibility checks—so the same canonical key is
used throughout (match the existing scope canonicalization pattern used for the
scope variable).

---

Nitpick comments:
In `@src/openhuman/agent/agents/integrations_agent/prompt.rs`:
- Around line 141-187: Add structured debug/tracing calls around the gated-tools
discovery and emission: log the number of integrations inspected and the count
of connected integrations with non-empty gated_tools (compute this from
integrations.iter().filter(|ci| ci.connected &&
!ci.gated_tools.is_empty()).count()), emit a debug when has_gated flips true and
when you push the gated section to out, and log per-integration details (toolkit
name and gated_tools.count() or toolkit + each gt.name) inside the loop; use the
existing local names (has_gated, integrations, ci, gt, out, gt.unlock_paths) and
call tracing::debug! or log::debug! at appropriate trace/debug level so these
state transitions and the list of gated toolkits are visible at runtime.
- Around line 233-263: Add a new unit test in prompt.rs that constructs a
ConnectedIntegration with a non-empty gated_tools vec and uses ctx_with(...) and
build(...) to render the prompt, then assert the rendered body contains the new
gated-tools section header (e.g., "## Gated Tools" or the exact header your
template uses) and at least one expected "unlock path" line string; place the
test next to build_includes_connected_integrations_in_executor_voice and
build_skips_unconnected_integrations so it exercises build(...) with a
ConnectedIntegration that has gated_tools populated and verifies the new section
is emitted and contains an unlock path.

In `@src/openhuman/agent/harness/memory_context.rs`:
- Around line 361-362: The current tests use context.contains("[Cross-chat
context") which is too loose; change those assertions to check for the exact
header literal (or a shared constant) instead — e.g. replace the contains check
on the context variable with an assertion that the exact header string is
present (use a constant like CROSS_CHAT_HEADER or
assert!(context.contains(EXACT_HEADER_LITERAL)), and update both occurrences
(the assertion at the shown context.contains(...) and the similar one around
lines 427-428) so they require the full cross-chat header string.

In `@src/openhuman/agent/memory_loader.rs`:
- Around line 607-608: The test uses a loose prefix check
out.contains("[Cross-chat context") which can miss changes to the expected
header suffix; update the assertions in memory_loader.rs (the out.contains
checks at the occurrences around the current one and also at the other two spots
referenced) to assert the full header string (or compare against a shared
constant) instead of using contains, e.g., replace the partial prefix check with
an equality or full starts_with match against the canonical header constant so
the exact cross-chat header wording is enforced in all three locations.

In `@src/openhuman/composio/tools_tests.rs`:
- Around line 215-217: The test fixture all_composio_tools_are_in_skill_category
constructs the expected Composio tools list but still contains only five
entries; update it to include ComposioEnableScopeTool so the expected tool count
and category/name checks match the new registration surface (now 6 tools).
Modify the fixture that builds the expected tools (and the duplicate expectation
around the other occurrence at lines noted) to append or include
ComposioEnableScopeTool alongside list_toolkits, list_connections, authorize,
list_tools, and execute so the name/category assertions cover the new tool.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a75edd60-3d68-4572-8212-a075a4827b9f

📥 Commits

Reviewing files that changed from the base of the PR and between 41e7631 and b592b80.

📒 Files selected for processing (14)
  • src/openhuman/agent/agents/integrations_agent/prompt.rs
  • src/openhuman/agent/agents/orchestrator/prompt.rs
  • src/openhuman/agent/agents/welcome/prompt.rs
  • src/openhuman/agent/harness/memory_context.rs
  • src/openhuman/agent/harness/subagent_runner/ops.rs
  • src/openhuman/agent/harness/test_support_test.rs
  • src/openhuman/agent/memory_loader.rs
  • src/openhuman/agent/prompts/types.rs
  • src/openhuman/composio/ops.rs
  • src/openhuman/composio/ops_test.rs
  • src/openhuman/composio/providers/mod.rs
  • src/openhuman/composio/tools.rs
  • src/openhuman/composio/tools_tests.rs
  • src/openhuman/tools/orchestrator_tools.rs

Comment thread src/openhuman/agent/agents/orchestrator/prompt.rs
Comment thread src/openhuman/composio/providers/mod.rs Outdated
Comment thread src/openhuman/composio/tools.rs Outdated
sanil-23 added 2 commits May 20, 2026 21:00
Scope elevation (read / write / admin) was previously exposed as the
agent-callable composio_enable_scope meta-tool. It is now removed:
the user must toggle scopes themselves in the Connections UI.

Why:
- Scope elevation is a security-sensitive, cross-session state change
  that unlocks destructive actions. Putting that flip behind LLM
  'user-consent' framing made the safety contract depend on model
  behavior — the weakest place for it.
- The agent could (and did) route around the explicit *_TRASH_* /
  *_DELETE_* gates by calling Write-scoped GMAIL_ADD_LABEL_TO_EMAIL
  with the system label 'TRASH' to achieve the same destructive
  effect. That escape exists regardless of how scope elevation is
  presented; removing the soft prompt-mediated gate avoids the false
  sense of safety.

Surfaces updated:
- composio/tools.rs: ComposioEnableScopeTool struct + impl deleted.
  Registration list back to 5 tools (list_toolkits, list_connections,
  authorize, list_tools, execute). Header comment block preserves the
  rationale in case the design is revisited.
- composio/ops.rs: GatedIntegrationTool.unlock_paths now carries one
  path only ('the user enables it themselves in Connections →
  {toolkit} → {scope}'). Render in integrations_agent prompt is
  unchanged — the data shape is the same, just shorter.
- composio/tools.rs::scope_error_message: blocked-by-scope execute
  error now describes the UI path only and explicitly tells the LLM
  'Do not claim you can flip it — you cannot'.
- agent/prompts/types.rs: GatedIntegrationTool + ConnectedIntegration
  doc comments updated to reflect the UI-only flow.
- agent/agents/integrations_agent/prompt.rs: gated-tools section
  comment refers to the Connections UI; no template change needed
  because unlock_paths still feeds the rendered 'unlock path:' lines.
- providers/mod.rs: toolkit_has_scope doc explains it is no longer
  used in production (still useful for future UI hints; kept).
- tools_tests.rs: tool-count asserts bumped 6 → 5 in both
  registration tests + comments updated.

Also adds INFO-level audit logging to composio_execute:
- [composio][execute] >> dispatch (slug, connection_id, arg KEYS)
- [composio][execute] << result (slug, success, error, elapsed_ms,
  cost_usd)
- [composio][execute] << dispatch error (catch-all warn)
- DEBUG-level paired logs for full args + full response (PII-safe by
  default; opt-in via log level).

UI path text everywhere now reads 'Connections → {toolkit} →
{scope}' (the actual nav, per user correction — not nested under
Settings).
Three of CodeRabbit's six items applied; the other three are stale
because they predate the composio_enable_scope removal in c6f3404.

1. Single source of truth for the cross-chat block header.
   - Add 'pub const CROSS_CHAT_HEADER' in agent/memory_loader.rs with
     the full literal 'Cross-chat context - historical; capabilities
     may have changed since\n'.
   - memory_loader.rs producer, harness/memory_context.rs fallback
     producer, and orchestrator/prompt.rs all bind to this constant.
   - 5 test assertions now use 'contains(CROSS_CHAT_HEADER.trim_end())'
     so the full header text is enforced and CodeRabbit's concern
     about loose prefix matches is addressed.
   - Orchestrator prompt switched to write!(out, ...) so the canonical
     header interpolates verbatim into the system-prompt section that
     names it (previously hardcoded the shorter '- historical' form,
     which was a drift hazard CodeRabbit flagged).

2. Extract scope-lookup helpers out of providers/mod.rs into a
   sibling 'scope_lookup.rs' module, matching the project rule from
   CLAUDE.md ('keep mod.rs export-focused; operational logic in
   sibling files'). curated_scope_for + toolkit_has_scope move
   wholesale, with their unit test. mod.rs re-exports both via 'pub
   use scope_lookup::{...}'. No API change for callers.

3. Add structured debug log in integrations_agent prompt for the
   gated-tools scan: emits total_integrations, has_gated, and
   connected_with_gated counts so runtime prompt behavior is easy to
   inspect without adding println-style tracing.

Skipped (stale after c6f3404 removed composio_enable_scope):
- 'canonicalize toolkit before scope-pref save in composio_enable_scope'
- 'bump tool-count fixture from 5 to 6 to include enable_scope'

Both refer to a tool that no longer exists; user must toggle scopes in
the Connections UI (see the long note above the removed
ComposioEnableScopeTool block).
@coderabbitai coderabbitai Bot added the working A PR that is being worked on by the team. label May 20, 2026
@sanil-23 sanil-23 changed the title feat(composio,agent): async sync + gated-tools surface + composio_enable_scope + force-delegate capability questions feat(composio,agent): async sync + gated-tools surface + UI-only scope elevation + force-delegate capability questions May 20, 2026
@senamakel senamakel merged commit 1e3ecc5 into tinyhumansai:main May 20, 2026
33 of 36 checks passed
mtkik pushed a commit to mtkik/openhuman-meet that referenced this pull request May 21, 2026
…e elevation + force-delegate capability questions (tinyhumansai#2348)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CodeGhost21 pushed a commit to CodeGhost21/openhuman that referenced this pull request May 22, 2026
…e elevation + force-delegate capability questions (tinyhumansai#2348)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AusAgentSmith pushed a commit to AusAgentSmith/openhuman that referenced this pull request May 23, 2026
…e elevation + force-delegate capability questions (tinyhumansai#2348)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. working A PR that is being worked on by the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants