drop the manager socket, the no-op migrate branch and other ruth special cases
start_manager bound the same path register_agent binds. The migrate branch, hive-priv-sock::MANAGER_NAME and MANAGER_CONTAINER were no-ops or dead. The send allow-list docs promised a manager exemption the code never had. Tests that used ruth as the manager fixture use plain names.
This commit is contained in:
parent
df2394963a
commit
746c522f4e
16 changed files with 36 additions and 117 deletions
|
|
@ -336,8 +336,7 @@ Differences from sub-agents:
|
||||||
and `/var/lib/hyperhive/applied` → `/applied` (RO) so the root agent
|
and `/var/lib/hyperhive/applied` → `/applied` (RO) so the root agent
|
||||||
can `git fetch` deployed/failed/denied tags from any agent's
|
can `git fetch` deployed/failed/denied tags from any agent's
|
||||||
authoritative applied repo (see "Root-agent view of applied" below).
|
authoritative applied repo (see "Root-agent view of applied" below).
|
||||||
- `socket_server::start_manager` binds the root agent's socket,
|
- Its socket comes from the generic per-agent registration, in the same
|
||||||
pure transport with no dedicated helpers — it uses the same
|
|
||||||
per-agent runtime dir as any other agent (`/run/hyperhive/agents/ruth/`),
|
per-agent runtime dir as any other agent (`/run/hyperhive/agents/ruth/`),
|
||||||
not a special manager-only path.
|
not a special manager-only path.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -174,9 +174,7 @@ Each row carries enough context that the caller renders
|
||||||
it directly as a bulleted list, no follow-up fetch needed.
|
it directly as a bulleted list, no follow-up fetch needed.
|
||||||
|
|
||||||
`GetLooseEnds` takes no target — it always returns the caller's own
|
`GetLooseEnds` takes no target — it always returns the caller's own
|
||||||
rows, on both the agent and manager sockets alike (the manager, `ruth`,
|
rows. `Reminder` rows always carry `owner == self`.
|
||||||
is a normal agent here, just deployed automatically with different
|
|
||||||
default permissions). `Reminder` rows always carry `owner == self`.
|
|
||||||
|
|
||||||
Per-variant fields:
|
Per-variant fields:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use rmcp::schemars;
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
|
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
|
||||||
pub struct SendArgs {
|
pub struct SendArgs {
|
||||||
/// Logical agent name to deliver the message to (e.g. `"manager"`,
|
/// Logical agent name to deliver the message to (e.g. `"bob"`,
|
||||||
/// `"alice"`). `"operator"` is refused: reach the operator over matrix
|
/// `"alice"`). `"operator"` is refused: reach the operator over matrix
|
||||||
/// if you have it.
|
/// if you have it.
|
||||||
pub to: String,
|
pub to: String,
|
||||||
|
|
@ -106,7 +106,7 @@ pub struct SetStatusArgs {
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
|
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
|
||||||
pub struct GetAgentMetaArgs {
|
pub struct GetAgentMetaArgs {
|
||||||
/// Logical name of the agent to query (e.g. `"iris"`, `"manager"`).
|
/// Logical name of the agent to query (e.g. `"iris"`, `"bob"`).
|
||||||
/// Omit to query your own identity + status — replaces the
|
/// Omit to query your own identity + status — replaces the
|
||||||
/// previous `whoami` self-introspection tool.
|
/// previous `whoami` self-introspection tool.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,7 @@ impl AgentServer {
|
||||||
let log = format!("{args:?}");
|
let log = format!("{args:?}");
|
||||||
let to = args.to.clone();
|
let to = args.to.clone();
|
||||||
// Check per-agent allow-list (services.hyperhive.agent.allowedRecipients). When no
|
// Check per-agent allow-list (services.hyperhive.agent.allowedRecipients). When no
|
||||||
// policy file is present (e.g. manager containers) the check is a no-op.
|
// policy file is present the check is a no-op.
|
||||||
if let Err(refusal) = crate::send_allow::check_send_allowed(&to) {
|
if let Err(refusal) = crate::send_allow::check_send_allowed(&to) {
|
||||||
return run_tool_envelope("send", log, async move { refusal }).await;
|
return run_tool_envelope("send", log, async move { refusal }).await;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,7 @@
|
||||||
/// Where the NixOS module writes the per-agent send allow-list (see
|
/// Where the NixOS module writes the per-agent send allow-list (see
|
||||||
/// `nix/agent-modules/mcp.nix`). Empty list = unrestricted (the
|
/// `nix/agent-modules/mcp.nix`). Empty list = unrestricted (the
|
||||||
/// default). Non-empty list constrains `mcp__hyperhive__send`'s `to`
|
/// default). Non-empty list constrains `mcp__hyperhive__send`'s `to`
|
||||||
/// field; the manager is always implicitly permitted regardless of
|
/// field.
|
||||||
/// the list contents.
|
|
||||||
const SEND_ALLOW_PATH: &str = "/etc/hyperhive/send-allow.json";
|
const SEND_ALLOW_PATH: &str = "/etc/hyperhive/send-allow.json";
|
||||||
|
|
||||||
/// Enforce the per-agent send allow-list. Returns `Ok` when the
|
/// Enforce the per-agent send allow-list. Returns `Ok` when the
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,8 @@ to track it:
|
||||||
- **`agent_config/`** — per-agent registries (permissions,
|
- **`agent_config/`** — per-agent registries (permissions,
|
||||||
resource limits, topology).
|
resource limits, topology).
|
||||||
- **`stats/`** — dashboard metrics aggregation + OTEL export.
|
- **`stats/`** — dashboard metrics aggregation + OTEL export.
|
||||||
- **`socket_server/`** — the unix-socket request server shared by
|
- **`socket_server/`** — the unix-socket request server for the
|
||||||
per-agent + manager sockets.
|
per-agent sockets.
|
||||||
- **`forge/`** — optional Forgejo wiring (`docs/integrations/forge.md`).
|
- **`forge/`** — optional Forgejo wiring (`docs/integrations/forge.md`).
|
||||||
- **`coordinator.rs`** — top-level wiring for `serve`.
|
- **`coordinator.rs`** — top-level wiring for `serve`.
|
||||||
- **`meta.rs`**, **`migrate.rs`** — the meta flake + schema/state
|
- **`meta.rs`**, **`migrate.rs`** — the meta flake + schema/state
|
||||||
|
|
|
||||||
|
|
@ -165,14 +165,11 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn apply_reconcile_adds_missing_live_agent() {
|
fn apply_reconcile_adds_missing_live_agent() {
|
||||||
let live = vec![
|
let live = vec!["alice".to_owned(), "newbie".to_owned()];
|
||||||
crate::lifecycle::MANAGER_NAME.to_owned(),
|
|
||||||
"newbie".to_owned(),
|
|
||||||
];
|
|
||||||
let (next, changed) = apply_reconcile(&BTreeSet::new(), &live, &[]);
|
let (next, changed) = apply_reconcile(&BTreeSet::new(), &live, &[]);
|
||||||
assert!(changed);
|
assert!(changed);
|
||||||
assert!(next.contains("newbie"));
|
assert!(next.contains("newbie"));
|
||||||
assert!(next.contains(crate::lifecycle::MANAGER_NAME));
|
assert!(next.contains("alice"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -296,7 +296,6 @@ async fn reload_gateway_nginx() {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::lifecycle::MANAGER_NAME;
|
|
||||||
|
|
||||||
// ── legacy mode (frontend_dir = None) ──────────────────────────────
|
// ── legacy mode (frontend_dir = None) ──────────────────────────────
|
||||||
|
|
||||||
|
|
@ -309,13 +308,10 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn render_includes_manager() {
|
fn render_includes_every_agent() {
|
||||||
let names: Vec<String> = [MANAGER_NAME, "iris"]
|
let names: Vec<String> = ["alice", "iris"].iter().map(|s| (*s).to_owned()).collect();
|
||||||
.iter()
|
|
||||||
.map(|s| (*s).to_owned())
|
|
||||||
.collect();
|
|
||||||
let body = render(&names, None);
|
let body = render(&names, None);
|
||||||
assert!(body.contains(&format!("/agent/{MANAGER_NAME}/")));
|
assert!(body.contains("/agent/alice/"));
|
||||||
assert!(body.contains("/agent/iris/"));
|
assert!(body.contains("/agent/iris/"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -461,13 +457,10 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn split_mode_includes_manager() {
|
fn split_mode_includes_every_agent() {
|
||||||
let names: Vec<String> = [MANAGER_NAME, "iris"]
|
let names: Vec<String> = ["alice", "iris"].iter().map(|s| (*s).to_owned()).collect();
|
||||||
.iter()
|
|
||||||
.map(|s| (*s).to_owned())
|
|
||||||
.collect();
|
|
||||||
let body = render(&names, Some(FAKE_FRONTEND));
|
let body = render(&names, Some(FAKE_FRONTEND));
|
||||||
assert!(body.contains(&format!("/agent/{MANAGER_NAME}/")));
|
assert!(body.contains("/agent/alice/"));
|
||||||
assert!(body.contains("/agent/iris/"));
|
assert!(body.contains("/agent/iris/"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,12 +34,6 @@ pub const MAX_AGENT_NAME: usize = 9;
|
||||||
/// Logical name of the manager agent (broker recipient, state-dir key,
|
/// Logical name of the manager agent (broker recipient, state-dir key,
|
||||||
/// meta flake attribute). All persistent state lives under `ruth/`.
|
/// meta flake attribute). All persistent state lives under `ruth/`.
|
||||||
pub const MANAGER_NAME: &str = "ruth";
|
pub const MANAGER_NAME: &str = "ruth";
|
||||||
/// Container name of the manager. Uses the same `h-` prefix as sub-agents
|
|
||||||
/// so `nixos-container list` output is uniform and the list filter is
|
|
||||||
/// a single `starts_with(AGENT_PREFIX)` check. Logical name → container
|
|
||||||
/// name: `ruth` → `h-ruth`.
|
|
||||||
pub const MANAGER_CONTAINER: &str = "h-ruth";
|
|
||||||
|
|
||||||
/// Mount point of the per-agent runtime directory inside the container.
|
/// Mount point of the per-agent runtime directory inside the container.
|
||||||
pub const CONTAINER_RUNTIME_MOUNT: &str = "/run/hive";
|
pub const CONTAINER_RUNTIME_MOUNT: &str = "/run/hive";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
//! Loose-ends aggregator. Assembles a `Vec<LooseEnd>` for a single agent
|
//! Loose-ends aggregator. Assembles a `Vec<LooseEnd>` for a single agent
|
||||||
//! (`for_agent`) — always the caller's own. `Request::GetLooseEnds` from
|
//! (`for_agent`) — always the caller's own. `Request::GetLooseEnds` from
|
||||||
//! either the agent or manager socket lands here. Reminders and todos are
|
//! an agent socket lands here. Reminders and todos are
|
||||||
//! agent-local (in-container `hive-agent` stores) and never sourced from
|
//! agent-local (in-container `hive-agent` stores) and never sourced from
|
||||||
//! here.
|
//! here.
|
||||||
//!
|
//!
|
||||||
|
|
|
||||||
|
|
@ -267,7 +267,6 @@ async fn cmd_serve(
|
||||||
// Copy value first for the dashboard + knowledge-webhook tasks.
|
// Copy value first for the dashboard + knowledge-webhook tasks.
|
||||||
let dashboard_port = env.dashboard_port;
|
let dashboard_port = env.dashboard_port;
|
||||||
let coord = Arc::new(Coordinator::open(&db, env, model_prices, build_slots)?);
|
let coord = Arc::new(Coordinator::open(&db, env, model_prices, build_slots)?);
|
||||||
socket_server::start_manager(coord.clone())?;
|
|
||||||
// Idempotent pre-flight: rewrite pre-meta-layout applied
|
// Idempotent pre-flight: rewrite pre-meta-layout applied
|
||||||
// repos, ensure proposed repos carry the `applied`
|
// repos, ensure proposed repos carry the `applied`
|
||||||
// remote, bootstrap the meta repo, repoint containers at
|
// remote, bootstrap the meta repo, repoint containers at
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ use anyhow::{Context, Result};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
use crate::coordinator::Coordinator;
|
use crate::coordinator::Coordinator;
|
||||||
use crate::lifecycle::{self, AGENT_PREFIX, MANAGER_CONTAINER, MANAGER_NAME};
|
use crate::lifecycle::{self, AGENT_PREFIX};
|
||||||
use crate::meta;
|
use crate::meta;
|
||||||
|
|
||||||
const KILL_SWITCH: &str = "HIVE_SKIP_META_MIGRATION";
|
const KILL_SWITCH: &str = "HIVE_SKIP_META_MIGRATION";
|
||||||
|
|
@ -157,11 +157,7 @@ async fn enumerate_agents() -> Result<Vec<hive_types::Ident>> {
|
||||||
Ok(containers
|
Ok(containers
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|c| {
|
.filter_map(|c| {
|
||||||
let name = if c == MANAGER_CONTAINER {
|
let name = c.strip_prefix(AGENT_PREFIX)?;
|
||||||
MANAGER_NAME
|
|
||||||
} else {
|
|
||||||
c.strip_prefix(AGENT_PREFIX)?
|
|
||||||
};
|
|
||||||
hive_types::Ident::parse(name).ok()
|
hive_types::Ident::parse(name).ok()
|
||||||
})
|
})
|
||||||
.collect())
|
.collect())
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,8 @@
|
||||||
//! Unix-socket request server, shared by the per-agent sockets and the
|
//! Unix-socket request server for the per-agent sockets. The socket file's
|
||||||
//! (pure-transport) manager socket. The socket file's existence on disk
|
//! existence on disk authenticates the caller: connecting to
|
||||||
//! authenticates the caller: connecting to `<.../agents/foo/mcp.sock>` means
|
//! `<.../agents/foo/mcp.sock>` means you are `foo`. There is no privilege
|
||||||
//! you are `foo`; the manager socket simply serves as `ruth`. There is no
|
//! flag; [`serve`] / [`dispatch`] derive authority uniformly from the caller's
|
||||||
//! privilege flag — both transports run the same [`serve`] / [`dispatch`]
|
//! identity. An agent-targeting verb may name any agent.
|
||||||
//! code, and authority derives uniformly from the caller's identity. An
|
|
||||||
//! agent-targeting verb may name any agent, so `ruth` reaches every agent
|
|
||||||
//! exactly the way every other agent does, not via any hardcoded name match.
|
|
||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
@ -13,7 +10,6 @@ use std::sync::Arc;
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use hive_core_agent_sock::{Request, Response};
|
use hive_core_agent_sock::{Request, Response};
|
||||||
use hive_sh4re::inbox::Message;
|
use hive_sh4re::inbox::Message;
|
||||||
use hive_sh4re::manager::MANAGER_AGENT;
|
|
||||||
use tokio::io::{AsyncBufRead, AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
|
use tokio::io::{AsyncBufRead, AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
|
||||||
use tokio::net::{UnixListener, UnixStream};
|
use tokio::net::{UnixListener, UnixStream};
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
|
|
@ -77,48 +73,6 @@ pub fn start(agent: &str, socket_path: &Path, coord: Arc<Coordinator>) -> Result
|
||||||
Ok(AgentSocket { path, handle })
|
Ok(AgentSocket { path, handle })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Bind + serve the manager socket. This is now **pure transport**: it grants
|
|
||||||
/// no authority of its own — it just serves requests as `agent = MANAGER_AGENT`
|
|
||||||
/// ("ruth"), and ruth's reach comes entirely from the permissions it
|
|
||||||
/// holds, identical to connecting on a per-agent socket — ruth
|
|
||||||
/// uses the standard per-agent runtime dir + socket, with no dedicated helpers.
|
|
||||||
pub fn start_manager(coord: Arc<Coordinator>) -> Result<()> {
|
|
||||||
use std::os::unix::fs::PermissionsExt as _;
|
|
||||||
let dir = crate::paths::agent_runtime_dir(crate::lifecycle::MANAGER_NAME);
|
|
||||||
std::fs::create_dir_all(&dir)
|
|
||||||
.with_context(|| format!("create manager dir {}", dir.display()))?;
|
|
||||||
let socket = Coordinator::socket_path(crate::lifecycle::MANAGER_NAME);
|
|
||||||
if socket.exists() {
|
|
||||||
std::fs::remove_file(&socket).context("remove stale manager socket")?;
|
|
||||||
}
|
|
||||||
let listener = UnixListener::bind(&socket)
|
|
||||||
.with_context(|| format!("bind manager socket {}", socket.display()))?;
|
|
||||||
// 0666 so the in-container root user (non-root) can connect; the bind
|
|
||||||
// source dir is manager-only on host (see the per-agent socket above).
|
|
||||||
std::fs::set_permissions(&socket, std::fs::Permissions::from_mode(0o666))
|
|
||||||
.with_context(|| format!("chmod manager socket {}", socket.display()))?;
|
|
||||||
tracing::info!(socket = %socket.display(), "manager socket listening");
|
|
||||||
|
|
||||||
tokio::spawn(async move {
|
|
||||||
let err = accept_until_fatal(
|
|
||||||
MANAGER_AGENT,
|
|
||||||
|| async { listener.accept().await.map(|(stream, _)| stream) },
|
|
||||||
|stream| {
|
|
||||||
let coord = coord.clone();
|
|
||||||
tokio::spawn(async move {
|
|
||||||
// Pure transport: serve as `ruth`, no privilege grant.
|
|
||||||
if let Err(e) = serve(stream, MANAGER_AGENT.to_owned(), coord).await {
|
|
||||||
tracing::warn!(error = ?e, "manager connection failed");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
exit_on_dead_listener(MANAGER_AGENT, &err);
|
|
||||||
});
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pause after an accept error that is not about a single connection. EMFILE
|
/// Pause after an accept error that is not about a single connection. EMFILE
|
||||||
/// and ENFILE leave the listener readable, so retrying at once would spin until
|
/// and ENFILE leave the listener readable, so retrying at once would spin until
|
||||||
/// some fd is closed. Same policy as axum's `serve`, which the dashboard runs.
|
/// some fd is closed. Same policy as axum's `serve`, which the dashboard runs.
|
||||||
|
|
@ -164,9 +118,9 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Exit on a listener that can no longer accept. A running hive-c0re re-binds
|
/// Exit on a listener that can no longer accept. A running hive-c0re re-binds
|
||||||
/// an agent socket only on that agent's next start, and the manager socket
|
/// an agent socket only on that agent's next start; on restart
|
||||||
/// never; on restart (`Restart = "on-failure"` on the unit) `start_manager` and
|
/// (`Restart = "on-failure"` on the unit) `mcp_sockets::sync_on_start` re-binds
|
||||||
/// `mcp_sockets::sync_on_start` re-bind every one.
|
/// every one.
|
||||||
fn exit_on_dead_listener(agent: &str, e: &std::io::Error) -> ! {
|
fn exit_on_dead_listener(agent: &str, e: &std::io::Error) -> ! {
|
||||||
tracing::error!(
|
tracing::error!(
|
||||||
%agent,
|
%agent,
|
||||||
|
|
@ -176,7 +130,7 @@ fn exit_on_dead_listener(agent: &str, e: &std::io::Error) -> ! {
|
||||||
std::process::exit(1)
|
std::process::exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Longest request line accepted on the agent and manager sockets, newline
|
/// Longest request line accepted on the agent sockets, newline
|
||||||
/// included. The largest request a client sends is an `OperatorMsg` from the
|
/// included. The largest request a client sends is an `OperatorMsg` from the
|
||||||
/// agent web UI's `/send` (`hive-agent/src/web_ui/actions.rs:21`), whose body
|
/// agent web UI's `/send` (`hive-agent/src/web_ui/actions.rs:21`), whose body
|
||||||
/// axum's default limit caps at 2 MiB; JSON escaping can roughly double that to
|
/// axum's default limit caps at 2 MiB; JSON escaping can roughly double that to
|
||||||
|
|
@ -286,8 +240,7 @@ pub(crate) fn recv_timeout(wait_seconds: Option<u64>) -> std::time::Duration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unified dispatch for every socket connection — per-agent sockets and the
|
/// Unified dispatch for every socket connection. There is no privilege bit;
|
||||||
/// (now pure-transport) manager socket alike. There is no privilege bit;
|
|
||||||
/// authority derives uniformly from the caller's identity.
|
/// authority derives uniformly from the caller's identity.
|
||||||
async fn dispatch(
|
async fn dispatch(
|
||||||
req: &hive_core_agent_sock::Request,
|
req: &hive_core_agent_sock::Request,
|
||||||
|
|
|
||||||
|
|
@ -209,7 +209,6 @@ pub fn spawn_poll() {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::lifecycle::MANAGER_NAME;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn socket_path_for_uses_subdir_layout() {
|
fn socket_path_for_uses_subdir_layout() {
|
||||||
|
|
@ -235,13 +234,13 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn build_map_includes_manager() {
|
fn build_map_includes_every_agent() {
|
||||||
let names: Vec<String> = ["iris", MANAGER_NAME, "argus"]
|
let names: Vec<String> = ["iris", "alice", "argus"]
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| (*s).to_owned())
|
.map(|s| (*s).to_owned())
|
||||||
.collect();
|
.collect();
|
||||||
let map = build_map_with(&names, |_| true);
|
let map = build_map_with(&names, |_| true);
|
||||||
assert!(map.contains_key(MANAGER_NAME));
|
assert!(map.contains_key("alice"));
|
||||||
assert!(map.contains_key("iris"));
|
assert!(map.contains_key("iris"));
|
||||||
assert!(map.contains_key("argus"));
|
assert!(map.contains_key("argus"));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,6 @@ pub const PRIV_SOCK: &str = "/run/hive/priv.sock";
|
||||||
/// exactly the failure mode a shared constant exists to prevent.
|
/// exactly the failure mode a shared constant exists to prevent.
|
||||||
pub const PAUSED_MARKER_FILE: &str = "paused";
|
pub const PAUSED_MARKER_FILE: &str = "paused";
|
||||||
|
|
||||||
/// Manager logical agent name. The manager's system container name is
|
|
||||||
/// `h-ruth` (same `h-` prefix convention as every other agent).
|
|
||||||
pub const MANAGER_NAME: &str = "ruth";
|
|
||||||
|
|
||||||
/// Sub-agent container prefix. System container name = `h-<agent_name>`.
|
/// Sub-agent container prefix. System container name = `h-<agent_name>`.
|
||||||
pub const AGENT_PREFIX: &str = "h-";
|
pub const AGENT_PREFIX: &str = "h-";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,23 +27,19 @@ in
|
||||||
default = [ ];
|
default = [ ];
|
||||||
example = [
|
example = [
|
||||||
"alice"
|
"alice"
|
||||||
"manager"
|
"bob"
|
||||||
];
|
];
|
||||||
description = ''
|
description = ''
|
||||||
Names this agent is allowed to `send` to via
|
Names this agent is allowed to `send` to via
|
||||||
`mcp__hyperhive__send`. Empty list (the default) means
|
`mcp__hyperhive__send`. Empty list (the default) means
|
||||||
unrestricted — the agent can message any peer or the
|
unrestricted — the agent can message any peer. A non-empty list
|
||||||
manager. Non-empty list constrains the
|
allows only the listed names; anything else returns an error
|
||||||
surface: only the listed names + the manager (always
|
|
||||||
allowed) get through; anything else returns an error
|
|
||||||
string to claude without touching the broker.
|
string to claude without touching the broker.
|
||||||
`operator` is refused either way: agents reach the operator
|
`operator` is refused either way: agents reach the operator
|
||||||
over matrix.
|
over matrix.
|
||||||
|
|
||||||
Useful for sandboxing untrusted sub-agents — set
|
Useful for sandboxing untrusted sub-agents — set
|
||||||
`[ "manager" ]` to scope them to manager-only chatter.
|
`[ "alice" ]` to scope one to chatter with alice.
|
||||||
The manager itself is always exempt; this option only
|
|
||||||
affects sub-agent `send`.
|
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue