Watch
0
0
Fork
You've already forked hyperhive
0

swarm: cancel an agent's turn from the swarm queue

A separate $SWARM.control.<agent> subject carries commands, apart from
the text subject. swarm-controller exposes POST /api/agents/{name}/cancel
as a NATS request; the harness answers whether a turn was actually
stopped, through the same cancel path as the dashboard button. The agent
credential gets subscribe on its control subject and allow_responses to
answer; only the controller may publish there.

Refs #3803
This commit is contained in:
damocles 2026-10-14 23:08:57 +02:00 • committed by mara
commit fefc565794
11 changed files with 558 additions and 69 deletions

View file

@ -8,10 +8,11 @@
//! refused rather than sent anonymously.
//!
//! **Sent as a NATS request** so that an agent with no subscriber is a 503
//! instead of text that silently goes nowhere. The agent does not reply: no
//! agent grant covers a reply subject. A request that times out therefore
//! means a subscriber held the message, and that is all "sent" means. Core
//! NATS, no stream: an agent that restarts before its turn loses the text.
//! instead of text that silently goes nowhere. The agent does not reply to
//! input (it does to a command, see [`crate::agent_control`]). A request that
//! times out therefore means a subscriber held the message, and that is all
//! "sent" means. Core NATS, no stream: an agent that restarts before its turn
//! loses the text.
use std::time::Duration;
@ -119,6 +120,22 @@ pub(crate) async fn post_agent_input(
send(&client, &agent, &input).await
}
/// Who the gateway says is asking, or the 401 for a request that did not come
/// through it. Shared with [`crate::agent_control`].
pub(crate) fn sender(headers: &HeaderMap) -> Result<String, (StatusCode, &'static str)> {
headers
.get(SENDER_HEADER)
.and_then(|v| v.to_str().ok())
.map(str::trim)
.filter(|s| !s.is_empty())
.map(str::to_owned)
.ok_or((
StatusCode::UNAUTHORIZED,
"no Remote-User header: the gateway names the sender, and this request \
did not come through it",
))
}
/// The agent and the message, or the refusal. Everything decided before the
/// queue is touched.
fn message(
@ -126,17 +143,7 @@ fn message(
agent: &str,
body: String,
) -> Result<(String, AgentInput), (StatusCode, &'static str)> {
let from = headers
.get(SENDER_HEADER)
.and_then(|v| v.to_str().ok())
.map(str::trim)
.filter(|s| !s.is_empty())
.ok_or((
StatusCode::UNAUTHORIZED,
"no Remote-User header: the gateway names the sender, and this request \
did not come through it",
))?
.to_owned();
let from = sender(headers)?;
let agent = hive_types::Ident::parse(agent)
.map_err(|reason| (StatusCode::BAD_REQUEST, reason))?
.into_string();