agent loop: claude drives; tool envelope (log/run/status/log)

This commit is contained in:
müde 2026-05-15 14:54:10 +02:00
parent a061f83cfa
commit 3c9d42b2a7
6 changed files with 147 additions and 47 deletions

View file

@ -48,6 +48,50 @@ impl AgentServer {
pub fn new(socket: PathBuf) -> Self {
Self { socket }
}
/// Wrap every tool handler in the same envelope:
/// 1. Log the request (tool name + args via `Debug`).
/// 2. Run the tool's actual logic.
/// 3. Append a status line (inbox state) to the result so claude always
/// has a current "how many unread messages" hint without an extra
/// tool call.
/// 4. Log the result body.
///
/// New tools just call `self.run_tool("name", &args, async { ... })`
/// and get the same shape for free.
async fn run_tool<F>(&self, tool: &'static str, args: String, body: F) -> String
where
F: std::future::Future<Output = String>,
{
tracing::info!(tool, %args, "tool: request");
let result = body.await;
let status = self.status_line().await;
let full = if status.is_empty() {
result
} else {
format!("{result}\n\n[status] {status}")
};
tracing::info!(tool, result = %full, "tool: result");
full
}
/// Non-mutating peek used in the status line. Falls back to a vague
/// note rather than failing the whole tool call when the socket
/// hiccups.
async fn status_line(&self) -> String {
match client::request::<_, hive_sh4re::AgentResponse>(
&self.socket,
&hive_sh4re::AgentRequest::Status,
)
.await
{
Ok(hive_sh4re::AgentResponse::Status { unread }) => {
format!("{unread} unread message(s) in inbox")
}
Ok(other) => format!("status: unexpected response {other:?}"),
Err(e) => format!("status: transport error: {e:#}"),
}
}
}
#[tool_router]
@ -57,33 +101,42 @@ impl AgentServer {
Use this to talk to peers or to surface output for the human at the dashboard."
)]
async fn send(&self, Parameters(args): Parameters<SendArgs>) -> String {
let req = hive_sh4re::AgentRequest::Send {
to: args.to.clone(),
body: args.body,
};
match client::request::<_, hive_sh4re::AgentResponse>(&self.socket, &req).await {
Ok(hive_sh4re::AgentResponse::Ok) => format!("sent to {}", args.to),
Ok(hive_sh4re::AgentResponse::Err { message }) => format!("send failed: {message}"),
Ok(other) => format!("send unexpected response: {other:?}"),
Err(e) => format!("send transport error: {e:#}"),
}
let log = format!("{args:?}");
let to = args.to.clone();
self.run_tool("send", log, async move {
let req = hive_sh4re::AgentRequest::Send {
to: args.to,
body: args.body,
};
match client::request::<_, hive_sh4re::AgentResponse>(&self.socket, &req).await {
Ok(hive_sh4re::AgentResponse::Ok) => format!("sent to {to}"),
Ok(hive_sh4re::AgentResponse::Err { message }) => format!("send failed: {message}"),
Ok(other) => format!("send unexpected response: {other:?}"),
Err(e) => format!("send transport error: {e:#}"),
}
})
.await
}
#[tool(
description = "Pop one message from this agent's inbox. Returns the sender and body, \
or an empty marker if nothing is waiting."
)]
async fn recv(&self, Parameters(_): Parameters<RecvArgs>) -> String {
let req = hive_sh4re::AgentRequest::Recv;
match client::request::<_, hive_sh4re::AgentResponse>(&self.socket, &req).await {
Ok(hive_sh4re::AgentResponse::Message { from, body }) => {
format!("from: {from}\n\n{body}")
async fn recv(&self, Parameters(args): Parameters<RecvArgs>) -> String {
let log = format!("{args:?}");
self.run_tool("recv", log, async move {
let req = hive_sh4re::AgentRequest::Recv;
match client::request::<_, hive_sh4re::AgentResponse>(&self.socket, &req).await {
Ok(hive_sh4re::AgentResponse::Message { from, body }) => {
format!("from: {from}\n\n{body}")
}
Ok(hive_sh4re::AgentResponse::Empty) => "(empty)".into(),
Ok(hive_sh4re::AgentResponse::Err { message }) => format!("recv failed: {message}"),
Ok(other) => format!("recv unexpected response: {other:?}"),
Err(e) => format!("recv transport error: {e:#}"),
}
Ok(hive_sh4re::AgentResponse::Empty) => "(empty)".into(),
Ok(hive_sh4re::AgentResponse::Err { message }) => format!("recv failed: {message}"),
Ok(other) => format!("recv unexpected response: {other:?}"),
Err(e) => format!("recv transport error: {e:#}"),
}
})
.await
}
}