Watch
0
0
Fork
You've already forked hyperhive
0

swarm-controller: create every agent's subagent stream

swarm-controller now creates `term-sub-<agent>` for every agent a hive is
declared to run, at start and every minute after, with the config
`swarm_queue_client::subagent_term::open_or_create` spells (subjects
`$SWARM.term.<agent>.sub.>`, max_age 24h). An existing stream is opened as
it is, as the controller does for its other streams and buckets, under the
`$JS.API.STREAM.CREATE.*` grant it already holds.

The agent no longer creates the stream: its token is granted publish on
`$SWARM.term.<agent>.sub.>` and no `$JS.API.STREAM.CREATE|INFO` subject,
and the subagent daemon only publishes. A `CREATE` carries the stream's
config in its payload, which no subject grant narrows, so the agent could
otherwise pick the stream's subjects and limits.
This commit is contained in:
atlas 2026-10-02 23:34:27 +02:00 • committed by mara
commit 318f67cda9
13 changed files with 135 additions and 102 deletions

View file

@ -30,8 +30,8 @@ kv = ["async-nats/kv"]
# `cargo check -p swarm-nats-auth` — no `kv` anywhere in that build —
# surfaced it as `cannot find jetstream in async_nats`).
notices = ["async-nats/jetstream"]
# `subagent_term::open_or_create`, for the one publisher that creates its own
# stream. Explicit for the same reason as `notices`.
# `subagent_term::open_or_create`, for swarm-controller, which creates every
# agent's subagent stream. Explicit for the same reason as `notices`.
subagent-term = ["async-nats/jetstream"]
[dependencies]

View file

@ -3,21 +3,20 @@
//!
//! An agent's subagent daemon publishes each subagent's classified terminal
//! rows on `$SWARM.term.<agent>.sub.<subagent>`, under the agent's own queue
//! credential. The queue grants that credential this subject family and
//! `CREATE`/`INFO` on the one stream [`crate::subagent_term::stream_name`]
//! names, nothing else of `JetStream`, so the agent creates its own stream on
//! first use.
//! credential. The queue grants that credential publish on this subject
//! family and no `JetStream` subject.
//!
//! The stream exists so the swarm can list an agent's subagents: subagents
//! are spawned on demand under caller-chosen names, and the set of subjects
//! the stream holds is the list. A reader takes it from the stream's subject
//! counts ([`crate::subagent_term::subagent_names`]) and never creates the
//! stream.
//! the stream holds is the list. `swarm-controller` creates one stream per
//! agent with the fixed config [`crate::subagent_term::open_or_create`]
//! spells, and takes the list from its subject counts
//! ([`crate::subagent_term::subagent_names`]).
//!
//! The name and the subject live here because three crates must agree on
//! them: the publisher in `hive-subagent-mcp`, the reader in
//! `swarm-controller`, and the queue grant in `swarm-nats.nix`, whose
//! `{agent}` templates spell the same strings.
//! them: the publisher in `hive-subagent-mcp`, `swarm-controller`, and the
//! queue grant in `swarm-nats.nix`, whose `{agent}` template spells the same
//! subjects.
/// The subject family every agent terminal shares.
const TERM_PREFIX: &str = "$SWARM.term";
@ -79,10 +78,8 @@ pub fn subagent_names<'a>(agent: &str, subjects: impl IntoIterator<Item = &'a st
names
}
/// Open `agent`'s subagent stream, creating it if it does not exist yet.
///
/// Called by the publisher only. The reader opens the stream with `get_stream`
/// and reads a missing one as "no subagents".
/// Open `agent`'s subagent stream, creating it if it does not exist yet. An
/// existing stream is opened as it is.
#[cfg(feature = "subagent-term")]
pub async fn open_or_create(
client: &async_nats::Client,