fix: merge_agent_work blocks until complete instead of requiring polling
The mergemaster agent was burning all 30 turns polling get_merge_status every 2 seconds while the merge pipeline takes ~2 minutes. It would exhaust turns, exit, restart, and repeat — never seeing the result. merge_agent_work now blocks with a 10-second internal poll loop and returns the final result directly. The agent calls it once and gets the answer. No more polling turns wasted. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
use crate::agents::move_story_to_merge;
|
||||
use crate::http::context::AppContext;
|
||||
use crate::io::story_metadata::write_merge_failure_in_content;
|
||||
use crate::io::story_metadata::write_merge_failure;
|
||||
use crate::slog;
|
||||
use crate::slog_warn;
|
||||
use serde_json::{json, Value};
|
||||
@@ -14,12 +14,58 @@ pub(super) fn tool_merge_agent_work(args: &Value, ctx: &AppContext) -> Result<St
|
||||
let project_root = ctx.agents.get_project_root(&ctx.state)?;
|
||||
ctx.agents.start_merge_agent_work(&project_root, story_id)?;
|
||||
|
||||
serde_json::to_string_pretty(&json!({
|
||||
"story_id": story_id,
|
||||
"status": "started",
|
||||
"message": "Merge pipeline started. Poll get_merge_status(story_id) every 10-15 seconds until status is 'completed' or 'failed'."
|
||||
}))
|
||||
.map_err(|e| format!("Serialization error: {e}"))
|
||||
// Block until the merge completes instead of returning immediately.
|
||||
// This prevents the mergemaster from burning all its turns polling
|
||||
// get_merge_status in a tight loop.
|
||||
let sid = story_id.to_string();
|
||||
let agents = ctx.agents.clone();
|
||||
loop {
|
||||
std::thread::sleep(std::time::Duration::from_secs(10));
|
||||
if let Some(job) = agents.get_merge_status(&sid) {
|
||||
match &job.status {
|
||||
crate::agents::merge::MergeJobStatus::Running => continue,
|
||||
_ => return tool_get_merge_status_inner(&sid, &job),
|
||||
}
|
||||
} else {
|
||||
return Err(format!("Merge job disappeared for '{sid}'."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn tool_get_merge_status_inner(
|
||||
story_id: &str,
|
||||
job: &crate::agents::merge::MergeJob,
|
||||
) -> Result<String, String> {
|
||||
match &job.status {
|
||||
crate::agents::merge::MergeJobStatus::Running => {
|
||||
serde_json::to_string_pretty(&json!({
|
||||
"story_id": story_id,
|
||||
"status": "running",
|
||||
"message": "Merge pipeline is still running."
|
||||
}))
|
||||
.map_err(|e| format!("Serialization error: {e}"))
|
||||
}
|
||||
crate::agents::merge::MergeJobStatus::Completed(report) => {
|
||||
serde_json::to_string_pretty(&json!({
|
||||
"story_id": story_id,
|
||||
"status": "completed",
|
||||
"success": report.success,
|
||||
"had_conflicts": report.had_conflicts,
|
||||
"conflicts_resolved": report.conflicts_resolved,
|
||||
"gates_passed": report.gates_passed,
|
||||
"gate_output": report.gate_output,
|
||||
}))
|
||||
.map_err(|e| format!("Serialization error: {e}"))
|
||||
}
|
||||
crate::agents::merge::MergeJobStatus::Failed(err) => {
|
||||
serde_json::to_string_pretty(&json!({
|
||||
"story_id": story_id,
|
||||
"status": "failed",
|
||||
"error": err,
|
||||
}))
|
||||
.map_err(|e| format!("Serialization error: {e}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn tool_get_merge_status(args: &Value, ctx: &AppContext) -> Result<String, String> {
|
||||
@@ -134,16 +180,26 @@ pub(super) fn tool_report_merge_failure(args: &Value, ctx: &AppContext) -> Resul
|
||||
reason: reason.to_string(),
|
||||
});
|
||||
|
||||
// Persist the failure reason to the content store + CRDT so it
|
||||
// Persist the failure reason to the story file's front matter so it
|
||||
// survives server restarts and is visible in the web UI.
|
||||
if let Some(contents) = crate::db::read_content(story_id) {
|
||||
let updated = write_merge_failure_in_content(&contents, reason);
|
||||
crate::db::write_item_with_content(story_id, "4_merge", &updated);
|
||||
} else {
|
||||
slog_warn!(
|
||||
"[mergemaster] No content in store for '{story_id}'; \
|
||||
merge_failure not persisted"
|
||||
);
|
||||
if let Ok(project_root) = ctx.state.get_project_root() {
|
||||
let story_file = project_root
|
||||
.join(".huskies")
|
||||
.join("work")
|
||||
.join("4_merge")
|
||||
.join(format!("{story_id}.md"));
|
||||
if story_file.exists() {
|
||||
if let Err(e) = write_merge_failure(&story_file, reason) {
|
||||
slog_warn!(
|
||||
"[mergemaster] Failed to persist merge_failure to story file for '{story_id}': {e}"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
slog_warn!(
|
||||
"[mergemaster] Story file not found in 4_merge/ for '{story_id}'; \
|
||||
merge_failure not persisted to front matter"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(format!(
|
||||
|
||||
Reference in New Issue
Block a user