Skip to content

Commit b7c2057

Browse files
ComputelessComputerwarp-agent
andcommitted
strengthen AI duplicate prevention with hierarchical context
- build_triage_context now shows subtasks indented under parents so the AI can see the full hierarchy before deciding create vs update - added CRITICAL deduplication rules to triage system prompt - strengthened create_task/update_task tool descriptions to prefer updates over creating duplicates - increased context limit from 40 to 60 to accommodate subtask lines Co-Authored-By: Warp <agent@warp.dev>
1 parent 9145a8d commit b7c2057

2 files changed

Lines changed: 48 additions & 5 deletions

File tree

src/llm.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1149,7 +1149,7 @@ fn triage_tool_defs(provider: Provider, bucket_names: &[String]) -> serde_json::
11491149
make_tool_def(
11501150
provider,
11511151
"create_task",
1152-
"Create a new task. Use when the user describes genuinely new work.",
1152+
"Create a new task. Use ONLY when the user describes genuinely new work that does NOT overlap with any existing task or sub-task. Always check existing tasks first.",
11531153
json!({
11541154
"type": "object",
11551155
"properties": {
@@ -1176,7 +1176,7 @@ fn triage_tool_defs(provider: Provider, bucket_names: &[String]) -> serde_json::
11761176
make_tool_def(
11771177
provider,
11781178
"update_task",
1179-
"Update an existing task. Use for status changes, field updates, or adding sub-tasks.",
1179+
"Update an existing task. PREFER this over create_task when similar work already exists. Use for status changes, field updates, or adding sub-tasks.",
11801180
json!({
11811181
"type": "object",
11821182
"properties": {
@@ -1323,6 +1323,11 @@ fn triage_task(cfg: &LlmConfig, job: &AiJob, raw_input: &str) -> AiResult {
13231323
let system = "You are an expert AI project manager. Analyze the user's message and call \
13241324
the appropriate tool.\n\
13251325
Rules:\n\
1326+
- CRITICAL: Before creating ANY task, carefully check ALL existing tasks AND their sub-tasks \
1327+
(lines starting with ↳). If a task or sub-task with similar meaning already exists, use \
1328+
update_task instead of create_task. NEVER create a duplicate.\n\
1329+
- When adding sub-tasks, check the parent's existing sub-tasks first. Do NOT create sub-tasks \
1330+
that overlap with ones already listed.\n\
13261331
- Generate clean, actionable titles (do NOT use the user's raw words verbatim).\n\
13271332
- Infer progress from context (e.g. \"already working on X\"\"In progress\").\n\
13281333
- If the user asks to break down, decompose, split, or create sub-tasks, use decompose_task.\n\

src/main.rs

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2868,12 +2868,20 @@ fn build_ai_context(tasks: &[Task]) -> Vec<llm::ContextTask> {
28682868
}
28692869

28702870
/// Build rich context for triage: full task details so the AI can match intent.
2871+
/// Shows parent tasks with their sub-tasks indented to expose the full hierarchy.
28712872
fn build_triage_context(tasks: &[Task]) -> String {
2872-
let mut refs: Vec<&Task> = tasks.iter().collect();
2873-
refs.sort_by(|a, b| b.updated_at.cmp(&a.updated_at));
2873+
// Collect parent (root) tasks sorted by recency.
2874+
let mut parents: Vec<&Task> = tasks.iter().filter(|t| t.parent_id.is_none()).collect();
2875+
parents.sort_by(|a, b| b.updated_at.cmp(&a.updated_at));
28742876

28752877
let mut out = String::new();
2876-
for t in refs.iter().take(40) {
2878+
let mut count = 0usize;
2879+
let limit = 60;
2880+
2881+
for t in &parents {
2882+
if count >= limit {
2883+
break;
2884+
}
28772885
let short = t.id.to_string().chars().take(8).collect::<String>();
28782886
let desc = if t.description.trim().is_empty() {
28792887
""
@@ -2893,6 +2901,36 @@ fn build_triage_context(tasks: &[Task]) -> String {
28932901
desc
28942902
}
28952903
));
2904+
count += 1;
2905+
2906+
// Show children indented under their parent.
2907+
let children = children_of(tasks, t.id);
2908+
for &idx in &children {
2909+
if count >= limit {
2910+
break;
2911+
}
2912+
let child = &tasks[idx];
2913+
let child_short = child.id.to_string().chars().take(8).collect::<String>();
2914+
let child_desc = if child.description.trim().is_empty() {
2915+
""
2916+
} else {
2917+
child.description.trim()
2918+
};
2919+
out.push_str(&format!(
2920+
" ↳ {} [{}] {} | {} | {} | {}\n",
2921+
child_short,
2922+
child.bucket,
2923+
child.title,
2924+
child.progress.title(),
2925+
child.priority.title(),
2926+
if child_desc.is_empty() {
2927+
"no description"
2928+
} else {
2929+
child_desc
2930+
}
2931+
));
2932+
count += 1;
2933+
}
28962934
}
28972935
out
28982936
}

0 commit comments

Comments
 (0)