worker: separate subworkers from workspace workers

This commit is contained in:
2026-08-05 03:36:59 +09:00
parent 50726e4cf3
commit ba009c0a20
35 changed files with 747 additions and 261 deletions
+12 -12
View File
@@ -88,9 +88,9 @@ pub enum WorkerPrompt {
WorkerOrchestrationGuidanceSection,
/// Weak Companion Notify payload for explicit Orchestrator Ticket events.
TicketEventCompanionNotice,
/// LLM-facing description for the SpawnWorker tool, including discovered
/// LLM-facing description for the SubWorkerSpawn tool, including discovered
/// profile selectors.
SpawnWorkerToolDescription,
SubWorkerSpawnToolDescription,
}
impl WorkerPrompt {
@@ -107,7 +107,7 @@ impl WorkerPrompt {
Self::ResidentMemorySummarySection => "resident_memory_summary_section",
Self::WorkerOrchestrationGuidanceSection => "worker_orchestration_guidance_section",
Self::TicketEventCompanionNotice => "ticket_event_companion_notice",
Self::SpawnWorkerToolDescription => "spawn_worker_tool_description",
Self::SubWorkerSpawnToolDescription => "sub_worker_spawn_tool_description",
}
}
@@ -126,7 +126,7 @@ impl WorkerPrompt {
WorkerPrompt::ResidentMemorySummarySection,
WorkerPrompt::WorkerOrchestrationGuidanceSection,
WorkerPrompt::TicketEventCompanionNotice,
WorkerPrompt::SpawnWorkerToolDescription,
WorkerPrompt::SubWorkerSpawnToolDescription,
];
pub const KEYS: &'static [&'static str] = &[
@@ -141,7 +141,7 @@ impl WorkerPrompt {
"resident_memory_summary_section",
"worker_orchestration_guidance_section",
"ticket_event_companion_notice",
"spawn_worker_tool_description",
"sub_worker_spawn_tool_description",
];
}
@@ -384,8 +384,8 @@ impl PromptCatalog {
)
}
/// Render `WorkerPrompt::SpawnWorkerToolDescription`.
pub fn spawn_worker_tool_description(
/// Render `WorkerPrompt::SubWorkerSpawnToolDescription`.
pub fn sub_worker_spawn_tool_description(
&self,
available_profiles: &str,
default_profile: &str,
@@ -396,7 +396,7 @@ impl PromptCatalog {
m.insert("available_profiles", Value::from(available_profiles));
m.insert("default_profile", Value::from(default_profile));
m.insert("profile_diagnostic", Value::from(profile_diagnostic));
self.render(WorkerPrompt::SpawnWorkerToolDescription, Value::from(m))
self.render(WorkerPrompt::SubWorkerSpawnToolDescription, Value::from(m))
}
}
@@ -722,8 +722,8 @@ compact_system = "PREFIX\n{% include \"$yoi/internal/compact_system\" %}"
fn worker_orchestration_guidance_section_renders_resource_body() {
let cat = PromptCatalog::builtins_only().unwrap();
let rendered = cat.worker_orchestration_guidance_section().unwrap();
assert!(rendered.contains("## Worker orchestration"));
assert!(rendered.contains("spawned Worker notifications are background signals"));
assert!(rendered.contains("## SubWorker orchestration"));
assert!(rendered.contains("SubWorker notifications are background signals"));
assert!(rendered.contains("does not need to keep a turn open"));
assert!(rendered.contains("Do not use `sleep` or polling loops"));
assert!(rendered.contains("worktree state, diff, and test results"));
@@ -732,10 +732,10 @@ compact_system = "PREFIX\n{% include \"$yoi/internal/compact_system\" %}"
}
#[test]
fn spawn_worker_tool_description_renders_profile_block() {
fn sub_worker_spawn_tool_description_renders_profile_block() {
let cat = PromptCatalog::builtins_only().unwrap();
let rendered = cat
.spawn_worker_tool_description(
.sub_worker_spawn_tool_description(
"- `project:coder` — Coder\n- `project:reviewer` — Reviewer",
"project:coder",
"",
+27 -25
View File
@@ -206,12 +206,12 @@ struct ToolCapabilities {
memory_query: bool,
memory_read_document: bool,
memory_update_document: bool,
worker_spawn: bool,
worker_send: bool,
worker_read_output: bool,
worker_stop: bool,
worker_list: bool,
worker_restore: bool,
sub_worker_spawn: bool,
sub_worker_send: bool,
sub_worker_read_output: bool,
sub_worker_stop: bool,
sub_worker_list: bool,
sub_worker_restore: bool,
}
impl ToolCapabilities {
@@ -222,12 +222,11 @@ impl ToolCapabilities {
"MemoryQuery" => capabilities.memory_query = true,
"MemoryReadDocument" => capabilities.memory_read_document = true,
"MemoryUpdateDocument" => capabilities.memory_update_document = true,
"SpawnWorker" => capabilities.worker_spawn = true,
"SendToWorker" => capabilities.worker_send = true,
"ReadWorkerOutput" => capabilities.worker_read_output = true,
"StopWorker" => capabilities.worker_stop = true,
"ListWorkers" => capabilities.worker_list = true,
"RestoreWorker" => capabilities.worker_restore = true,
"SubWorkerSpawn" => capabilities.sub_worker_spawn = true,
"SubWorkerSend" => capabilities.sub_worker_send = true,
"SubWorkerReadOutput" => capabilities.sub_worker_read_output = true,
"SubWorkerStop" => capabilities.sub_worker_stop = true,
"SubWorkerList" => capabilities.sub_worker_list = true,
_ => {}
}
}
@@ -246,13 +245,13 @@ impl ToolCapabilities {
self.memory_update_document
}
fn worker_management(self) -> bool {
self.worker_spawn
|| self.worker_send
|| self.worker_read_output
|| self.worker_stop
|| self.worker_list
|| self.worker_restore
fn sub_worker_management(self) -> bool {
self.sub_worker_spawn
|| self.sub_worker_send
|| self.sub_worker_read_output
|| self.sub_worker_stop
|| self.sub_worker_list
|| self.sub_worker_restore
}
fn to_minijinja_value(self) -> Value {
@@ -269,7 +268,10 @@ impl ToolCapabilities {
Value::from(self.memory_update_document),
);
map.insert("memory_mutation", Value::from(self.memory_mutation()));
map.insert("worker_management", Value::from(self.worker_management()));
map.insert(
"sub_worker_management",
Value::from(self.sub_worker_management()),
);
Value::from(map)
}
}
@@ -419,7 +421,7 @@ mod tests {
.unwrap()
}
fn worker_orchestration_instruction() -> FeatureInstructionDeclaration {
fn sub_worker_orchestration_instruction() -> FeatureInstructionDeclaration {
FeatureInstructionDeclaration::new(
crate::feature::FeatureInstructionId::builtin("worker.orchestration"),
"$yoi/common/worker-orchestration",
@@ -595,13 +597,13 @@ mod tests {
let tmpl = SystemPromptTemplate::parse("$yoi/default", loader).unwrap();
let dir = TempDir::new().unwrap();
let scope = build_scope(dir.path());
let instructions = [worker_orchestration_instruction()];
let instructions = [sub_worker_orchestration_instruction()];
let mut ctx = ctx(dir.path(), &scope, vec!["Read".into()], None);
ctx.feature_instructions = &instructions;
let rendered = tmpl.render(&ctx).unwrap();
assert!(rendered.contains("## Worker orchestration"));
assert!(rendered.contains("spawned Worker notifications are background signals"));
assert!(rendered.contains("## SubWorker orchestration"));
assert!(rendered.contains("SubWorker notifications are background signals"));
assert!(rendered.contains("does not need to keep a turn open"));
assert!(rendered.contains("Do not use `sleep` or polling loops"));
assert!(rendered.contains("worktree state, diff, and test results"));
@@ -610,7 +612,7 @@ mod tests {
}
#[test]
fn worker_orchestration_guidance_is_omitted_without_worker_management_tools() {
fn worker_orchestration_guidance_is_omitted_without_sub_worker_management_tools() {
let loader = PromptLoader::builtins_only();
let tmpl = SystemPromptTemplate::parse("$yoi/default", loader).unwrap();
let dir = TempDir::new().unwrap();