harness: remove deprecated manager-side request_spawn surface (closes #442)
This commit is contained in:
parent
03d39788d9
commit
dca62fda40
8 changed files with 56 additions and 124 deletions
|
|
@ -120,7 +120,6 @@ can't:
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `RequestInitConfig` | seed an agent's proposed config repo | **topology** — descendants only |
|
| `RequestInitConfig` | seed an agent's proposed config repo | **topology** — descendants only |
|
||||||
| `RequestApplyCommit` | submit a commit sha for operator approval | **topology** — descendants only |
|
| `RequestApplyCommit` | submit a commit sha for operator approval | **topology** — descendants only |
|
||||||
| `RequestSpawn` (deprecated) | shortcut for spawn | **topology** — descendants only |
|
|
||||||
| `Kill` / `Start` / `Restart` / `Update` | container lifecycle on an existing agent | **topology** — descendants only |
|
| `Kill` / `Start` / `Restart` / `Update` | container lifecycle on an existing agent | **topology** — descendants only |
|
||||||
| `RequestUpdateMetaInputs` | bump meta `flake.lock` | **per-agent cap** (root-only today; a future "let coder bump its own input" might grant it) |
|
| `RequestUpdateMetaInputs` | bump meta `flake.lock` | **per-agent cap** (root-only today; a future "let coder bump its own input" might grant it) |
|
||||||
| `GetLogs` | journalctl scrape of a sub-agent | **topology** — descendants only |
|
| `GetLogs` | journalctl scrape of a sub-agent | **topology** — descendants only |
|
||||||
|
|
|
||||||
|
|
@ -294,12 +294,13 @@ meta's.
|
||||||
on operator approve, hive-c0re seeds the proposed config repo
|
on operator approve, hive-c0re seeds the proposed config repo
|
||||||
with a default `agent.nix` template and sends the manager a
|
with a default `agent.nix` template and sends the manager a
|
||||||
`HelperEvent::ConfigReady { agent }`. The manager then edits
|
`HelperEvent::ConfigReady { agent }`. The manager then edits
|
||||||
`agent.nix`, commits the changes, and calls `request_spawn`.
|
`agent.nix`, commits the changes, and calls `request_apply_commit`
|
||||||
Fails if a proposed repo for this name already exists.
|
with the commit sha — the first ApplyCommit on a freshly-init'd
|
||||||
- `request_spawn(name)` — second step of a two-step spawn. Queues
|
config creates the container. Fails if a proposed repo for this
|
||||||
a Spawn approval; requires the proposed config repo to exist
|
name already exists. (The pre-#442 path through a separate
|
||||||
(from a prior approved `request_init_config`). Operator approves
|
manager-side `request_spawn` was removed; operator can still
|
||||||
on the dashboard to create the container.
|
direct-spawn an empty agent from the dashboard's `◆ R3QU3ST SP4WN`
|
||||||
|
button which routes via `HostRequest::RequestSpawn`.)
|
||||||
- `kill(name)` — graceful stop. No approval required.
|
- `kill(name)` — graceful stop. No approval required.
|
||||||
- `start(name)` — start a stopped sub-agent. No approval.
|
- `start(name)` — start a stopped sub-agent. No approval.
|
||||||
- `restart(name)` — stop + start. No approval.
|
- `restart(name)` — stop + start. No approval.
|
||||||
|
|
@ -375,9 +376,9 @@ meta's.
|
||||||
|
|
||||||
The boundary: lifecycle ops on *existing* sub-agents
|
The boundary: lifecycle ops on *existing* sub-agents
|
||||||
(`kill`/`start`/`restart`) are at the manager's discretion — no
|
(`kill`/`start`/`restart`) are at the manager's discretion — no
|
||||||
operator approval. Creating a new agent (`request_spawn`) and
|
operator approval. Creating a new agent (`request_init_config` →
|
||||||
changing any agent's config (`request_apply_commit`) still go
|
`request_apply_commit` for the first sha) and changing any agent's
|
||||||
through the approval queue.
|
config (`request_apply_commit`) still go through the approval queue.
|
||||||
|
|
||||||
### Authoritative state
|
### Authoritative state
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1056,7 +1056,6 @@ window.marked = marked;
|
||||||
if (input.max != null) parts.push('max ' + input.max);
|
if (input.max != null) parts.push('max ' + input.max);
|
||||||
return short + (parts.length ? ' ' + parts.join(' · ') : '()');
|
return short + (parts.length ? ' ' + parts.join(' · ') : '()');
|
||||||
}
|
}
|
||||||
case 'mcp__hyperhive__request_spawn': return short + ' ' + (input.name || '');
|
|
||||||
case 'mcp__hyperhive__kill': return short + ' ' + (input.name || '');
|
case 'mcp__hyperhive__kill': return short + ' ' + (input.name || '');
|
||||||
case 'mcp__hyperhive__request_apply_commit':
|
case 'mcp__hyperhive__request_apply_commit':
|
||||||
return short + ' ' + (input.agent || '') + ' @ ' + (input.commit_ref || '').slice(0, 12);
|
return short + ' ' + (input.agent || '') + ' @ ' + (input.commit_ref || '').slice(0, 12);
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,8 @@ enum Cmd {
|
||||||
},
|
},
|
||||||
/// Run the manager MCP server on stdio. Spawned by claude via
|
/// Run the manager MCP server on stdio. Spawned by claude via
|
||||||
/// `--mcp-config`; same shape as `hive-ag3nt mcp` but with the
|
/// `--mcp-config`; same shape as `hive-ag3nt mcp` but with the
|
||||||
/// manager tool surface (`request_spawn`, `kill`, `start`, `restart`,
|
/// manager tool surface (`request_init_config`, `request_apply_commit`,
|
||||||
/// `request_apply_commit`, `ask`, `answer`, `remind`).
|
/// `kill`, `start`, `restart`, `ask`, `answer`, `remind`, …).
|
||||||
Mcp,
|
Mcp,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@
|
||||||
//!
|
//!
|
||||||
//! Two server flavors:
|
//! Two server flavors:
|
||||||
//! - `AgentServer` — sub-agent tools (`send`, `recv`).
|
//! - `AgentServer` — sub-agent tools (`send`, `recv`).
|
||||||
//! - `ManagerServer` — agent tools + lifecycle (`request_spawn`, `kill`,
|
//! - `ManagerServer` — agent tools + lifecycle (`kill`,
|
||||||
//! `request_apply_commit`).
|
//! `request_init_config`, `request_apply_commit`).
|
||||||
//!
|
//!
|
||||||
//! Both go through the same `run_tool_envelope` helper so logging + status
|
//! Both go through the same `run_tool_envelope` helper so logging + status
|
||||||
//! line stay uniform.
|
//! line stay uniform.
|
||||||
|
|
@ -792,26 +792,16 @@ pub async fn serve_manager_stdio(socket: PathBuf) -> Result<()> {
|
||||||
pub struct RequestInitConfigArgs {
|
pub struct RequestInitConfigArgs {
|
||||||
/// New sub-agent name (≤9 chars). Queues an `InitConfig` approval; on
|
/// New sub-agent name (≤9 chars). Queues an `InitConfig` approval; on
|
||||||
/// approval hive-c0re seeds the proposed config repo at
|
/// approval hive-c0re seeds the proposed config repo at
|
||||||
/// `/agents/<name>/config/agent.nix` with the default template.
|
/// `/agents/<name>/config/agent.nix` with the default template. After
|
||||||
/// After the approval the manager can edit and commit the config before
|
/// the approval the manager edits + commits the config and calls
|
||||||
/// calling `request_spawn`.
|
/// `request_apply_commit` to pin the customised sha for the container's
|
||||||
|
/// first build.
|
||||||
pub name: String,
|
pub name: String,
|
||||||
/// Optional description shown on the dashboard approval card.
|
/// Optional description shown on the dashboard approval card.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
|
|
||||||
pub struct RequestSpawnArgs {
|
|
||||||
/// New sub-agent name (≤9 chars). Queues a Spawn approval; the
|
|
||||||
/// operator approves on the dashboard before the container is created.
|
|
||||||
pub name: String,
|
|
||||||
/// Optional description shown on the dashboard approval card so the
|
|
||||||
/// operator knows what the new agent is for without a separate message.
|
|
||||||
#[serde(default)]
|
|
||||||
pub description: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
|
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
|
||||||
pub struct KillArgs {
|
pub struct KillArgs {
|
||||||
/// Sub-agent name (without the `h-` container prefix).
|
/// Sub-agent name (without the `h-` container prefix).
|
||||||
|
|
@ -1121,8 +1111,9 @@ impl ManagerServer {
|
||||||
repo and queue an InitConfig approval. On operator approval hive-c0re seeds \
|
repo and queue an InitConfig approval. On operator approval hive-c0re seeds \
|
||||||
`/agents/<name>/config/agent.nix` with the default template so the manager can \
|
`/agents/<name>/config/agent.nix` with the default template so the manager can \
|
||||||
customise it before spawning. After the ConfigReady helper event arrives, edit \
|
customise it before spawning. After the ConfigReady helper event arrives, edit \
|
||||||
agent.nix, commit the changes, then call `request_spawn`. Fails if a config repo \
|
agent.nix, commit the changes, then call `request_apply_commit` with the commit \
|
||||||
for this name already exists (use `request_apply_commit` to update an existing agent)."
|
sha — that's what creates the container. Fails if a config repo for this name \
|
||||||
|
already exists (use `request_apply_commit` directly to update an existing agent)."
|
||||||
)]
|
)]
|
||||||
async fn request_init_config(
|
async fn request_init_config(
|
||||||
&self,
|
&self,
|
||||||
|
|
@ -1149,34 +1140,6 @@ impl ManagerServer {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tool(
|
|
||||||
description = "Step 2 of 2 for creating a new agent: queue a Spawn approval after \
|
|
||||||
the config has been initialised and customised via `request_init_config`. Requires \
|
|
||||||
a prior approved InitConfig so the manager can review and edit agent.nix first. \
|
|
||||||
Fails if no proposed config repo exists for the given name."
|
|
||||||
)]
|
|
||||||
async fn request_spawn(&self, Parameters(args): Parameters<RequestSpawnArgs>) -> String {
|
|
||||||
let log = format!("{args:?}");
|
|
||||||
let name = args.name.clone();
|
|
||||||
run_tool_envelope("request_spawn", log, async move {
|
|
||||||
let (resp, retries) = self
|
|
||||||
.dispatch(hive_sh4re::ManagerRequest::RequestSpawn {
|
|
||||||
name: args.name,
|
|
||||||
description: args.description,
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
annotate_retries(
|
|
||||||
format_ack(
|
|
||||||
resp,
|
|
||||||
"request_spawn",
|
|
||||||
format!("spawn approval queued for {name}"),
|
|
||||||
),
|
|
||||||
retries,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tool(
|
#[tool(
|
||||||
description = "Stop a sub-agent container (graceful). The state dir is kept; \
|
description = "Stop a sub-agent container (graceful). The state dir is kept; \
|
||||||
recreating reuses prior config + Claude credentials. No approval required."
|
recreating reuses prior config + Claude credentials. No approval required."
|
||||||
|
|
@ -1711,10 +1674,12 @@ impl ManagerServer {
|
||||||
#[tool_handler(
|
#[tool_handler(
|
||||||
instructions = "You are the hyperhive manager (hm1nd). You coordinate sub-agents and \
|
instructions = "You are the hyperhive manager (hm1nd). You coordinate sub-agents and \
|
||||||
relay between them and the operator. Use `send` to talk to agents/operator, `recv` \
|
relay between them and the operator. Use `send` to talk to agents/operator, `recv` \
|
||||||
to drain your inbox. Privileged: `request_init_config` + `request_spawn` (two-step \
|
to drain your inbox. Privileged: `request_init_config` (step 1 of new-agent \
|
||||||
new agent creation - init config first, customise agent.nix, then spawn, both \
|
creation — seeds the proposed config repo so you can customise agent.nix; \
|
||||||
gated on operator approval), `kill` (graceful stop), `request_apply_commit` (config change for \
|
operator-approved), `kill` (graceful stop), `request_apply_commit` (config \
|
||||||
any agent including yourself), `ask` (structured question to the operator or a \
|
change for any agent including yourself — also doubles as step 2 of new-agent \
|
||||||
|
creation: the first ApplyCommit on a freshly-init'd config creates the \
|
||||||
|
container), `ask` (structured question to the operator or a \
|
||||||
sub-agent — non-blocking, answer arrives later as a `question_answered` event), \
|
sub-agent — non-blocking, answer arrives later as a `question_answered` event), \
|
||||||
`answer` (respond to a `question_asked` event directed at you), \
|
`answer` (respond to a `question_asked` event directed at you), \
|
||||||
`get_loose_ends` (hive-wide loose ends — pending approvals + unanswered \
|
`get_loose_ends` (hive-wide loose ends — pending approvals + unanswered \
|
||||||
|
|
@ -1771,7 +1736,6 @@ pub fn allowed_mcp_tools(flavor: Flavor) -> Vec<String> {
|
||||||
"send",
|
"send",
|
||||||
"recv",
|
"recv",
|
||||||
"request_init_config",
|
"request_init_config",
|
||||||
"request_spawn",
|
|
||||||
"kill",
|
"kill",
|
||||||
"start",
|
"start",
|
||||||
"restart",
|
"restart",
|
||||||
|
|
|
||||||
|
|
@ -203,34 +203,6 @@ async fn dispatch(req: &ManagerRequest, coord: &Arc<Coordinator>) -> ManagerResp
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ManagerRequest::RequestSpawn { name, description } => {
|
|
||||||
tracing::info!(%name, "manager: request_spawn");
|
|
||||||
let proposed_dir = crate::coordinator::Coordinator::agent_proposed_dir(name);
|
|
||||||
if !proposed_dir.join(".git").exists() {
|
|
||||||
return ManagerResponse::Err {
|
|
||||||
message: format!(
|
|
||||||
"no proposed config repo found for '{name}' - \
|
|
||||||
call request_init_config first to initialise and customise \
|
|
||||||
the config before spawning"
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
match coord.approvals.submit_kind(
|
|
||||||
name,
|
|
||||||
hive_sh4re::ApprovalKind::Spawn,
|
|
||||||
"",
|
|
||||||
description.as_deref(),
|
|
||||||
) {
|
|
||||||
Ok(id) => {
|
|
||||||
tracing::info!(%id, %name, "spawn approval queued");
|
|
||||||
coord.emit_approval_added(id, name, "spawn", None, None, description.clone());
|
|
||||||
ManagerResponse::Ok
|
|
||||||
}
|
|
||||||
Err(e) => ManagerResponse::Err {
|
|
||||||
message: format!("{e:#}"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ManagerRequest::Kill { name } => {
|
ManagerRequest::Kill { name } => {
|
||||||
tracing::info!(%name, "manager: kill");
|
tracing::info!(%name, "manager: kill");
|
||||||
if name == crate::lifecycle::MANAGER_NAME {
|
if name == crate::lifecycle::MANAGER_NAME {
|
||||||
|
|
|
||||||
|
|
@ -568,16 +568,19 @@ async fn dispatch(
|
||||||
crate::actions::run_approval_spawn(coord, Some(entry.id), approval_id).await
|
crate::actions::run_approval_spawn(coord, Some(entry.id), approval_id).await
|
||||||
}
|
}
|
||||||
(QueueKind::Spawn, None) => {
|
(QueueKind::Spawn, None) => {
|
||||||
// No non-approval Spawn caller today. The variant exists so
|
// Unreachable today: every Spawn entry is born from an
|
||||||
// operator-triggered `RequestSpawn` (deprecated) and the
|
// approval (HostRequest::RequestSpawn → submit_kind →
|
||||||
// future direct-spawn admin path can route through here
|
// approve → enqueue with approval_id). The manager-side
|
||||||
// without a wire change.
|
// `RequestSpawn` surface that used to bypass approvals
|
||||||
tracing::debug!(
|
// was removed in #442; if a future direct-spawn admin
|
||||||
id = entry.id,
|
// path needs to skip the approval ride it should wire
|
||||||
agent = %entry.agent,
|
// its own action call rather than route through here.
|
||||||
"rebuild_queue: Spawn entry without approval_id is a no-op"
|
anyhow::bail!(
|
||||||
);
|
"rebuild_queue: Spawn entry id={} agent={} arrived without an approval_id — \
|
||||||
Ok(())
|
nothing should enqueue this shape today",
|
||||||
|
entry.id,
|
||||||
|
entry.agent,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
(QueueKind::Destroy, _) => {
|
(QueueKind::Destroy, _) => {
|
||||||
// Reserved for future `destroy --purge` integration.
|
// Reserved for future `destroy --purge` integration.
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,14 @@ pub enum HostRequest {
|
||||||
/// approval queue intentionally so test scripts and one-off recoveries
|
/// approval queue intentionally so test scripts and one-off recoveries
|
||||||
/// don't need a separate approve step.
|
/// don't need a separate approve step.
|
||||||
Spawn { name: String },
|
Spawn { name: String },
|
||||||
/// Submit a spawn request for the user to approve. On approval the host
|
/// Submit a spawn request for the operator to approve. On approval the
|
||||||
/// creates and starts the container. Mirrors the manager's
|
/// host creates and starts the container with the default `agent.nix`
|
||||||
/// `RequestSpawn` — exposed on the admin socket so the dashboard and CLI
|
/// template. The dashboard's `◆ R3QU3ST SP4WN` button and the
|
||||||
/// can also queue spawns through the approval flow.
|
/// `hive-c0re request-spawn` CLI both go through this. The
|
||||||
|
/// previously-mirrored manager-side `RequestSpawn` was removed
|
||||||
|
/// (#442) — managers now go through the two-step `request_init_config`
|
||||||
|
/// + `request_apply_commit` flow so the spawn captures the manager's
|
||||||
|
/// customised config.
|
||||||
RequestSpawn { name: String },
|
RequestSpawn { name: String },
|
||||||
/// Stop a managed container (graceful).
|
/// Stop a managed container (graceful).
|
||||||
Kill { name: String },
|
Kill { name: String },
|
||||||
|
|
@ -107,12 +111,16 @@ pub enum ApprovalKind {
|
||||||
#[default]
|
#[default]
|
||||||
ApplyCommit,
|
ApplyCommit,
|
||||||
/// Create + start a new sub-agent container with the given name.
|
/// Create + start a new sub-agent container with the given name.
|
||||||
|
/// Used today by the host admin socket / dashboard "request spawn"
|
||||||
|
/// button; the manager-side `RequestSpawn` surface was removed in
|
||||||
|
/// favour of the two-step `InitConfig` → `ApplyCommit` flow (#442).
|
||||||
Spawn,
|
Spawn,
|
||||||
/// Initialise a new agent's proposed config repo so the manager can
|
/// Initialise a new agent's proposed config repo so the manager can
|
||||||
/// customise it before submitting a `RequestSpawn`. On approval
|
/// customise it before submitting a `request_apply_commit`. On
|
||||||
/// hive-c0re seeds `proposed/<name>/` with the default `agent.nix`
|
/// approval hive-c0re seeds `proposed/<name>/` with the default
|
||||||
/// template but does NOT create the container - that requires a
|
/// `agent.nix` template but does NOT create the container — that
|
||||||
/// subsequent `RequestSpawn` approval.
|
/// requires a subsequent `ApplyCommit` approval pinning the
|
||||||
|
/// customised config sha.
|
||||||
InitConfig,
|
InitConfig,
|
||||||
/// Run `nix flake update [inputs...]` on the meta flake and commit
|
/// Run `nix flake update [inputs...]` on the meta flake and commit
|
||||||
/// the resulting lock changes. The `commit_ref` field stores the
|
/// the resulting lock changes. The `commit_ref` field stores the
|
||||||
|
|
@ -767,20 +775,6 @@ pub enum ManagerRequest {
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
description: Option<String>,
|
description: Option<String>,
|
||||||
},
|
},
|
||||||
/// Submit a spawn request for the user to approve. On approval the host
|
|
||||||
/// creates and starts the container. Requires a prior approved
|
|
||||||
/// `request_init_config` so the manager can customise `agent.nix` first.
|
|
||||||
/// Fails if the proposed config repo for this name does not exist yet.
|
|
||||||
///
|
|
||||||
/// Deprecated: prefer `request_apply_commit` after `config_ready` —
|
|
||||||
/// it pins the exact commit sha, handles both first-time spawns and
|
|
||||||
/// subsequent rebuilds, and always reflects the committed config.
|
|
||||||
RequestSpawn {
|
|
||||||
name: String,
|
|
||||||
/// Optional description shown on the dashboard approval card.
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
description: Option<String>,
|
|
||||||
},
|
|
||||||
/// Stop a sub-agent (graceful).
|
/// Stop a sub-agent (graceful).
|
||||||
Kill {
|
Kill {
|
||||||
name: String,
|
name: String,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue