prompt: add pod orchestration guidance
This commit is contained in:
parent
5af9a5b1b6
commit
6437580600
|
|
@ -92,6 +92,9 @@ pub enum PodPrompt {
|
|||
/// knowledge when Workflow resident injection is enabled and at least one
|
||||
/// workflow advertises `model_invokation: true`.
|
||||
ResidentWorkflowsSection,
|
||||
/// Trailing Pod orchestration guidance, appended when registered tools
|
||||
/// include Pod-management capabilities.
|
||||
PodOrchestrationGuidanceSection,
|
||||
/// LLM-facing description for the SpawnPod tool, including discovered
|
||||
/// profile selectors.
|
||||
SpawnPodToolDescription,
|
||||
|
|
@ -111,6 +114,7 @@ impl PodPrompt {
|
|||
Self::ResidentMemorySummarySection => "resident_memory_summary_section",
|
||||
Self::ResidentKnowledgeSection => "resident_knowledge_section",
|
||||
Self::ResidentWorkflowsSection => "resident_workflows_section",
|
||||
Self::PodOrchestrationGuidanceSection => "pod_orchestration_guidance_section",
|
||||
Self::SpawnPodToolDescription => "spawn_pod_tool_description",
|
||||
}
|
||||
}
|
||||
|
|
@ -130,6 +134,7 @@ impl PodPrompt {
|
|||
PodPrompt::ResidentMemorySummarySection,
|
||||
PodPrompt::ResidentKnowledgeSection,
|
||||
PodPrompt::ResidentWorkflowsSection,
|
||||
PodPrompt::PodOrchestrationGuidanceSection,
|
||||
PodPrompt::SpawnPodToolDescription,
|
||||
];
|
||||
|
||||
|
|
@ -145,6 +150,7 @@ impl PodPrompt {
|
|||
"resident_memory_summary_section",
|
||||
"resident_knowledge_section",
|
||||
"resident_workflows_section",
|
||||
"pod_orchestration_guidance_section",
|
||||
"spawn_pod_tool_description",
|
||||
];
|
||||
}
|
||||
|
|
@ -402,6 +408,11 @@ impl PromptCatalog {
|
|||
)
|
||||
}
|
||||
|
||||
/// Render `PodPrompt::PodOrchestrationGuidanceSection` (no inputs).
|
||||
pub fn pod_orchestration_guidance_section(&self) -> Result<String, CatalogError> {
|
||||
self.render(PodPrompt::PodOrchestrationGuidanceSection, Value::UNDEFINED)
|
||||
}
|
||||
|
||||
/// Render `PodPrompt::SpawnPodToolDescription`.
|
||||
pub fn spawn_pod_tool_description(
|
||||
&self,
|
||||
|
|
@ -715,6 +726,19 @@ compact_system = "PREFIX\n{% include \"$insomnia/internal/compact_system\" %}"
|
|||
assert!(rendered.contains("write_summary"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pod_orchestration_guidance_section_renders_resource_body() {
|
||||
let cat = PromptCatalog::builtins_only().unwrap();
|
||||
let rendered = cat.pod_orchestration_guidance_section().unwrap();
|
||||
assert!(rendered.contains("## Pod orchestration"));
|
||||
assert!(rendered.contains("spawned Pod 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 status, diff, and test results"));
|
||||
assert!(rendered.contains("not scheduler or auto-maintain authorization"));
|
||||
assert!(rendered.contains("bypass user/workflow authorization"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn_pod_tool_description_renders_profile_block() {
|
||||
let cat = PromptCatalog::builtins_only().unwrap();
|
||||
|
|
|
|||
|
|
@ -7,10 +7,11 @@
|
|||
//! eagerly syntax-checks it at Pod construction. The final system
|
||||
//! prompt is materialised exactly once just before the first LLM turn:
|
||||
//! the rendered body is appended with a fixed trailing section carrying
|
||||
//! the Pod's `Scope` summary and (if present) the project's `AGENTS.md`
|
||||
//! contents plus resident memory sections, and the whole string is handed
|
||||
//! to the Worker via `set_system_prompt`. Subsequent turns and compactions
|
||||
//! reuse that materialised string verbatim.
|
||||
//! the Pod's `Scope` summary, (if present) the project's `AGENTS.md`
|
||||
//! contents, resident memory sections, and conditional Pod-orchestration
|
||||
//! guidance, then the whole string is handed to the Worker via
|
||||
//! `set_system_prompt`. Subsequent turns and compactions reuse that
|
||||
//! materialised string verbatim.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::Path;
|
||||
|
|
@ -216,6 +217,12 @@ struct ToolCapabilities {
|
|||
memory_write: bool,
|
||||
memory_edit: bool,
|
||||
memory_delete: bool,
|
||||
pod_spawn: bool,
|
||||
pod_send: bool,
|
||||
pod_read_output: bool,
|
||||
pod_stop: bool,
|
||||
pod_list: bool,
|
||||
pod_restore: bool,
|
||||
}
|
||||
|
||||
impl ToolCapabilities {
|
||||
|
|
@ -229,6 +236,12 @@ impl ToolCapabilities {
|
|||
"MemoryWrite" => capabilities.memory_write = true,
|
||||
"MemoryEdit" => capabilities.memory_edit = true,
|
||||
"MemoryDelete" => capabilities.memory_delete = true,
|
||||
"SpawnPod" => capabilities.pod_spawn = true,
|
||||
"SendToPod" => capabilities.pod_send = true,
|
||||
"ReadPodOutput" => capabilities.pod_read_output = true,
|
||||
"StopPod" => capabilities.pod_stop = true,
|
||||
"ListPods" => capabilities.pod_list = true,
|
||||
"RestorePod" => capabilities.pod_restore = true,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
@ -251,6 +264,15 @@ impl ToolCapabilities {
|
|||
self.memory_write || self.memory_edit || self.memory_delete
|
||||
}
|
||||
|
||||
fn pod_management(self) -> bool {
|
||||
self.pod_spawn
|
||||
|| self.pod_send
|
||||
|| self.pod_read_output
|
||||
|| self.pod_stop
|
||||
|| self.pod_list
|
||||
|| self.pod_restore
|
||||
}
|
||||
|
||||
fn to_minijinja_value(self) -> Value {
|
||||
let mut map: BTreeMap<&'static str, Value> = BTreeMap::new();
|
||||
map.insert("memory_any", Value::from(self.memory_any()));
|
||||
|
|
@ -262,6 +284,7 @@ impl ToolCapabilities {
|
|||
map.insert("memory_edit", Value::from(self.memory_edit));
|
||||
map.insert("memory_delete", Value::from(self.memory_delete));
|
||||
map.insert("memory_mutation", Value::from(self.memory_mutation()));
|
||||
map.insert("pod_management", Value::from(self.pod_management()));
|
||||
Value::from(map)
|
||||
}
|
||||
}
|
||||
|
|
@ -329,6 +352,12 @@ fn append_trailing_section(
|
|||
out.push('\n');
|
||||
}
|
||||
}
|
||||
if tool_capabilities.pod_management() {
|
||||
out.push('\n');
|
||||
let section = prompts.pod_orchestration_guidance_section()?;
|
||||
out.push_str(section.trim_end_matches(&['\n', ' '][..]));
|
||||
out.push('\n');
|
||||
}
|
||||
// Canonicalise the tail so the emitted prompt has a single form
|
||||
// regardless of how individual templates chose to end.
|
||||
while out.ends_with('\n') || out.ends_with(' ') {
|
||||
|
|
@ -496,6 +525,20 @@ mod tests {
|
|||
.collect()
|
||||
}
|
||||
|
||||
fn pod_management_tool_names() -> Vec<String> {
|
||||
[
|
||||
"SpawnPod",
|
||||
"SendToPod",
|
||||
"ReadPodOutput",
|
||||
"StopPod",
|
||||
"ListPods",
|
||||
"RestorePod",
|
||||
]
|
||||
.into_iter()
|
||||
.map(String::from)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Lazily-initialised builtin catalog shared across system-prompt
|
||||
/// tests, so every `ctx()` can hand out a `&'static PromptCatalog`
|
||||
/// reference without forcing test bodies to create one per call.
|
||||
|
|
@ -586,6 +629,46 @@ mod tests {
|
|||
assert!(!rendered.contains("MemoryDelete"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pod_orchestration_guidance_is_included_for_pod_management_tools() {
|
||||
let loader = PromptLoader::builtins_only();
|
||||
let tmpl = SystemPromptTemplate::parse("$insomnia/default", loader).unwrap();
|
||||
let dir = TempDir::new().unwrap();
|
||||
let scope = build_scope(dir.path());
|
||||
let rendered = tmpl
|
||||
.render(&ctx(dir.path(), &scope, pod_management_tool_names(), None))
|
||||
.unwrap();
|
||||
|
||||
assert!(rendered.contains("## Pod orchestration"));
|
||||
assert!(rendered.contains("spawned Pod 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 status, diff, and test results"));
|
||||
assert!(rendered.contains("not scheduler or auto-maintain authorization"));
|
||||
assert!(rendered.contains("bypass user/workflow authorization"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pod_orchestration_guidance_is_omitted_without_pod_management_tools() {
|
||||
let loader = PromptLoader::builtins_only();
|
||||
let tmpl = SystemPromptTemplate::parse("$insomnia/default", loader).unwrap();
|
||||
let dir = TempDir::new().unwrap();
|
||||
let scope = build_scope(dir.path());
|
||||
let rendered = tmpl
|
||||
.render(&ctx(
|
||||
dir.path(),
|
||||
&scope,
|
||||
vec!["Read".into(), "Edit".into(), "MemoryRead".into()],
|
||||
None,
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
assert!(!rendered.contains("## Pod orchestration"));
|
||||
assert!(!rendered.contains("spawned Pod 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"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn instruction_prefix_addressing_user() {
|
||||
let (_tmp, loader) = user_loader_with("greet.md", "HELLO from {{ cwd }}");
|
||||
|
|
|
|||
10
resources/prompts/common/pod-orchestration.md
Normal file
10
resources/prompts/common/pod-orchestration.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
## Pod orchestration
|
||||
|
||||
When Pod-management tools are available, spawned Pod notifications are background signals for the parent to handle at a natural stopping point. Do not ignore routine follow-up, but do not interrupt the current user request unnecessarily.
|
||||
|
||||
The parent does not need to keep a turn open or call tools solely to wait for a notification. Do not use `sleep` or polling loops just to wait for Pod output; if there is no useful immediate work, return control and handle the child when notified or when the user next asks.
|
||||
|
||||
Before treating delegated work as complete, read the child output and inspect concrete evidence such as worktree status, diff, and test results. Notifications are hints, not proof of completion.
|
||||
|
||||
This guidance is not scheduler or auto-maintain authorization. Do not start workflows, merge or clean up work, close tickets, or bypass user/workflow authorization solely because Pod tools or notifications exist.
|
||||
|
|
@ -66,6 +66,8 @@ The following workflows are advertised resident. When a user request matches one
|
|||
{{ entries }}\
|
||||
"""
|
||||
|
||||
pod_orchestration_guidance_section = "{% include \"$insomnia/common/pod-orchestration\" %}"
|
||||
|
||||
spawn_pod_tool_description = """\
|
||||
Spawn a new Pod process to work on a delegated task. The spawner's write scope is reduced by the scope passed here; the spawned Pod receives its own socket and starts running `task` immediately. The spawned Pod outlives the spawner's current turn and can be contacted again through its socket path.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user