harness-tools + app: task tool, subagent spawner, board wiring (M4)
The `task` tool delegates to a SubagentSpawner (owned by the composition root): resolves the agent, enforces the depth limit, applies permission intersection, filters tools per-agent, and runs the child session foreground or background. Foreground returns the child's final text; background registers on the job board and detaches under the parent run token. The run loop injects the board into primary-agent requests and reconciles terminal jobs each step. Integration tests cover foreground run + alias reuse continuing the same child session, depth-limit and unknown-agent rejection, and board prompt injection. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -10,10 +10,13 @@ use crate::event::{AppEvent, EventBus};
|
||||
use crate::llm::{FinishReason, LlmEvent, LlmEventStream, ProviderError};
|
||||
use crate::permission::{PermissionService, Ruleset};
|
||||
use crate::store::Store;
|
||||
use crate::tool::{MetadataSink, PermissionHandle, Tool, ToolCtx, ToolError, ToolRegistry};
|
||||
use crate::tool::{
|
||||
MetadataSink, PermissionHandle, SubagentSpawner, Tool, ToolCtx, ToolError, ToolRegistry,
|
||||
};
|
||||
use crate::types::{Message, MessageId, Part, PartBody, PartId, SessionId, TokenUsage, ToolState};
|
||||
|
||||
use super::doomloop::DoomLoopGuard;
|
||||
use super::jobs::JobBoard;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum StepResult {
|
||||
@@ -47,6 +50,9 @@ pub struct StepContext {
|
||||
pub permissions: Arc<PermissionService>,
|
||||
pub static_rules: Ruleset,
|
||||
pub extra_rules: Arc<Mutex<Ruleset>>,
|
||||
/// Parent-effective ruleset for a subagent session; empty for a root session. Enables
|
||||
/// permission intersection on this session's tool calls.
|
||||
pub parent_rules: Ruleset,
|
||||
pub session_id: SessionId,
|
||||
pub cwd: PathBuf,
|
||||
/// Session's `tool-output` spill directory (see `tool::truncate`).
|
||||
@@ -54,6 +60,11 @@ pub struct StepContext {
|
||||
pub cancel: CancellationToken,
|
||||
/// Wall-clock for `created_at` stamps — passed in so tests stay deterministic.
|
||||
pub now: i64,
|
||||
/// Lets the `task` tool spawn subagents. `None` disables delegation (headless/tests).
|
||||
pub spawner: Option<Arc<dyn SubagentSpawner>>,
|
||||
/// This session's background job board (as a parent). Injected into requests when the
|
||||
/// running agent can delegate; `None` disables the board.
|
||||
pub job_board: Option<Arc<JobBoard>>,
|
||||
}
|
||||
|
||||
struct FlushTracker {
|
||||
@@ -483,7 +494,8 @@ impl<'a> Run<'a> {
|
||||
self.ctx.static_rules.clone(),
|
||||
self.ctx.extra_rules.clone(),
|
||||
call_cancel.clone(),
|
||||
);
|
||||
)
|
||||
.with_parent_rules(self.ctx.parent_rules.clone());
|
||||
let tool_ctx = ToolCtx {
|
||||
session_id: self.ctx.session_id.clone(),
|
||||
message_id: self.message_id(),
|
||||
@@ -493,6 +505,7 @@ impl<'a> Run<'a> {
|
||||
cancel: call_cancel.clone(),
|
||||
ask,
|
||||
metadata: metadata_sink,
|
||||
spawner: self.ctx.spawner.clone(),
|
||||
};
|
||||
|
||||
let result = tokio::select! {
|
||||
|
||||
Reference in New Issue
Block a user