agent_permissions, agent_term and subagent_term take their wire contracts from swarm-wire
This commit is contained in:
parent
c06b1325e7
commit
839efd1642
27 changed files with 661 additions and 607 deletions
|
|
@ -138,10 +138,7 @@ mod tests {
|
|||
&swarm_queue_client::agent_state_subject("iris"),
|
||||
&header(Some(true)),
|
||||
);
|
||||
reported.record(
|
||||
&swarm_queue_client::term_subject("atlas"),
|
||||
&header(Some(true)),
|
||||
);
|
||||
reported.record(&swarm_wire::term_subject("atlas"), &header(Some(true)));
|
||||
assert_eq!(reported.get("iris"), Some(true));
|
||||
assert_eq!(reported.get("atlas"), None);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,10 @@ use futures_util::StreamExt as _;
|
|||
use hive_types::Permission;
|
||||
use problem_details::ProblemDetails;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use swarm_queue_client::agent_permissions::{self, AgentPermissions, Import, ImportReply};
|
||||
use swarm_queue_client::agent_permissions;
|
||||
use swarm_wire::agent_permissions::{
|
||||
AgentPermissions, Import, ImportReply, hive_from_import_subject, import_subject_wildcard,
|
||||
};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use crate::routes::{AppState, error_problem, wanted_error_status};
|
||||
|
|
@ -377,7 +380,7 @@ pub(crate) fn spawn(client: async_nats::Client, wanted: Arc<WantedWriter>, hives
|
|||
.await;
|
||||
}
|
||||
});
|
||||
let subject = agent_permissions::import_subject_wildcard();
|
||||
let subject = import_subject_wildcard();
|
||||
loop {
|
||||
match client.subscribe(subject.clone()).await {
|
||||
Ok(mut requests) => {
|
||||
|
|
@ -413,7 +416,7 @@ async fn answer_import(
|
|||
tracing::warn!(subject = %msg.subject, %error, "agent permissions: import refused");
|
||||
ImportReply::Failed { error }
|
||||
};
|
||||
let Some(hive) = agent_permissions::hive_from_import_subject(&msg.subject) else {
|
||||
let Some(hive) = hive_from_import_subject(&msg.subject) else {
|
||||
return failed(format!("{} is not an import subject", msg.subject));
|
||||
};
|
||||
if !hives.iter().any(|h| h == hive) {
|
||||
|
|
@ -529,7 +532,7 @@ mod tests {
|
|||
use axum::extract::{Path, State};
|
||||
use axum::http::StatusCode;
|
||||
use hive_types::Permission;
|
||||
use swarm_queue_client::agent_permissions::{Import, ImportReply};
|
||||
use swarm_wire::agent_permissions::{Import, ImportReply};
|
||||
|
||||
async fn published(client: &async_nats::Client, agent: &str) -> Option<String> {
|
||||
let store = swarm_queue_client::agent_permissions::open_or_create(client)
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ use futures_util::{Stream, StreamExt as _, stream};
|
|||
use hive_types::term_msg::{SEND_ICON, TURN_ICON, send_recipient, turn_sender};
|
||||
use serde::Deserialize;
|
||||
use swarm_controller_api::messages::SwarmMessage;
|
||||
use swarm_queue_client::agent_term;
|
||||
use swarm_wire::agent_term;
|
||||
use utoipa::IntoParams;
|
||||
|
||||
use crate::routes::{AppState, error_problem};
|
||||
|
|
@ -104,12 +104,9 @@ pub(crate) async fn stream_messages(
|
|||
)
|
||||
})?;
|
||||
|
||||
let subscriber = crate::term_stream::subscribe(
|
||||
&client,
|
||||
swarm_queue_client::term_subject_wildcard(),
|
||||
"messages",
|
||||
)
|
||||
.await?;
|
||||
let subscriber =
|
||||
crate::term_stream::subscribe(&client, swarm_wire::term_subject_wildcard(), "messages")
|
||||
.await?;
|
||||
let roster: Arc<HashSet<String>> = Arc::new(roster.into_iter().collect());
|
||||
let history = history(&client, &roster, minutes).await;
|
||||
let seen: HashSet<SwarmMessage> = history.iter().cloned().collect();
|
||||
|
|
@ -170,7 +167,7 @@ async fn agent_history(
|
|||
let replayed = crate::term_stream::replay(
|
||||
&client,
|
||||
&agent_term::stream_name(&agent),
|
||||
swarm_queue_client::term_subject(&agent),
|
||||
swarm_wire::term_subject(&agent),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
|
@ -203,7 +200,7 @@ fn live(
|
|||
/// The agent a main terminal subject belongs to. A subagent's subject has
|
||||
/// more tokens and names none.
|
||||
fn agent_of(subject: &str) -> Option<&str> {
|
||||
let agent = subject.strip_prefix(&swarm_queue_client::term_subject(""))?;
|
||||
let agent = subject.strip_prefix(&swarm_wire::term_subject(""))?;
|
||||
(!agent.is_empty() && !agent.contains('.')).then_some(agent)
|
||||
}
|
||||
|
||||
|
|
@ -307,12 +304,12 @@ mod tests {
|
|||
let nats = TestNats::start();
|
||||
let client = nats.client().await;
|
||||
let subscriber = client
|
||||
.subscribe(swarm_queue_client::term_subject_wildcard())
|
||||
.subscribe(swarm_wire::term_subject_wildcard())
|
||||
.await
|
||||
.expect("subscribe");
|
||||
let mut messages = Box::pin(live(subscriber, roster()));
|
||||
let atlas = swarm_queue_client::term_subject("atlas");
|
||||
let iris = swarm_queue_client::term_subject("iris");
|
||||
let atlas = swarm_wire::term_subject("atlas");
|
||||
let iris = swarm_wire::term_subject("iris");
|
||||
|
||||
publish(
|
||||
&client,
|
||||
|
|
@ -350,7 +347,7 @@ mod tests {
|
|||
.await;
|
||||
publish(
|
||||
&client,
|
||||
swarm_queue_client::subagent_term::subject("atlas", "scout"),
|
||||
swarm_wire::subagent_term::subject("atlas", "scout"),
|
||||
row(SEND_ICON, "send* → iris", "from a subagent"),
|
||||
)
|
||||
.await;
|
||||
|
|
@ -366,7 +363,7 @@ mod tests {
|
|||
let nats = TestNats::start();
|
||||
let client = nats.client().await;
|
||||
for agent in ["atlas", "iris"] {
|
||||
agent_term::open_or_create(&client, agent)
|
||||
swarm_queue_client::agent_term::open_or_create(&client, agent)
|
||||
.await
|
||||
.expect("the stream");
|
||||
}
|
||||
|
|
@ -381,8 +378,8 @@ mod tests {
|
|||
.as_secs();
|
||||
let now = iso8601_utc(i64::try_from(secs).expect("fits"));
|
||||
let earlier = iso8601_utc(i64::try_from(secs - 600).expect("fits"));
|
||||
let atlas = swarm_queue_client::term_subject("atlas");
|
||||
let iris = swarm_queue_client::term_subject("iris");
|
||||
let atlas = swarm_wire::term_subject("atlas");
|
||||
let iris = swarm_wire::term_subject("iris");
|
||||
publish(&client, atlas, stamped(&now, "send* → iris", "newer")).await;
|
||||
publish(
|
||||
&client,
|
||||
|
|
|
|||
|
|
@ -711,7 +711,7 @@ pub(crate) async fn create_agent(
|
|||
struct CreateSettings {
|
||||
limits: Option<swarm_wire::agent_limits::AgentCaps>,
|
||||
inference: Option<swarm_wire::agent_inference::AgentInference>,
|
||||
permissions: Option<swarm_queue_client::agent_permissions::AgentPermissions>,
|
||||
permissions: Option<swarm_wire::agent_permissions::AgentPermissions>,
|
||||
archetype: Option<swarm_queue_client::agent_archetype::ArchetypeRef>,
|
||||
agents_md: Option<String>,
|
||||
}
|
||||
|
|
@ -794,7 +794,7 @@ impl StoredSettings {
|
|||
agent: &str,
|
||||
limits: Option<&swarm_wire::agent_limits::AgentCaps>,
|
||||
inference: Option<&swarm_wire::agent_inference::AgentInference>,
|
||||
permissions: Option<&swarm_queue_client::agent_permissions::AgentPermissions>,
|
||||
permissions: Option<&swarm_wire::agent_permissions::AgentPermissions>,
|
||||
archetype: Option<&swarm_queue_client::agent_archetype::ArchetypeRef>,
|
||||
agents_md: Option<&str>,
|
||||
) -> Result<Self, problem_details::ProblemDetails> {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ use axum::http::{HeaderMap, StatusCode};
|
|||
use axum::response::sse::{Event, KeepAlive, Sse};
|
||||
use futures_util::{Stream, StreamExt as _, TryStreamExt as _};
|
||||
use serde::Serialize;
|
||||
use swarm_queue_client::{agent_term, subagent_term};
|
||||
use swarm_wire::subagent_term;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use crate::routes::AppState;
|
||||
|
|
@ -72,14 +72,18 @@ pub(crate) fn spawn(client: async_nats::Client, wanted: Arc<WantedWriter>, hives
|
|||
continue;
|
||||
}
|
||||
for agent in live_agents(&wanted, &hives).await {
|
||||
if let Err(e) = subagent_term::open_or_create(&client, &agent).await {
|
||||
if let Err(e) =
|
||||
swarm_queue_client::subagent_term::open_or_create(&client, &agent).await
|
||||
{
|
||||
tracing::warn!(
|
||||
%agent,
|
||||
error = %swarm_queue_client::chain(&e),
|
||||
"subagent streams: creating the agent's stream failed; next pass"
|
||||
);
|
||||
}
|
||||
if let Err(e) = agent_term::open_or_create(&client, &agent).await {
|
||||
if let Err(e) =
|
||||
swarm_queue_client::agent_term::open_or_create(&client, &agent).await
|
||||
{
|
||||
tracing::warn!(
|
||||
%agent,
|
||||
error = %swarm_queue_client::chain(&e),
|
||||
|
|
@ -287,7 +291,7 @@ fn read_failed(agent: &str, e: &dyn std::error::Error) -> problem_details::Probl
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use swarm_queue_client::subagent_term::subagent_names;
|
||||
use swarm_wire::subagent_term::subagent_names;
|
||||
|
||||
/// The list is what `STREAM.INFO` reports for the stream's subjects, by
|
||||
/// name: a subagent that published once and one that published many rows
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
//! **Replay, then the live tail, through one consumer.** `hive-agent`
|
||||
//! publishes each already-classified `TermMsg` row to the core subject
|
||||
//! `$SWARM.term.{agent}`, and the stream `term-main-{agent}`
|
||||
//! (`swarm_queue_client::agent_term`) keeps them. The handler creates an
|
||||
//! (`swarm_wire::agent_term`) keeps them. The handler creates an
|
||||
//! ephemeral consumer on that stream starting at the newest
|
||||
//! `agent_term::REPLAY_ROWS` rows, so the reader gets those and then every
|
||||
//! new row, in stream order, with no gap and no duplicate between the two.
|
||||
|
|
@ -35,7 +35,7 @@ use axum::http::HeaderMap;
|
|||
use axum::response::sse::{Event, KeepAlive, Sse};
|
||||
use futures_util::stream::BoxStream;
|
||||
use futures_util::{Stream, StreamExt as _};
|
||||
use swarm_queue_client::agent_term;
|
||||
use swarm_wire::agent_term;
|
||||
|
||||
use crate::routes::AppState;
|
||||
|
||||
|
|
@ -121,7 +121,7 @@ pub(crate) async fn stream_agent_term(
|
|||
let stream = replay_or_live(
|
||||
&client,
|
||||
&agent_term::stream_name(&agent),
|
||||
swarm_queue_client::term_subject(&agent),
|
||||
swarm_wire::term_subject(&agent),
|
||||
last_event_id(&headers),
|
||||
"term",
|
||||
)
|
||||
|
|
@ -198,8 +198,12 @@ pub(crate) async fn replay(
|
|||
"term-replay-{}",
|
||||
deliver_subject.rsplit('.').next().unwrap_or_default()
|
||||
);
|
||||
let config =
|
||||
agent_term::replay_consumer_config(subject.clone(), consumer_name, deliver_subject, start);
|
||||
let config = swarm_queue_client::agent_term::replay_consumer_config(
|
||||
subject.clone(),
|
||||
consumer_name,
|
||||
deliver_subject,
|
||||
start,
|
||||
);
|
||||
let consumer = stream.create_consumer(config).await.map_err(|e| {
|
||||
format!(
|
||||
"creating a consumer on {stream_name}: {}",
|
||||
|
|
@ -301,10 +305,7 @@ mod tests {
|
|||
async fn publish_rows(client: &async_nats::Client, rows: std::ops::RangeInclusive<u64>) {
|
||||
for i in rows {
|
||||
client
|
||||
.publish(
|
||||
swarm_queue_client::term_subject(AGENT),
|
||||
format!("row{i}").into(),
|
||||
)
|
||||
.publish(swarm_wire::term_subject(AGENT), format!("row{i}").into())
|
||||
.await
|
||||
.expect("publish");
|
||||
}
|
||||
|
|
@ -335,7 +336,7 @@ mod tests {
|
|||
replay(
|
||||
client,
|
||||
&agent_term::stream_name(AGENT),
|
||||
swarm_queue_client::term_subject(AGENT),
|
||||
swarm_wire::term_subject(AGENT),
|
||||
resume_after,
|
||||
)
|
||||
.await
|
||||
|
|
@ -352,7 +353,7 @@ mod tests {
|
|||
for subagent in ["scout", "builder"] {
|
||||
client
|
||||
.publish(
|
||||
swarm_queue_client::subagent_term::subject(AGENT, subagent),
|
||||
swarm_wire::subagent_term::subject(AGENT, subagent),
|
||||
format!("{subagent}{i}").into(),
|
||||
)
|
||||
.await
|
||||
|
|
@ -368,8 +369,8 @@ mod tests {
|
|||
) -> Result<BoxStream<'static, Row>, String> {
|
||||
replay(
|
||||
client,
|
||||
&swarm_queue_client::subagent_term::stream_name(AGENT),
|
||||
swarm_queue_client::subagent_term::subject(AGENT, "scout"),
|
||||
&swarm_wire::subagent_term::stream_name(AGENT),
|
||||
swarm_wire::subagent_term::subject(AGENT, "scout"),
|
||||
resume_after,
|
||||
)
|
||||
.await
|
||||
|
|
@ -413,7 +414,7 @@ mod tests {
|
|||
|
||||
let nats = TestNats::start();
|
||||
let client = nats.client().await;
|
||||
let stream = agent_term::open_or_create(&client, AGENT)
|
||||
let stream = swarm_queue_client::agent_term::open_or_create(&client, AGENT)
|
||||
.await
|
||||
.expect("the stream");
|
||||
publish_rows(&client, 1..=3).await;
|
||||
|
|
@ -445,12 +446,12 @@ mod tests {
|
|||
async fn ensuring_the_terminal_stream_again_leaves_it_as_it_is() {
|
||||
let nats = TestNats::start();
|
||||
let client = nats.client().await;
|
||||
let created = agent_term::open_or_create(&client, AGENT)
|
||||
let created = swarm_queue_client::agent_term::open_or_create(&client, AGENT)
|
||||
.await
|
||||
.expect("the first ensure creates the stream");
|
||||
publish_rows(&client, 1..=3).await;
|
||||
stored(&client, 3).await;
|
||||
agent_term::open_or_create(&client, AGENT)
|
||||
swarm_queue_client::agent_term::open_or_create(&client, AGENT)
|
||||
.await
|
||||
.expect("a second ensure succeeds");
|
||||
// Read back from the server, not from what the ensure returned.
|
||||
|
|
@ -484,7 +485,7 @@ mod tests {
|
|||
.expect("an earlier stream");
|
||||
publish_rows(&client, 1..=2).await;
|
||||
stored(&client, 2).await;
|
||||
let opened = agent_term::open_or_create(&client, AGENT)
|
||||
let opened = swarm_queue_client::agent_term::open_or_create(&client, AGENT)
|
||||
.await
|
||||
.expect("an existing stream opens");
|
||||
assert_eq!(opened.cached_info().config.max_message_size, 4096);
|
||||
|
|
@ -497,7 +498,7 @@ mod tests {
|
|||
async fn the_replay_reaches_the_reader_before_the_live_tail() {
|
||||
let nats = TestNats::start();
|
||||
let client = nats.client().await;
|
||||
agent_term::open_or_create(&client, AGENT)
|
||||
swarm_queue_client::agent_term::open_or_create(&client, AGENT)
|
||||
.await
|
||||
.expect("the stream");
|
||||
publish_rows(&client, 1..=150).await;
|
||||
|
|
@ -514,7 +515,7 @@ mod tests {
|
|||
async fn a_reconnect_gets_every_later_row_once() {
|
||||
let nats = TestNats::start();
|
||||
let client = nats.client().await;
|
||||
agent_term::open_or_create(&client, AGENT)
|
||||
swarm_queue_client::agent_term::open_or_create(&client, AGENT)
|
||||
.await
|
||||
.expect("the stream");
|
||||
publish_rows(&client, 1..=150).await;
|
||||
|
|
@ -541,7 +542,7 @@ mod tests {
|
|||
async fn a_subagent_replays_its_own_newest_rows_before_the_live_tail() {
|
||||
let nats = TestNats::start();
|
||||
let client = nats.client().await;
|
||||
let stream_name = swarm_queue_client::subagent_term::stream_name(AGENT);
|
||||
let stream_name = swarm_wire::subagent_term::stream_name(AGENT);
|
||||
swarm_queue_client::subagent_term::open_or_create(&client, AGENT)
|
||||
.await
|
||||
.expect("the stream");
|
||||
|
|
@ -559,7 +560,7 @@ mod tests {
|
|||
async fn a_subagent_reconnect_gets_every_later_row_once() {
|
||||
let nats = TestNats::start();
|
||||
let client = nats.client().await;
|
||||
let stream_name = swarm_queue_client::subagent_term::stream_name(AGENT);
|
||||
let stream_name = swarm_wire::subagent_term::stream_name(AGENT);
|
||||
swarm_queue_client::subagent_term::open_or_create(&client, AGENT)
|
||||
.await
|
||||
.expect("the stream");
|
||||
|
|
|
|||
Loading…
Reference in a new issue