remove hive-agent-wake — no shipped consumer

This commit is contained in:
damocles 2026-07-25 20:05:32 +02:00
commit 2316287327
18 changed files with 25 additions and 295 deletions

View file

@ -38,8 +38,8 @@ hand-maintained per-file tree drifts out of sync with the code.
destroy|rebuild|restart|list|set-parent|…>`, `approvals <pending|
approve|deny>`, `forge`/`matrix`/`github`/`gateway` provisioning,
`choom`, `stop`/`start`, `wg`/`peer-config`.
- **`hive-agent/`**, **`hive-agent-mcp/`**, **`hive-agent-wake/`**
in-container harness, three sibling crates for every agent (not a
- **`hive-agent/`**, **`hive-agent-mcp/`** —
in-container harness, two sibling crates for every agent (not a
single `hive-ag3nt/` dir — that's the runtime/binary-family nickname,
not a directory).
- **`hive-agent/`** — the serve-loop binary: turn-loop *policy* layer
@ -50,8 +50,6 @@ hand-maintained per-file tree drifts out of sync with the code.
streamable-http listener, `hive-mcp-http` systemd unit) + its claude
launch-config layer (tool-group/capability → `--allowedTools`,
`--mcp-config` render).
- **`hive-agent-wake/`** — small external wake CLI for extra MCP
servers/helpers to nudge claude on external events.
- **`hive-claude/`** — reusable, app-agnostic driver for headless
`claude --print`: spawns the CLI, streams + classifies stream-json,
parses per-turn `Telemetry`, and drives a durable self-compacting

14
Cargo.lock generated
View file

@ -1574,20 +1574,6 @@ dependencies = [
"serde",
]
[[package]]
name = "hive-agent-wake"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"hive-core-agent-sock",
"serde",
"serde_json",
"tokio",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "hive-bash-mcp"
version = "0.1.0"

View file

@ -5,7 +5,6 @@ members = [
"hive-agent-mcp",
"hive-agent-sock",
"hive-core-agent-sock",
"hive-agent-wake",
"hive-bash-mcp",
"hive-c0re",
"hive-claude",

View file

@ -213,8 +213,8 @@ nspawn agent. Open questions, not yet wired:
## Harness systemd unit shape
One harness serve binary (`hive-agent`, with its `hive-agent-mcp` /
`hive-agent-wake` siblings), one shared `nix/agent-modules/` tree, one
One harness serve binary (`hive-agent`, with its `hive-agent-mcp`
sibling), one shared `nix/agent-modules/` tree, one
service unit (`systemd.services.hive-agent`) 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

View file

@ -52,7 +52,7 @@ agents) runs:
## Harness binary shape
Three sibling binaries out of the one `hive-ag3nt` crate, all
Two 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
@ -66,10 +66,6 @@ refused by the standard agent socket regardless of who sends them.)
8790); claude connects to its URL via `--mcp-config`. HTTP is the sole
transport — no per-turn stdio child (eliminates the re-registration
race).
- `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

View file

@ -160,18 +160,14 @@ at_unix_timestamp?)`, `request_next_turn()`.
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-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
co-process daemons (matrix bridge, webhook listeners, scrapers).
- **Speak the wire protocol directly** — JSON-line over the
unix socket: `{"cmd":"wake","from":"matrix","body":"new dm
from @alice"}\n`. Same shape as any other `AgentRequest`;
see `hive-sh4re::AgentRequest::Wake`.
socket at `/run/hive/mcp.sock`. Speak the wire protocol directly —
JSON-line over the unix socket: `{"cmd":"wake","from":"matrix","body":
"new dm from @alice"}\n`. Same shape as any other `AgentRequest`; see
`hive-sh4re::AgentRequest::Wake`. (An earlier `hive-agent-wake` CLI
wrapper existed for this but was removed — no shipped co-process
daemon actually shelled out to it; every one that wakes the harness
(matrix, bash) dials the socket directly, so the raw protocol is the
only path now.)
The wake event lands in the broker as `{from:<label>,
to:<agent>, body}`, waking whatever `recv` call the harness

View file

@ -86,7 +86,6 @@
inherit (self.packages.${pkgs.stdenv.hostPlatform.system})
hive-agent
hive-agent-mcp
hive-agent-wake
hive-bash-daemon
hive-forge
hive-matrix-daemon

View file

@ -30,8 +30,5 @@ or their own daemon instead of living here.
harness's own `paths.rs` conventions.
Sibling of `hive-agent` (the serve loop that renders the
`--mcp-config` blob pointing here) and `hive-agent-wake` (the small
CLI extra MCP daemons use to nudge claude on external events, since
they don't have a tool surface of their own to call `remind`/`send`
through). Standalone bin crate so the always-on MCP server doesn't
need to link the whole turn-loop lib.
`--mcp-config` blob pointing here). Standalone bin crate so the
always-on MCP server doesn't need to link the whole turn-loop lib.

View file

@ -4,8 +4,7 @@
//! hyperhive broker. claude reconnects to the stable URL each turn via
//! `--mcp-config`, avoiding the per-turn re-registration race. HTTP is the
//! sole transport — there is no stdio mode. Sibling of `hive-agent` (the
//! serve loop that renders the `--mcp-config` blob pointing here) and
//! `hive-agent-wake`.
//! serve loop that renders the `--mcp-config` blob pointing here).
//!
//! Standalone bin crate: the MCP surface (`mcp/`) plus its small support
//! modules (socket client, send allow-list, loose-end scanner, path

View file

@ -1,22 +0,0 @@
[package]
name = "hive-agent-wake"
version.workspace = true
edition.workspace = true
readme = "README.md"
[[bin]]
name = "hive-agent-wake"
path = "src/main.rs"
[lints]
workspace = true
[dependencies]
anyhow.workspace = true
clap.workspace = true
hive-core-agent-sock.workspace = true
serde.workspace = true
serde_json.workspace = true
tokio.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true

View file

@ -1,25 +0,0 @@
# hive-agent-wake
A tiny CLI that injects a wake-up event into an agent's own harness
inbox, so the next turn fires with a given `--from`/`--body`. That's
the whole crate.
## When to use it
Reach for this when you're wiring up an `extraMcpServers` daemon
(scraper, webhook listener, custom integration) that needs to nudge
claude on some external event — a new item arrived, a long job
finished, whatever. The built-in daemons (matrix, bash) don't use
this: they talk to the per-agent MCP socket directly instead. This CLI
exists so a helper author only needs one small, dependency-light crate
to get the same effect without linking `hive-agent-mcp` or the full
`hive-agent` harness lib.
## Shape
One binary, `src/main.rs`: parses `--socket` (defaults to
`/run/hive/mcp.sock`, the per-agent MCP socket bind-mounted from the
host), `--from`, and `--body` (pass `-` to read the body from stdin),
dials the socket with `hive-core-agent-sock`'s `Request`/`Response`
wire types, sends a `Wake` request, and exits. It carries its own copy
of the retrying request client rather than depending on `hive-agent-mcp`'s.

View file

@ -1,189 +0,0 @@
//! 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.
//!
//! Standalone bin crate: it dials the per-agent MCP socket directly and
//! carries its own copy of the retrying request client (below), so it
//! does not link the whole `hive-agent` harness lib — a helper author
//! wiring up an `extraMcpServers` binary only needs this one small crate.
use std::path::PathBuf;
use anyhow::Result;
use clap::Parser;
use hive_core_agent_sock::{Request, Response};
/// Per-agent MCP socket, bind-mounted from the host into every container.
const DEFAULT_SOCKET: &str = "/run/hive/mcp.sock";
#[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: Response = client::request(
&cli.socket,
&Request::Wake {
from: cli.from,
body,
},
)
.await?;
match resp {
Response::Ok => Ok(()),
Response::Err { message } => anyhow::bail!("wake: {message}"),
other => anyhow::bail!("wake: unexpected response {other:?}"),
}
}
/// Self-contained retrying unix-socket JSON request client. A trimmed
/// copy of `hive_agent::client` (no `request_retried` variant — the wake
/// CLI never needs the retry-count) so this bin does not link the harness
/// lib. Keeping the retry matters: the socket can be briefly absent while
/// hive-c0re restarts under an operator redeploy, and a helper firing a
/// wake shouldn't spuriously fail then.
mod client {
use std::path::Path;
use std::time::Duration;
use anyhow::{Result, anyhow};
use serde::Serialize;
use serde::de::DeserializeOwned;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::net::UnixStream;
/// Backoff schedule between attempts. Five entries → up to 5 retries on
/// top of the initial attempt; total wall-clock cap = 2+4+8+16+30 = 60s.
/// Sized to ride out a hive-c0re restart (systemd usually has the unix
/// socket back inside ~5s) without the caller having to handle the
/// transient itself.
const RETRY_BACKOFFS_MS: &[u64] = &[2_000, 4_000, 8_000, 16_000, 30_000];
/// Send `req` over the unix socket and decode the single-line JSON
/// response, retrying transient connect/IO failures on the backoff
/// schedule above.
///
/// # Errors
///
/// Returns an error if the socket is unreachable after all retries, or
/// if serialization / deserialization of the request or response fails.
pub async fn request<Req, Resp>(socket: &Path, req: &Req) -> Result<Resp>
where
Req: Serialize + ?Sized,
Resp: DeserializeOwned,
{
let mut last_err: Option<anyhow::Error> = None;
// One attempt per backoff entry, plus a final attempt with no sleep
// after it — so `len + 1` tries, retrying only transient failures.
for attempt in 0..=RETRY_BACKOFFS_MS.len() {
match try_once::<Req, Resp>(socket, req).await {
Ok(resp) => return Ok(resp),
Err(RequestError::Fatal(e)) => return Err(e),
Err(RequestError::Transient(e)) => {
if let Some(&sleep_ms) = RETRY_BACKOFFS_MS.get(attempt) {
tracing::warn!(
attempt = attempt + 1,
sleep_ms,
error = %e,
"hive socket attempt failed; retrying"
);
tokio::time::sleep(Duration::from_millis(sleep_ms)).await;
}
last_err = Some(e);
}
}
}
// The final iteration always sets `last_err` on a transient failure.
Err(last_err.expect("a transient failure on the final attempt set last_err"))
}
/// Transient = connect / IO error worth a retry (server restart, broken
/// pipe). Fatal = serialization / deserialization / protocol error
/// where retrying would just repeat the same failure.
enum RequestError {
Transient(anyhow::Error),
Fatal(anyhow::Error),
}
async fn try_once<Req, Resp>(socket: &Path, req: &Req) -> Result<Resp, RequestError>
where
Req: Serialize + ?Sized,
Resp: DeserializeOwned,
{
let stream = match UnixStream::connect(socket).await {
Ok(stream) => stream,
Err(e) => {
// A refused or missing socket usually means hive-c0re is
// mid-restart (operator redeploy / rebuild) — the socket is
// recreated on its boot and the retry loop rides it out.
let restarting = matches!(
e.kind(),
std::io::ErrorKind::ConnectionRefused | std::io::ErrorKind::NotFound
);
let mut err =
anyhow::Error::new(e).context(format!("connect to {}", socket.display()));
if restarting {
err = err.context(
"hive-c0re may be restarting (e.g. an operator redeploy); \
the CLI already retried ~60s before surfacing this",
);
}
return Err(RequestError::Transient(err));
}
};
let (read, mut write) = stream.into_split();
let mut payload = serde_json::to_string(req).map_err(|e| RequestError::Fatal(e.into()))?;
payload.push('\n');
write
.write_all(payload.as_bytes())
.await
.map_err(|e| RequestError::Transient(e.into()))?;
write
.flush()
.await
.map_err(|e| RequestError::Transient(e.into()))?;
let mut reader = BufReader::new(read);
let mut line = String::new();
let read_bytes = reader
.read_line(&mut line)
.await
.map_err(|e| RequestError::Transient(e.into()))?;
if read_bytes == 0 || line.is_empty() {
return Err(RequestError::Transient(anyhow!(
"server closed connection without responding"
)));
}
serde_json::from_str(line.trim()).map_err(|e| RequestError::Fatal(e.into()))
}
}

View file

@ -38,7 +38,7 @@ tracing-subscriber.workspace = true
tempfile = "3"
# Single harness serve-loop binary: `hive-agent` (from `src/main.rs`).
# The sibling MCP server + external wake CLI are their own bin crates
# now (`hive-agent-mcp`, `hive-agent-wake`). Privilege boundary is
# enforced server-side at the socket (tool groups / manager surface).
# The sibling MCP server is its own bin crate now (`hive-agent-mcp`).
# Privilege boundary is enforced server-side at the socket (tool
# groups / manager surface).
# See `docs/turn-loop.md::Harness binary shape`.

View file

@ -46,7 +46,6 @@ map of the module tree.
- **`paths.rs`** — canonical path resolution for state/harness dirs and
the harness-local sqlite files.
Siblings: **`hive-agent-mcp`** (the MCP server this loop points claude
at every turn) and **`hive-agent-wake`** (external wake CLI for extra
MCP daemons). All three are described together in
Sibling: **`hive-agent-mcp`** (the MCP server this loop points claude
at every turn). Both are described together in
[`docs/turn-loop.md::Harness binary shape`](../docs/turn-loop.md).

View file

@ -1,8 +1,8 @@
//! 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. Siblings: `hive-agent-mcp` (the MCP server this
//! loop points claude at) and `hive-agent-wake` (external wake CLI).
//! generic and testable. Sibling: `hive-agent-mcp` (the MCP server this
//! loop points claude at).
//! Architecture lives in
//! [`docs/turn-loop.md::Harness binary shape`](../../../docs/turn-loop.md).
//!

View file

@ -176,13 +176,11 @@
# the harness execs (hive-agent{,-mcp}, hive-bash-daemon,
# hive-matrix-daemon, hive-matrix-mcp) are wired via their own
# ExecStart/command lines in the sibling modules — they don't need
# to be on PATH too. Only these two are actually looked up on PATH
# to be on PATH too. Only this one is actually looked up on PATH
# by claude/shell code inside the container:
# `hive-agent-wake` (external wake CLI, docs/turn-loop/mcp.md) and
# `hive-metric` (agent-emitted custom metrics CLI,
# docs/observability.md).
environment.systemPackages = [
config.hyperhive.packages.hive-agent-wake
config.hyperhive.packages.hive-metric
]
++ (with pkgs; [

View file

@ -11,7 +11,7 @@
description = ''
hyperhive package outputs consumed by the harness modules: the
per-binary daemon/CLI packages (`hive-agent`, `hive-agent-mcp`,
`hive-agent-wake`, `hive-bash-daemon`,
`hive-bash-daemon`,
`hive-forge`, `hive-matrix-daemon`,
`hive-metric`, `hive-screen-mcp`) plus the `assets`, `frontend` and
`reference-docs` trees. Wired by the flake's agent-base/ruth

View file

@ -26,7 +26,6 @@ let
hive-priv = "hyperhive privileged root helper";
hive-agent = "hyperhive in-container agent harness serve loop";
hive-agent-mcp = "hyperhive agent-surface MCP server";
hive-agent-wake = "hyperhive external wake CLI push a message into an agent's own inbox";
hive-bash-daemon = "hyperhive per-agent bash-task runner daemon (serves its MCP tools directly over streamable-http)";
hive-matrix-daemon = "hyperhive per-agent matrix-sdk daemon (serves its MCP tools directly over streamable-http)";
hive-metric = "hyperhive agent-emitted custom metrics CLI";