feat: Added explicit guardrail handling for pending agents

This commit is contained in:
2026-06-11 20:20:14 -06:00
parent 6ebd32d47c
commit 00939e4634
7 changed files with 270 additions and 21 deletions
+30
View File
@@ -93,6 +93,36 @@ pub(in crate::config) const DEFAULT_SPAWN_INSTRUCTIONS: &str = indoc! {"
agent__collect --id agent_explore_e5f6g7h8
```
### CRITICAL: Never end your turn with pending agents
Spawned agents do NOT report back on their own. They run in the background until you
actively reclaim them with `agent__collect` (to get their output) or `agent__cancel`
(to discard them). If you spawn agents and then emit a final message without reclaiming
them, the system will detect the unreclaimed agents and reject the turn-end, injecting
a reminder forcing you to handle them. After several such reminders, the system will
auto-cancel them and warn you that work was lost.
The correct flow when you have nothing else to do:
```
# WRONG - do NOT do this:
agent__spawn --agent explore --prompt \"...\"
agent__spawn --agent explore --prompt \"...\"
# ... emit text like \"I will synthesize once they report back.\" and stop
# ^ The agents will be abandoned. Their output will be lost.
# RIGHT - always do this:
agent__spawn --agent explore --prompt \"...\"
agent__spawn --agent explore --prompt \"...\"
agent__collect --id <first_id> # blocks until done
agent__collect --id <second_id> # blocks until done
# ... NOW you can synthesize and end your turn
```
`agent__collect` is a **blocking wait**: it pauses your execution until the agent
completes, then returns the output as a tool result. Use it freely — it is the
correct primitive for \"I'm done with my own work and just need the agents' results\".
### Parallel Spawning (DEFAULT for multi-agent work)
When a task needs multiple agents, **spawn them all at once**, then collect:
+7 -1
View File
@@ -120,6 +120,7 @@ pub struct RequestContext {
pub escalation_queue: Option<Arc<EscalationQueue>>,
pub current_depth: usize,
pub auto_continue_count: usize,
pub pending_agents_guardrail_count: u32,
pub todo_list: TodoList,
pub skill_registry: SkillRegistry,
pub last_continuation_response: Option<String>,
@@ -149,6 +150,7 @@ impl RequestContext {
escalation_queue: None,
current_depth: 0,
auto_continue_count: 0,
pending_agents_guardrail_count: 0,
todo_list: TodoList::default(),
skill_registry: SkillRegistry::default(),
last_continuation_response: None,
@@ -204,6 +206,7 @@ impl RequestContext {
escalation_queue: None,
current_depth: 0,
auto_continue_count: 0,
pending_agents_guardrail_count: 0,
todo_list: TodoList::default(),
skill_registry: SkillRegistry::default(),
last_continuation_response: None,
@@ -246,6 +249,7 @@ impl RequestContext {
escalation_queue: self.escalation_queue.clone(),
current_depth: self.current_depth,
auto_continue_count: 0,
pending_agents_guardrail_count: 0,
todo_list: self.todo_list.clone(),
skill_registry: self.skill_registry.clone(),
last_continuation_response: None,
@@ -286,6 +290,7 @@ impl RequestContext {
escalation_queue: parent.escalation_queue.clone(),
current_depth,
auto_continue_count: 0,
pending_agents_guardrail_count: 0,
todo_list: TodoList::default(),
skill_registry: SkillRegistry::default(),
last_continuation_response: None,
@@ -2787,7 +2792,7 @@ impl RequestContext {
if self.agent.take().is_some() {
if let Some(supervisor) = self.supervisor.clone() {
supervisor.read().cancel_all();
supervisor.read().cancel_recursive();
}
self.supervisor = None;
self.parent_supervisor = None;
@@ -2796,6 +2801,7 @@ impl RequestContext {
self.escalation_queue = None;
self.current_depth = 0;
self.auto_continue_count = 0;
self.pending_agents_guardrail_count = 0;
self.todo_list = TodoList::default();
self.rag.take();
self.discontinuous_last_message();