use bevy_ecs::prelude::*;
use leviath_core::blueprint::{ContentTransform, RegionMapping};
use tokio::sync::mpsc::UnboundedReceiver;
use crate::compaction_bridge::{CompactionJob, CompactionOutcome, run_compaction_job};
use crate::components::{AgentState, AgentStatus, ContextWindow};
use crate::pipeline::{AgentBlueprint, CompactionSettings, InferenceStage, Providers};
pub fn apply_context_transforms(world: &mut World, parent: Entity, child: Entity) {
let Some(mappings) = collect_transform_mappings(world, parent, child) else {
return;
};
let mut writes: Vec<(String, String)> = Vec::new();
let mut to_summarize: Vec<(String, String)> = Vec::new();
if let Some(parent_window) = world.get::<ContextWindow>(parent) {
for m in &mappings {
if let Some(region) = parent_window.get_region(&m.from_region) {
let joined = region
.content
.iter()
.map(|e| e.content.as_str())
.collect::<Vec<_>>()
.join("\n");
if !joined.is_empty() {
let content = apply_content_transform(&joined, &m.transform);
if matches!(m.transform, Some(ContentTransform::Summarize)) {
to_summarize.push((m.to_region.clone(), content.clone()));
}
writes.push((m.to_region.clone(), content));
}
}
}
}
let mut wrote_to_child = false;
if let Some(mut child_window) = world.get_mut::<ContextWindow>(child) {
for (to_region, content) in writes {
let tokens = leviath_core::estimate_tokens(&content);
let _ = child_window.add_to_region(&to_region, content, tokens);
}
wrote_to_child = true;
}
if wrote_to_child && !to_summarize.is_empty() {
world
.entity_mut(child)
.insert(PendingContentSummary(to_summarize));
}
}
#[derive(Component, Debug, Clone)]
pub struct PendingContentSummary(pub Vec<(String, String)>);
#[derive(Component, Debug, Clone, Copy)]
pub struct AwaitingContentSummary;
#[derive(Resource)]
pub struct ContentSummaryResults(pub UnboundedReceiver<CompactionOutcome>);
pub fn dispatch_content_summary(
agents: Query<(
Entity,
&AgentState,
&PendingContentSummary,
Option<&CompactionSettings>,
)>,
stage: Res<InferenceStage>,
providers: Res<Providers>,
mut commands: Commands,
) {
crate::tick_scope::clear();
for (entity, state, pending, settings) in agents.iter() {
crate::tick_scope::enter(entity);
if state.status != AgentStatus::Active {
continue; }
let Some(settings) = settings else {
commands.entity(entity).remove::<PendingContentSummary>();
continue;
};
let config = &settings.0;
let Some(provider) = providers.0.get(&config.provider) else {
commands.entity(entity).remove::<PendingContentSummary>();
continue;
};
let Some(permit) = stage.pools.try_acquire(&config.model) else {
continue; };
let requests = pending
.0
.iter()
.map(|(region, content)| {
(
region.clone(),
crate::pipeline::compaction_request(config, content, region),
)
})
.collect();
stage.runtime.spawn(run_compaction_job(
CompactionJob {
entity,
provider,
requests,
permit,
},
std::time::Duration::from_secs(leviath_providers::DEFAULT_INFERENCE_TIMEOUT_SECS),
stage.content_summary_outcomes.clone(),
stage.wake.clone(),
));
commands
.entity(entity)
.remove::<PendingContentSummary>()
.insert(AwaitingContentSummary);
}
}
pub fn collect_content_summary(
mut results: ResMut<ContentSummaryResults>,
mut agents: Query<&mut ContextWindow, With<AwaitingContentSummary>>,
mut commands: Commands,
) {
crate::tick_scope::clear();
while let Ok(outcome) = results.0.try_recv() {
let Ok(mut window) = agents.get_mut(outcome.entity) else {
continue; };
crate::tick_scope::enter(outcome.entity);
if let Ok(summaries) = outcome.result {
for (region, summary) in summaries {
let tokens = leviath_core::estimate_tokens(&summary);
window.replace_region(®ion, summary, tokens);
}
}
commands
.entity(outcome.entity)
.remove::<AwaitingContentSummary>();
}
}
fn collect_transform_mappings(
world: &World,
parent: Entity,
child: Entity,
) -> Option<Vec<RegionMapping>> {
let parent_name = world.get::<AgentBlueprint>(parent)?.0.name.clone();
let child_name = world.get::<AgentBlueprint>(child)?.0.name.clone();
if parent_name == child_name {
return None;
}
for entity in [parent, child] {
let bp = world
.get::<AgentBlueprint>(entity)
.expect("parent/child blueprint checked above");
let found =
bp.0.transforms
.iter()
.find(|t| t.from_blueprint == parent_name && t.to_blueprint == child_name)
.map(|t| t.mappings.clone())
.filter(|m| !m.is_empty());
if found.is_some() {
return found;
}
}
None
}
fn apply_content_transform(content: &str, transform: &Option<ContentTransform>) -> String {
match transform {
None | Some(ContentTransform::Direct) => content.to_string(),
Some(ContentTransform::Extract { fields }) => extract_fields(content, fields),
Some(ContentTransform::Summarize) => content.to_string(),
}
}
fn extract_fields(content: &str, fields: &[String]) -> String {
match serde_json::from_str::<serde_json::Value>(content) {
Ok(serde_json::Value::Object(map)) => {
let filtered: serde_json::Map<String, serde_json::Value> = fields
.iter()
.filter_map(|f| map.get(f).map(|v| (f.clone(), v.clone())))
.collect();
serde_json::to_string_pretty(&serde_json::Value::Object(filtered))
.expect("a JSON object always serializes")
}
_ => content.to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use leviath_core::blueprint::{ContextTransform, RegionMapping};
use leviath_core::{Region, RegionKind};
fn bp_with_transforms(name: &str, transforms: Vec<ContextTransform>) -> AgentBlueprint {
let layout = leviath_core::layout::ContextLayout::new(vec![], 10_000);
let mut bp = leviath_core::Blueprint::new(
name.to_string(),
"d".to_string(),
vec![leviath_core::Stage::new(
"s".to_string(),
leviath_core::blueprint::ModelConfig::new("p".to_string(), "m".to_string()),
)],
layout,
);
bp.transforms = transforms;
AgentBlueprint(bp)
}
fn window_with(regions: &[(&str, &str)]) -> ContextWindow {
let mut w = ContextWindow::new(100_000);
for (name, content) in regions {
w.add_region(Region::new(name.to_string(), RegionKind::Clearable, 10_000));
if !content.is_empty() {
w.add_to_region(name, content.to_string(), 5).unwrap();
}
}
w
}
fn mapping(from: &str, to: &str, transform: Option<ContentTransform>) -> RegionMapping {
RegionMapping {
from_region: from.to_string(),
to_region: to.to_string(),
transform,
}
}
fn transform(from_bp: &str, to_bp: &str, mappings: Vec<RegionMapping>) -> ContextTransform {
ContextTransform {
from_blueprint: from_bp.to_string(),
to_blueprint: to_bp.to_string(),
mappings,
}
}
#[test]
fn apply_content_transform_variants() {
assert_eq!(apply_content_transform("x", &None), "x");
assert_eq!(
apply_content_transform("x", &Some(ContentTransform::Direct)),
"x"
);
assert_eq!(
apply_content_transform("x", &Some(ContentTransform::Summarize)),
"x"
);
let out = apply_content_transform(
r#"{"a":1,"b":2}"#,
&Some(ContentTransform::Extract {
fields: vec!["a".to_string()],
}),
);
assert!(out.contains("\"a\""));
assert!(!out.contains("\"b\""));
}
#[test]
fn extract_fields_handles_objects_missing_fields_and_non_objects() {
let out = extract_fields(r#"{"a":1,"b":2}"#, &["a".to_string(), "z".to_string()]);
assert!(out.contains("\"a\""));
assert!(!out.contains("\"b\""));
assert!(!out.contains("\"z\"")); assert_eq!(extract_fields("[1,2]", &["a".to_string()]), "[1,2]");
assert_eq!(
extract_fields("plain text", &["a".to_string()]),
"plain text"
);
}
#[test]
fn collect_mappings_finds_on_parent_then_child_and_rejects_mismatches() {
let m = vec![mapping("plan", "task", None)];
let mut w = World::new();
let p = w
.spawn(bp_with_transforms(
"planner",
vec![transform("planner", "coder", m.clone())],
))
.id();
let c = w.spawn(bp_with_transforms("coder", vec![])).id();
assert_eq!(collect_transform_mappings(&w, p, c).unwrap().len(), 1);
let mut w2 = World::new();
let p2 = w2.spawn(bp_with_transforms("planner", vec![])).id();
let c2 = w2
.spawn(bp_with_transforms(
"coder",
vec![transform("planner", "coder", m.clone())],
))
.id();
assert_eq!(collect_transform_mappings(&w2, p2, c2).unwrap().len(), 1);
let mut w3 = World::new();
let p3 = w3
.spawn(bp_with_transforms(
"same",
vec![transform("same", "same", m.clone())],
))
.id();
let c3 = w3.spawn(bp_with_transforms("same", vec![])).id();
assert!(collect_transform_mappings(&w3, p3, c3).is_none());
let mut w4 = World::new();
let p4 = w4
.spawn(bp_with_transforms(
"planner",
vec![transform("planner", "other", m.clone())],
))
.id();
let c4 = w4.spawn(bp_with_transforms("coder", vec![])).id();
assert!(collect_transform_mappings(&w4, p4, c4).is_none());
let mut w5 = World::new();
let p5 = w5.spawn(bp_with_transforms("planner", vec![])).id();
let c5 = w5.spawn_empty().id();
assert!(collect_transform_mappings(&w5, p5, c5).is_none());
let mut w6 = World::new();
let p6 = w6.spawn_empty().id();
let c6 = w6.spawn(bp_with_transforms("coder", vec![])).id();
assert!(collect_transform_mappings(&w6, p6, c6).is_none());
}
#[test]
fn apply_context_transforms_copies_and_transforms_regions() {
let mut w = World::new();
let parent = w
.spawn((
bp_with_transforms(
"planner",
vec![transform(
"planner",
"coder",
vec![
mapping("plan", "task", Some(ContentTransform::Direct)),
mapping("empty", "unused", None), mapping("absent", "ghost", None), mapping(
"data",
"inputs",
Some(ContentTransform::Extract {
fields: vec!["keep".to_string()],
}),
),
],
)],
),
window_with(&[
("plan", "the plan"),
("empty", ""),
("data", r#"{"keep":1,"drop":2}"#),
]),
))
.id();
let child = w
.spawn((
bp_with_transforms("coder", vec![]),
window_with(&[("task", ""), ("inputs", "")]),
))
.id();
apply_context_transforms(&mut w, parent, child);
let cw = w.get::<ContextWindow>(child).unwrap();
let task = cw.get_region("task").unwrap();
assert!(task.current_tokens > 0);
assert_eq!(task.content[0].content, "the plan");
let inputs = cw.get_region("inputs").unwrap();
assert!(inputs.content[0].content.contains("\"keep\""));
assert!(!inputs.content[0].content.contains("\"drop\""));
}
#[test]
fn apply_context_transforms_noop_without_a_matching_transform_or_windows() {
let mut w = World::new();
let p = w
.spawn((
bp_with_transforms("a", vec![]),
window_with(&[("plan", "x")]),
))
.id();
let c = w
.spawn((
bp_with_transforms("b", vec![]),
window_with(&[("task", "")]),
))
.id();
apply_context_transforms(&mut w, p, c);
assert_eq!(
w.get::<ContextWindow>(c)
.unwrap()
.get_region("task")
.unwrap()
.current_tokens,
0
);
let m = vec![mapping("plan", "task", None)];
let mut w2 = World::new();
let p2 = w2
.spawn(bp_with_transforms("a", vec![transform("a", "b", m)]))
.id(); let c2 = w2
.spawn((
bp_with_transforms("b", vec![]),
window_with(&[("task", "")]),
))
.id();
apply_context_transforms(&mut w2, p2, c2);
assert_eq!(
w2.get::<ContextWindow>(c2)
.unwrap()
.get_region("task")
.unwrap()
.current_tokens,
0
);
let m2 = vec![mapping("plan", "task", None)];
let mut w3 = World::new();
let p3 = w3
.spawn((
bp_with_transforms("a", vec![transform("a", "b", m2)]),
window_with(&[("plan", "content")]),
))
.id();
let c3 = w3.spawn(bp_with_transforms("b", vec![])).id(); apply_context_transforms(&mut w3, p3, c3);
assert!(w3.get::<ContextWindow>(c3).is_none());
}
use crate::components::AgentStatus;
use crate::inference_pool::{InferencePoolConfig, InferencePools};
use crate::providers::ProviderRegistry;
use leviath_providers::{
FinishReason, InferenceRequest, InferenceResponse, ModelCapabilities, Provider,
ProviderError, TokenUsage,
};
use std::sync::Arc;
use tokio::runtime::Handle;
use tokio::sync::Notify;
use tokio::sync::mpsc;
struct FakeProvider {
reply: String,
fail: bool,
}
#[async_trait::async_trait]
impl Provider for FakeProvider {
async fn infer(
&self,
_req: InferenceRequest,
) -> leviath_providers::Result<InferenceResponse> {
if self.fail {
return Err(ProviderError::Other("boom".to_string()));
}
Ok(InferenceResponse {
content: self.reply.clone(),
tool_calls: vec![],
tokens_used: TokenUsage {
prompt_tokens: 1,
completion_tokens: 1,
total_tokens: 2,
cached_tokens: 0,
cache_write_tokens: 0,
},
finish_reason: FinishReason::Complete,
})
}
async fn count_tokens(&self, _t: &str, _m: &str) -> usize {
1
}
fn max_context_tokens(&self, _m: &str) -> usize {
100_000
}
fn name(&self) -> &str {
"fake"
}
fn capabilities(&self, _m: &str) -> ModelCapabilities {
ModelCapabilities::default()
}
}
fn agent_state(status: AgentStatus) -> AgentState {
AgentState {
agent_id: "child".to_string(),
current_stage: "s".to_string(),
iteration: 0,
status,
spawned_children_ids: vec![],
pending_wait: None,
accepts_messages: true,
}
}
fn settings() -> CompactionSettings {
CompactionSettings(leviath_core::CompactionConfig {
provider: "p".to_string(),
model: "m".to_string(),
system_prompt: None,
user_prompt_template: None,
max_summary_tokens: 200,
temperature: 0.2,
})
}
fn summary_world(
register: bool,
fail: bool,
pools: InferencePools,
) -> (World, mpsc::UnboundedReceiver<CompactionOutcome>) {
let mut registry = ProviderRegistry::new();
if register {
registry.register(
"p".to_string(),
Arc::new(FakeProvider {
reply: "SUMMARY".to_string(),
fail,
}),
);
}
let (cs_tx, cs_rx) = mpsc::unbounded_channel();
let (a, _a) = mpsc::unbounded_channel();
let (b, _b) = mpsc::unbounded_channel();
let (c, _c) = mpsc::unbounded_channel();
let mut world = World::new();
world.insert_resource(Providers(registry));
world.insert_resource(InferenceStage {
pools: Arc::new(pools),
outcomes: a,
transition_outcomes: b,
compaction_outcomes: c,
content_summary_outcomes: cs_tx,
wake: Arc::new(Notify::new()),
runtime: Handle::current(),
exact_token_counting: false,
});
(world, cs_rx)
}
fn run_dispatch(world: &mut World) {
let mut s = Schedule::default();
s.add_systems(dispatch_content_summary);
s.run(world);
}
#[tokio::test]
async fn fake_provider_metadata_is_exercised() {
let p = FakeProvider {
reply: String::new(),
fail: false,
};
assert_eq!(p.name(), "fake");
assert_eq!(p.count_tokens("t", "m").await, 1);
assert_eq!(p.max_context_tokens("m"), 100_000);
let _ = p.capabilities("m");
}
#[test]
fn apply_context_transforms_queues_a_summarize_mapping() {
let mut w = World::new();
let parent = w
.spawn((
bp_with_transforms(
"planner",
vec![transform(
"planner",
"coder",
vec![mapping("plan", "task", Some(ContentTransform::Summarize))],
)],
),
window_with(&[("plan", "the long plan")]),
))
.id();
let child = w
.spawn((
bp_with_transforms("coder", vec![]),
window_with(&[("task", "")]),
))
.id();
apply_context_transforms(&mut w, parent, child);
assert_eq!(
w.get::<ContextWindow>(child)
.unwrap()
.get_region("task")
.unwrap()
.content[0]
.content,
"the long plan"
);
let pending = w.get::<PendingContentSummary>(child).unwrap();
assert_eq!(
pending.0,
vec![("task".to_string(), "the long plan".to_string())]
);
}
#[tokio::test]
async fn dispatch_summarizes_and_marks_awaiting() {
let (mut world, mut rx) =
summary_world(true, false, InferencePools::new(InferencePoolConfig::new()));
let e = world
.spawn((
agent_state(AgentStatus::Active),
settings(),
PendingContentSummary(vec![("task".to_string(), "raw".to_string())]),
))
.id();
run_dispatch(&mut world);
assert!(world.get::<PendingContentSummary>(e).is_none());
assert!(world.get::<AwaitingContentSummary>(e).is_some());
for _ in 0..8 {
tokio::task::yield_now().await;
}
let outcome = rx.try_recv().expect("summary job ran");
assert_eq!(outcome.entity, e);
assert_eq!(
outcome.result.unwrap(),
vec![("task".to_string(), "SUMMARY".to_string())]
);
}
#[tokio::test]
async fn dispatch_without_settings_or_provider_drops_pending() {
let (mut world, _rx) =
summary_world(true, false, InferencePools::new(InferencePoolConfig::new()));
let e = world
.spawn((
agent_state(AgentStatus::Active),
PendingContentSummary(vec![("task".to_string(), "raw".to_string())]),
))
.id();
run_dispatch(&mut world);
assert!(world.get::<PendingContentSummary>(e).is_none());
assert!(world.get::<AwaitingContentSummary>(e).is_none());
let (mut world2, _rx2) = summary_world(
false,
false,
InferencePools::new(InferencePoolConfig::new()),
);
let e2 = world2
.spawn((
agent_state(AgentStatus::Active),
settings(),
PendingContentSummary(vec![("task".to_string(), "raw".to_string())]),
))
.id();
run_dispatch(&mut world2);
assert!(world2.get::<PendingContentSummary>(e2).is_none());
assert!(world2.get::<AwaitingContentSummary>(e2).is_none());
}
#[tokio::test]
async fn dispatch_keeps_pending_on_full_pool_and_skips_non_active() {
let (mut world, _rx) = summary_world(
true,
false,
InferencePools::new(InferencePoolConfig::new().with_default(Some(0))),
);
let e = world
.spawn((
agent_state(AgentStatus::Active),
settings(),
PendingContentSummary(vec![("task".to_string(), "raw".to_string())]),
))
.id();
run_dispatch(&mut world);
assert!(world.get::<PendingContentSummary>(e).is_some());
assert!(world.get::<AwaitingContentSummary>(e).is_none());
let (mut world2, _rx2) =
summary_world(true, false, InferencePools::new(InferencePoolConfig::new()));
let e2 = world2
.spawn((
agent_state(AgentStatus::Waiting),
settings(),
PendingContentSummary(vec![("task".to_string(), "raw".to_string())]),
))
.id();
run_dispatch(&mut world2);
assert!(world2.get::<PendingContentSummary>(e2).is_some());
}
fn run_collect(world: &mut World) {
let mut s = Schedule::default();
s.add_systems(collect_content_summary);
s.run(world);
}
#[test]
fn collect_replaces_region_with_summary_and_leaves_raw_on_error() {
let mut world = World::new();
let (tx, rx) = mpsc::unbounded_channel();
world.insert_resource(ContentSummaryResults(rx));
let e = world
.spawn((
AwaitingContentSummary,
window_with(&[("task", "raw content")]),
))
.id();
tx.send(CompactionOutcome {
entity: e,
result: Ok(vec![("task".to_string(), "SHORT".to_string())]),
})
.unwrap();
run_collect(&mut world);
assert_eq!(
world
.get::<ContextWindow>(e)
.unwrap()
.get_region("task")
.unwrap()
.content[0]
.content,
"SHORT"
);
assert!(world.get::<AwaitingContentSummary>(e).is_none());
let e2 = world
.spawn((AwaitingContentSummary, window_with(&[("task", "keep me")])))
.id();
tx.send(CompactionOutcome {
entity: e2,
result: Err(leviath_providers::ProviderError::Other("x".to_string())),
})
.unwrap();
tx.send(CompactionOutcome {
entity: Entity::from_raw_u32(9999)
.expect("a small literal index is always a valid entity id"),
result: Ok(vec![("task".to_string(), "ignored".to_string())]),
})
.unwrap();
run_collect(&mut world);
assert_eq!(
world
.get::<ContextWindow>(e2)
.unwrap()
.get_region("task")
.unwrap()
.content[0]
.content,
"keep me"
);
assert!(world.get::<AwaitingContentSummary>(e2).is_none());
}
}