Skip to content

feat: parallel sub-agent execution + read-only classifier hardening - #429

Merged
jexShain merged 3 commits into
AI-Shell-Team:mainfrom
jexShain:feat/parallel-sub-agents
Aug 5, 2026
Merged

feat: parallel sub-agent execution + read-only classifier hardening#429
jexShain merged 3 commits into
AI-Shell-Team:mainfrom
jexShain:feat/parallel-sub-agents

Conversation

@jexShain

@jexShain jexShain commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Problem: (1) Independent Agent (sub-agent) calls run sequentially, so multi-area requests take the sum of every sub-agent; a fast-finishing sibling also reset the shell's sub-agent flag prematurely. (2) The read-only bash classifier over-blocks harmless inspection (ls 2>&1, ls *.txt, find -exec grep) and let a real write (echo x >& /tmp/f) through as read-only.
  • Changes:
    • run_tool_calls dispatches all-Agent batches (len>1) concurrently via join_all; other batches stay sequential (preserve stop-on-first-short-circuit).
    • Each sub-agent run wrapped in catch_unwind → panic degrades to ToolResult::error, not an unwind through siblings.
    • Shell sub-agent flag: AtomicBoolAtomicU32 active count (saturating decrement; ignore sub-agent OpEnd).
    • ApprovalMemoryparking_lot::Mutex (no poisoning) so an unwound sub-agent can't cascade a .lock().unwrap() panic.
    • read-only: >&N/>&- read-only; >&word (non-digit) correctly a write; unquoted globs allowed; find -exec <cmd> recursively classified.
    • Review fix: short-circuit / failure-threshold now appends a tool_result for every assistant tool_call (the short-circuiting call + backfilled siblings), so the persisted history never carries a dangling tool_call_id that providers reject next turn.
  • Related Issue: closes [Feature]: execute independent Agent (sub-agent) calls in parallel #427, closes [Feature]: harden read-only bash classifier (fd redirects, globs, find -exec) #428

Change Type

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Other

Scope

  • Core shell / PTY
  • AI agent / LLM
  • Skills / Tools
  • Security
  • Configuration
  • CLI / Interface
  • Packaging / Installation
  • CI/CD
  • Documentation

User-visible Changes

  • Independent sub-agent requests finish in roughly the max of their runtimes instead of the sum; the spinner stays active while any sibling runs.
  • Read-only sandbox no longer blocks 2>&1, unquoted globs, or find -exec grep/wc/head; find -exec rm and >&file writes stay blocked.
  • After a cancelled or security-blocked tool in a multi-call batch, the next AI turn no longer fails with a provider "missing tool_result" error.

Compatibility

  • Backward compatible? Yes
  • Config changes? No

Testing

  • cargo clippy -p aish-llm -p aish-tools -p aish-shell -- -D warnings — clean
  • cargo test --workspace — all green (CI: 1823 passed / 0 failed on Rust 1.95.0)
  • New tests: parallel result-ordering; concurrency probe (peak ≥ 2 real overlap); parallel_short_circuit_pairs_every_tool_call (every tool_call_id matched after a short-circuit); read-only redirect/glob/find-exec regressions.

Checklist

  • Code follows project style
  • Tests added if needed
  • Documentation updated if needed

Summary by CodeRabbit

  • New Features

    • Independent sub-agent tasks can now run concurrently while preserving result order.
    • Improved guidance helps select appropriate sub-agents and parallelize independent work.
    • Read-only shell commands now support common file-descriptor redirects and unquoted glob patterns.
  • Bug Fixes

    • Sub-agent failures caused by panics are reported as actionable errors instead of terminating execution.
    • Shell safety checks more accurately handle find actions and redirect operators.
    • Sub-agent activity indicators now remain accurate during nested operations.
    • Tool results remain complete and correctly paired when operations stop early.

- fd redirects: treat `>&N`/`>&-` as read-only fd duplication, but
  `>&word` (non-digit, e.g. `echo x >& /tmp/f`) as a write redirect.
  Same distinction applied in split_compound_segments and
  has_background_operator so `2>&1` is not split or misread as a
  background job.
- globs: allow unquoted globs; writes (`rm *.txt`, `cp *.x /d`) are
  still caught by the blocked-command list and `echo *.x > f` by the
  write-redirect check.
- find -exec: recursively classify the embedded command so
  `find -exec grep {} \;` passes and `find -exec rm {} \;` is blocked;
  `-delete` stays destructive. Handles `\;` and `+` terminators.

Closes AI-Shell-Team#428
When a tool-call batch is entirely `Agent` calls and len > 1, dispatch
them concurrently via futures::join_all; all other batches stay
sequential to preserve stop-on-first-short-circuit semantics. Each
sub-agent is an isolated SubSession (own history, tools, cancellation,
counters) so there is no shared mutable parent state under the
concurrent execute_tool(&self) calls.

- session.rs: extract process_tool_call_result (shared by both loops);
  add run_tool_calls with the parallel/sequential branch.
- agent_tool.rs: wrap each sub-agent run in catch_unwind so a panicking
  sub-session degrades to ToolResult::error instead of unwinding
  through join_all and aborting its siblings.
- app.rs: replace the AtomicBool sub-agent flag with an AtomicU32
  active count (saturating decrement; ignore sub-agent OpEnd) so the
  spinner survives a fast-finishing sibling.
- prompt.rs: steer the model to fan out multiple independent Agent
  calls in one response.
- ApprovalMemory migrated to parking_lot::Mutex (no poisoning) so an
  unwound sub-agent cannot poison a lock shared via the parent and
  cascade a panic.

Tests: join_all+zip preserves tool_call id order; concurrency probe
proves real overlap (peak >= 2).

Closes AI-Shell-Team#427
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Thanks for the pull request. A maintainer will review it when available.

Please keep the PR focused, explain the why in the description, and make sure local checks pass before requesting review.

Contribution guide: https://github.com/AI-Shell-Team/aish/blob/main/CONTRIBUTING.md

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Template check passed. Thanks for updating the pull request description.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: acd42bee-bab3-4e11-8e6b-3d91b1118064

📥 Commits

Reviewing files that changed from the base of the PR and between 64d6781 and 23d6357.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • crates/aish-llm/Cargo.toml
  • crates/aish-llm/src/session.rs
  • crates/aish-shell/src/app.rs
  • crates/aish-tools/src/agent_tool/agent_tool.rs
  • crates/aish-tools/src/agent_tool/prompt.rs
  • crates/aish-tools/src/bash/read_only.rs
🚧 Files skipped from review as they are similar to previous changes (6)
  • crates/aish-llm/Cargo.toml
  • crates/aish-tools/src/agent_tool/prompt.rs
  • crates/aish-tools/src/agent_tool/agent_tool.rs
  • crates/aish-tools/src/bash/read_only.rs
  • crates/aish-shell/src/app.rs
  • crates/aish-llm/src/session.rs

📝 Walkthrough

Walkthrough

The PR executes independent Agent calls concurrently, preserves result order, converts sub-agent panics into tool errors, tracks active sub-agents with an atomic counter, migrates approval memory to parking_lot::Mutex, and expands read-only Bash classification.

Changes

Concurrent Agent execution

Layer / File(s) Summary
Approval-memory synchronization
crates/aish-llm/Cargo.toml, crates/aish-llm/src/session.rs, crates/aish-shell/src/app.rs
Approval memory now uses parking_lot::Mutex across session and shell access, construction, and tests.
Concurrent Agent tool execution
crates/aish-llm/src/session.rs, crates/aish-tools/src/agent_tool/agent_tool.rs, crates/aish-tools/src/agent_tool/prompt.rs
Independent Agent calls use join_all. Shared result processing preserves order, handles failures and short-circuit backfilling, and converts panics into error results. Prompt guidance describes parallel and sequential task handling. Tests cover ordering, overlap, and backfilling.
Sub-agent activity tracking
crates/aish-shell/src/app.rs
The shell tracks active sub-agents with an atomic counter and updates parent-operation UI state from start and completion events.

Read-only Bash classification

Layer / File(s) Summary
Read-only Bash rule handling
crates/aish-tools/src/bash/read_only.rs
Descriptor duplication redirects and unquoted globs are accepted as read-only. File redirects, background operators, destructive find actions, and mutating embedded commands remain blocked. Tests cover these cases.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant LlmSession
  participant AgentTool
  participant SubSession
  participant Shell
  LlmSession->>AgentTool: dispatch independent Agent calls
  AgentTool->>SubSession: execute calls concurrently
  SubSession->>Shell: emit start and completion events
  SubSession-->>AgentTool: return results or panic errors
  AgentTool-->>LlmSession: return results
  LlmSession->>LlmSession: process results in original order
Loading

Possibly related PRs

Poem

A rabbit watched the agents race,
While ordered results stayed in place.
The spinner counted every hare,
And Bash checked redirects with care.
No panic broke the flow,
Said Bunny, twitching nose aglow.

🚥 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 two primary changes: parallel sub-agent execution and read-only classifier hardening.
Linked Issues check ✅ Passed The changes satisfy the requirements in issues #427 and #428, including parallel Agent calls, panic handling, active counts, result backfilling, and classifier updates.
Out of Scope Changes check ✅ Passed All reviewed changes support the linked issue objectives, including the explicitly required parking_lot migration and related tests.
Docstring Coverage ✅ Passed Docstring coverage is 93.75% which is sufficient. The required threshold is 80.00%.
✨ 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.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (2)
crates/aish-tools/src/agent_tool/agent_tool.rs (1)

259-266: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

These two assertions couple the test to example wording.

"scan" and "are healthy" come from two illustrative examples in USAGE_SECTION. A prompt rewrite that keeps the parallel guidance but rephrases the examples breaks this test without any behavior change. The first four assertions already cover the contract the test describes.

♻️ Proposed fix to drop the wording-coupled assertions
-        assert!(
-            desc.contains("scan"),
-            "parallel guidance must cover file-scan scenarios"
-        );
-        assert!(
-            desc.contains("are healthy"),
-            "parallel guidance must cover multi-service diagnosis scenarios"
-        );
🤖 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 `@crates/aish-tools/src/agent_tool/agent_tool.rs` around lines 259 - 266,
Remove the two wording-specific assertions checking desc.contains("scan") and
desc.contains("are healthy") from the relevant test in agent_tool.rs. Keep the
first four assertions unchanged, so the test validates the parallel-guidance
contract without depending on illustrative example text in USAGE_SECTION.
crates/aish-llm/src/session.rs (1)

1496-1504: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Bound the Agent sub-agent fan-out.

run_tool_calls starts every Agent call in the batch at once with join_all. The Agent prompt encourages emitting multiple Agent calls, and each sub-agent runs its own LLM loop, so a large batch can multiply concurrent provider requests and hit rate limits.

Use a small cap, such as futures::stream::iter(...).buffered(N).collect().await, so the ordered results still work with the existing zip.

🤖 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 `@crates/aish-llm/src/session.rs` around lines 1496 - 1504, Bound concurrency
in the parallel branch of run_tool_calls when executing multiple Agent tool
calls: replace the unbounded futures::future::join_all over
self.execute_tool(tc) with an ordered buffered stream using a small concurrency
limit. Preserve result ordering and the existing results-to-tool-calls zip
behavior.
🤖 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 `@crates/aish-llm/src/session.rs`:
- Around line 1406-1434: Update the short-circuit branch in
process_tool_call_result to append a ChatMessage::tool_result for the current
call and every remaining parallel tool call before returning. Ensure
new_messages contains a matching tool_result for each assistant tool_call,
including sibling calls that completed before the short circuit, while
preserving the existing cancellation text behavior.

---

Nitpick comments:
In `@crates/aish-llm/src/session.rs`:
- Around line 1496-1504: Bound concurrency in the parallel branch of
run_tool_calls when executing multiple Agent tool calls: replace the unbounded
futures::future::join_all over self.execute_tool(tc) with an ordered buffered
stream using a small concurrency limit. Preserve result ordering and the
existing results-to-tool-calls zip behavior.

In `@crates/aish-tools/src/agent_tool/agent_tool.rs`:
- Around line 259-266: Remove the two wording-specific assertions checking
desc.contains("scan") and desc.contains("are healthy") from the relevant test in
agent_tool.rs. Keep the first four assertions unchanged, so the test validates
the parallel-guidance contract without depending on illustrative example text in
USAGE_SECTION.
🪄 Autofix

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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8cdca27c-170e-467f-825b-16430efc0bca

📥 Commits

Reviewing files that changed from the base of the PR and between 64d6781 and 4ca691c.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • crates/aish-llm/Cargo.toml
  • crates/aish-llm/src/session.rs
  • crates/aish-shell/src/app.rs
  • crates/aish-tools/src/agent_tool/agent_tool.rs
  • crates/aish-tools/src/agent_tool/prompt.rs
  • crates/aish-tools/src/bash/read_only.rs

Comment thread crates/aish-llm/src/session.rs
jexShain added a commit to jexShain/aish that referenced this pull request Aug 5, 2026
A short-circuit / failure-threshold return from the tool loop left the
remaining assistant tool_calls without a matching tool_result. The shell
persists new_messages verbatim, so the next turn would send the provider
an assistant message with dangling tool_call_ids (rejected by OpenAI /
Anthropic, which require a tool_result for every tool_call).

- process_tool_call_result: append the short-circuiting call's own
  tool_result before returning (previously skipped).
- run_tool_calls: on early return, backfill tool_results for every
  remaining call — the already-computed result in the parallel branch,
  a synthetic "[skipped ...]" in the sequential branch (those calls
  never executed).
- Add regression test: a parallel Agent batch whose first call
  short-circuits must still produce a tool_result for every tool_call_id.

Also drop two wording-coupled assertions in the Agent prompt test
(scan / are healthy) that broke on example rewording without any
behavior change; the remaining four still cover the parallelism contract.

Addresses CodeRabbit review on AI-Shell-Team#429.
A short-circuit / failure-threshold return from the tool loop left the
remaining assistant tool_calls without a matching tool_result. The shell
persists new_messages verbatim, so the next turn would send the provider
an assistant message with dangling tool_call_ids (rejected by OpenAI /
Anthropic, which require a tool_result for every tool_call).

- process_tool_call_result: append the short-circuiting call's own
  tool_result before returning (previously skipped).
- run_tool_calls: on early return, backfill tool_results for every
  remaining call — the already-computed result in the parallel branch,
  a synthetic "[skipped ...]" in the sequential branch (those calls
  never executed).
- Add regression test: a parallel Agent batch whose first call
  short-circuits must still produce a tool_result for every tool_call_id.

Also drop two wording-coupled assertions in the Agent prompt test
(scan / are healthy) that broke on example rewording without any
behavior change; the remaining four still cover the parallelism contract.

Addresses CodeRabbit review on AI-Shell-Team#429.
@jexShain
jexShain force-pushed the feat/parallel-sub-agents branch from 6cd9e09 to 23d6357 Compare August 5, 2026 05:35
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@jexShain
jexShain merged commit 6efa14f into AI-Shell-Team:main Aug 5, 2026
8 checks passed
@jexShain
jexShain deleted the feat/parallel-sub-agents branch August 5, 2026 05:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: harden read-only bash classifier (fd redirects, globs, find -exec) [Feature]: execute independent Agent (sub-agent) calls in parallel

1 participant