harness: add request_next_turn MCP tool — immediate self-continuation (closes #216)
This commit is contained in:
parent
de6ff3da29
commit
c99261b042
3 changed files with 68 additions and 0 deletions
|
|
@ -273,6 +273,11 @@ async fn serve(
|
|||
if pending > 0 {
|
||||
tracing::info!(%pending, "pending messages after turn; fetching next");
|
||||
}
|
||||
// `request_next_turn` MCP tool: agent wrote a sentinel
|
||||
// requesting an immediate self-continuation turn. Clear
|
||||
// the file and inject a synthetic wake so the outer loop
|
||||
// fires a bare turn even if the inbox is empty.
|
||||
check_and_inject_continue(socket, label).await;
|
||||
}
|
||||
Ok(AgentResponse::Messages { .. }) => {
|
||||
// Idle: empty list = nothing pending. Brief sleep
|
||||
|
|
@ -408,3 +413,41 @@ async fn fetch_agent_post_turn_counts(socket: &Path) -> (Option<u64>, Option<u64
|
|||
(threads, reminders)
|
||||
}
|
||||
|
||||
/// Check for the `request_next_turn` sentinel file. If present, remove it
|
||||
/// and inject a synthetic `from: "self", body: "continue"` message so the
|
||||
/// serve loop fires an immediate follow-up turn even when the inbox is empty.
|
||||
/// Best-effort: any I/O error is logged and ignored (the agent just waits
|
||||
/// for a real message as normal).
|
||||
async fn check_and_inject_continue(socket: &Path, label: &str) {
|
||||
let sentinel = hive_ag3nt::paths::state_dir().join("hyperhive-continue");
|
||||
if !sentinel.exists() {
|
||||
return;
|
||||
}
|
||||
if let Err(e) = std::fs::remove_file(&sentinel) {
|
||||
tracing::warn!(error = %e, "check_and_inject_continue: remove sentinel failed");
|
||||
return;
|
||||
}
|
||||
// Sentinel was present: inject a wake so the outer loop fires immediately.
|
||||
// Route through the `Wake` request which is already wired in agent_server.
|
||||
let res = client::request::<_, AgentResponse>(
|
||||
socket,
|
||||
&AgentRequest::Wake {
|
||||
from: "self".into(),
|
||||
body: "continue".into(),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
match res {
|
||||
Ok(AgentResponse::Ok) => {
|
||||
tracing::info!(%label, "request_next_turn: injected self-continue wake");
|
||||
}
|
||||
Ok(AgentResponse::Err { message }) => {
|
||||
tracing::warn!(%message, "check_and_inject_continue: wake rejected");
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!(error = ?e, "check_and_inject_continue: wake transport error");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -626,6 +626,30 @@ impl AgentServer {
|
|||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[tool(
|
||||
description = "Ask the harness to start another turn immediately after this one \
|
||||
completes, even if the inbox is empty. Use this when you have ongoing work that \
|
||||
spans multiple turns (long builds, multi-step tasks) and you want to continue \
|
||||
without waiting for an external message. The next turn will start with \
|
||||
`from: \"self\"` and `body: \"continue\"`. Has no effect if a new inbox message \
|
||||
arrives before this turn ends — the harness already loops immediately on pending \
|
||||
messages. No args."
|
||||
)]
|
||||
async fn request_next_turn(&self) -> String {
|
||||
run_tool_envelope("request_next_turn", String::new(), async move {
|
||||
let sentinel = crate::paths::state_dir().join("hyperhive-continue");
|
||||
match std::fs::write(&sentinel, b"") {
|
||||
Ok(()) => "ok — harness will start another turn immediately after this one",
|
||||
Err(e) => {
|
||||
tracing::warn!(error = %e, path = %sentinel.display(), "request_next_turn: write failed");
|
||||
return format!("request_next_turn failed: {e}");
|
||||
}
|
||||
}
|
||||
.to_string()
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[tool_handler(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue