Skip to content

fix(composio): gate per-action tools on their full contract (#4853)#4995

Merged
senamakel merged 6 commits into
tinyhumansai:mainfrom
M3gA-Mind:fix/GH-4853-composio-gmail-search
Jul 18, 2026
Merged

fix(composio): gate per-action tools on their full contract (#4853)#4995
senamakel merged 6 commits into
tinyhumansai:mainfrom
M3gA-Mind:fix/GH-4853-composio-gmail-search

Conversation

@M3gA-Mind

@M3gA-Mind M3gA-Mind commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Composio GMAIL_FETCH_EMAILS (and other per-action tools handed to integrations_agent) are built from the thin list_tools schema — often {"type":"object"} with no field descriptions — so the model guesses argument formats and sends unquoted Gmail queries that return zero results.
  • Add a per-turn contract gate: on the first call to an action, surface its FULL live input schema + description (from the cached live toolkit catalog) as a recoverable tool error; the retry, now with the contract in context, executes normally.
  • Gate degrades to a normal execute whenever the contract can't be resolved (unconfigured / offline client, unknown action), so it never blocks an action more than once and never blocks when it cannot help.
  • New self-contained src/openhuman/composio/contract_gate.rs module + unit tests; wired into the per-action surface (ComposioActionTool).

Problem

The per-action tools for integrations_agent are built by fetch_toolkit_actions (src/openhuman/composio/connected_integrations.rs:1016) from the lightweight list_tools response, whose parameters is often NoneComposioActionTool falls back to {"type":"object"} (src/openhuman/composio/action_tool.rs:96). The model therefore never sees the action's real schema/description (e.g. that Gmail query needs quoting for multi-word phrases) and composes zero-result calls. Telling the agent to read the contract first fixes it — because only then does the full schema enter context. The full contract already exists in-tree via fetch_live_toolkit_catalog (src/openhuman/tinyflows/caps.rs:1789), just not on this surface.

Solution

  • contract_gate::ContractGate records which action contracts have been surfaced this turn (interior-mutable set, one gate per ComposioActionTool instance, which lives for one integrations_agent spawn — so "seen" is scoped to the turn without task-local plumbing).
  • contract_gate::consult (src/openhuman/composio/contract_gate.rs): on the first consult for a slug, resolves the full contract from the process-cached fetch_live_toolkit_catalog and returns GateDecision::Surface(<formatted contract>); later consults, and any consult with no resolvable contract, return GateDecision::Proceed. No network when the catalog is cached or the client is unconfigured (fails fast).
  • ComposioActionTool::execute consults the gate after the sandbox check and returns the contract as ToolResult::error before dispatch (src/openhuman/composio/action_tool.rs).
  • Debug logging with a grep-friendly [composio][contract-gate] prefix; logs only action slugs and booleans/counts — no user data / PII.

Scope note — dispatcher / MCP / Workflows / compaction-reset are follow-up

This PR gates the per-action Composio surface, the direct root cause of the Gmail repro. The issue also asks to generalise the gate to the composio_execute dispatcher, the MCP bridges (mcp_call_tool / mcp_registry_tool_call) and the Workflow dispatchers (run_workflow), and to reset the per-turn state on context compaction. The clean home for that is a single ToolMiddleware at the turn-harness seam (assemble_turn_harness, src/openhuman/tinyagents/mod.rs), where a wrap_tool hook can short-circuit any late-bound tool uniformly and per-turn state lives in RunContext::stores. That is a larger change in the agent core; it is deliberately deferred here so this fix stays contained and unit-verifiable (local cargo build/test is disabled on this machine — CI is the authority), and it builds on the reusable ContractGate primitive this PR adds.

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) per Testing Strategycontract_gate_tests.rs (surface-then-proceed, unknown-action degrades to proceed, independent gating) + action_tool.rs execute-level test covering both gate arms.
  • Diff coverage ≥ 80% — the gate module and the execute wiring are exercised by the new unit tests; Rust-only change, CI rust-core-coverage is the authority.
  • Coverage matrix updated — N/A: bug-fix, no new user-facing feature row.
  • All affected feature IDs from the matrix are listed in the PR description under ## Related — N/A: no matrix rows affected.
  • No new external network dependencies introduced (mock backend used per Testing Strategy) — tests use the seeded process cache; no client is built.
  • Manual smoke checklist updated if this touches release-cut surfaces (docs/RELEASE-MANUAL-SMOKE.md) — N/A: internal agent-tool behaviour, no release-cut surface.
  • Linked issue referenced in the ## Related section — uses Addresses #4853 (NOT Closes) so the partially-resolved P1 is not auto-closed on merge, per maintainer review.

Impact

  • Runtime/platform: Rust core, all platforms. Affects the per-action Composio tools an integrations_agent spawns (Gmail/Notion/Slack/GitHub/Linear/etc.).
  • Performance: on the first call to an action per turn the gate reads the live toolkit catalog — cached process-wide (one fetch per toolkit), so at most one extra round-trip per toolkit per process; cached and unconfigured paths do no network.
  • Security: no new surface; the sandbox read-only gate still runs first, and logs carry no PII. Migration/compat: none.

Related

  • Addresses Composio Gmail search returns no results because agents call actions before seeing the full parameter schema #4853 (partial — kept OPEN intentionally per maintainer review: this PR resolves the immediate Gmail-search repro on the per-action surface; unknown-id "not found + valid list", compaction-reset, and MCP/Workflow-dispatcher generalization are deferred to follow-ups)
  • Follow-up PR(s)/TODOs: generalise the gate to the composio_execute dispatcher, the MCP bridges, and the Workflow dispatchers via a shared ToolMiddleware, and reset the per-turn seen-set on context compaction.

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

Linear Issue

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

Commit & Branch

  • Branch: fix/GH-4853-composio-gmail-search
  • Commit SHA: d80b1e3fadf8c3ab951bceb07f125f64583d5b0c

Validation Run

  • pnpm --filter openhuman-app format:check — N/A: no frontend files changed.
  • pnpm typecheck — N/A: no TypeScript changed.
  • Focused tests: cargo test -p openhuman contract_gate — not run locally (cargo disabled on this machine per disk policy); CI is authority.
  • Rust fmt/check (if changed): ran standalone rustfmt on the changed files (clean); cargo check/clippy deferred to CI.
  • Tauri fmt/check (if changed): N/A: no Tauri changes.

Validation Blocked

  • command: cargo test / cargo check / cargo clippy
  • error: local Rust builds are disabled on this machine (disk-fill policy); CI performs full verification.
  • impact: compile + test + clippy verification deferred to CI (rust-core-coverage, Rust Quality).

Behavior Changes

  • Intended behavior change: the first per-action Composio call each turn returns the action's full contract; the retry executes with well-formed arguments.
  • User-visible effect: Gmail (and other provider) searches via integrations_agent return results without the user first telling the agent to read the tool contract.

Parity Contract

  • Legacy behavior preserved: the sandbox read-only gate still runs before the contract gate; when no live contract can be resolved the action executes exactly as before (the gate degrades to a no-op).
  • Guard/fallback/dispatch parity checks: dispatch, calendar-arg defaults, and per-call client resolution (Prioritize fully local speech and Composer operation #1710) are unchanged; only a pre-dispatch first-call gate was added.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): none
  • Canonical PR: this
  • Resolution: N/A

Summary by CodeRabbit

  • New Features

    • Added per-turn contract guidance for Composio actions by surfacing the provider description, input JSON schema (when available), and required arguments on first attempt.
    • After guidance, retry proceeds with normal dispatch on subsequent attempts (without re-surfacing the full contract).
    • Guidance is scoped per action slug and doesn’t block missing/uncached actions.
  • Bug Fixes

    • Ensured guidance and execution share the same live config snapshot during a call to improve retry correctness.
  • Tests

    • Added feature-gated regression tests for surface-then-proceed, missing actions, and independent per-action gating.

…nsai#4853)

Per-action Composio tools handed to integrations_agent are built from the
thin list_tools schema (often {"type":"object"} with no field
descriptions), so the model composes calls before the action's real
contract is in context and guesses argument formats — most visibly sending
unquoted Gmail queries that return zero results.

Add a per-turn contract gate: on the first call to an action, surface its
FULL live input schema/description (from the cached live toolkit catalog)
as a recoverable tool error and let the retry execute. Degrades to a normal
execute whenever the contract can't be resolved, so an unconfigured/offline
client never blocks the action. Wired into the per-action surface; the
dispatcher, MCP, and Workflow surfaces are follow-up.
@M3gA-Mind
M3gA-Mind requested a review from a team July 16, 2026 13:34
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dafc9028-b95e-4381-adf8-6656d92f0793

📥 Commits

Reviewing files that changed from the base of the PR and between 8006af8 and 8714bb4.

📒 Files selected for processing (2)
  • src/openhuman/agent/harness/subagent_runner/ops/provider.rs
  • src/openhuman/composio/contract_gate.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/openhuman/composio/contract_gate.rs

📝 Walkthrough

Walkthrough

The Composio action tool now consults a per-instance contract gate before dispatch. The first call can return the live action description and input schema as a recoverable error; retries proceed using the same live configuration snapshot. Tests cover surfacing, retries, unknown actions, and independent slugs.

Changes

Composio contract gating

Layer / File(s) Summary
Contract gate and live contract formatting
src/openhuman/composio/contract_gate.rs, src/openhuman/composio/mod.rs
Adds per-turn action tracking, cached contract lookup, formatted schema and description output, and public module export.
Action execution integration
src/openhuman/composio/action_tool.rs, src/openhuman/agent/harness/subagent_runner/ops/provider.rs
Initializes a gate per action tool, consults it before dispatch, and reuses one live configuration snapshot for gating and routing; documents resolver lifetime behavior.
Contract gate regression coverage
src/openhuman/composio/contract_gate_tests.rs, src/openhuman/composio/action_tool.rs
Tests first-call surfacing, retry behavior, unknown actions, independent action slugs, and dispatch progression.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Suggested labels: feature, rust-core, bug

Suggested reviewers: senamakel

Poem

I’m a rabbit with contracts tucked neat in my hat,
First call shows the schema—imagine that!
Retry with good queries, the action runs bright,
Unknown slugs pass onward instead of a fight.
Hop, hop, the tool path is clear and right!

Sequence Diagram(s)

sequenceDiagram
  participant Model
  participant ComposioActionTool
  participant ContractGate
  participant LiveToolkitCatalog
  participant ComposioDispatch
  Model->>ComposioActionTool: execute(arguments)
  ComposioActionTool->>ContractGate: consult(live_config, action_slug)
  ContractGate->>LiveToolkitCatalog: resolve cached live contract
  LiveToolkitCatalog-->>ContractGate: contract or no match
  ContractGate-->>ComposioActionTool: Surface(contract) or Proceed
  ComposioActionTool-->>Model: recoverable contract error
  Model->>ComposioActionTool: retry with corrected arguments
  ComposioActionTool->>ContractGate: consult(live_config, action_slug)
  ContractGate-->>ComposioActionTool: Proceed
  ComposioActionTool->>ComposioDispatch: dispatch action
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: gating Composio action tools on their full contract.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d80b1e3fad

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/openhuman/composio/contract_gate.rs
… feature

The tinyhumansai#4912 flows gate (merged after this branch was cut) put
`openhuman::tinyflows` behind `#[cfg(feature = "flows")]`, so the
gates-off build could not compile contract_gate.rs's unconditional
`tinyflows::caps` import (E0433 in the "Rust Feature-Gate Smoke (gates
off)" lane).

The contract gate's live-catalog lookup is sourced entirely from the
flows/tinyflows caps layer. With flows compiled out there is no catalog
source, so the gate simply has no fuller contract to surface and always
proceeds (the per-action Composio tool still runs; it just loses the
pre-execute contract nudge). Guard the import and the lookup/format
helpers on `feature = "flows"` in lockstep, restructure `consult` to
Proceed when the feature is off, and gate the flows-seeding unit tests
(contract_gate_tests.rs + the action_tool retry test) on the same
feature. No behavior change in the default (flows-on) build.
@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

Synced onto current upstream/main and fixed the Rust Feature-Gate Smoke (gates off) failure (E0433: cannot find tinyflows in openhuman at contract_gate.rs:33).

Root cause: the #4912 flows gate merged after this branch was cut, putting openhuman::tinyflows behind #[cfg(feature = "flows")]. The contract gate imported tinyflows::caps unconditionally, so the gates-off build could not compile it.

Fix: the gate's live-contract lookup is sourced entirely from the flows/tinyflows caps layer, so with flows off there is simply no catalog to surface and the gate always proceeds (the per-action tool still runs; it just loses the pre-execute contract nudge). Guarded the tinyflows/providers import plus the lookup_contract/format_contract helpers on feature = "flows" in lockstep, restructured consult to Proceed when the feature is off, and gated the flows-seeding unit tests (contract_gate_tests.rs + the action_tool retry test) on the same feature. No behavior change in the default flows-on build.

@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

Gated the contract gate's tinyflows dependency on feature = "flows" to fix the gates-off build.

@coderabbitai coderabbitai Bot added bug feature Net-new user-facing capability or product behavior. rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. labels Jul 17, 2026
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

@M3gA-Mind I’ll review the feature-gating update, focusing on parity between the flows-enabled contract lookup and the flows-disabled proceed fallback.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/openhuman/composio/contract_gate.rs (1)

91-143: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add diagnostics around the live-catalog lookup.

The flow logs its final branch, but not lookup entry, duration, cache/fetch outcome, or failure reason. Preserve enough detail to distinguish unknown actions, unavailable configuration, and catalog-fetch failures.

As per coding guidelines, new flows require grep-friendly diagnostics covering entry/exit, external calls, state transitions, and errors.

🤖 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/contract_gate.rs` around lines 91 - 143, Add
grep-friendly diagnostics around the flows-gated lookup_contract path: log
lookup entry, toolkit derivation failures, catalog fetch start/completion with
duration and cache/fetch outcome, and explicit reasons for unavailable
configuration, fetch failure, or unknown actions. Preserve the existing
GateDecision behavior and final proceed/surface logs, using lookup_contract and
fetch_live_toolkit_catalog as the primary instrumentation points.

Source: Coding guidelines

🤖 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/composio/action_tool.rs`:
- Around line 205-206: Reload the current configuration before the contract
lookup in the action execution flow, rather than passing the captured
self.config to contract_gate::consult. Store that single reloaded snapshot and
reuse it for subsequent client creation and dispatch, ensuring contract lookup
and execution use identical live configuration.

In `@src/openhuman/composio/contract_gate.rs`:
- Around line 92-101: Update the gate flow around gate.mark_seen and the
contract lookup to distinguish resolving from surfaced states, preventing
concurrent sibling calls from returning GateDecision::Proceed while the first
lookup is still pending. Have siblings wait for completion or reuse the surfaced
contract, then preserve proceeding only after the contract has been surfaced.
Add a regression test that exercises concurrent calls and verifies neither
dispatches with thin arguments before resolution.

---

Nitpick comments:
In `@src/openhuman/composio/contract_gate.rs`:
- Around line 91-143: Add grep-friendly diagnostics around the flows-gated
lookup_contract path: log lookup entry, toolkit derivation failures, catalog
fetch start/completion with duration and cache/fetch outcome, and explicit
reasons for unavailable configuration, fetch failure, or unknown actions.
Preserve the existing GateDecision behavior and final proceed/surface logs,
using lookup_contract and fetch_live_toolkit_catalog as the primary
instrumentation points.
🪄 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: 72a85496-39cd-4e6e-b05c-0ee2fd55d024

📥 Commits

Reviewing files that changed from the base of the PR and between 2fd56ee and f002ced.

📒 Files selected for processing (4)
  • src/openhuman/composio/action_tool.rs
  • src/openhuman/composio/contract_gate.rs
  • src/openhuman/composio/contract_gate_tests.rs
  • src/openhuman/composio/mod.rs

Comment thread src/openhuman/composio/action_tool.rs Outdated
Comment thread src/openhuman/composio/contract_gate.rs

@graycyrus graycyrus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Solid, well-scoped fix for the immediate Gmail-search repro — the contract gate + per-action wiring is clean, and the follow-up commit fixing the flows-off build break shows good CI discipline.

That said, I can't approve this yet.

CodeRabbit's two major findings are both real and should block merge:

  • action_tool.rs: the gate consults self.config (the captured snapshot) before the function reloads live_config a few lines later (the "[#1710 Wave 4] reload config fresh per execute" block). That reload exists specifically so a mid-session composio.mode/credential/workspace change takes effect at the very next call — the contract lookup bypasses that invariant and can resolve off a stale config while the actual dispatch uses the fresh one.
  • contract_gate.rs: mark_seen flips the slug to "seen" before the async catalog lookup resolves. Two concurrent calls to the same action within one turn — plausible in a tool-calling agent — will race: the second sees first_time == false and dispatches immediately with the thin schema, i.e. exactly the zero-result bug this PR exists to fix, just for the unlucky sibling call.

New finding — this PR claims Closes #4853 but doesn't meet the issue's own acceptance criteria.
The issue lists four criteria: repro gone, regression safety (first-call/retry + unknown-id-with-valid-list + compaction reset), >=80% diff coverage, and generalization to the MCP/Workflow dispatchers. This PR nails the first criterion and half of the second — first-call/retry is tested, but an unknown action just degrades silently instead of surfacing "not found + valid list", and there's no compaction-reset mechanism or test. Generalization to MCP/Workflows is explicitly deferred to a follow-up PR.

On top of that, the gate's own doc comments describe the seen-set as "per-turn," but it's actually scoped to the lifetime of the ComposioActionTool instance — which the PR description itself acknowledges can span multiple turns (hence the deferred "reset on compaction" TODO). So in a longer-running integrations_agent spawn, once a contract is surfaced once, it's gone for good even after the schema has dropped out of context via compaction — exactly the scenario the issue's compaction-reset criterion exists to cover.

Given that, Closes #4853 will auto-close a P1 issue on merge that's only partially resolved. Either drop the Closes keyword and reference it as partial progress (e.g. Progress on #4853), or file a tracking issue for the deferred generalization/compaction work and link it here — don't let the P1 silently close.

None of this is a knock on the overall approach — the gate design (surface-once, degrade-gracefully) is sound, and scoping the first PR to the per-action surface is reasonable. But the config-staleness and race-condition fixes are small and contained, and worth doing before merge, and the issue-closure framing needs fixing regardless.

…Rabbit)

The per-action execute path consulted the contract gate with the captured
spawn-time `self.config`, then separately reloaded a fresh snapshot for
client creation + dispatch. After a mid-session `composio.mode` /
credential / workspace change the gate's live-catalog fetch could resolve
(or skip) a contract against stale routing while dispatch used fresh
routing. Reload the snapshot once, up front (mirroring the existing tinyhumansai#1710
Wave-4 principle), and reuse it for the gate consult, client creation, and
dispatch so all three share identical live config.
@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

Addressed all three items: (1) Codex — flows-gate independence already fixed by #[cfg(feature = "flows")] guards (Feature-Gate Smoke green); (2) config-reload consistency — the gate now consults the single reloaded live_config reused for dispatch; (3) concurrency race — declined in-thread with reasoning (best-effort nudge, deliberate non-blocking design, near-zero same-slug concurrency, self-healing next turn).

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

@M3gA-Mind I’ll review the updated feature-gating and live_config flow, and reassess the concurrency behavior against the intended best-effort, non-blocking gate semantics.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 17, 2026

@graycyrus graycyrus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Continuation Review — New commits address first major finding

Summary

Commit 3561130 (reload live config once before the contract gate) fixes the stale-config concern from the prior review: the gate, client factory, and dispatch now consistently share a single live_config snapshot reloaded up front, exactly as requested. CodeRabbit has confirmed the fix.

Findings Update

Prior major #1 (config reload before gate) — RESOLVED

  • Commit 3561130 moves the live_config reload before the gate consult and passes the same snapshot to both gate and dispatch. This aligns with the #1710 Wave 4 convention and eliminates the stale-routing window.
  • CodeRabbit confirmed: "The gate, client factory, and dispatch now consistently share the single reloaded live_config snapshot, resolving the stale-routing concern."

Prior major #2 (TOCTOU race on concurrent calls) — ACCEPTED TRADEOFF

  • The code still marks the action seen before the async lookup completes. However, CodeRabbit has reviewed this and agreed: the rare concurrent path degrades to the established thin-schema execution path (existing behavior) rather than introducing a new failure mode. Given the per-turn gate scope and sequential tool execution in the common case, this pragmatic trade-off is acceptable.

Prior major #3 (PR closes P1 #4853 with partial scope) — NEEDS ACTION

  • The PR description now explicitly documents the deferred scope (dispatcher/MPC/Workflow generalisation, compaction-reset), but the PR body still uses Closes #4853, which will auto-close the issue on merge despite only partially implementing it.
  • Action required: Change the ## Related section to use "Addresses #4853" (or remove the "Closes") so the P1 issue remains open for the follow-up PRs. Do not auto-close a partially-addressed P1.

Gates

  • CI: pending (Rust Quality check still running, but all prior checks passed).
  • Conflicts: none.
  • Code quality: the fix is solid and well-integrated.

@M3gA-Mind, the code fix is ready to go — just need you to adjust the issue reference in the PR body from "Closes" to "Addresses" to avoid auto-closing the P1 with incomplete scope.

oxoxDev
oxoxDev previously approved these changes Jul 17, 2026

@oxoxDev oxoxDev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approving, with one framing note up front: this is a fail-open schema nudge, not a security/authz gate — it intentionally proceeds whenever the contract can't be resolved, and the real execution guard (the sandbox read-only check at action_tool.rs:168) runs before it and is unchanged. So "fail-closed / bypass" doesn't apply here.

Correctness verified: the deny path short-circuits before dispatch; per-turn scoping holds on the live path (eager dynamic_tools built once per spawn at runner.rs:766, and the same tool+gate services both the first-call surface and the retry-proceed); flows-gated so the slim build degrades to Proceed. The deny path is genuinely asserted in tests, not just allow. CI + coverage green.

Non-blocking (CodeRabbit's two "Major" — valid, but both fail-open, worst case equals pre-fix behavior):

  • The gate consults captured self.config while dispatch reloads a fresh snapshot at action_tool.rs:215 — stale after a mid-session mode/credential toggle. Cheapest fix: reload once before the gate and pass the snapshot in.
  • mark_seen runs before the awaited lookup, so a concurrent sibling call to the same slug can proceed before the contract surfaces. Real race, harmless while fail-open.

Forward risk worth a note on #4249-1b: LazyToolkitResolver::resolve() builds a fresh gate per call (currently dead code — let _ = &lazy_resolver). When that path is wired for dispatch, a fresh gate per resolve means the contract is surfaced on every call and the action never proceeds. Cache the resolved tool per turn.

Codex P1 (unconditional tinyflows import) is already fixed at head — no action.

Addresses maintainer review (graycyrus / oxoxDev), docs-only:

- contract_gate.rs: the ContractGate's seen-set doc overclaimed "per-turn".
  It is actually scoped to the ContractGate instance (one per
  ComposioActionTool spawn) — a single turn in the common case, but a
  long-lived integrations_agent spawn can span turns, and the gate does not
  reset on context compaction (deferred). Reworded to say so accurately.
- subagent_runner/ops/provider.rs: document that LazyToolkitResolver::resolve
  builds a fresh ComposioActionTool (and gate) per call, so once wired to
  dispatch the resolved tool must be cached per turn or a surfaced action can
  never proceed (oxoxDev forward note).

No behavior change.
@M3gA-Mind
M3gA-Mind dismissed stale reviews from oxoxDev and coderabbitai[bot] via 8714bb4 July 17, 2026 19:34
@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

@graycyrus @oxoxDev — addressing the review:

Major #1 (gate consults stale self.config before dispatch reloads): ✅ fixed at 3561130 (last cycle) — the reload moved before the gate; gate, client factory, and dispatch now share one live_config snapshot. You and CodeRabbit both confirmed resolved.

Major #2 (TOCTOU race on concurrent same-action calls): per your continuation review + CodeRabbit, accepted tradeoff — the rare concurrent sibling degrades to the pre-existing thin-schema path (not a new failure mode), tool execution is sequential in the common case, and the gate is an intentionally non-blocking best-effort nudge. No change.

Major #3 (Closes #4853 on a partially-resolved P1):fixed at 8714bb42e## Related now reads Addresses #4853 (not Closes), so the P1 stays open for the follow-ups; the deferred scope (unknown-id "not found + valid list", compaction-reset, MCP/Workflow generalization) is spelled out inline. Title's (#4853) is a bare reference, not an auto-close keyword. I also corrected the gate's doc comments you flagged: they claimed the seen-set is "per-turn" but it's scoped to the ContractGate/ComposioActionTool instance lifetime (a turn in the common case, but a long spawn can span turns and it doesn't reset on compaction) — reworded to say so accurately.

oxoxDev forward note (LazyToolkitResolver::resolve builds a fresh gate per call): ✅ added a code comment at 8714bb42e documenting that once this path dispatches, the resolved tool must be cached per turn or a surfaced action never proceeds.

Re-requesting review — the only code change since your continuation is docs/comments + the ClosesAddresses body edit. 🙏

@M3gA-Mind
M3gA-Mind requested a review from graycyrus July 17, 2026 19:34
@senamakel
senamakel merged commit 48cba1a into tinyhumansai:main Jul 18, 2026
23 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in Team Openhuman Jul 18, 2026
yh928 added a commit to yh928/openhuman that referenced this pull request Jul 18, 2026
…llowlist

The feature-gate-smoke lane compiles AND runs the lib test binary with the
domain gates off, so the contract-gate tests that name gated symbols
(`resolve_composio`/`render_composio_contract`/`ToolContract` → `flows`,
`resolve_mcp`/`render_mcp_contract` → `mcp`, `join_capped` → either) must carry
the matching `#[cfg(feature = …)]` or the gates-off test binary fails to compile.
Gate the five affected tests to match the resolvers they exercise.

The lane's self-maintaining allowlist then flags the newly-gated file, so add
`openhuman/tinyagents/contract_gate_tests.rs`. It also surfaced a latent gap the
main lane hides (the guard is skipped on main pushes, runs only on PRs):
`openhuman/composio/action_tool.rs` already carries a `#[cfg(feature = "flows")]`
test from tinyhumansai#4995 but was never added — add it too so the guard is consistent.

Verified gates-off: `cargo check` + `cargo test --lib` (compile + the lane's
scoped run, 230 pass) clean; the guard allowlist == the grep's actual set.
Default `openhuman::tinyagents::contract_gate` tests still green (33).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SRSNnqQsokuGmkbpLoLCGy
yh928 added a commit to yh928/openhuman that referenced this pull request Jul 18, 2026
…n the middleware)

Upstream merged tinyhumansai#4995 ("gate per-action tools on their full contract"), a
per-`ComposioActionTool` in-memory gate (`composio::contract_gate`). This PR's
`tinyagents::contract_gate` middleware already gates per-action Composio calls —
and more robustly: its "already surfaced" state is derived from the transcript
with a payload-digest, so a contract summarised or compacted OUT of context is
re-delivered. tinyhumansai#4995's own docs flag that its in-memory `seen`-set does NOT reset
on compaction (the model then acts on a schema it can no longer see).

To avoid double-gating (the middleware short-circuits the first call before
execute, so tinyhumansai#4995's tool-layer consult would fire a SECOND contract on the retry
and delay execution a turn), remove the tool-layer hookup: the `gate` field, its
init, the `consult` call in `execute`, and its regression test. Changes to the
merged code are kept minimal — `composio::contract_gate` itself is left in place
(now unused in production, tracked for removal) and its unit tests still pass;
only the per-action wiring in `action_tool.rs` (+ a stale lifecycle note in
`provider.rs`) is touched. The middleware is now the single per-action gate.

Verified: default `clippy --lib` clean, `openhuman::tinyagents::contract_gate`
(33) + `composio::action_tool` (8) + dormant `composio::contract_gate` (3) green;
gates-off `cargo check` + `cargo test --lib` compile clean; the feature-gate
smoke allowlist matches (action_tool.rs drops its only gated test).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SRSNnqQsokuGmkbpLoLCGy
yh928 added a commit to yh928/openhuman that referenced this pull request Jul 19, 2026
Sync the 22-commit lag with main. Clean auto-merge (i18n additions and the
coverage matrix merged without conflict). Brings in main's CI restructure —
including the rust-feature-gate-smoke allowlist guard (tinyhumansai#5022) and the composio
contract gate (tinyhumansai#4995) — which the next commit reconciles with.
yh928 added a commit to yh928/openhuman that referenced this pull request Jul 19, 2026
…after tinyhumansai#4995

Merging main surfaced two pre-existing gaps left by tinyhumansai#4995 (gate per-action
tools on their full contract) that any PR merging current main hits:

- `composio_credentials_state_raw_coverage_e2e` asserted a per-action tool's
  first `execute` returns "Fetched 1 inbox message". tinyhumansai#4995 made the first call
  this turn surface the action's full contract as a recoverable error and let
  the retry execute — tinyhumansai#4995 changed the tool but not this test. Call execute
  twice: assert the first surfaces the contract, the second returns the fetch
  result. Verified passing locally.

- `composio/action_tool.rs` gained a `#[cfg(feature = "flows")]` gated test but
  was not added to the rust-feature-gate-smoke allowlist (tinyhumansai#5022), so the guard
  failed on the merged tree. Added it; the guard's ACTUAL set now equals the
  allowlist.

Neither file is part of the custom-servers feature; they ride here only because
this PR now merges a main that is internally inconsistent on them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug feature Net-new user-facing capability or product behavior. rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants