agent socket: external wake-up path for in-container MCP servers
new AgentRequest::Wake { from, body } drops a message into
this agent's inbox via the per-agent socket. matrix-style MCP
servers can use it when they receive an external event
(matrix message, webhook, scrape result) to nudge claude
into running a turn. broker.send wakes whatever Recv is
currently long-polling, the harness picks the message up,
formats a wake prompt with the caller's chosen from label
('matrix: new dm', 'webhook: deploy succeeded', etc.).
new `hive-ag3nt wake --from <label> --body <text>` subcommand
on the harness binary so MCP servers can shell out instead of
implementing the line-JSON protocol themselves; body=='-'
reads from stdin for multi-line / quoting-friendly payloads.
identity = socket: anything that can connect to /run/hive/mcp
.sock is implicitly trusted to inject. that's fine because the
bind-mount is the agent's own container; no new auth surface
opens up.
docs/turn-loop.md gets a new 'Waking the agent from inside
the container' section pointing at both paths (CLI + raw
JSON).
This commit is contained in:
parent
96cb9f84c9
commit
90df2106bf
4 changed files with 79 additions and 0 deletions
|
|
@ -112,6 +112,33 @@ it as a stdio child via `--mcp-config`. The hyperhive socket name is
|
|||
answer routes back to the asker's own inbox as
|
||||
`HelperEvent::OperatorAnswered` via `coord.notify_agent`.
|
||||
|
||||
### Waking the agent from inside the container
|
||||
|
||||
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-ag3nt 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.
|
||||
|
||||
- **Speak the wire protocol directly** — JSON-line over the
|
||||
unix socket: `{"cmd":"wake","from":"matrix","body":"new dm
|
||||
from @alice"}\n`. Same shape any other AgentRequest uses;
|
||||
see `hive-sh4re::AgentRequest::Wake`.
|
||||
|
||||
The wake event lands in the broker as `{from:<label>,
|
||||
to:<agent>, body}`, which wakes whatever `recv` call the
|
||||
harness is currently blocked on. Next turn fires with the
|
||||
wake prompt formed from that message — claude sees "from:
|
||||
matrix" (or whatever label) and reacts.
|
||||
|
||||
Identity = socket: anything that can connect to
|
||||
`/run/hive/mcp.sock` is implicitly trusted to inject these,
|
||||
which is fine because the bind-mount is the agent's own
|
||||
container only.
|
||||
|
||||
### Extra MCP servers (per-agent)
|
||||
|
||||
Each agent's NixOS config can declare additional MCP servers via
|
||||
|
|
|
|||
|
|
@ -33,6 +33,19 @@ enum Cmd {
|
|||
/// `--mcp-config`; tools dispatch through `/run/hive/mcp.sock` back into
|
||||
/// the hyperhive broker.
|
||||
Mcp,
|
||||
/// Inject a wake-up event into this agent's inbox so the next turn
|
||||
/// fires with the given body. Intended for extra MCP servers /
|
||||
/// helpers running inside the container (matrix bridge, scraper,
|
||||
/// webhook listener) that need to nudge claude on external events.
|
||||
/// `from` is the sender label that appears in the wake prompt
|
||||
/// (claude sees "from: matrix" etc.).
|
||||
Wake {
|
||||
#[arg(long)]
|
||||
from: String,
|
||||
/// Body of the wake message. Pass `-` to read from stdin.
|
||||
#[arg(long)]
|
||||
body: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
|
|
@ -94,6 +107,25 @@ async fn main() -> Result<()> {
|
|||
}
|
||||
}
|
||||
Cmd::Mcp => mcp::serve_agent_stdio(cli.socket).await,
|
||||
Cmd::Wake { from, body } => {
|
||||
// Read body from stdin if caller passed `-`. Same convention
|
||||
// many CLI tools use; keeps multi-line / shell-quoting
|
||||
// friction out of the body content.
|
||||
let body = if body == "-" {
|
||||
let mut buf = String::new();
|
||||
std::io::Read::read_to_string(&mut std::io::stdin(), &mut buf)?;
|
||||
buf
|
||||
} else {
|
||||
body
|
||||
};
|
||||
let resp: AgentResponse =
|
||||
client::request(&cli.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:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -141,6 +141,16 @@ async fn dispatch(req: &AgentRequest, agent: &str, coord: &Arc<Coordinator>) ->
|
|||
message: format!("{e:#}"),
|
||||
},
|
||||
},
|
||||
AgentRequest::Wake { from, body } => match broker.send(&Message {
|
||||
from: from.clone(),
|
||||
to: agent.to_owned(),
|
||||
body: body.clone(),
|
||||
}) {
|
||||
Ok(()) => AgentResponse::Ok,
|
||||
Err(e) => AgentResponse::Err {
|
||||
message: format!("{e:#}"),
|
||||
},
|
||||
},
|
||||
AgentRequest::Recent { limit } => match broker.recent_for(agent, *limit) {
|
||||
Ok(rows) => AgentResponse::Recent { rows },
|
||||
Err(e) => AgentResponse::Err {
|
||||
|
|
|
|||
|
|
@ -191,6 +191,16 @@ pub enum AgentRequest {
|
|||
/// per-agent equivalent of the old dashboard T4LK form, but scoped to
|
||||
/// the agent whose page the operator is on.
|
||||
OperatorMsg { body: String },
|
||||
/// Wake-up event injected from inside the container — typically an
|
||||
/// extra MCP server (matrix, scraper, webhook bridge) signalling
|
||||
/// that external work has arrived for this agent. Recipient is
|
||||
/// implicit (this agent); `from` is caller-chosen so the wake
|
||||
/// prompt can label the source ("matrix: new message in
|
||||
/// #general"). Identity = socket means anything that can connect
|
||||
/// to `/run/hive/mcp.sock` is implicitly trusted to inject these,
|
||||
/// which is fine: the bind-mount is restricted to the agent's
|
||||
/// own container.
|
||||
Wake { from: String, body: String },
|
||||
/// Last `limit` messages addressed to this agent, newest-first.
|
||||
/// Non-mutating — pulls from the broker without delivering. The
|
||||
/// per-agent web UI uses this to render its own inbox section.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue