Skip to content

feat(agent): cap runtime subagent spawn depth at MAX_SPAWN_DEPTH=3#2234

Merged
senamakel merged 3 commits into
tinyhumansai:mainfrom
srikaanthh:feat/agent-spawn-depth-gate
May 19, 2026
Merged

feat(agent): cap runtime subagent spawn depth at MAX_SPAWN_DEPTH=3#2234
senamakel merged 3 commits into
tinyhumansai:mainfrom
srikaanthh:feat/agent-spawn-depth-gate

Conversation

@srikaanthh
Copy link
Copy Markdown
Contributor

@srikaanthh srikaanthh commented May 19, 2026

Summary

  • Add a task-local MAX_SPAWN_DEPTH = 3 runtime guard around run_subagent so nested sub-agent delegation cannot recurse past the harness contract.
  • Introduce SubagentRunError::SpawnDepthExceeded { attempted_depth, max_depth } and a new spawn_depth_context module holding the task-local counter.
  • Update gitbooks/developing/architecture/agent-harness.md and harness_gap_tests.rs to flip the depth gate from "planned" to "live".
  • Add two tokio::test cases that pin the boundary: allow at MAX_SPAWN_DEPTH - 1, reject at MAX_SPAWN_DEPTH, and assert the task-local does not leak out of the scope.

Problem

The agent harness already validated the spawn hierarchy at loader time via validate_tier_hierarchy, but the matching runtime depth gate documented in gitbooks/developing/architecture/agent-harness.md was not implemented. harness_gap_tests.rs explicitly listed SpawnDepthExceeded as an "untestable because no underlying code exists" gap. A workspace TOML that drops the agent_tier annotation, or any tool / MCP surface that calls run_subagent recursively, could therefore recurse past the intended chat → reasoning → worker chain. The static tier check is insufficient on its own as a defence-in-depth layer.

Solution

  • New module src/openhuman/agent/harness/spawn_depth_context.rs exposes MAX_SPAWN_DEPTH = 3, a CURRENT_SPAWN_DEPTH tokio::task_local!, current_spawn_depth(), and with_spawn_depth(depth, future). The module follows the existing sandbox_context pattern so the additive surface is consistent with the rest of the harness.
  • run_subagent reads current_spawn_depth() before any provider work, computes attempted_depth = current + 1, and returns the new SpawnDepthExceeded variant early if it exceeds MAX_SPAWN_DEPTH. On the allowed path it wraps the inner with_current_sandbox_mode(...) future inside with_spawn_depth(attempted_depth, ...) so the counter naturally increments for every nested run_subagent call inside the same tokio task and unwinds when the scope ends.
  • Tracing on the dispatch and completion log lines now includes spawn_depth so production traces can confirm the gate is engaged.
  • Docs and gap-tests note: agent-harness.md flips the status from "planned" to "live"; harness_gap_tests.rs removes the "no underlying code" entry and points to the new coverage in ops_tests.rs.

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) per Testing Strategyrunner_allows_spawn_at_max_depth (happy boundary) and runner_rejects_spawn_beyond_max_depth (failure boundary) in src/openhuman/agent/harness/subagent_runner/ops_tests.rs, plus three task-local unit tests in spawn_depth_context.rs.
  • Diff coverage ≥ 80% — N/A locally: cargo test could not run in this environment because whisper-rs-sys requires cmake, which is not installed on the local machine. The added lines are exercised by the new tests; please rely on CI cargo-llvm-cov for the official diff-cover gate.
  • Coverage matrix updated — N/A: behaviour-only change inside an existing feature (agent harness spawn safety), no new top-level matrix row added.
  • All affected feature IDs from the matrix are listed in the PR description under ## Related — N/A: no matrix IDs touched.
  • No new external network dependencies introduced (mock backend used per Testing Strategy) — change is core-only and uses the existing in-tree ScriptedProvider test double.
  • Manual smoke checklist updated if this touches release-cut surfaces — N/A: release manual smoke does not exercise sub-agent recursion limits.
  • Linked issue closed via Closes #NNN in the ## Related section — N/A: no tracked issue, opening as a follow-up to the documented gap in harness_gap_tests.rs.

Impact

  • Runtime/platform impact: desktop (in-process core). No frontend or Tauri shell surface changes.
  • Performance: one task-local read per run_subagent call plus a scoped task_local! setter; negligible compared with provider/tool work.
  • Security / safety: defence-in-depth against runaway sub-agent recursion that the loader-time tier check cannot see (user TOMLs without agent_tier, tools / MCP that call run_subagent directly).
  • Migration / compatibility: error taxonomy gains one variant on an enum already annotated #[derive(Error)]. Existing matchers default-branch on it like any other SubagentRunError. No public function signatures changed.

Related

  • Closes:
  • Follow-up PR(s)/TODOs: none.

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

Linear Issue

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

Commit & Branch

  • Branch: feat/agent-spawn-depth-gate
  • Commit SHA: c25d2b9

Validation Run

  • pnpm --filter openhuman-app format:check — N/A: Rust-only change.
  • pnpm typecheck — N/A: Rust-only change.
  • Focused tests: attempted cargo test --manifest-path Cargo.toml runner_ — blocked, see "Validation Blocked".
  • Rust fmt/check (if changed): cargo fmt --manifest-path Cargo.toml -- --check passed; git diff --check clean.
  • Tauri fmt/check (if changed): N/A: Tauri shell untouched.

Validation Blocked

  • command: cargo test --manifest-path Cargo.toml runner_
  • error: whisper-rs-sys build script panicked with failed to execute command: No such file or directory (os error 2) — is cmake not installed?. cmake is not installed in this local environment.
  • impact: Did not run the focused Rust tests locally. CI will exercise them on its cargo-llvm-cov job; both new tests are deterministic and use the existing ScriptedProvider harness.

Behavior Changes

  • Intended behavior change: run_subagent now refuses to dispatch a sub-agent beyond MAX_SPAWN_DEPTH = 3 nested invocations within the same tokio task, returning SubagentRunError::SpawnDepthExceeded early.
  • User-visible effect: a runaway agent loop that would previously recurse past three hops now surfaces a typed error to the parent tool-result block instead. Normal chat → reasoning → worker chains are unchanged.

Parity Contract

  • Legacy behavior preserved: depth ≤ 3 keeps the same provider, tool filter, sandbox-mode, and progress-event behavior as before; the new wrapper is the outermost layer and only adds the task-local scope.
  • Guard/fallback/dispatch parity checks: existing with_current_sandbox_mode scope, sandbox mode propagation, max-iteration handling, and max_result_chars truncation are all preserved verbatim — the change only wraps that future in with_spawn_depth(...).

Duplicate / Superseded PR Handling

  • Duplicate PR(s): none.
  • Canonical PR: this PR.
  • Resolution (closed/superseded/updated): N/A.

Summary by CodeRabbit

  • New Features

    • Enforced runtime limit on sub-agent nesting (max depth = 3); further delegation is rejected with a clear error.
  • Documentation

    • Updated enforcement docs to reflect that runtime depth bounding and loader-time tier checks are active.
  • Tests

    • Added tests verifying allowed and rejected spawn attempts, proper error reporting, and isolation of depth state after runs.

Review Change Stack

Add a task-local depth guard around run_subagent so runtime delegation cannot recurse beyond the documented harness limit.

Co-authored-by: Cursor <cursoragent@cursor.com>
@srikaanthh srikaanthh requested a review from a team May 19, 2026 17:55
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 19, 2026

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: cdf719e1-1831-4631-bb5d-0e1936d8d5a5

📥 Commits

Reviewing files that changed from the base of the PR and between e952153 and f0d3245.

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

📝 Walkthrough

Walkthrough

Adds a task-local spawn-depth tracker (MAX_SPAWN_DEPTH = 3), enforces it in run_subagent (returns SpawnDepthExceeded when exceeded), wraps execution in the scoped context, updates traces, and adds tests and docs reflecting runtime enforcement.

Changes

Spawn-Depth Enforcement for Sub-Agent Delegation

Layer / File(s) Summary
Task-Local Spawn Depth Tracking Mechanism
src/openhuman/agent/harness/spawn_depth_context.rs, src/openhuman/agent/harness/mod.rs
A new spawn_depth_context module introduces a task-local counter with MAX_SPAWN_DEPTH = 3, current_spawn_depth() to read the active depth, and with_spawn_depth(depth, future) to run an async future under a scoped depth value. Unit tests verify default behavior, scoping visibility, and nested scope restoration. The module is declared and re-exported at the harness level.
Spawn Depth Enforcement in run_subagent
src/openhuman/agent/harness/subagent_runner/types.rs, src/openhuman/agent/harness/subagent_runner/ops.rs
A new SpawnDepthExceeded error variant reports attempted and maximum depths. run_subagent imports depth utilities, computes attempted_depth, checks it against the limit, and returns an error on violation. The sandboxed execution is wrapped with with_spawn_depth(attempted_depth, ...) to maintain the context during typed-mode operation, and completion logging records the enforced depth.
Spawn Depth Enforcement Tests
src/openhuman/agent/harness/subagent_runner/ops_tests.rs
Two tests validate enforcement: one confirms successful spawning at MAX_SPAWN_DEPTH - 1 with correct task-local reset, and another confirms rejection at MAX_SPAWN_DEPTH with correct error values and no provider dispatch.
Documentation Updates
gitbooks/developing/architecture/agent-harness.md, src/openhuman/agent/harness/harness_gap_tests.rs
Architecture documentation now describes the runtime depth gate as live and implemented using a task-local counter. Gap-test documentation reclassifies spawn-depth enforcement as covered in subagent_runner/ops_tests.rs.

Sequence Diagram(s)

sequenceDiagram
  participant RunSubagent
  participant DepthAPI as current_spawn_depth / MAX_SPAWN_DEPTH
  participant Checker as Depth Check
  participant Context as with_spawn_depth
  participant TypedMode as run_typed_mode
  participant Provider as ToolProvider
  participant Error as SpawnDepthExceeded

  RunSubagent->>DepthAPI: read current depth
  RunSubagent->>Checker: compute attempted_depth = current + 1
  Checker->>Checker: compare attempted_depth to MAX_SPAWN_DEPTH
  alt depth exceeded
    Checker->>Error: return SpawnDepthExceeded
    Error-->>RunSubagent: error
  else within limit
    RunSubagent->>Context: with_spawn_depth(attempted_depth, ...)
    Context->>TypedMode: run_typed_mode (sandboxed)
    TypedMode->>Provider: dispatch provider call
    Provider-->>TypedMode: result
    TypedMode-->>Context: complete
    Context-->>RunSubagent: depth restored
  end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Suggested reviewers

  • senamakel

Poem

🐰 Depth bounds now locked in place,
Three layers deep—no deeper race,
Task-locals guard the nesting tower,
With scoped runs kept safe by power,
Tests hop in to prove the flower.

🚥 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 accurately summarizes the primary change: implementing a runtime guard that caps subagent spawn depth at MAX_SPAWN_DEPTH=3, which is the core feature introduced across all modified files.
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.


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.

@coderabbitai coderabbitai Bot added the working A PR that is being worked on by the team. label May 19, 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: 1

🤖 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/harness/subagent_runner/ops.rs`:
- Around line 232-234: The code computes attempted_depth using plain addition
(current_spawn_depth() + 1) which can wrap on usize overflow and bypass the
MAX_SPAWN_DEPTH guard; change construction of attempted_depth to use saturating
arithmetic (e.g., current_spawn_depth().saturating_add(1)) so it never wraps and
the MAX_SPAWN_DEPTH check in the spawn path remains effective (alternatively use
checked_add and handle None), updating the binding named attempted_depth and any
uses of current_spawn_depth() accordingly.
🪄 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: 55b6fb7f-9815-448b-9249-6ae95f95316e

📥 Commits

Reviewing files that changed from the base of the PR and between 049d1ea and c25d2b9.

📒 Files selected for processing (7)
  • gitbooks/developing/architecture/agent-harness.md
  • src/openhuman/agent/harness/harness_gap_tests.rs
  • src/openhuman/agent/harness/mod.rs
  • src/openhuman/agent/harness/spawn_depth_context.rs
  • src/openhuman/agent/harness/subagent_runner/ops.rs
  • src/openhuman/agent/harness/subagent_runner/ops_tests.rs
  • src/openhuman/agent/harness/subagent_runner/types.rs

Comment thread src/openhuman/agent/harness/subagent_runner/ops.rs
Prevents usize wrap-around that could bypass MAX_SPAWN_DEPTH guard
in release builds. Addresses CodeRabbit review on PR tinyhumansai#2234.
coderabbitai[bot]
coderabbitai Bot previously approved these changes May 19, 2026
The new with_spawn_depth task-local scope adds a second stacked
task_local wrapper around run_typed_mode. Under cargo-llvm-cov
instrumentation, the combined future state machine overflows tokio's
default 2 MiB per-thread test stack in
turn_dispatches_spawn_subagent_through_full_path. Box::pin the inner
future so the large state machine lives on the heap.
Copy link
Copy Markdown
Contributor

@graycyrus graycyrus left a comment

Choose a reason for hiding this comment

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

Solid work — the spawn-depth gate is well-designed and follows the existing sandbox_context pattern cleanly.

Area Files Verdict
New module spawn_depth_context.rs Clean task-local implementation, good unwrap_or(0) default, scoped nesting works correctly
Runtime guard ops.rs Depth check before provider dispatch is correct; saturating_add overflow fix already landed
Error variant types.rs SpawnDepthExceeded fits naturally in the existing enum
Tests ops_tests.rs, spawn_depth_context.rs Boundary tests (allow at MAX-1, reject at MAX) plus task-local isolation — good coverage
Docs agent-harness.md Status flipped from planned to live, accurate
Gap tests harness_gap_tests.rs Gap entry correctly removed and pointer added

The Box::pin wrapper to prevent stack overflow under cargo-llvm-cov instrumentation is a pragmatic fix and benefits production too (deeply nested async state machines).

No findings beyond what CodeRabbit already caught (and which was addressed). Clean PR.

@senamakel senamakel merged commit 73ea22e into tinyhumansai:main May 19, 2026
24 checks passed
CodeGhost21 pushed a commit to CodeGhost21/openhuman that referenced this pull request May 22, 2026
AusAgentSmith pushed a commit to AusAgentSmith/openhuman that referenced this pull request May 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

working A PR that is being worked on by the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants