Skip to content

feat(planner): read-only sandbox gate on composio meta-tools#904

Merged
senamakel merged 6 commits intotinyhumansai:mainfrom
obchain:feat/planner-composio-readonly-gate
Apr 29, 2026
Merged

feat(planner): read-only sandbox gate on composio meta-tools#904
senamakel merged 6 commits intotinyhumansai:mainfrom
obchain:feat/planner-composio-readonly-gate

Conversation

@obchain
Copy link
Copy Markdown
Contributor

@obchain obchain commented Apr 24, 2026

Summary

  • Planner agent now carries the composio meta-tools (composio_list_toolkits / list_connections / list_tools / composio_execute) in its whitelist, but composio_execute refuses any Write / Admin-scoped action slug when the calling agent's sandbox_mode is ReadOnly.
  • New CURRENT_AGENT_SANDBOX_MODE task-local (sibling of the existing PARENT_CONTEXT / FORK_CONTEXT in harness/fork_context.rs) scoped around every tool.execute so the gate has zero impact on the Tool trait signature and zero blast radius outside the agent runtime.
  • Gate reuses the existing ToolScope catalog (find_curated + classify_unknown fallback) — no new classification rules, no new per-action metadata.
  • Bundles a one-line chore(tauri) fix for an unrelated pre-existing dup-key in app/src-tauri/Cargo.toml that was making cargo fmt / cargo metadata / cargo check abort on main. The fix is self-contained and can be dropped from this PR if maintainers would rather split it.

Problem

#685 asks for the planner agent to use Composio tools in strict read-only mode. Two concrete things were missing:

  1. No agent-level enforcement layer — the existing evaluate_tool_visibility in composio_execute gates by the user's scope preference, which is the user's per-toolkit choice. There was no way to say "this specific agent may never mutate state even if the user allows writes."
  2. sandbox_mode was dormant metadataAgentDefinition::sandbox_mode has been on agent TOMLs for a while (planner, critic, welcome, morning_briefing, trigger_triage all declare read_only) but nothing in the runtime ever read it. The planner's own whitelist was also capped at file_read / memory_recall / web_search_tool with no composio access at all.

Solution

Commits land in dependency order, each single-concern:

  • chore(tauri): drop duplicate base64 dep key — one-line prereq. base64 = "0.22" was declared twice in app/src-tauri/Cargo.toml; removed the second occurrence so cargo * parses.
  • feat(agent): add CURRENT_AGENT_SANDBOX_MODE task-local for tool-exec scope — new sandbox_context module with tokio::task_local! carrier, current_sandbox_mode() reader, with_current_sandbox_mode() scope setter, and four unit tests covering all four state transitions (unset / scoped / leak / nested override).
  • feat(agent): scope sandbox_mode task-local around tool execution — wires the task-local at the two production call sites: agent::bus::register_agent_handlers resolves target_agent_idAgentDefinitionRegistry::global().get(id) and wraps run_tool_call_loop, and subagent_runner::run_subagent wraps its fork-mode / typed-mode dispatch in the sub-agent's own definition.sandbox_mode. Task-local scope semantics propagate across every .await boundary inside the wrapped future, so tool.execute at every depth observes the mode without per-call-site plumbing.
  • feat(composio): reject Write/Admin actions under ReadOnly sandbox — new resolve_action_scope helper (curated catalog + classify_unknown fallback, defaults unparseable slugs to Write so the gate fails closed), plus the gate itself in ComposioExecuteTool::execute runs before evaluate_tool_visibility so a read-only agent takes the cheapest rejection path. ToolResult::error with a concrete "agent is read-only, action is write" message the model can read and recover from. tracing::info! audit line. Five unit tests cover ReadOnly + Write / Admin → blocked, ReadOnly + Read → passes to downstream gates, task-local unset → no-op (back-compat), and Sandboxed → not read-only, no trigger.
  • feat(planner): allow composio meta-tools under read-only sandbox — extends planner/agent.toml's [tools] named list with the four composio meta-tools and adds an inline comment pointing at where enforcement actually lives.

Submission Checklist

  • Unit tests — 9 new tests: 4 on the task-local (openhuman::agent::harness::sandbox_context::tests::*) and 5 on the gate (openhuman::composio::tools::tests::sandbox_*). All pass locally.
  • Regression sweep — full cargo test --manifest-path Cargo.toml -p openhuman --lib suite: 5229 passed, 0 failed. Planner-definition parsing test (agents::loader) also green after the agent.toml change.
  • Back-compatcurrent_sandbox_mode() returns None outside any scope (direct CLI / JSON-RPC / tests), so the gate is strictly additive; no existing path gets stricter without a ReadOnly agent actually invoking it.
  • Doc comments/// headers on every new public item (CURRENT_AGENT_SANDBOX_MODE, current_sandbox_mode, with_current_sandbox_mode, resolve_action_scope) plus a file-level //! rationale on sandbox_context.rs. Inline comments at every gate site + in planner/agent.toml pointing future readers at the enforcement location.
  • Observabilitytracing::info! audit line in the rejection path following the existing [composio][scopes] / [composio][sandbox] prefix convention.

Impact

  • Runtime: ReadOnly-sandbox agents that invoke composio_execute with a Write- or Admin-scoped slug now get a ToolResult::error instead of executing. Today the only such agent with composio_execute in its whitelist is the planner (added in this PR). Other ReadOnly agents (welcome / critic / morning_briefing / trigger_triage) don't have composio access yet, so they see zero change — but they'd inherit the same enforcement for free if anyone adds composio to their TOML later.
  • Back-compat: SandboxMode::None and SandboxMode::Sandboxed pass through unchanged. The task-local being None (unit tests, CLI, direct JSON-RPC dispatch) is explicitly treated as "no gate" — tests hit this path regularly and stay green.
  • ComposioActionTool (per-action tools spawned dynamically under integrations_agent): unaffected. integrations_agent is sandbox_mode = "none", so the task-local carries None and the per-action path is not gated — correct by design since integrations_agent is specifically the write-capable dispatcher.
  • Performance: one task_local::try_with + one scope classification per composio_execute call on the gated path. Zero cost on all other paths.

Related

Closes #685

  • No release.yml / CI changes.
  • Sandbox enforcement for other write-shaped tools (file_write, memory_store, cron_create, …) is intentionally out of scope; happy to take that as a follow-up if you want to generalise.

Summary by CodeRabbit

  • New Features

    • Planner agent now supports Composio integration tools, enabling inspection of connected integrations and execution of Composio actions.
    • Added sandbox mode enforcement for read-only execution of Composio actions, preventing write and admin-scoped operations in restricted environments.
  • Chores

    • Removed unused base64 dependency.

@obchain obchain requested a review from a team April 24, 2026 20:03
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

Warning

Rate limit exceeded

@obchain has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 6 minutes and 49 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 6 minutes and 49 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: afecb490-334d-4993-9b22-7a1296622fd8

📥 Commits

Reviewing files that changed from the base of the PR and between 3faf43e and 13e842a.

📒 Files selected for processing (9)
  • src/openhuman/agent/agents/loader.rs
  • src/openhuman/agent/agents/planner/agent.toml
  • src/openhuman/agent/bus.rs
  • src/openhuman/agent/harness/mod.rs
  • src/openhuman/agent/harness/sandbox_context.rs
  • src/openhuman/agent/harness/subagent_runner/ops.rs
  • src/openhuman/composio/action_tool.rs
  • src/openhuman/composio/tools.rs
  • src/openhuman/composio/tools_tests.rs
📝 Walkthrough

Walkthrough

This PR implements per-agent sandbox mode enforcement through a task-local context system. It removes an unused dependency, expands the planner's Composio tool configuration, and adds infrastructure to read per-agent sandbox modes from the registry and enforce read-only restrictions for Composio actions at runtime.

Changes

Cohort / File(s) Summary
Dependency Cleanup
app/src-tauri/Cargo.toml
Removed unused base64 crate dependency.
Planner Configuration
src/openhuman/agent/agents/planner/agent.toml
Extended planner tool configuration to include Composio meta-tools (composio_list_toolkits, composio_list_connections, composio_list_tools, composio_execute) alongside existing read-only tools; read-only sandbox mode will block write/admin-scoped actions.
Sandbox Context Infrastructure
src/openhuman/agent/harness/sandbox_context.rs
New module implementing task-local sandbox mode tracking with CURRENT_AGENT_SANDBOX_MODE task-local variable, current_sandbox_mode() reader, and with_current_sandbox_mode() async scope runner; includes comprehensive test coverage for scope isolation and nesting.
Harness Module Exports
src/openhuman/agent/harness/mod.rs
Exposed new sandbox_context module and re-exported current_sandbox_mode and with_current_sandbox_mode helpers for external code.
Bus Handler Integration
src/openhuman/agent/bus.rs
Modified agent.run_turn handler to resolve per-turn sandbox mode from AgentDefinitionRegistry and execute tool-call loop within the task-local sandbox context using with_current_sandbox_mode().
Subagent Sandbox Enforcement
src/openhuman/agent/harness/subagent_runner/ops.rs
Wrapped run_subagent tool invocation in task-local sandbox mode context derived from sub-agent's definition.
Composio Tool Gating
src/openhuman/composio/tools.rs
Added agent-level sandbox enforcement layer in composio_execute that resolves action slugs to ToolScope and rejects Write or Admin scoped actions when task-local sandbox mode is ReadOnly; includes unit tests for gate behavior across different modes.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Bus as Bus Handler
    participant Registry as AgentDefinition<br/>Registry
    participant Context as Sandbox<br/>Context
    participant ToolLoop as Tool-Call<br/>Loop
    participant Composio as Composio<br/>Tools

    Client->>Bus: run_turn(target_agent_id)
    Bus->>Registry: resolve agent definition
    Registry-->>Bus: SandboxMode
    Bus->>Context: with_current_sandbox_mode(mode)
    Context-->>Bus: Enter scope
    Bus->>ToolLoop: execute tool calls
    ToolLoop->>Composio: composio_execute(action)
    Composio->>Context: current_sandbox_mode()
    Context-->>Composio: ReadOnly?
    alt ReadOnly Mode
        Composio->>Composio: resolve action to ToolScope
        alt Write/Admin Scope
            Composio-->>ToolLoop: reject with error
        else Read Scope
            Composio->>Composio: proceed to execution
            Composio-->>ToolLoop: result
        end
    else Other Modes
        Composio->>Composio: proceed to execution
        Composio-->>ToolLoop: result
    end
    ToolLoop-->>Bus: completion
    Context-->>Bus: Exit scope
    Bus-->>Client: turn result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • Allow the planner agent to have compose tools with read access only. #685: Directly implements the planner-facing Composio read-only tools requirement with task-local sandbox gating for write/admin action blocking.
  • tinyhumansai/openhuman#658: Addresses per-agent sandboxing and read-only Composio enforcement through the new task-local sandbox context and planner configuration updates.

Possibly related PRs

Suggested reviewers

  • senamakel

Poem

🐰 A sandbox blooms, task-local and true,
Where modes flow soft as morning dew—
Read-only gates keep actions tight,
Composio tools stay safe and bright! ✨

🚥 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 and specifically describes the main change: adding a read-only sandbox gate for composio meta-tools in the planner agent. It is concise, directly related to the primary feature, and would help a teammate quickly understand the purpose of this changeset.
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.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/openhuman/composio/tools.rs`:
- Around line 462-488: ComposioActionTool::execute bypasses the agent-level
read-only sandbox; add the same sandbox gate as in tools.rs by calling
current_sandbox_mode() at the start of ComposioActionTool::execute, and if
Some(SandboxMode::ReadOnly) resolve the tool scope (reuse resolve_action_scope
or the same logic) and refuse ToolScope::Write | ToolScope::Admin with a
ToolResult::error message matching the existing composio_execute block;
alternatively, if you prefer spawn-time enforcement, filter out
Write/Admin-scoped Composio actions where parent agent current_sandbox_mode() ==
Some(SandboxMode::ReadOnly) in subagent_runner/ops.rs before registering
per-action tools.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 089ed428-457b-4de6-9e3b-554e2f49c2ed

📥 Commits

Reviewing files that changed from the base of the PR and between d2f0457 and 3faf43e.

📒 Files selected for processing (7)
  • app/src-tauri/Cargo.toml
  • src/openhuman/agent/agents/planner/agent.toml
  • src/openhuman/agent/bus.rs
  • src/openhuman/agent/harness/mod.rs
  • src/openhuman/agent/harness/sandbox_context.rs
  • src/openhuman/agent/harness/subagent_runner/ops.rs
  • src/openhuman/composio/tools.rs
💤 Files with no reviewable changes (1)
  • app/src-tauri/Cargo.toml

Comment thread src/openhuman/composio/tools.rs
obchain added a commit to obchain/openhuman that referenced this pull request Apr 24, 2026
Addresses CodeRabbit feedback on tinyhumansai#904. The dispatcher path
(`ComposioExecuteTool::execute`) gates Write/Admin slugs when the
calling agent is `sandbox_mode = "read_only"`, but
`ComposioActionTool::execute` — the per-action surface spawned
dynamically under `integrations_agent` — called
`client.execute_tool` directly, bypassing the gate. Safe today
because `integrations_agent` is `sandbox_mode = "none"` and no
read-only agent spawns per-action tools, but the invariant
"read-only blocks all Composio writes" becomes implicit rather
than enforced. This mirrors the gate on the per-action path so both
routes to the backend honour the same rule.

Touches `src/openhuman/composio/tools.rs` +
`src/openhuman/composio/action_tool.rs`:

- `resolve_action_scope` → `pub(super)` so sibling modules reuse
  the same scope resolution (curated catalog + `classify_unknown`
  fallback).
- `ComposioActionTool::execute` → new gate block at the top that
  reads `current_sandbox_mode()`, resolves the action's scope, and
  returns a `ToolResult::error` + `tracing::info!` audit line when
  the mode is `ReadOnly` and the scope is `Write` / `Admin`.
  `SandboxMode::None` / `Sandboxed` and the unset task-local value
  pass through unchanged.

Three new tests mirror the existing gate tests on
`ComposioExecuteTool`: `ReadOnly + Write slug → blocked`,
`ReadOnly + Admin slug → blocked`, `unset → gate is a no-op`.
Copy link
Copy Markdown
Member

@senamakel senamakel left a comment

Choose a reason for hiding this comment

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

this is pretty cool @obchain great addition to the planning stage but only one thing is i don't see the planner agent as being sanboxed even though the code for sandboxing is there..

also there's a merge conflict.

obchain added 6 commits April 26, 2026 18:00
…scope

Adds the plumbing tinyhumansai#685 needs to gate tool behavior by the calling
agent's `sandbox_mode` without touching the `Tool` trait signature.

Touches `src/openhuman/agent/harness/mod.rs` +
`src/openhuman/agent/harness/sandbox_context.rs` (new):

- `tokio::task_local! CURRENT_AGENT_SANDBOX_MODE: SandboxMode` →
  scoped per agent turn. Sibling of the existing `PARENT_CONTEXT` /
  `FORK_CONTEXT` in `harness/fork_context.rs` but carries the
  *currently-executing* agent's mode rather than the parent's.
- `current_sandbox_mode()` → reader. Returns `None` outside any
  scope (direct CLI / JSON-RPC / unit-test tool invocation) so
  callers can treat the addition as strictly additive.
- `with_current_sandbox_mode(mode, future)` → setter wrapping the
  `tokio::task_local!` scope. Intended call site is the tool loop
  immediately around each `tool.execute(args)`.
- re-exports from `harness/mod.rs` next to the existing fork-context
  re-exports.

Four unit tests cover the four paths: unset → `None`; scoped → mode
observed inside; no leak after scope ends; nested scope overrides
outer. No behavior change yet — the task-local is not read by any
production code in this commit; wiring lands in the next one.
Wires the task-local added in the previous commit so every tool
invoked inside an agent turn observes the calling agent's
`sandbox_mode`. No tool reads the mode yet — this commit only sets
it; enforcement lands next.

Touches `src/openhuman/agent/bus.rs` +
`src/openhuman/agent/harness/subagent_runner/ops.rs`:

- `bus::register_agent_handlers` → resolves `target_agent_id` to an
  `AgentDefinition` via `AgentDefinitionRegistry::global()` and
  wraps the `run_tool_call_loop(...)` call in
  `with_current_sandbox_mode(sandbox_mode, …)`. Falls back to
  `SandboxMode::None` when no id is set or the registry isn't
  bootstrapped (legacy "generic unfiltered turn" + tests).
- `subagent_runner::run_subagent` → wraps the fork-mode / typed-mode
  dispatch in `with_current_sandbox_mode(definition.sandbox_mode, …)`
  so sub-agents carry their own sandbox mode rather than inheriting
  the parent's.

Task-local scope semantics (`tokio::task_local!`) means the mode
propagates across every `.await` boundary inside the wrapped future,
including `tool.execute` at `tool_loop.rs:545` and
`subagent_runner/ops.rs:1025` — no per-call-site wrapping needed.
Core behaviour change for tinyhumansai#685 — layers an agent-level strict
read-only check on top of the existing per-user scope preference so
an agent declaring `sandbox_mode = "read_only"` in its `agent.toml`
can never mutate user state via the composio surface, regardless of
what the user's scope pref allows.

Touches `src/openhuman/composio/tools.rs`:

- new `resolve_action_scope(slug)` → prefers the toolkit's curated
  catalog (`find_curated`), falls back to the existing
  `classify_unknown` heuristic, defaults unparseable slugs to
  `Write` so unknown slugs fail-closed under the gate.
- `ComposioExecuteTool::execute` → reads `current_sandbox_mode()`
  (task-local from the previous commit); when it's
  `SandboxMode::ReadOnly` and the resolved scope is `Write` or
  `Admin`, returns a `ToolResult::error` explaining the refusal and
  emits a `tracing::info!` audit line. `SandboxMode::None` /
  `Sandboxed` and the `None` task-local value (direct CLI /
  JSON-RPC / tests) pass through unchanged — strictly additive.
- ordering → sandbox gate runs *before* `evaluate_tool_visibility`
  so a read-only agent hits the cheapest, most definitive rejection
  first without waking up the user-scope lookup / curated-catalog
  path.

Five unit tests cover: `ReadOnly + Write` slug → blocked,
`ReadOnly + Admin` slug → blocked, `ReadOnly + Read` slug → passes
through to downstream gates, task-local unset → gate is a no-op
(back-compat), and `Sandboxed` → not read-only, no gate trigger.
Now that the sandbox gate (previous commit) refuses Write/Admin
actions when the calling agent is `sandbox_mode = "read_only"`, the
planner can safely carry the composio meta-tools on its whitelist —
the agent-level gate guarantees the planner cannot send mail / create
pages / mutate state via the composio surface, matching the issue's
"strict read-only mode" requirement (tinyhumansai#685).

Touches `src/openhuman/agent/agents/planner/agent.toml`:

- `[tools] named` → add `composio_list_toolkits`,
  `composio_list_connections`, `composio_list_tools`, and
  `composio_execute` alongside the existing
  `file_read`/`memory_recall`/`web_search_tool` trio.
- explanatory comment → documents the gate relationship so a future
  reader sees why `composio_execute` is on a read-only agent and
  understands the enforcement lives in `composio/tools.rs` rather
  than here.

`agent.toml` is the only planner-facing change required; the gate in
`composio_execute::execute` keys on `SandboxMode::ReadOnly` and
applies uniformly to every `ReadOnly`-sandbox agent, so the planner
is the first consumer and the other read-only agents (welcome,
critic, morning_briefing, trigger_triage) inherit the same behaviour
automatically if they ever pick up composio.
Addresses CodeRabbit feedback on tinyhumansai#904. The dispatcher path
(`ComposioExecuteTool::execute`) gates Write/Admin slugs when the
calling agent is `sandbox_mode = "read_only"`, but
`ComposioActionTool::execute` — the per-action surface spawned
dynamically under `integrations_agent` — called
`client.execute_tool` directly, bypassing the gate. Safe today
because `integrations_agent` is `sandbox_mode = "none"` and no
read-only agent spawns per-action tools, but the invariant
"read-only blocks all Composio writes" becomes implicit rather
than enforced. This mirrors the gate on the per-action path so both
routes to the backend honour the same rule.

Touches `src/openhuman/composio/tools.rs` +
`src/openhuman/composio/action_tool.rs`:

- `resolve_action_scope` → `pub(super)` so sibling modules reuse
  the same scope resolution (curated catalog + `classify_unknown`
  fallback).
- `ComposioActionTool::execute` → new gate block at the top that
  reads `current_sandbox_mode()`, resolves the action's scope, and
  returns a `ToolResult::error` + `tracing::info!` audit line when
  the mode is `ReadOnly` and the scope is `Write` / `Admin`.
  `SandboxMode::None` / `Sandboxed` and the unset task-local value
  pass through unchanged.

Three new tests mirror the existing gate tests on
`ComposioExecuteTool`: `ReadOnly + Write slug → blocked`,
`ReadOnly + Admin slug → blocked`, `unset → gate is a no-op`.
Make the planner's read-only posture explicit at the agent-definition
layer so a future TOML edit that drops `sandbox_mode` cannot silently
turn the planner into a write-capable agent.

`src/openhuman/agent/agents/loader.rs`:

- Add `planner_is_read_only_with_composio_meta_tools` test alongside
  the existing `critic_is_read_only` / `code_executor_is_sandboxed_*`
  pattern. Asserts:
  - `def.sandbox_mode == SandboxMode::ReadOnly` — the runtime hook the
    `ComposioExecuteTool::execute` and `ComposioActionTool::execute`
    gates key on. Without this, both gates' `current_sandbox_mode()`
    check would return `None` and Write/Admin actions would slip
    through.
  - `def.tools` is `ToolScope::Named` and contains all four composio
    meta-tools (`composio_list_toolkits`, `composio_list_connections`,
    `composio_list_tools`, `composio_execute`) so the planner can
    discover + execute Read-scoped actions while the gate filters out
    everything mutating.
- Test docstring spells out the failure mode (planner becomes
  write-capable) so a maintainer reading the test in isolation knows
  why each assertion is load-bearing.
@obchain obchain force-pushed the feat/planner-composio-readonly-gate branch from 968696d to 13e842a Compare April 26, 2026 13:00
@obchain
Copy link
Copy Markdown
Contributor Author

obchain commented Apr 26, 2026

Rebased clean. Planner is sandboxed — sandbox_mode = "read_only" is set at agent.toml line 7. I added a loader test so it's visible at the parse layer. Take another pass when free.

@obchain obchain requested a review from senamakel April 26, 2026 13:05
@senamakel
Copy link
Copy Markdown
Member

@coderabbitai could you verify that the planner agent is the only one that runs in composeio tools in sandbox mode and point out the code for me that does that?

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 26, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== All agent.toml files ==="
fd 'agent.toml' --type f

echo ""
echo "=== sandbox_mode occurrences in agent definitions ==="
rg -n 'sandbox_mode' --type toml

echo ""
echo "=== composio_execute in agent definitions ==="
rg -n 'composio_execute' --type toml

echo ""
echo "=== bus.rs sandbox_mode wiring ==="
rg -n 'sandbox_mode\|with_current_sandbox_mode\|SandboxMode' src/openhuman/agent/bus.rs

echo ""
echo "=== subagent_runner ops.rs sandbox_mode wiring ==="
rg -n 'sandbox_mode\|with_current_sandbox_mode\|SandboxMode' src/openhuman/agent/harness/subagent_runner/ops.rs

echo ""
echo "=== AgentDefinition sandbox_mode field ==="
rg -n 'sandbox_mode' src/openhuman/agent/

echo ""
echo "=== planner agent.toml full content ==="
cat $(fd 'agent.toml' --type f | grep planner)

echo ""
echo "=== All agent.toml full contents ==="
for f in $(fd 'agent.toml' --type f); do echo "--- FILE: $f ---"; cat "$f"; echo ""; done

Length of output: 29507


@senamakel Here's what the codebase shows — the planner is not the only agent running Composio tools under read_only sandbox mode. There are two agents with composio_execute access under read_only, and a third that is intentionally ungated:


Agents with composio_execute access

Agent sandbox_mode How composio_execute is included Gate active?
planner read_only named = [...] explicit ✅ Write/Admin blocked
integrations_agent none named = [...] explicit ❌ ungated (by design — this is the actual execution agent)
morning_briefing read_only wildcard = {} (picks up all tools) ✅ Write/Admin blocked

morning_briefing (src/openhuman/agent/agents/morning_briefing/agent.toml) uses wildcard = {} with sandbox_mode = "read_only", so it also runs composio_execute under the sandbox gate.


The enforcement chain (three pieces of code)

1. Per-agent declarationsandbox_mode is set in each agent.toml:

  • src/openhuman/agent/agents/planner/agent.toml Line 7: sandbox_mode = "read_only"
  • src/openhuman/agent/agents/morning_briefing/agent.toml Line 6: sandbox_mode = "read_only"

2. Task-local wiring — the declared mode is installed around every tool-call loop in two places:

  • src/openhuman/agent/harness/subagent_runner/ops.rs Line 74 (subagent path):

    let outcome = with_current_sandbox_mode(definition.sandbox_mode, async {
        // fork/typed dispatch runs here
    })
  • src/openhuman/agent/bus.rs Lines 182–188 (bus/turn path):

    let sandbox_mode = target_agent_id
        ...
        .map(|def| def.sandbox_mode)
        ...;
    let text = with_current_sandbox_mode(sandbox_mode, async {
        run_tool_call_loop(...)
    })

3. The gate itselfsrc/openhuman/composio/tools.rs in ComposioExecuteTool::execute: reads current_sandbox_mode() from the task-local; if it is ReadOnly, it resolves the action slug to a ToolScope (curated catalog → classify_unknown fallback, defaulting unknown slugs to Write) and returns ToolResult::error for Write or Admin scopes before any backend dispatch occurs.


Summary

The read-only Composio gate applies to any agent whose agent.toml declares sandbox_mode = "read_only" — currently planner (explicit tools) and morning_briefing (via wildcard). integrations_agent deliberately runs without a sandbox since it is the designated write-execution agent downstream. The gate is not planner-specific; it's a general mechanism keyed off the task-local populated from whatever agent definition is active at runtime.


🧠 Learnings used
Learnt from: sanil-23
Repo: tinyhumansai/openhuman PR: 437
File: src/openhuman/subconscious/executor.rs:33-50
Timestamp: 2026-04-08T16:46:58.073Z
Learning: In `src/openhuman/subconscious/executor.rs`, `needs_tools(title: &str)` is a UX routing convenience only — it selects the execution path (local model vs. agentic-v1 full vs. analysis-only) based on task title heuristics. Write-action prevention is enforced at a deeper layer: (1) the analysis-only prompt sent to agentic-v1 explicitly instructs it not to execute write actions for read-only tasks, and (2) tool execution is controlled by the agent runtime. Do not flag `needs_tools()` as an insufficient write-authorization boundary.

@obchain
Copy link
Copy Markdown
Contributor Author

obchain commented Apr 26, 2026

Matches what i see. one thing coderabbit missed welcome is also read_only, but only has composio_authorize, no execute, so gate doesn't fire there. Real set is planner + morning_briefing. Gate's keyed off def.sandbox_mode, not hardcoded — any new read_only agent picks it up.

@senamakel senamakel merged commit 74074cd into tinyhumansai:main Apr 29, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow the planner agent to have compose tools with read access only.

2 participants