diff --git a/CLAUDE.md b/CLAUDE.md index d8ce5e71..5b0d3719 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -123,18 +123,15 @@ hand-maintained per-file tree drifts out of sync with the code. + manager socket (`/run/hive/mcp.sock`), the protocol an agent's harness speaks to `hive-c0re`. Same split rationale as the two above; the shared payload types it references stay in `hive-sh4re`. -- **`hive-agent-sock/`** — what's common across the in-container "agent - plugins" (matrix/bash/subagent MCP daemons, `forge_notify`, any - user-configured MCP server). Wire types for the *in-agent* socket, served - by the harness to those producers — nothing restricts the `subsystem` set - on the todo ops, any producer can push its own. Carries the loose-ends-v2 - **todo** ops plus harness-local reminders. ⚠️ Distinct from +- **`hive-agent-sock/`** — wire types for the *in-agent* socket, served by + the harness to in-container producers — matrix / bash MCP daemons and + `forge_notify` are the built-in ones, but any user-configured MCP server + can push todos here too, nothing restricts the `subsystem` set. Carries + the loose-ends-v2 **todo** ops plus + harness-local reminders. ⚠️ Distinct from `hive-core-agent-sock` above: **this socket never leaves the container** and `hive-c0re` is not in the path at all — no broker round-trip, no - long-poll, no marker files. Also carries `extra_mcp`: the - `hyperhive.extraMcpServers` spec + parsing, shared by `hive-agent` and - `hive-subagent-mcp` — not a socket protocol, just the natural home since - both already depend on this crate. + long-poll, no marker files. - **`hive-types/`** — zero-dependency (bar serde) leaf crate holding the foundational newtypes, chiefly `Ident` (1–63 chars of `[a-z0-9-]`, constructed only via the validating parser). Lets every wire-type crate diff --git a/Cargo.lock b/Cargo.lock index 447ae62b..aa828666 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1705,8 +1705,6 @@ version = "0.1.0" dependencies = [ "hive-sh4re", "serde", - "serde_json", - "tracing", ] [[package]] diff --git a/docs/tools/subagent.md b/docs/tools/subagent.md index ec3a18ed..6f19d885 100644 --- a/docs/tools/subagent.md +++ b/docs/tools/subagent.md @@ -54,21 +54,3 @@ agent that needs a stable or non-default port. Own systemd unit, defined alongside the other per-agent MCP daemons in `nix/agent-modules/mcp.nix`. - -## MCP servers available to a subagent - -A subagent runs with `--strict-mcp-config` and no `--mcp-config` by -default — zero MCP servers, full stop; it falls back to claude's own -native tools (`Bash`, `WebFetch`, etc.), not the parent's `mcp__bash__*` / -`mcp__hyperhive__*` surface. Nothing implicit reaches it: the built-in -hyperhive surface (todos/messaging) isn't an `extraMcpServers` entry at -all, and the automatically injected `bash`/`subagent` entries default to excluded -too (a subagent can't spawn hive-bash tasks or its own nested subagents -unless an operator opts them in explicitly, same as anything else). - -Set `hyperhive.extraMcpServers..availableToSubagents = true` on a -specific entry to hand that one server to subagents as well — useful for, -say, a read-only lookup or scraper MCP a subagent's bounded, single-batch -task might need. `hive-subagent-mcp`'s `mcp_config` module renders the -opted-in subset into its own `--mcp-config` file per turn; an entry left -at the default `false` never appears there. diff --git a/docs/turn-loop/mcp.md b/docs/turn-loop/mcp.md index 90abcc89..64152412 100644 --- a/docs/turn-loop/mcp.md +++ b/docs/turn-loop/mcp.md @@ -13,10 +13,7 @@ entry (`type = "stdio" | "http"`, default `"stdio"`): `matrix` stays a stdio bridge, `bash` runs its own persistent streamable-http listener (`hive-bash-daemon`) — same reasoning as the built-in surface. The server name is `hyperhive`, so the tools land in claude as -`mcp__hyperhive__`. Each entry also has an `availableToSubagents` -toggle (default `false`) — see -[`docs/tools/subagent.md`](../tools/subagent.md#mcp-servers-available-to-a-subagent) -for what reaches a subagent's own `--mcp-config` and what doesn't. +`mcp__hyperhive__`. Tool groups (`HIVE_TOOL_GROUPS`) gate tool access. The default preset (`AGENT_DEFAULT`) includes `messaging`, `meta`, `inbox`, and diff --git a/hive-agent-sock/Cargo.toml b/hive-agent-sock/Cargo.toml index b0585ffd..0c6b82e7 100644 --- a/hive-agent-sock/Cargo.toml +++ b/hive-agent-sock/Cargo.toml @@ -9,6 +9,4 @@ workspace = true [dependencies] serde.workspace = true -serde_json.workspace = true -tracing.workspace = true hive-sh4re.workspace = true diff --git a/hive-agent-sock/README.md b/hive-agent-sock/README.md index 59ed906b..e133aad8 100644 --- a/hive-agent-sock/README.md +++ b/hive-agent-sock/README.md @@ -1,15 +1,14 @@ # hive-agent-sock -What's common across the in-container "agent plugins" — the matrix / bash / -subagent MCP daemons and `forge_notify` today, plus any user-configured MCP -server declared in an agent's `agent.nix`. Two things live here: +Wire types for the **in-agent socket** — the one the `hive-agent` harness serves +_inside_ the container to its local producers. The matrix / bash MCP daemons +and `forge_notify` are the _built-in_ producers that ship today, but any +user-configured MCP server declared in an agent's `agent.nix` can dial this +socket and push its own todos with an arbitrary `subsystem` marker — the +wire format doesn't restrict the set. Unlike the host-served sockets, this +one **never leaves the container**. -## Wire types for the in-agent socket - -The socket the `hive-agent` harness serves _inside_ the container to those -local producers. Any producer can dial it and push its own todos with an -arbitrary `subsystem` marker — the wire format doesn't restrict the set. -Unlike the host-served sockets, this one **never leaves the container**. +## What it carries - the loose-ends-v2 **todo** op family - the harness-local **reminder** op family @@ -17,23 +16,14 @@ Unlike the host-served sockets, this one **never leaves the container**. More in-agent request families may be added over time — the socket is deliberately named for the agent, not for the todos. -**Why in-container**: the harness owns the todo + reminder stores locally and -signals its own turn loop directly, so `hive-c0re` is not in either path: no -broker round-trip, no long-poll, no marker files. That locality is the -point — it's also what lets these stores travel with the agent for hive -portability. +## Why in-container -**Not to be confused with `hive-core-agent-sock`**: that crate is the -_host_-served core↔agent protocol on `/run/hive/mcp.sock` (the harness -talking out to `hive-c0re`). This socket is purely intra-container. +The harness owns the todo + reminder stores locally and signals its own turn +loop directly, so `hive-c0re` is not in either path: no broker round-trip, no +long-poll, no marker files. That locality is the point — it's also what lets +these stores travel with the agent for hive portability. -## `extra_mcp`: the `hyperhive.extraMcpServers` spec +## Not to be confused with `hive-core-agent-sock` -Not a socket protocol — a shared config type. Parses -`/etc/hyperhive/extra-mcp.json` (the nix-rendered `hyperhive.extraMcpServers` -option) and renders each entry into the shape claude expects in a -`--mcp-config` blob. Shared by `hive-agent` (the main session's servers) and -`hive-subagent-mcp` (the subagent-eligible subset, gated on -`availableToSubagents`) — both already depend on this crate for the socket -types above, so a new plugin-facing shared type belongs here rather than in -a crate of its own. +That crate is the _host_-served core↔agent protocol on `/run/hive/mcp.sock` +(the harness talking out to `hive-c0re`). This socket is purely intra-container. diff --git a/hive-agent-sock/src/extra_mcp.rs b/hive-agent-sock/src/extra_mcp.rs deleted file mode 100644 index 57681c57..00000000 --- a/hive-agent-sock/src/extra_mcp.rs +++ /dev/null @@ -1,186 +0,0 @@ -//! Shared spec for `hyperhive.extraMcpServers` entries: the on-disk JSON -//! shape the nix module (`nix/agent-modules/mcp.nix`) renders to -//! `/etc/hyperhive/extra-mcp.json`, plus the parsing and per-entry -//! JSON-rendering logic every consumer needs. Lives in `hive-agent-sock` -//! rather than a crate of its own: this crate is already what's common -//! across the in-container "agent plugins" (matrix/bash/subagent MCP -//! daemons, `forge_notify`) — the todo-socket wire types above, and now -//! this — so a new plugin-facing shared type belongs here rather than in -//! a fresh single-purpose crate. - -use std::collections::BTreeMap; -use std::path::Path; - -/// Where the NixOS module writes the per-agent extra-MCP spec. Each entry -/// becomes an additional `mcpServers.` block in a rendered claude -/// `--mcp-config`. -pub const EXTRA_MCP_PATH: &str = "/etc/hyperhive/extra-mcp.json"; - -/// An extra MCP server declared via `hyperhive.extraMcpServers`. Two -/// transports: `Stdio` (the consumer spawns `command` fresh each turn, talks -/// JSON-RPC over its stdin/stdout) and `Http` (point claude at a long-lived -/// streamable-http `url` instead — no per-turn spawn, no re-registration -/// race). Internally tagged on the nix-rendered `type` field; unrecognised -/// fields for the inactive variant (e.g. `command` on an `Http` entry) are -/// ignored by serde's default struct deserialization. -#[derive(Debug, Clone, serde::Deserialize)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum ExtraMcpServer { - Stdio { - command: String, - #[serde(default)] - args: Vec, - #[serde(default)] - env: BTreeMap, - #[serde(default = "default_allowed_tools", rename = "allowedTools")] - allowed_tools: Vec, - #[serde(default, rename = "availableToSubagents")] - available_to_subagents: bool, - }, - Http { - url: String, - #[serde(default = "default_allowed_tools", rename = "allowedTools")] - allowed_tools: Vec, - #[serde(default, rename = "availableToSubagents")] - available_to_subagents: bool, - }, -} - -impl ExtraMcpServer { - #[must_use] - pub fn allowed_tools(&self) -> &[String] { - match self { - Self::Stdio { allowed_tools, .. } | Self::Http { allowed_tools, .. } => allowed_tools, - } - } - - /// Whether this entry is opted in to subagent sessions - /// (`hyperhive.extraMcpServers..availableToSubagents`, default - /// `false`). Subagents run `--strict-mcp-config` with no discovery, so - /// this flag is the only path an entry reaches a subagent's own - /// `--mcp-config` at all — see `hive-subagent-mcp`'s `mcp_config` - /// module. - #[must_use] - pub fn available_to_subagents(&self) -> bool { - match self { - Self::Stdio { - available_to_subagents, - .. - } - | Self::Http { - available_to_subagents, - .. - } => *available_to_subagents, - } - } - - /// Render this entry into the shape claude expects under - /// `mcpServers.` in a `--mcp-config` blob. `state_dir` is injected - /// as `HYPERHIVE_STATE_DIR` into a stdio entry's env when the entry - /// doesn't already set it, so an extra stdio MCP server can resolve the - /// agent's durable state dir without its author hard-coding it; an http - /// entry has no child-process env to inject into (the daemon behind the - /// URL resolves its own state dir independently). - #[must_use] - pub fn to_json_entry(&self, state_dir: &Path) -> serde_json::Value { - match self { - Self::Stdio { - command, args, env, .. - } => { - let mut env = env.clone(); - env.entry("HYPERHIVE_STATE_DIR".to_owned()) - .or_insert_with(|| state_dir.display().to_string()); - serde_json::json!({ "command": command, "args": args, "env": env }) - } - Self::Http { url, .. } => serde_json::json!({ "type": "http", "url": url }), - } - } -} - -fn default_allowed_tools() -> Vec { - vec!["*".to_owned()] -} - -/// Read + parse the extra-MCP spec from [`EXTRA_MCP_PATH`]. Returns an empty -/// map when the file is missing or unparsable (the agent has none -/// configured, or the file is malformed — both cases degrade to "no extra -/// servers"). -#[must_use] -pub fn load_extra_mcp() -> BTreeMap { - let Ok(raw) = std::fs::read_to_string(EXTRA_MCP_PATH) else { - return BTreeMap::new(); - }; - serde_json::from_str(&raw).unwrap_or_else(|e| { - tracing::warn!( - path = EXTRA_MCP_PATH, - error = ?e, - "extra-mcp spec parse failed; ignoring", - ); - BTreeMap::new() - }) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn available_to_subagents_defaults_false() { - let stdio: ExtraMcpServer = - serde_json::from_value(serde_json::json!({"type": "stdio", "command": "/bin/foo"})) - .unwrap(); - assert!(!stdio.available_to_subagents()); - - let http: ExtraMcpServer = serde_json::from_value( - serde_json::json!({"type": "http", "url": "http://127.0.0.1:1/mcp"}), - ) - .unwrap(); - assert!(!http.available_to_subagents()); - } - - #[test] - fn available_to_subagents_true_is_read() { - let stdio: ExtraMcpServer = serde_json::from_value(serde_json::json!({ - "type": "stdio", - "command": "/bin/foo", - "availableToSubagents": true, - })) - .unwrap(); - assert!(stdio.available_to_subagents()); - } - - #[test] - fn to_json_entry_injects_state_dir_when_absent() { - let stdio: ExtraMcpServer = serde_json::from_value(serde_json::json!({ - "type": "stdio", - "command": "/bin/foo", - })) - .unwrap(); - let entry = stdio.to_json_entry(Path::new("/agents/x/state")); - assert_eq!(entry["env"]["HYPERHIVE_STATE_DIR"], "/agents/x/state"); - } - - #[test] - fn to_json_entry_respects_explicit_state_dir() { - let stdio: ExtraMcpServer = serde_json::from_value(serde_json::json!({ - "type": "stdio", - "command": "/bin/foo", - "env": {"HYPERHIVE_STATE_DIR": "/custom"}, - })) - .unwrap(); - let entry = stdio.to_json_entry(Path::new("/agents/x/state")); - assert_eq!(entry["env"]["HYPERHIVE_STATE_DIR"], "/custom"); - } - - #[test] - fn http_entry_has_no_env() { - let http: ExtraMcpServer = serde_json::from_value(serde_json::json!({ - "type": "http", - "url": "http://127.0.0.1:1/mcp", - })) - .unwrap(); - let entry = http.to_json_entry(Path::new("/agents/x/state")); - assert_eq!(entry["url"], "http://127.0.0.1:1/mcp"); - assert!(entry.get("env").is_none()); - } -} diff --git a/hive-agent-sock/src/lib.rs b/hive-agent-sock/src/lib.rs index 9458f1e0..aab4e2e7 100644 --- a/hive-agent-sock/src/lib.rs +++ b/hive-agent-sock/src/lib.rs @@ -1,31 +1,23 @@ -//! Rescoped beyond its original name: what's common across the in-container -//! "agent plugins" (matrix/bash/subagent MCP daemons, `forge_notify`, and any -//! user-configured MCP server) — not just the in-agent socket wire types. -//! -//! **Wire types for the *in-agent* socket**, served by the hive-agent harness -//! to those producers. `subsystem` on the todo ops below is a plain string, -//! not a closed set: any user-configured MCP server declared in an agent's -//! `agent.nix` can dial this socket and push its own todos the same way. -//! Carries the loose-ends-v2 *todo* op family plus the harness-local -//! *reminder* op family; more in-agent request families may be added over -//! time (the socket is deliberately named for the agent, not the todos). +//! Wire types for the *in-agent* socket, served by the hive-agent harness +//! to the in-container producers. Matrix / bash MCP daemons and +//! `forge_notify` are the built-in ones today, but `subsystem` on the todo +//! ops below is a plain string, not a closed set: any user-configured MCP +//! server declared in an agent's `agent.nix` can dial this socket and push +//! its own todos the same way. Carries the loose-ends-v2 *todo* op family +//! plus the harness-local *reminder* op family; more in-agent request +//! families may be added over time (the socket is deliberately named for +//! the agent, not the todos). //! //! Distinct from `hive-core-agent-sock`, the *host*-served core↔agent //! protocol on `/run/hive/mcp.sock`: this socket never leaves the //! container. The harness owns the todo + reminder stores locally and //! signals its own turn loop directly, so hive-c0re is not in either path — //! no broker round-trip, no long-poll, no marker files. -//! -//! **`extra_mcp`**: the `hyperhive.extraMcpServers` spec + parsing, shared by -//! `hive-agent` (the main session's `--mcp-config`) and `hive-subagent-mcp` -//! (the subagent-eligible subset) — not a socket protocol at all, but this -//! crate is the natural home since both consumers already depend on it. use serde::{Deserialize, Serialize}; use hive_sh4re::inbox::LooseEnd; -pub mod extra_mcp; pub mod paths; /// In-container path of the harness-served in-agent socket. The harness diff --git a/hive-agent/src/mcp_config.rs b/hive-agent/src/mcp_config.rs index e13a4585..614518e8 100644 --- a/hive-agent/src/mcp_config.rs +++ b/hive-agent/src/mcp_config.rs @@ -184,7 +184,7 @@ pub fn allowed_mcp_tools(groups: &[hive_sh4re::permissions::ToolGroup]) -> Vec__` so claude can call // them without per-tool operator approval. `["*"]` (the default) // expands to `mcp____*` — every tool from that server. - for (server, spec) in hive_agent_sock::extra_mcp::load_extra_mcp() { + for (server, spec) in load_extra_mcp() { if server == SERVER_NAME || !extra_server_enabled(&server, groups) { continue; } @@ -241,6 +241,70 @@ pub fn builtin_tools_arg() -> String { tools.join(",") } +/// Where the NixOS module writes the per-agent extra-MCP spec (see +/// `nix/agent-modules/mcp.nix`). Each entry becomes an additional +/// `mcpServers.` block in the rendered claude config + a +/// `mcp____` pattern in `--allowedTools`. +const EXTRA_MCP_PATH: &str = "/etc/hyperhive/extra-mcp.json"; + +/// An extra MCP server declared via `hyperhive.extraMcpServers`. Two +/// transports: `Stdio` (claude spawns `command` fresh each turn, talks +/// JSON-RPC over its stdin/stdout) and `Http` (claude points at a +/// long-lived streamable-http `url` instead — no per-turn spawn, no +/// re-registration race, same shape as the built-in hyperhive surface). +/// Internally tagged on the nix-rendered `type` field; unrecognised +/// fields for the inactive variant (e.g. `command` on an `Http` entry) +/// are ignored by serde's default struct deserialization. +#[derive(Debug, serde::Deserialize)] +#[serde(tag = "type", rename_all = "snake_case")] +enum ExtraMcpServer { + Stdio { + command: String, + #[serde(default)] + args: Vec, + #[serde(default)] + env: std::collections::BTreeMap, + #[serde(default = "default_allowed_tools")] + #[serde(rename = "allowedTools")] + allowed_tools: Vec, + }, + Http { + url: String, + #[serde(default = "default_allowed_tools")] + #[serde(rename = "allowedTools")] + allowed_tools: Vec, + }, +} + +impl ExtraMcpServer { + fn allowed_tools(&self) -> &[String] { + match self { + Self::Stdio { allowed_tools, .. } | Self::Http { allowed_tools, .. } => allowed_tools, + } + } +} + +fn default_allowed_tools() -> Vec { + vec!["*".to_owned()] +} + +/// Read + parse the extra-MCP spec. Returns an empty map when +/// the file is missing or unparsable (the agent has none configured, +/// or the file is malformed — both cases degrade to "no extra servers"). +fn load_extra_mcp() -> std::collections::BTreeMap { + let Ok(raw) = std::fs::read_to_string(EXTRA_MCP_PATH) else { + return std::collections::BTreeMap::new(); + }; + serde_json::from_str(&raw).unwrap_or_else(|e| { + tracing::warn!( + path = EXTRA_MCP_PATH, + error = ?e, + "extra-mcp spec parse failed; ignoring", + ); + std::collections::BTreeMap::new() + }) +} + /// Render the MCP config blob claude reads from `--mcp-config `. /// The built-in `hyperhive` surface is an HTTP entry pointing at the /// persistent `hive-mcp-http` daemon (see [`DEFAULT_MCP_HTTP_PORT`]); there @@ -300,7 +364,7 @@ fn build_mcp_servers() -> serde_json::Map { // agent isn't entitled to must not even appear in the MCP config, or the // agent could call it directly (there is no later enforcement point). let groups = effective_tool_groups(); - for (name, spec) in hive_agent_sock::extra_mcp::load_extra_mcp() { + for (name, spec) in load_extra_mcp() { if name == SERVER_NAME { tracing::warn!( "extra MCP server name `{SERVER_NAME}` collides with the built-in surface; ignoring", @@ -314,7 +378,20 @@ fn build_mcp_servers() -> serde_json::Map { ); continue; } - servers.insert(name, spec.to_json_entry(&state_dir)); + let entry = match spec { + ExtraMcpServer::Stdio { + command, + args, + mut env, + .. + } => { + env.entry("HYPERHIVE_STATE_DIR".to_owned()) + .or_insert_with(|| state_dir.display().to_string()); + serde_json::json!({ "command": command, "args": args, "env": env }) + } + ExtraMcpServer::Http { url, .. } => serde_json::json!({ "type": "http", "url": url }), + }; + servers.insert(name, entry); } servers } diff --git a/hive-subagent-mcp/src/lib.rs b/hive-subagent-mcp/src/lib.rs index 666088be..180efee8 100644 --- a/hive-subagent-mcp/src/lib.rs +++ b/hive-subagent-mcp/src/lib.rs @@ -10,6 +10,5 @@ //! in-memory `name -> Cancel` map, live only as long as the process is. pub mod mcp; -pub mod mcp_config; pub mod paths; pub mod session; diff --git a/hive-subagent-mcp/src/mcp_config.rs b/hive-subagent-mcp/src/mcp_config.rs deleted file mode 100644 index 74520314..00000000 --- a/hive-subagent-mcp/src/mcp_config.rs +++ /dev/null @@ -1,57 +0,0 @@ -//! Builds a subagent's own filtered `--mcp-config`: only -//! `hyperhive.extraMcpServers` entries with `availableToSubagents = true` -//! (see `hive_agent_sock::extra_mcp::ExtraMcpServer::available_to_subagents`) ever reach -//! a subagent's claude invocation. Everything else — the built-in hyperhive -//! surface (todos/messaging), the automatically injected `bash` and `subagent` -//! entries — stays unreachable by construction: none of those default to -//! opted in, and the built-in surface isn't an `extraMcpServers` entry at -//! all, so there's no name for an operator to opt it in under even if they -//! wanted to. - -use std::path::PathBuf; - -/// Filename the rendered config lives at, under [`crate::paths::harness_dir`]. -const CONFIG_FILE: &str = "subagent-mcp-config.json"; - -/// Render the subagent-eligible extra-MCP servers to a `--mcp-config` file, -/// returning its path — or `None` when no entry opts in (or the render/write -/// fails), so [`hive_claude::Config::mcp_config`] stays unset and the -/// subagent gets literally zero MCP servers, the same default as before this -/// toggle existed. Re-rendered on every call (cheap: a filter plus a small -/// file write) rather than cached once at daemon startup, so a config change -/// takes effect on this subagent's next `start`/`continue` without needing -/// the daemon itself restarted. -#[must_use] -pub fn build() -> Option { - let state_dir = crate::paths::state_dir(); - let servers: serde_json::Map = - hive_agent_sock::extra_mcp::load_extra_mcp() - .into_iter() - .filter(|(_, spec)| spec.available_to_subagents()) - .map(|(name, spec)| (name, spec.to_json_entry(&state_dir))) - .collect(); - if servers.is_empty() { - return None; - } - let body = serde_json::to_string_pretty(&serde_json::json!({ "mcpServers": servers })) - .unwrap_or_else(|_| "{}".into()); - let dir = crate::paths::harness_dir(); - if let Err(e) = std::fs::create_dir_all(&dir) { - tracing::warn!( - error = ?e, - dir = %dir.display(), - "subagent mcp-config: harness dir create failed; subagent gets zero extra MCP servers this turn", - ); - return None; - } - let path = dir.join(CONFIG_FILE); - if let Err(e) = std::fs::write(&path, body) { - tracing::warn!( - error = ?e, - path = %path.display(), - "subagent mcp-config: write failed; subagent gets zero extra MCP servers this turn", - ); - return None; - } - Some(path) -} diff --git a/hive-subagent-mcp/src/paths.rs b/hive-subagent-mcp/src/paths.rs index c6140a64..8a05e334 100644 --- a/hive-subagent-mcp/src/paths.rs +++ b/hive-subagent-mcp/src/paths.rs @@ -1,4 +1,4 @@ -//! The per-agent paths this daemon needs. +//! The one per-agent path this daemon needs. use std::path::PathBuf; @@ -13,28 +13,3 @@ pub fn agent_socket() -> PathBuf { PathBuf::from, ) } - -/// Durable state directory for the current agent. Same resolution as -/// `hive-agent`'s own `paths::state_dir` — reads `HYPERHIVE_STATE_DIR` -/// (always injected via `systemd.globalEnvironment` by the meta flake), -/// falling back to the `HIVE_LABEL`-derived pattern for dev/test -/// environments where the env var may not be set. Copied rather than shared -/// with `hive-agent` (a binary crate with no lib target) — a five-line env -/// read isn't worth a crate dependency to dedupe. -#[must_use] -pub fn state_dir() -> PathBuf { - if let Some(p) = std::env::var_os("HYPERHIVE_STATE_DIR") { - return PathBuf::from(p); - } - let label = std::env::var("HIVE_LABEL").unwrap_or_default(); - PathBuf::from(format!("/agents/{label}/state")) -} - -/// Harness-internal scratch dir this daemon writes its own generated -/// subagent `--mcp-config` file into (see `crate::mcp_config`). Delegates to -/// the shared resolver so this and `hive-agent`'s own harness dir always -/// agree on the same path. -#[must_use] -pub fn harness_dir() -> PathBuf { - hive_agent_sock::paths::harness_dir() -} diff --git a/hive-subagent-mcp/src/session.rs b/hive-subagent-mcp/src/session.rs index 98678a93..79263a4b 100644 --- a/hive-subagent-mcp/src/session.rs +++ b/hive-subagent-mcp/src/session.rs @@ -185,14 +185,8 @@ fn subagent_otel_attrs(name: &str) -> String { /// instructions. `dir`, when given, becomes `Config::cwd` (e.g. a worktree /// the caller already prepared); `None` inherits this daemon's own working /// directory, same as before this field existed. Always -/// `--dangerously-skip-permissions --strict-mcp-config` — the safety -/// property is `strict_mcp_config: true` with no ambient MCP discovery, not -/// an unconditional absence of `--mcp-config`: a subagent gets exactly the -/// `hyperhive.extraMcpServers` entries an operator has explicitly opted in -/// via `availableToSubagents = true` (`crate::mcp_config::build`), nothing -/// implicit and nothing more. With no entry opted in — the default — that -/// resolves to `None` and the invocation is unchanged from before this -/// toggle existed: zero MCP servers, full stop. +/// `--dangerously-skip-permissions --strict-mcp-config` (no `--mcp-config` +/// override — a safety property, not a knob). fn build_config( name: &str, model: Option, @@ -207,7 +201,6 @@ fn build_config( Config { model, cwd: dir.map(PathBuf::from), - mcp_config: crate::mcp_config::build(), strict_mcp_config: true, extra_args, env: vec![( diff --git a/nix/agent-modules/mcp.nix b/nix/agent-modules/mcp.nix index 70b6db16..151bbd00 100644 --- a/nix/agent-modules/mcp.nix +++ b/nix/agent-modules/mcp.nix @@ -124,27 +124,6 @@ in `mcp____` at build time. ''; }; - availableToSubagents = lib.mkOption { - type = lib.types.bool; - default = false; - description = '' - Whether a claude subagent (`mcp__subagent__start`/`continue`, - served by `hive-subagent-daemon`) also gets this MCP server. - Default `false`: a subagent runs `--strict-mcp-config` with no - ambient MCP discovery, so this is the *only* path an - `extraMcpServers` entry reaches a subagent's own claude - invocation at all — leaving it unset keeps a subagent exactly - as narrow as before this option existed (its native built-in - tools only, zero MCP servers). - - The automatically injected `bash` and `subagent` entries below stay at - this default — a subagent never gets hive-bash (it already has - claude's native `Bash` tool instead) or the ability to spawn - its own nested subagents, unless an operator opts a - *different* entry in explicitly. Set `true` on an entry (e.g. - a scraper or read-only lookup MCP) to hand it to subagents too. - ''; - }; }; } );