The Diagnostic blueprint is Magpie’s structured workflow for fixing bugs. Unlike the TDD blueprint which starts with planning, the Diagnostic blueprint enforces a root cause investigation step before any fixes are made.The flow:
Scan the repo structure
Investigate the bug WITHOUT modifying files
Plan a targeted fix based on findings
Write regression test that reproduces the bug
Verify test fails (the bug exists)
Implement fix for the root cause
Verify tests pass (bug is fixed)
Lint for code quality
The key difference from TDD is the investigate step (step 2) which forces the agent to trace the root cause before planning a fix. This prevents band-aid solutions.
The agent traces the root cause WITHOUT modifying any files:
Step { name: "investigate".to_string(), kind: StepKind::Agent( AgentStep::new(format!( "You are investigating a bug. The file tree of the repository \ is provided as previous step output.\n\n\ Bug report: {}\n\n\ Your job is to find the ROOT CAUSE. Do NOT plan a fix yet. \ Do NOT modify any files.\n\n\ Instructions:\n\ 1. Trace the data flow through the affected code path\n\ 2. Read the relevant source files\n\ 3. Identify the exact location where behavior diverges from expectation\n\ 4. Name the specific files, functions, and line numbers involved\n\ 5. Explain WHY the bug occurs (not just WHAT happens)\n\n\ Output a clear investigation report with your findings.", trigger.message )) .with_last_output() .with_context_from_metadata("chat_history"), ), condition: Condition::Always, continue_on_error: false,}
From crates/magpie-core/src/pipeline.rs:572-594Why this matters:
Prevents jumping to conclusions (“just add a null check”)
Forces understanding of the data flow
Results in targeted fixes instead of workarounds
Example investigation output:
ROOT CAUSE ANALYSIS:The panic occurs in src/parser.rs:142 when parse_config() receivesan empty input string.Data flow:1. main.rs:45 reads config file with fs::read_to_string()2. If file is empty, returns Ok("") (not an error)3. parser.rs:142 calls unwrap() on split(",").next(), which panics on empty stringWHY IT HAPPENS:The parser assumes input is non-empty and has at least one comma-separated value.No validation exists before the unwrap().AFFECTED CODE:src/parser.rs:142 — let first = parts.next().unwrap();
Based on the investigation findings, plan a targeted fix:
Step { name: "plan".to_string(), kind: StepKind::Agent( AgentStep::new(format!( "Based on the investigation findings from the previous step, \ plan a targeted fix.\n\n\ Bug report: {}\n\n\ Create a brief plan:\n\ 1. What is the root cause (from investigation)\n\ 2. What specific changes to make and why\n\ 3. What regression test to write\n\n\ Be concise — this plan guides the next steps.", trigger.message )) .with_last_output() .with_context_from_metadata("chat_history"), ), condition: Condition::Always, continue_on_error: false,}
From crates/magpie-core/src/pipeline.rs:610-628Example plan:
1. Root cause: parser.rs:142 panics on empty input due to unwrap() on empty iterator2. Specific changes: - Replace unwrap() with ok_or() to return Result<Config, ParseError> - Add validation at start of parse_config() to check for empty input - Update main.rs error handling to propagate ParseError3. Regression test: - test_parse_empty_config() in tests/parser_test.rs - Should verify parse_config("") returns Err(ParseError::EmptyInput)
Write a test that reproduces the bug with current code:
Step { name: "write-regression-test".to_string(), kind: StepKind::Agent( AgentStep::new(format!( "Based on the plan from the previous step, write a regression test \ that REPRODUCES the bug.\n\n\ Bug report: {}\n\n\ Rules:\n\ - The test should FAIL with the current (buggy) code\n\ - It should PASS once the fix is applied\n\ - Use the project's existing test patterns and framework\n\ - Do NOT implement the fix yet — only the test", trigger.message )) .with_last_output() .with_context_from_metadata("chat_history"), ), condition: Condition::Always, continue_on_error: false,}
From crates/magpie-core/src/pipeline.rs:645-662Example test:
#[test]fn test_parse_empty_config() { let result = parse_config(""); assert!(result.is_err()); match result.unwrap_err() { ParseError::EmptyInput => {}, // expected other => panic!("expected EmptyInput, got {other:?}"), }}
From crates/magpie-core/src/pipeline.rs:666-671Example output:
test parser_test::test_parse_empty_config ... FAILEDthread 'parser_test::test_parse_empty_config' panicked at 'called `Option::unwrap()` on a `None` value'
Step { name: "implement-fix".to_string(), kind: StepKind::Agent( AgentStep::new(format!( "The regression test from the previous step has been run. The output \ (including failures) is provided as previous step output.\n\n\ Bug report: {}\n\n\ Now fix the ROOT CAUSE identified in the investigation step.\n\ - Do NOT use workarounds or band-aids\n\ - Fix the underlying issue, not just the symptoms\n\ - Make sure the regression test passes\n\ - Do not break existing tests", trigger.message )) .with_last_output() .with_context_from_metadata("chat_history"), ), condition: Condition::Always, continue_on_error: false,}
From crates/magpie-core/src/pipeline.rs:687-704Example fix:
pub fn parse_config(input: &str) -> Result<Config, ParseError> { if input.is_empty() { return Err(ParseError::EmptyInput); } let parts: Vec<&str> = input.split(',').collect(); let first = parts.first().ok_or(ParseError::MissingField)?; Ok(Config { name: first.to_string(), })}
Vague Bug ReportsIf your task message is “fix the bug”, the agent has no context. Provide:
Where the bug happens (file, function, scenario)
What the expected behavior is
What actually happens (error message, panic, wrong output)
Good:fix crash in webhook handler when payload is missing signature headerBad:fix the bug
Investigation Output is GoldIf the fix fails, review the investigation step output in agent traces. Often the root cause analysis is correct but the fix implementation is wrong.