diff --git a/Cargo.lock b/Cargo.lock index 1710a9f..bb7c02a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2554,7 +2554,7 @@ dependencies = [ [[package]] name = "kit" -version = "0.1.111" +version = "0.1.112" dependencies = [ "a2a-protocol-client", "a2a-protocol-server", diff --git a/Cargo.toml b/Cargo.toml index ea71e71..b543417 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kit" -version = "0.1.111" +version = "0.1.112" edition = "2024" rust-version = "1.94.0" publish = false diff --git a/src/runtime.rs b/src/runtime.rs index 48eba28..95faea4 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -989,6 +989,11 @@ impl Runtime { } fn system_prompt(&self, depth: usize) -> String { + let delegation_context = if depth == 1 { + "This task was delegated to you by the primary agent. Investigate it and carry out the work.\n\n" + } else { + "" + }; format!( concat!( "You are a coding agent using Kit version {} as your harness, working in {}. This is your cwd and project context, not a filesystem boundary. ", @@ -1001,14 +1006,11 @@ impl Runtime { "Set the outer `background` argument to `true` to detach immediately or to a positive integer to wait that many seconds before detaching. ", "After detaching, continue any independent work, including launching more detached work. When the remaining work depends on background results, yield; yielding continues the task with those results, so the user's answer need not be completed first. ", "Keep work foregrounded when the next step needs its result in the current turn, and do not treat backgrounding as durable job execution.\n\n", - "When subagent tools are available and work changes phase or objective, start fresh subagents from concise summaries of prior results instead of carrying unrelated history. ", - "Keep outputs focused, pass only necessary context, reuse sessions only when continuity helps, and close subagents when no longer needed.\n\n", - "Current subagent depth: {depth}/{}." + "{}" ), env!("CARGO_PKG_VERSION"), self.root.display(), - self.max_subagent_depth, - depth = depth + delegation_context ) } } diff --git a/src/runtime/tests.rs b/src/runtime/tests.rs index 6f6ce44..c35fea2 100644 --- a/src/runtime/tests.rs +++ b/src/runtime/tests.rs @@ -542,6 +542,23 @@ fn maximum_depth_compose_omits_depth_increasing_tools() { ); } + let subagent_description = ToolSource::get(&below_maximum.compose, &ToolName::new("subagent")) + .unwrap() + .current_spec() + .unwrap() + .description; + assert!(subagent_description.contains( + "Use this only if you uncover independent workstreams whose parallel execution would yield quicker or better results." + )); + let fork_description = ToolSource::get(&below_maximum.compose, &ToolName::new("fork")) + .unwrap() + .current_spec() + .unwrap() + .description; + assert!(fork_description.contains( + "Use this only for an independent workstream whose parallel execution would yield quicker or better results" + )); + let at_maximum = runtime.compose(max_depth); for name in ["subagent", "fork"] { assert!( @@ -967,6 +984,11 @@ fn system_prompt_guides_compose_and_subagent_hygiene() { ); assert!(prompt.contains("keep intermediate results inside it")); assert!(prompt.contains("return only the bare minimum information necessary")); - assert!(prompt.contains("start fresh subagents from concise summaries")); - assert!(prompt.contains("close subagents when no longer needed")); + let delegated_prompt = runtime.system_prompt(1); + assert!(delegated_prompt.contains( + "This task was delegated to you by the primary agent. Investigate it and carry out the work." + )); + assert!(!prompt.contains("This task was delegated to you by the primary agent.")); + let max_depth_prompt = runtime.system_prompt(runtime.max_subagent_depth()); + assert!(!max_depth_prompt.contains("This task was delegated to you by the primary agent.")); } diff --git a/src/tools/subagent.rs b/src/tools/subagent.rs index caa24fd..a41024c 100644 --- a/src/tools/subagent.rs +++ b/src/tools/subagent.rs @@ -1305,7 +1305,21 @@ fn call_id_schema() -> serde_json::Value { impl SubagentTool { pub fn new(manager: Subagents, depth: usize) -> Self { let harnesses = manager.harness_references(); - Self { manager, depth, spec: ToolSpec::new(ToolName::new("subagent"), "Start a parent-owned configured ACP harness, preferably assign a concise role-oriented display name, prompt it, and return its reusable session value. Omit `harness` and `model` unless the user or active workflow explicitly supplies the exact override or a configured alias. Never choose an override based on your own model, provider, publisher, familiarity, cost, or perceived quality; advertised choices indicate availability, not preference.", json!({"type":"object","properties":{"prompt":{"type":"string"},"name":display_name_schema(),"harness":{"type":"string","enum":harnesses,"description":"Override the user's configured harness preference with this value. Default to omitting it."},"model":{"type":"string","minLength":1,"description":"Exact ACP model selection ID or configured alias explicitly requested by the user or active workflow. Applies only to this new session; default to omitting it."},"cwd":{"type":"string","minLength":1,"description":"Working directory for the new subagent. Relative paths resolve from Kit's working directory."},"output_schema":{"oneOf":[{"type":"object"},{"type":"boolean"}]}},"required":["prompt"],"additionalProperties":false})).with_output_schema(value_schema()).with_annotations(ToolAnnotations::new()) } + let usage = if depth == 1 { + concat!( + "Use this only if you uncover independent workstreams whose parallel execution would yield quicker or better results. ", + "Give each subagent a focused assignment based on what you discovered, and synthesize its findings into your response. " + ) + } else { + concat!( + "Use a fresh subagent for work that changes phase or objective instead of carrying unrelated history. ", + "Keep outputs focused, pass only necessary context, reuse sessions only when continuity helps, and close subagents when no longer needed. " + ) + }; + let description = format!( + "Start a parent-owned configured ACP harness, preferably assign a concise role-oriented display name, prompt it, and return its reusable session value. {usage}Omit `harness` and `model` unless the user or active workflow explicitly supplies the exact override or a configured alias. Never choose an override based on your own model, provider, publisher, familiarity, cost, or perceived quality; advertised choices indicate availability, not preference." + ); + Self { manager, depth, spec: ToolSpec::new(ToolName::new("subagent"), description, json!({"type":"object","properties":{"prompt":{"type":"string"},"name":display_name_schema(),"harness":{"type":"string","enum":harnesses,"description":"Override the user's configured harness preference with this value. Default to omitting it."},"model":{"type":"string","minLength":1,"description":"Exact ACP model selection ID or configured alias explicitly requested by the user or active workflow. Applies only to this new session; default to omitting it."},"cwd":{"type":"string","minLength":1,"description":"Working directory for the new subagent. Relative paths resolve from Kit's working directory."},"output_schema":{"oneOf":[{"type":"object"},{"type":"boolean"}]}},"required":["prompt"],"additionalProperties":false})).with_output_schema(value_schema()).with_annotations(ToolAnnotations::new()) } } } impl PromptTool { @@ -1324,7 +1338,15 @@ impl PromptTool { } impl ForkTool { pub fn new(manager: Subagents, depth: usize) -> Self { - Self { manager, depth, spec: ToolSpec::new(ToolName::new("fork"), "Fork a completed ACP subagent session using native capability support or the isolated Kit fallback, preferably assign the fork a concise role-oriented display name, prompt it, and return the new session value.", json!({"type":"object","properties":{"subagent":value_schema(),"prompt":{"type":"string"},"name":display_name_schema(),"output_schema":{"oneOf":[{"type":"object"},{"type":"boolean"}]}},"required":["subagent","prompt"],"additionalProperties":false})).with_output_schema(value_schema()).with_annotations(ToolAnnotations::new()) } + let usage = if depth == 1 { + " Use this only for an independent workstream whose parallel execution would yield quicker or better results, and synthesize its findings into your response." + } else { + "" + }; + let description = format!( + "Fork a completed ACP subagent session using native capability support or the isolated Kit fallback, preferably assign the fork a concise role-oriented display name, prompt it, and return the new session value.{usage}" + ); + Self { manager, depth, spec: ToolSpec::new(ToolName::new("fork"), description, json!({"type":"object","properties":{"subagent":value_schema(),"prompt":{"type":"string"},"name":display_name_schema(),"output_schema":{"oneOf":[{"type":"object"},{"type":"boolean"}]}},"required":["subagent","prompt"],"additionalProperties":false})).with_output_schema(value_schema()).with_annotations(ToolAnnotations::new()) } } } impl SubagentsTool {