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
|
|
@ -11,6 +11,7 @@ Tools (hyperhive surface):
|
|||
- `mcp__hyperhive__cancel_loose_end(kind, id)` — cancel one of your own open threads. `kind` is `"question"` (the asker — you, in this case — gets a `[cancelled by <you>]` answer so the waiter unblocks) or `"reminder"` (hard-deleted before it fires). `id` from the matching `get_loose_ends` row or the original submission reply.
|
||||
- `mcp__hyperhive__remind(message, delay_seconds? | at_unix_timestamp?, file_path?)` — schedule a message to land in your *own* inbox at a future time (sender shows as `reminder`). Set exactly one of `delay_seconds` (relative) or `at_unix_timestamp` (absolute). Use for self-paced follow-ups instead of blocking a whole turn on a long `recv` wait. A large `message` auto-spills to a file under `/agents/{label}/state/reminders/`; pass `file_path` to point at one yourself. Each agent's pending-reminder count is capped (default 50) — the tool will error if the cap is already reached.
|
||||
- `mcp__hyperhive__whoami()` — self-introspection: returns your canonical agent name (from socket identity, not the prompt-substituted label), role, and current hyperhive rev. No args. Use it when you want a trustworthy identity stamp for state files, commit messages, or cross-agent attribution that won't drift across renames.
|
||||
- `mcp__hyperhive__request_next_turn()` — ask the harness to start another turn immediately after this one ends, even if the inbox is empty. Use for multi-turn tasks (long builds, sequential steps) where you want to continue without waiting for an external message. The next turn starts with `from: "self"` and `body: "continue"`. No-op if new inbox messages arrive before this turn ends (the harness already loops immediately on pending messages). No args.
|
||||
|
||||
Need new packages, env vars, or other NixOS config for yourself? You can't edit your own config directly — message the manager (recipient `manager`) describing what you need + why. The manager evaluates the request (it doesn't rubber-stamp), edits `/agents/{label}/config/agent.nix` on your behalf, commits, and submits an approval that the operator can accept on the dashboard; on approve hive-c0re rebuilds your container with the new config.
|
||||
|
||||
|
|
|
|||
|
|
@ -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