use super::*;
pub(super) struct ChildRunSetup<'a> {
pub(super) id: &'a str,
pub(super) task: &'a SubagentTask,
pub(super) cwd: &'a Path,
pub(super) config: &'a SubagentRunConfig,
pub(super) task_activity_id: &'a ActivityId,
pub(super) batch_id: &'a ActivityId,
pub(super) finisher: &'a TaskActivityFinisher,
pub(super) reporter: Option<&'a SchedulerTaskReporter>,
pub(super) cancellation: &'a AgentCancellation,
}
pub(super) struct PreparedChildRun {
pub(super) run: ChildAgentRun,
pub(super) request_agent: AgentSession,
pub(super) session: Option<Session>,
pub(super) tools: ToolRuntime,
pub(super) hooks: Option<HookRuntime>,
}
pub(super) struct ChildSetupFailure {
pub(super) error: String,
pub(super) session: Option<Session>,
}
impl ChildRunSetup<'_> {
pub(super) fn prepare(&self) -> Result<PreparedChildRun, ChildSetupFailure> {
let config = self.config;
let run = child_agent_for_task(self.task, self.cwd, config).map_err(|error| {
ChildSetupFailure {
error: error.to_string(),
session: None,
}
})?;
self.finisher.try_enrich(config, || ActivityEvent::Started {
id: self.task_activity_id.clone(),
parent_id: Some(self.batch_id.clone()),
kind: ActivityKind::SubagentTask,
status: ActivityStatus::Running,
metadata: subagent_task_activity_metadata(
self.id,
self.task,
config.depth + 1,
Some(&run),
),
});
let session = self.open_linked_session()?;
if let (Some(reporter), Some(session)) = (self.reporter, &session) {
reporter.session(session.id().to_string(), session.path().to_path_buf());
reporter.progress();
}
let (tools, request_agent) = match self.child_tools(&run, session.as_ref()) {
Ok(tools) => tools,
Err(error) => {
return Err(ChildSetupFailure {
error: error.to_string(),
session,
});
}
};
let hooks = match config.inherited_hooks.as_ref() {
Some(hooks) => match hooks.clone_for_cwd(self.cwd) {
Ok(hooks) => Some(hooks),
Err(error) => {
return Err(ChildSetupFailure {
error: format!("subagent inherited hook setup failed: {error}"),
session,
});
}
},
None => None,
};
Ok(PreparedChildRun {
run,
request_agent,
session,
tools,
hooks,
})
}
fn open_linked_session(&self) -> Result<Option<Session>, ChildSetupFailure> {
let config = self.config;
let session = match config.sessions_root.as_ref() {
Some(root) => Some(
SessionManager::new(root.join("subagents"))
.create()
.and_then(Session::activate)
.map_err(|error| ChildSetupFailure {
error: format!("subagent session creation failed: {error}"),
session: None,
})?,
),
None => None,
};
if let (Some(root), Some(parent_id), Some(child)) =
(&config.sessions_root, &config.parent_session_id, &session)
{
let parent_root = if config.depth == 0 {
root.clone()
} else {
root.join("subagents")
};
let linked = SessionManager::new(parent_root)
.open_existing(parent_id.clone())
.and_then(|parent| {
crate::sessions::record_session_event(
Some(&parent),
&config.parent_cwd,
SessionEventKind::SubagentSession,
json!({"session_id": child.id()}),
)
});
if let Err(error) = linked {
return Err(ChildSetupFailure {
error: format!("subagent session linkage failed: {error}"),
session,
});
}
}
Ok(session)
}
fn child_tools(
&self,
run: &ChildAgentRun,
session: Option<&Session>,
) -> anyhow::Result<(ToolRuntime, AgentSession)> {
let (task, cwd, config) = (self.task, self.cwd, self.config);
let profile_disabled_tools = task
.identity
.as_deref()
.and_then(|identity| config.profiles.get(identity))
.map(|profile| &profile.disabled_tools)
.filter(|disabled_tools| !disabled_tools.is_empty());
let mut tools = match profile_disabled_tools {
Some(disabled_tools) => config
.parent_tools
.clone_for_cwd_with_subagent_depth_and_additional_disabled_tools(
cwd,
config.depth + 1,
disabled_tools,
),
None => config
.parent_tools
.clone_for_cwd_with_subagent_depth(cwd, config.depth + 1),
}?;
tools.restrict_to_inspection(task.mode == crate::subagents::SubagentMode::Inspect);
let scope = crate::sessions::task_scope::TaskScope::new(task, &tools)?;
crate::sessions::record_session_event(
session,
cwd,
SessionEventKind::TaskScope,
serde_json::to_value(&scope)?,
)?;
let tools = tools.with_task_scope(scope)?;
let child_agent = run.agent.clone();
let request_agent = if tools.subagents_available_on_any_surface()
&& let Some(subagent_profiles_prompt) = &config.subagent_profiles_prompt
{
child_agent.with_appended_system_prompt(subagent_profiles_prompt)
} else {
child_agent.clone()
};
let child_config = SubagentRunConfig {
parent_agent: child_agent,
provider: Arc::clone(&run.provider),
provider_override: config.provider_override.clone(),
parent_tools: tools.clone(),
parent_cwd: cwd.to_path_buf(),
cancellation: self.cancellation.clone(),
subagent_controls: config.subagent_controls.clone(),
profiles: config.profiles.clone(),
subagent_profiles_prompt: if tools.subagents_available_on_any_surface() {
config.subagent_profiles_prompt.clone()
} else {
None
},
sessions_root: config.sessions_root.clone(),
parent_session_id: None,
depth: config.depth + 1,
parent_activity_id: Some(self.task_activity_id.clone()),
activity_sender: config.activity_sender.clone(),
inherited_hooks: config.inherited_hooks.clone(),
semantic_progress_timeout: config.semantic_progress_timeout,
schema_validation_max_retries: config.schema_validation_max_retries,
compaction: run.compaction.clone(),
};
let tools = tools.with_subagents(move |arguments, context| {
let mut config = child_config.clone();
config.parent_session_id = context.hook_context.session_id;
config.parent_activity_id = context.parent_activity_id.map(|id| {
match child_config.parent_activity_id.as_ref() {
Some(parent) if id != *parent && !id.is_path_descendant_of(parent) => {
parent.child(id.as_str())
}
_ => id,
}
});
config.activity_sender = context.activity_sender;
dispatch_subagents(arguments, config)
});
Ok((tools, request_agent))
}
}