refactor(hive-ag3nt): split hive bin into hive-agent / hive-agent-mcp / hive-agent-wake
This commit is contained in:
parent
2486251b32
commit
5b062dca55
14 changed files with 182 additions and 144 deletions
|
|
@ -32,8 +32,9 @@ hand-maintained per-file tree drifts out of sync with the code.
|
|||
the meta flake, lifecycle (`nixos-container` shellouts), gateway /
|
||||
forge / matrix provisioning, per-container stats, and the axum
|
||||
operator dashboard (`dashboard.rs`). Largest crate.
|
||||
- **`hive-ag3nt/`** — in-container harness; one `hive` binary for every
|
||||
agent. Turn-loop *policy* layer (`turn.rs`) over the `hive-claude`
|
||||
- **`hive-ag3nt/`** — in-container harness; three sibling binaries for
|
||||
every agent (`hive-agent` serve loop, `hive-agent-mcp`,
|
||||
`hive-agent-wake`). Turn-loop *policy* layer (`turn.rs`) over the `hive-claude`
|
||||
driver, embedded MCP server (`mcp.rs`) + its claude launch-config layer
|
||||
(`mcp_config.rs`: tool-group/capability → `--allowedTools`, `--mcp-config`
|
||||
render), per-agent web UI (`web_ui/` module dir), event + turn-stats
|
||||
|
|
|
|||
|
|
@ -214,7 +214,8 @@ nspawn agent. Open questions, not yet wired:
|
|||
|
||||
## Harness systemd unit shape
|
||||
|
||||
One harness binary (`hive`), one `harness-base.nix` template, one
|
||||
One harness serve binary (`hive-agent`, with its `hive-agent-mcp` /
|
||||
`hive-agent-wake` siblings), one `harness-base.nix` template, one
|
||||
service unit (`systemd.services.hive-ag3nt`) for all agents. There
|
||||
is no longer a separate manager service name or role distinction in
|
||||
the harness — privilege differences live server-side in the broker
|
||||
|
|
@ -259,7 +260,8 @@ bit set` regardless of `hyperhive.user.passwordlessSudo`.
|
|||
|
||||
### `serviceConfig` highlights
|
||||
|
||||
- `ExecStart = pkgs.hyperhive/bin/hive serve` — single binary.
|
||||
- `ExecStart = pkgs.hyperhive/bin/hive-agent` — same binary for
|
||||
every agent.
|
||||
- `Restart = on-failure`, `RestartSec = 2` — keeps the harness
|
||||
resilient across transient crashes without thundering retries.
|
||||
- `RuntimeDirectory = "hive-config"` → `/run/hive-config/` owned by
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ claude has access to in return.
|
|||
|
||||
## The loop
|
||||
|
||||
Each agent harness (`hive serve` — one binary for all agents) runs:
|
||||
Each agent harness (`hive-agent` — one serve-loop binary for all
|
||||
agents) runs:
|
||||
|
||||
1. Long-poll `Recv` on its socket. The host-side broker
|
||||
(`broker.rs::recv_blocking_batch`) returns immediately if there's
|
||||
|
|
@ -51,38 +52,37 @@ Each agent harness (`hive serve` — one binary for all agents) runs:
|
|||
|
||||
## Harness binary shape
|
||||
|
||||
One `hive` binary for all agents. The earlier split into
|
||||
`hive-ag3nt` + `hive-m1nd` was collapsed because the privilege
|
||||
boundary lives server-side at the broker socket
|
||||
(`/run/hive/mcp.sock`): `ManagerRequest` calls are refused by the
|
||||
standard agent socket regardless of who sends them.
|
||||
Three sibling binaries out of the one `hive-ag3nt` crate, all
|
||||
role-agnostic. (The earlier split into `hive-ag3nt` + `hive-m1nd`
|
||||
was collapsed because the privilege boundary lives server-side at
|
||||
the broker socket (`/run/hive/mcp.sock`): `ManagerRequest` calls are
|
||||
refused by the standard agent socket regardless of who sends them.)
|
||||
|
||||
Three subcommands:
|
||||
|
||||
- `serve` — long-running harness loop (the inbox poll +
|
||||
- `hive-agent` — long-running harness loop (the inbox poll +
|
||||
claude-pump + ack/requeue cycle described above).
|
||||
- `mcp` — MCP server. Default: stdio child claude spawns via
|
||||
`--mcp-config` per turn. With `--http <addr>`, runs as a persistent
|
||||
streamable-HTTP daemon instead (used by the `hive-mcp-http`
|
||||
systemd unit when `hyperhive.mcp.httpPort` is set).
|
||||
- `wake --from <name> --body <body>` — push a message into our own
|
||||
inbox so the next turn fires with the given body. Used by
|
||||
co-process daemons (matrix bridge, scraper, webhook listeners)
|
||||
to nudge claude on external events. `--body -` reads from stdin.
|
||||
- `hive-agent-mcp` — MCP server. Default: stdio child claude spawns
|
||||
via `--mcp-config` per turn (the serve loop renders the config to
|
||||
point at this sibling of its own `/proc/self/exe`). With
|
||||
`--http <addr>`, runs as a persistent streamable-HTTP daemon
|
||||
instead (used by the `hive-mcp-http` systemd unit when
|
||||
`hyperhive.mcp.httpPort` is set).
|
||||
- `hive-agent-wake --from <name> --body <body>` — push a message into
|
||||
our own inbox so the next turn fires with the given body. Used by
|
||||
co-process helpers (scrapers, webhook listeners) to nudge claude on
|
||||
external events. `--body -` reads from stdin.
|
||||
|
||||
### `Surface` trait + zero-sized type tags
|
||||
|
||||
`AgentRequest` / `AgentResponse` (= `ManagerRequest` / `ManagerResponse` —
|
||||
type aliases) are the wire types. There is one role: agent.
|
||||
`bin/hive.rs` factors the turn loop through a `Surface` trait with one
|
||||
zero-sized impl (`AgentSurface`) wrapping:
|
||||
`bin/hive-agent.rs` factors the turn loop through a `Surface` trait
|
||||
with one zero-sized impl (`AgentSurface`) wrapping:
|
||||
|
||||
- One async method per wire op: `ack_turn`, `requeue_inflight`,
|
||||
`inbox_unread`, `post_turn_counts`, `send_to_parent`,
|
||||
`recv_next`, `wake_external`.
|
||||
`inbox_unread`, `post_turn_counts`, `send_to_parent`, `recv_next`.
|
||||
|
||||
`main()` calls `serve_main::<AgentSurface>` for all roles. The turn
|
||||
loop (`serve_loop` / `handle_turn` / `wake`) has no per-role branches.
|
||||
loop (`serve_loop` / `handle_turn`) has no per-role branches.
|
||||
|
||||
### Boot wiring
|
||||
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ External MCP servers (and any other in-container process) can
|
|||
inject a wake-up event into the agent's inbox via the per-agent
|
||||
socket at `/run/hive/mcp.sock`. Two equivalent paths:
|
||||
|
||||
- **Shell out to `hive wake --from <label> --body <text>`**
|
||||
- **Shell out to `hive-agent-wake --from <label> --body <text>`**
|
||||
(use `--body -` to read body from stdin). Already on the
|
||||
container's `PATH` since the harness binary is in
|
||||
`systemPackages`. Convenient for shell-script integrations and
|
||||
|
|
|
|||
|
|
@ -28,9 +28,8 @@ tracing-subscriber.workspace = true
|
|||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
|
||||
[[bin]]
|
||||
# Unified harness binary for all agents. Privilege boundary is
|
||||
# Three sibling harness binaries for all agents (auto-discovered from
|
||||
# `src/bin/`): `hive-agent` (serve loop), `hive-agent-mcp` (MCP
|
||||
# server), `hive-agent-wake` (wake CLI). Privilege boundary is
|
||||
# enforced server-side at the socket (tool groups / manager surface).
|
||||
# See `docs/turn-loop.md::Harness binary shape`.
|
||||
name = "hive"
|
||||
path = "src/bin/hive.rs"
|
||||
|
|
|
|||
43
hive-ag3nt/src/bin/hive-agent-mcp.rs
Normal file
43
hive-ag3nt/src/bin/hive-agent-mcp.rs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
//! MCP-server binary. Default is stdio — spawned by `claude` via
|
||||
//! `--mcp-config`; tools dispatch through `/run/hive/mcp.sock` back into
|
||||
//! the hyperhive broker. Pass `--http <addr>` to instead run a long-lived
|
||||
//! streamable-http listener (persistent daemon, the `hive-mcp-http`
|
||||
//! systemd unit) that claude reconnects to each turn, avoiding the
|
||||
//! per-turn stdio re-registration race. Sibling of `hive-agent` (the
|
||||
//! serve loop that renders the `--mcp-config` blob pointing here) and
|
||||
//! `hive-agent-wake`.
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use hive_ag3nt::{DEFAULT_SOCKET, mcp};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "hive-agent-mcp", about = "hyperhive MCP server")]
|
||||
struct Cli {
|
||||
/// Path to the per-agent MCP socket (bind-mounted from the host).
|
||||
#[arg(long, default_value = DEFAULT_SOCKET)]
|
||||
socket: PathBuf,
|
||||
|
||||
/// Serve over streamable-http on this address (e.g.
|
||||
/// `127.0.0.1:8790`) instead of stdio. Bind loopback only.
|
||||
#[arg(long)]
|
||||
http: Option<std::net::SocketAddr>,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(
|
||||
tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
|
||||
)
|
||||
.init();
|
||||
|
||||
let cli = Cli::parse();
|
||||
match cli.http {
|
||||
Some(addr) => mcp::serve_http(cli.socket, addr).await,
|
||||
None => mcp::serve_agent_stdio(cli.socket).await,
|
||||
}
|
||||
}
|
||||
60
hive-ag3nt/src/bin/hive-agent-wake.rs
Normal file
60
hive-ag3nt/src/bin/hive-agent-wake.rs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
//! Wake CLI: inject a wake-up event into this container's harness inbox
|
||||
//! so the next turn fires with the given body. Intended for extra MCP
|
||||
//! servers / helpers (scraper, webhook listener, etc.) that need to
|
||||
//! nudge claude on external events; the built-in daemons (matrix, bash)
|
||||
//! talk to the socket directly instead. Sibling of `hive-agent` and
|
||||
//! `hive-agent-mcp`.
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use hive_ag3nt::{DEFAULT_SOCKET, client};
|
||||
use hive_sh4re::{AgentRequest, AgentResponse};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "hive-agent-wake", about = "hyperhive harness wake signal")]
|
||||
struct Cli {
|
||||
/// Path to the per-agent MCP socket (bind-mounted from the host).
|
||||
#[arg(long, default_value = DEFAULT_SOCKET)]
|
||||
socket: PathBuf,
|
||||
|
||||
#[arg(long)]
|
||||
from: String,
|
||||
|
||||
/// Body of the wake message. Pass `-` to read from stdin.
|
||||
#[arg(long)]
|
||||
body: String,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(
|
||||
tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
|
||||
)
|
||||
.init();
|
||||
|
||||
let cli = Cli::parse();
|
||||
let body = if cli.body == "-" {
|
||||
let mut buf = String::new();
|
||||
std::io::Read::read_to_string(&mut std::io::stdin(), &mut buf)?;
|
||||
buf
|
||||
} else {
|
||||
cli.body
|
||||
};
|
||||
let resp: AgentResponse = client::request(
|
||||
&cli.socket,
|
||||
&AgentRequest::Wake {
|
||||
from: cli.from,
|
||||
body,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
match resp {
|
||||
AgentResponse::Ok => Ok(()),
|
||||
AgentResponse::Err { message } => anyhow::bail!("wake: {message}"),
|
||||
other => anyhow::bail!("wake: unexpected response {other:?}"),
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
//! Unified hyperhive harness binary. Dispatches one of three subcommands
|
||||
//! (`serve` / `mcp` / `wake`). There is one role: agent. The `Surface`
|
||||
//! Harness serve-loop binary. Long-polls the broker inbox and drives one
|
||||
//! claude turn per message. There is one role: agent. The `Surface`
|
||||
//! trait + `AgentSurface` zero-sized type tag keeps the turn loop
|
||||
//! generic and testable. Architecture lives in
|
||||
//! generic and testable. Siblings: `hive-agent-mcp` (the MCP server this
|
||||
//! loop points claude at) and `hive-agent-wake` (external wake CLI).
|
||||
//! Architecture lives in
|
||||
//! [`docs/turn-loop.md::Harness binary shape`](../../../docs/turn-loop.md).
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
|
@ -9,58 +11,23 @@ use std::sync::{Arc, Mutex};
|
|||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, Subcommand};
|
||||
use clap::Parser;
|
||||
use hive_ag3nt::events::{Bus, LiveEvent, TurnState};
|
||||
use hive_ag3nt::login::{self, LoginState};
|
||||
use hive_ag3nt::turn_stats::TurnStats;
|
||||
use hive_ag3nt::{
|
||||
DEFAULT_SOCKET, DEFAULT_WEB_PORT, client, mcp, plugins, serve_common, turn, web_ui,
|
||||
};
|
||||
use hive_ag3nt::{DEFAULT_SOCKET, DEFAULT_WEB_PORT, client, plugins, serve_common, turn, web_ui};
|
||||
use hive_sh4re::{AgentRequest, AgentResponse, HelperEvent, SYSTEM_SENDER};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "hive", about = "hyperhive harness")]
|
||||
#[command(name = "hive-agent", about = "hyperhive harness serve loop")]
|
||||
struct Cli {
|
||||
/// Path to the per-agent MCP socket (bind-mounted from the host).
|
||||
#[arg(long, global = true, default_value = DEFAULT_SOCKET)]
|
||||
#[arg(long, default_value = DEFAULT_SOCKET)]
|
||||
socket: PathBuf,
|
||||
|
||||
#[command(subcommand)]
|
||||
cmd: Cmd,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Cmd {
|
||||
/// Run the long-lived harness loop. Polls inbox; replies via
|
||||
/// `claude --print` when available.
|
||||
Serve {
|
||||
/// Inbox poll interval in milliseconds.
|
||||
#[arg(long, default_value_t = 1000)]
|
||||
poll_ms: u64,
|
||||
},
|
||||
/// Run the MCP server. Default is stdio — spawned by `claude` via
|
||||
/// `--mcp-config`; tools dispatch through `/run/hive/mcp.sock` back
|
||||
/// into the hyperhive broker. Pass `--http <addr>` to instead run a
|
||||
/// long-lived streamable-http listener (persistent daemon) that
|
||||
/// claude reconnects to each turn, avoiding the per-turn stdio
|
||||
/// re-registration race.
|
||||
Mcp {
|
||||
/// Serve over streamable-http on this address (e.g.
|
||||
/// `127.0.0.1:8790`) instead of stdio. Bind loopback only.
|
||||
#[arg(long)]
|
||||
http: Option<std::net::SocketAddr>,
|
||||
},
|
||||
/// Inject a wake-up event into this harness's inbox so the next
|
||||
/// turn fires with the given body. Intended for extra MCP servers
|
||||
/// / helpers (matrix bridge, scraper, webhook listener, etc.) that
|
||||
/// need to nudge claude on external events.
|
||||
Wake {
|
||||
#[arg(long)]
|
||||
from: String,
|
||||
/// Body of the wake message. Pass `-` to read from stdin.
|
||||
#[arg(long)]
|
||||
body: String,
|
||||
},
|
||||
/// Inbox poll interval in milliseconds.
|
||||
#[arg(long, default_value_t = 1000)]
|
||||
poll_ms: u64,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
|
|
@ -73,15 +40,7 @@ async fn main() -> Result<()> {
|
|||
.init();
|
||||
|
||||
let cli = Cli::parse();
|
||||
|
||||
match cli.cmd {
|
||||
Cmd::Serve { poll_ms } => serve_main::<AgentSurface>(&cli.socket, poll_ms).await,
|
||||
Cmd::Mcp { http } => match http {
|
||||
Some(addr) => mcp::serve_http(cli.socket, addr).await,
|
||||
None => mcp::serve_agent_stdio(cli.socket).await,
|
||||
},
|
||||
Cmd::Wake { from, body } => wake::<AgentSurface>(&cli.socket, from, body).await,
|
||||
}
|
||||
serve_main::<AgentSurface>(&cli.socket, cli.poll_ms).await
|
||||
}
|
||||
|
||||
// ---------- shared turn helpers ----------
|
||||
|
|
@ -261,13 +220,6 @@ trait Surface {
|
|||
/// generic `serve_loop` doesn't need the per-role Response enum
|
||||
/// at all.
|
||||
fn recv_next(socket: &Path) -> impl Future<Output = RecvOutcome>;
|
||||
|
||||
/// External `wake` subcommand (the `hive wake` CLI command, used
|
||||
/// by co-process daemons like matrix to push events into the
|
||||
/// harness inbox). Errors out via `anyhow::bail!` so the calling
|
||||
/// binary surfaces them on stderr.
|
||||
fn wake_external(socket: &Path, from: String, body: String)
|
||||
-> impl Future<Output = Result<()>>;
|
||||
}
|
||||
|
||||
// ---------- AgentSurface ----------
|
||||
|
|
@ -383,16 +335,6 @@ impl Surface for AgentSurface {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn wake_external(socket: &Path, from: String, body: String) -> Result<()> {
|
||||
let resp: AgentResponse =
|
||||
client::request(socket, &AgentRequest::Wake { from, body }).await?;
|
||||
match resp {
|
||||
AgentResponse::Ok => Ok(()),
|
||||
AgentResponse::Err { message } => anyhow::bail!("wake: {message}"),
|
||||
other => anyhow::bail!("wake: unexpected response {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- generic turn loop ----------
|
||||
|
|
@ -673,20 +615,6 @@ async fn handle_turn<S: Surface>(
|
|||
}
|
||||
}
|
||||
|
||||
/// External `hive wake` subcommand — push a message into our own
|
||||
/// inbox so the next turn fires with the given body. Reads the body
|
||||
/// from stdin when `body == "-"`.
|
||||
async fn wake<S: Surface>(socket: &Path, from: String, body: String) -> Result<()> {
|
||||
let body = if body == "-" {
|
||||
let mut buf = String::new();
|
||||
std::io::Read::read_to_string(&mut std::io::stdin(), &mut buf)?;
|
||||
buf
|
||||
} else {
|
||||
body
|
||||
};
|
||||
S::wake_external(socket, from, body).await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod continue_tests {
|
||||
use super::{TurnControl, should_self_continue, synthetic_continue};
|
||||
|
|
@ -52,7 +52,7 @@ const TOKEN_RETRY_MAX: u32 = 20;
|
|||
/// claim it's "new" — see docs/forge.md, "new vs activity on".
|
||||
const NEW_ITEM_TOLERANCE_SECS: i64 = 120;
|
||||
|
||||
/// Spawn point: called once from `hive serve`. Returns immediately if the forge is not
|
||||
/// Spawn point: called once from the `hive-agent` serve loop. Returns immediately if the forge is not
|
||||
/// configured. Otherwise loops forever, polling every
|
||||
/// `POLL_INTERVAL_SECS` seconds. Errors are never fatal.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
//! Shared in-container harness code for the single `hive` binary that
|
||||
//! serves every agent role (the manager is just an agent role, not a
|
||||
//! separate binary).
|
||||
//! Shared in-container harness code for the sibling `hive-agent` /
|
||||
//! `hive-agent-mcp` / `hive-agent-wake` binaries, which serve every
|
||||
//! agent role (the manager is just an agent role, not a separate set
|
||||
//! of binaries).
|
||||
|
||||
pub mod client;
|
||||
pub mod events;
|
||||
|
|
|
|||
|
|
@ -330,13 +330,13 @@ fn load_extra_mcp() -> std::collections::BTreeMap<String, ExtraMcpServer> {
|
|||
}
|
||||
|
||||
/// Render the MCP config blob claude reads from `--mcp-config <path>`.
|
||||
/// `agent_binary` is the path (or PATH-resolvable name) of the `hive-ag3nt`
|
||||
/// executable; `socket` is the hyperhive per-agent socket bind-mounted into
|
||||
/// the container (forwarded to the child as `--socket <path>`). Merges in
|
||||
/// any extra MCP servers declared via `hyperhive.extraMcpServers` in the
|
||||
/// agent's NixOS config.
|
||||
/// `mcp_binary` is the path (or PATH-resolvable name) of the
|
||||
/// `hive-agent-mcp` bridge executable; `socket` is the hyperhive per-agent
|
||||
/// socket bind-mounted into the container (forwarded to the child as
|
||||
/// `--socket <path>`). Merges in any extra MCP servers declared via
|
||||
/// `hyperhive.extraMcpServers` in the agent's NixOS config.
|
||||
#[must_use]
|
||||
pub fn render_claude_config(agent_binary: &str, socket: &std::path::Path) -> String {
|
||||
pub fn render_claude_config(mcp_binary: &str, socket: &std::path::Path) -> String {
|
||||
let mut servers = serde_json::Map::new();
|
||||
// When the harness is configured to run the built-in server as a
|
||||
// persistent streamable-http daemon (loopback port in
|
||||
|
|
@ -353,8 +353,8 @@ pub fn render_claude_config(agent_binary: &str, socket: &std::path::Path) -> Str
|
|||
"url": format!("http://127.0.0.1:{port}/mcp"),
|
||||
}),
|
||||
None => serde_json::json!({
|
||||
"command": agent_binary,
|
||||
"args": ["--socket", socket.display().to_string(), "mcp"],
|
||||
"command": mcp_binary,
|
||||
"args": ["--socket", socket.display().to_string()],
|
||||
"env": {}
|
||||
}),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
//! Pure helpers factored out of the `hive` serve loop (`bin/hive.rs`).
|
||||
//! Pure helpers factored out of the harness serve loop
|
||||
//! (`bin/hive-agent.rs`).
|
||||
//! Only functions with no wire-type dependency live here;
|
||||
//! request/response-flavored helpers (`requeue_inflight`, `ack_turn`, etc.)
|
||||
//! stay in the binary because they touch the request enum variants directly.
|
||||
|
|
@ -60,7 +61,7 @@ pub struct TurnRowArgs<'a> {
|
|||
}
|
||||
|
||||
/// Assemble a `TurnStatRow` from the harness's per-turn state. Lives here
|
||||
/// (rather than inline in the `hive` serve loop) so it stays wire-type-free
|
||||
/// (rather than inline in the serve loop) so it stays wire-type-free
|
||||
/// and unit-testable; the binary just feeds it the post-turn counts.
|
||||
#[must_use]
|
||||
pub fn build_row(args: TurnRowArgs<'_>) -> TurnStatRow {
|
||||
|
|
|
|||
|
|
@ -108,8 +108,9 @@ impl TurnFiles {
|
|||
|
||||
/// Drop the MCP config blob claude reads from `--mcp-config <path>`.
|
||||
/// `socket` is the hyperhive per-container socket (forwarded to the child
|
||||
/// as `--socket <path>`). The MCP subcommand is always `mcp` on the single
|
||||
/// `hive` binary resolved from `/proc/self/exe`.
|
||||
/// as `--socket <path>`). The MCP server is the `hive-agent-mcp` binary
|
||||
/// installed next to the running `hive-agent` (resolved as a sibling of
|
||||
/// `/proc/self/exe`; PATH-resolvable name as the fallback).
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
|
|
@ -120,7 +121,8 @@ pub async fn write_mcp_config(socket: &Path) -> Result<PathBuf> {
|
|||
let path = parent.join("claude-mcp-config.json");
|
||||
let exe = std::env::current_exe()
|
||||
.ok()
|
||||
.map_or_else(|| "hive".into(), |p| p.display().to_string());
|
||||
.and_then(|p| Some(p.parent()?.join("hive-agent-mcp")))
|
||||
.map_or_else(|| "hive-agent-mcp".into(), |p| p.display().to_string());
|
||||
let body = mcp_config::render_claude_config(&exe, socket);
|
||||
tokio::fs::write(&path, body).await?;
|
||||
tracing::info!(path = %path.display(), "wrote claude MCP config");
|
||||
|
|
@ -146,7 +148,7 @@ pub async fn write_system_prompt(socket: &Path, label: &str) -> Result<PathBuf>
|
|||
/// `result_kind = "compacted"` in turn stats so the stats page can distinguish
|
||||
/// those turns. Both `Ok(true)` and `Ok(false)` are ack'd; the error cases
|
||||
/// each map to a distinct serve-loop action (see [`emit_turn_end`] and the
|
||||
/// `hive` serve loop).
|
||||
/// `hive-agent` serve loop).
|
||||
pub type TurnOutcome = std::result::Result<bool, TurnError>;
|
||||
|
||||
/// The ways a turn can end without a usable result. Each is deliberately *not*
|
||||
|
|
@ -287,7 +289,7 @@ pub fn make_session(bus: &Bus) -> AgentSession {
|
|||
/// the whole turn is retried a single time before bubbling `AuthFailed` to
|
||||
/// the serve loop (which parks for re-login).
|
||||
///
|
||||
/// Called once per turn by the `hive` serve loop, which owns the shared
|
||||
/// Called once per turn by the `hive-agent` serve loop, which owns the shared
|
||||
/// `session` ([`make_session`]) and threads it in.
|
||||
pub async fn drive_turn(
|
||||
prompt: &str,
|
||||
|
|
|
|||
|
|
@ -898,12 +898,13 @@ in
|
|||
streamable-http daemon on this loopback port instead of the
|
||||
default per-turn stdio child.
|
||||
|
||||
When null (the default) claude spawns a fresh `hive … mcp` stdio
|
||||
subprocess every turn — the shape that carries the per-turn MCP
|
||||
re-registration race (a resumed turn can emit its first tool call
|
||||
before that turn's async `initialize`/`tools-list` completes,
|
||||
When null (the default) claude spawns a fresh `hive-agent-mcp`
|
||||
stdio subprocess every turn — the shape that carries the per-turn
|
||||
MCP re-registration race (a resumed turn can emit its first tool
|
||||
call before that turn's async `initialize`/`tools-list` completes,
|
||||
stranding the agent with `No such tool`). When set, a long-lived
|
||||
`hive-mcp-http` systemd unit runs `hive mcp --http 127.0.0.1:<port>`
|
||||
`hive-mcp-http` systemd unit runs
|
||||
`hive-agent-mcp --http 127.0.0.1:<port>`
|
||||
and `render_claude_config` points claude at the stable
|
||||
`http://127.0.0.1:<port>/mcp` URL, which survives the per-turn
|
||||
claude re-spawn (and a host-side hive-c0re restart — each tool call
|
||||
|
|
@ -1846,7 +1847,7 @@ in
|
|||
# `render_claude_config`). Long-lived so claude reconnects to the
|
||||
# stable URL each turn instead of respawning + re-registering a stdio
|
||||
# subprocess (the per-turn MCP registration race). It dials the
|
||||
# control socket (`/run/hive/mcp.sock`, the `hive` binary default)
|
||||
# control socket (`/run/hive/mcp.sock`, the harness binaries' default)
|
||||
# fresh on every tool call, so a host-side hive-c0re restart is
|
||||
# transparent. `before = hive-ag3nt` so the URL is already listening
|
||||
# by the time the harness renders the first turn's config; the
|
||||
|
|
@ -1858,7 +1859,7 @@ in
|
|||
before = [ "hive-ag3nt.service" ];
|
||||
environment.RUST_LOG = "info";
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.hyperhive}/bin/hive mcp --http 127.0.0.1:${toString config.hyperhive.mcp.httpPort}";
|
||||
ExecStart = "${pkgs.hyperhive}/bin/hive-agent-mcp --http 127.0.0.1:${toString config.hyperhive.mcp.httpPort}";
|
||||
SyslogIdentifier = "hive-mcp-http";
|
||||
# `always` (not `on-failure`): this endpoint is load-bearing when
|
||||
# `httpPort` is set — a down window is total hyperhive-MCP loss with
|
||||
|
|
@ -1936,7 +1937,7 @@ in
|
|||
# appends /bin to every entry.
|
||||
systemd.services.hive-ag3nt =
|
||||
let
|
||||
binary = "hive";
|
||||
binary = "hive-agent";
|
||||
# OTEL is shipped declaratively via the managed claude settings
|
||||
# json (`environment.etc."claude-code/managed-settings.json"`,
|
||||
# `otelSettingsEnv` in the top-level let) — claude reads it for
|
||||
|
|
@ -1982,7 +1983,7 @@ in
|
|||
HIVE_GUI_VNC_PORT = toString config.hyperhive.gui.vncPort;
|
||||
};
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.hyperhive}/bin/${binary} serve";
|
||||
ExecStart = "${pkgs.hyperhive}/bin/${binary}";
|
||||
# Pin the journal identity to the binary name (otherwise systemd
|
||||
# derives SyslogIdentifier from the ExecStart basename).
|
||||
SyslogIdentifier = binary;
|
||||
|
|
|
|||
Loading…
Reference in a new issue