#![allow(clippy::unnecessary_literal_bound)]
use crate::agent::Agent;
use crate::context::{ContextKey, Fact};
use crate::effect::AgentEffect;
pub struct SeedAgent {
fact_id: String,
content: String,
}
impl SeedAgent {
#[must_use]
pub fn new(fact_id: impl Into<String>, content: impl Into<String>) -> Self {
Self {
fact_id: fact_id.into(),
content: content.into(),
}
}
}
impl Agent for SeedAgent {
fn name(&self) -> &str {
"SeedAgent"
}
fn dependencies(&self) -> &[ContextKey] {
&[] }
fn accepts(&self, ctx: &dyn crate::ContextView) -> bool {
!ctx.get(ContextKey::Seeds)
.iter()
.any(|f| f.id == self.fact_id)
}
fn execute(&self, _ctx: &dyn crate::ContextView) -> AgentEffect {
AgentEffect::with_fact(Fact {
key: ContextKey::Seeds,
id: self.fact_id.clone(),
content: self.content.clone(),
})
}
}
pub struct ReactOnceAgent {
fact_id: String,
content: String,
}
impl ReactOnceAgent {
#[must_use]
pub fn new(fact_id: impl Into<String>, content: impl Into<String>) -> Self {
Self {
fact_id: fact_id.into(),
content: content.into(),
}
}
}
impl Agent for ReactOnceAgent {
fn name(&self) -> &str {
"ReactOnceAgent"
}
fn dependencies(&self) -> &[ContextKey] {
&[ContextKey::Seeds] }
fn accepts(&self, ctx: &dyn crate::ContextView) -> bool {
ctx.has(ContextKey::Seeds)
&& !ctx
.get(ContextKey::Hypotheses)
.iter()
.any(|f| f.id == self.fact_id)
}
fn execute(&self, _ctx: &dyn crate::ContextView) -> AgentEffect {
AgentEffect::with_fact(Fact {
key: ContextKey::Hypotheses,
id: self.fact_id.clone(),
content: self.content.clone(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::context::Context;
use crate::engine::Engine;
#[test]
fn seed_agent_emits_once() {
let mut engine = Engine::new();
engine.register(SeedAgent::new("s1", "value"));
let result = engine.run(Context::new()).expect("converges");
assert!(result.converged);
assert_eq!(result.context.get(ContextKey::Seeds).len(), 1);
}
#[test]
fn react_once_agent_chains_from_seed() {
let mut engine = Engine::new();
engine.register(SeedAgent::new("s1", "seed"));
engine.register(ReactOnceAgent::new("h1", "hypothesis"));
let result = engine.run(Context::new()).expect("converges");
assert!(result.converged);
assert!(result.context.has(ContextKey::Seeds));
assert!(result.context.has(ContextKey::Hypotheses));
}
#[test]
fn multiple_seeds_all_converge() {
let mut engine = Engine::new();
engine.register(SeedAgent::new("s1", "first"));
engine.register(SeedAgent::new("s2", "second"));
engine.register(SeedAgent::new("s3", "third"));
let result = engine.run(Context::new()).expect("converges");
assert!(result.converged);
assert_eq!(result.context.get(ContextKey::Seeds).len(), 3);
}
#[test]
fn chain_of_three_converges() {
struct StrategyAgent;
impl Agent for StrategyAgent {
fn name(&self) -> &str {
"StrategyAgent"
}
fn dependencies(&self) -> &[ContextKey] {
&[ContextKey::Hypotheses]
}
fn accepts(&self, ctx: &dyn crate::ContextView) -> bool {
ctx.has(ContextKey::Hypotheses) && !ctx.has(ContextKey::Strategies)
}
fn execute(&self, _ctx: &dyn crate::ContextView) -> AgentEffect {
AgentEffect::with_fact(Fact {
key: ContextKey::Strategies,
id: "strat-1".into(),
content: "derived strategy".into(),
})
}
}
let mut engine = Engine::new();
engine.register(SeedAgent::new("s1", "seed"));
engine.register(ReactOnceAgent::new("h1", "hypothesis"));
engine.register(StrategyAgent);
let result = engine.run(Context::new()).expect("converges");
assert!(result.converged);
assert!(result.context.has(ContextKey::Seeds));
assert!(result.context.has(ContextKey::Hypotheses));
assert!(result.context.has(ContextKey::Strategies));
}
}