use crate::flow_dispatcher::pure_shape::{run_pure_shape, PureShapeStep};
use crate::flow_dispatcher::{DispatchCtx, DispatchError, NodeOutcome};
use crate::flow_execution_event::{now_ms, FlowExecutionEvent};
use crate::ir_nodes::{
IRAggregateStep, IRAssociateStep, IRCorroborateStep, IRExploreStep, IRFocusStep,
IRForgeBlock, IRIngestStep, IRNavigateStep, IRRecallStep, IRRememberStep,
};
pub async fn run_remember(
node: &IRRememberStep,
ctx: &mut DispatchCtx,
) -> Result<NodeOutcome, DispatchError> {
if ctx.cancel.is_cancelled() {
return Err(DispatchError::UpstreamCancelled);
}
let step_index = ctx.step_counter;
ctx.step_counter += 1;
let value = ctx
.let_bindings
.get(&node.expression)
.cloned()
.unwrap_or_else(|| node.expression.clone());
emit_step_start(ctx, &step_name_for_remember(node), step_index, "remember")?;
ctx.let_bindings
.insert(node.memory_target.clone(), value.clone());
if let Some(backend) = ctx.pem_backend.clone() {
write_through_pem(&backend, ctx, &node.memory_target, &value).await?;
}
emit_step_complete(
ctx,
&step_name_for_remember(node),
step_index,
&value,
0,
)?;
Ok(NodeOutcome::Completed {
output: value,
tokens_emitted: 0,
step_index,
})
}
fn step_name_for_remember(node: &IRRememberStep) -> String {
if node.memory_target.is_empty() {
"Remember".to_string()
} else {
node.memory_target.clone()
}
}
async fn write_through_pem(
backend: &std::sync::Arc<dyn crate::pem::PersistenceBackend>,
ctx: &DispatchCtx,
key: &str,
value: &str,
) -> Result<(), DispatchError> {
use crate::pem::state::{CognitiveState, MemoryEntry};
use chrono::{Duration as ChronoDuration, Utc};
let mut state = match backend.restore(&ctx.session_id).await {
Ok(s) => s,
Err(_) => CognitiveState::new(&ctx.session_id, &ctx.tenant_id, &ctx.flow_name),
};
state.short_term_memory.push(MemoryEntry {
key: key.to_string(),
payload: serde_json::Value::String(value.to_string()),
symbolic_refs: Vec::new(),
stored_at: Utc::now(),
});
state.last_updated_at = Utc::now();
backend
.persist(&ctx.session_id, &state, ChronoDuration::hours(24))
.await
.map_err(|e| DispatchError::BackendError {
name: "pem".to_string(),
message: format!("{e:?}"),
})?;
Ok(())
}
pub async fn run_recall(
node: &IRRecallStep,
ctx: &mut DispatchCtx,
) -> Result<NodeOutcome, DispatchError> {
if ctx.cancel.is_cancelled() {
return Err(DispatchError::UpstreamCancelled);
}
let step_index = ctx.step_counter;
ctx.step_counter += 1;
emit_step_start(ctx, &step_name_for_recall(node), step_index, "recall")?;
let resolved = resolve_recall_value(node, ctx).await;
ctx.let_bindings
.insert(node.query.clone(), resolved.clone());
emit_step_complete(
ctx,
&step_name_for_recall(node),
step_index,
&resolved,
0,
)?;
Ok(NodeOutcome::Completed {
output: resolved,
tokens_emitted: 0,
step_index,
})
}
fn step_name_for_recall(node: &IRRecallStep) -> String {
if node.query.is_empty() {
"Recall".to_string()
} else {
node.query.clone()
}
}
async fn resolve_recall_value(node: &IRRecallStep, ctx: &DispatchCtx) -> String {
if let Some(backend) = &ctx.pem_backend {
if let Ok(state) = backend.restore(&ctx.session_id).await {
if let Some(entry) = state
.short_term_memory
.iter()
.rev()
.find(|e| e.key == node.memory_source)
{
if let serde_json::Value::String(s) = &entry.payload {
return s.clone();
}
return entry.payload.to_string();
}
}
}
ctx.let_bindings
.get(&node.memory_source)
.cloned()
.unwrap_or_default()
}
async fn forge_phase(
ctx: &mut DispatchCtx,
name: &str,
prompt: String,
temperature: f64,
) -> Result<String, DispatchError> {
let shape = PureShapeStep {
name: name.to_string(),
user_prompt: prompt,
framing_addendum: Some(
"You are inside a directed creative-synthesis pipeline (`forge`). Produce vivid, \
concrete, ORIGINAL conceptual content — never a hedge or a restatement."
.into(),
),
kind_slug: "forge",
tools: Vec::new(),
requires_context: None,
temperature: Some(temperature),
now_tz: None,
};
match run_pure_shape(shape, ctx).await? {
NodeOutcome::Completed { output, .. } => Ok(output),
_ => Ok(String::new()),
}
}
pub async fn run_forge(
node: &IRForgeBlock,
ctx: &mut DispatchCtx,
) -> Result<NodeOutcome, DispatchError> {
if ctx.cancel.is_cancelled() {
return Err(DispatchError::UpstreamCancelled);
}
let step_index = ctx.step_counter;
let mode = if node.mode.is_empty() {
"exploratory"
} else {
node.mode.as_str()
};
let depth = node.depth.max(1) as usize;
let branches = node.branches.max(1) as usize;
let nov_floor = crate::forge::novelty_floor(node.novelty);
let tau_base = crate::forge::boden_profile(mode).tau_base;
let tau_incubate = crate::forge::incubation_temperature(mode, node.novelty);
let out_type = if node.output_type.is_empty() {
"concept".to_string()
} else {
node.output_type.clone()
};
let coherence_floor = ctx
.anchors
.iter()
.find(|a| a.name == node.constraints_ref)
.and_then(|a| a.confidence_floor)
.unwrap_or(0.0);
let baseline = forge_phase(
ctx,
&node.name,
format!(
"Expand this creative seed into its CONVENTIONAL, obvious interpretation — the first \
associations most people make. Seed: \"{}\". Be concrete, but deliberately \
conventional; this is the baseline we will surpass.",
node.seed
),
0.3,
)
.await?;
let mut incubated = baseline.clone();
for i in 0..depth {
incubated = forge_phase(
ctx,
&node.name,
format!(
"Speculatively explore FAR beyond the obvious — past cliché into unexpected \
territory (iteration {}/{}). Seed: \"{}\". Obvious baseline to surpass: {}. \
Prior exploration: {}. Go further; break the expected frame.",
i + 1,
depth,
node.seed,
baseline,
incubated
),
tau_incubate,
)
.await?;
}
let mut candidates: Vec<crate::forge::Branch> = Vec::with_capacity(branches);
for _ in 0..branches {
let output = forge_phase(
ctx,
&node.name,
format!(
"Crystallize a single, coherent, GENUINELY NOVEL {} from this incubated \
exploration. Seed: \"{}\". Exploration: {}. Deliver the finished creative \
concept — surprising yet coherent.",
out_type, node.seed, incubated
),
tau_base,
)
.await?;
let novelty = crate::forge::novelty_score(&baseline, &output);
candidates.push(crate::forge::Branch {
output,
coherence: 1.0, novelty,
});
}
let winner_idx = crate::forge::select_illumination(&candidates, coherence_floor, nov_floor);
let verdict = crate::forge::verify(winner_idx.map(|i| &candidates[i]), nov_floor);
ctx.step_counter += 1;
match verdict {
crate::forge::ForgeVerdict::Accepted {
output,
novelty,
coherence: _,
} => {
emit_step_start(ctx, "Forge", step_index, "forge")?;
emit_step_complete(ctx, "Forge", step_index, &output, 0)?;
let _ = novelty; Ok(NodeOutcome::Completed {
output,
tokens_emitted: 0,
step_index,
})
}
crate::forge::ForgeVerdict::Rejected(reason) => {
let detail = match &reason {
crate::forge::ForgeRejection::NoveltyFloorBreached { measured, floor } => format!(
"best branch novelty {:.3} < floor {:.3} — the synthesis was too derivative \
of the obvious reading of the seed",
measured, floor
),
crate::forge::ForgeRejection::NoFeasibleBranch => {
"no illumination branch satisfied the constraints anchor".to_string()
}
};
Err(DispatchError::BackendError {
name: "forge".to_string(),
message: format!("{}: {}", reason.slug(), detail),
})
}
}
}
pub async fn run_focus(
node: &IRFocusStep,
ctx: &mut DispatchCtx,
) -> Result<NodeOutcome, DispatchError> {
let shape = PureShapeStep {
name: if node.expression.is_empty() {
"Focus".to_string()
} else {
node.expression.clone()
},
user_prompt: format!("Focus on: {}", node.expression),
framing_addendum: Some(
"You are focusing your attention. Narrow scope to the target; surface what matters most.".into(),
),
kind_slug: "focus",
tools: Vec::new(),
requires_context: None,
temperature: None,
now_tz: None,
};
run_pure_shape(shape, ctx).await
}
pub async fn run_associate(
node: &IRAssociateStep,
ctx: &mut DispatchCtx,
) -> Result<NodeOutcome, DispatchError> {
let using_clause = if node.using_field.is_empty() {
String::new()
} else {
format!(" using `{}`", node.using_field)
};
let shape = PureShapeStep {
name: if node.left.is_empty() {
"Associate".to_string()
} else {
format!("{}↔{}", node.left, node.right)
},
user_prompt: format!(
"Associate {} with {}{}",
node.left, node.right, using_clause
),
framing_addendum: Some(
"You are associating. Find the meaningful relationship; return a structured link.".into(),
),
kind_slug: "associate",
tools: Vec::new(),
requires_context: None,
temperature: None,
now_tz: None,
};
run_pure_shape(shape, ctx).await
}
pub async fn run_aggregate(
node: &IRAggregateStep,
ctx: &mut DispatchCtx,
) -> Result<NodeOutcome, DispatchError> {
let group_clause = if node.group_by.is_empty() {
String::new()
} else {
format!(" grouped by [{}]", node.group_by.join(", "))
};
let alias_clause = if node.alias.is_empty() {
String::new()
} else {
format!(" as `{}`", node.alias)
};
let shape = PureShapeStep {
name: if node.target.is_empty() {
"Aggregate".to_string()
} else {
node.target.clone()
},
user_prompt: format!(
"Aggregate {}{}{}",
node.target, group_clause, alias_clause
),
framing_addendum: Some(
"You are aggregating. Group + summarize over the declared dimensions; surface the structure.".into(),
),
kind_slug: "aggregate",
tools: Vec::new(),
requires_context: None,
temperature: None,
now_tz: None,
};
run_pure_shape(shape, ctx).await
}
pub async fn run_explore(
node: &IRExploreStep,
ctx: &mut DispatchCtx,
) -> Result<NodeOutcome, DispatchError> {
let limit_clause = match node.limit {
Some(n) => format!(" (top {})", n),
None => String::new(),
};
let shape = PureShapeStep {
name: if node.target.is_empty() {
"Explore".to_string()
} else {
node.target.clone()
},
user_prompt: format!("Explore: {}{}", node.target, limit_clause),
framing_addendum: Some(
"You are exploring. Sample broadly; surface the most-relevant directions.".into(),
),
kind_slug: "explore",
tools: Vec::new(),
requires_context: None,
temperature: None,
now_tz: None,
};
run_pure_shape(shape, ctx).await
}
pub async fn run_ingest(
node: &IRIngestStep,
ctx: &mut DispatchCtx,
) -> Result<NodeOutcome, DispatchError> {
let shape = PureShapeStep {
name: if node.target.is_empty() {
"Ingest".to_string()
} else {
node.target.clone()
},
user_prompt: format!("Ingest from `{}` into `{}`", node.source, node.target),
framing_addendum: Some(
"You are ingesting. Map the source's structure into the target; preserve fidelity.".into(),
),
kind_slug: "ingest",
tools: Vec::new(),
requires_context: None,
temperature: None,
now_tz: None,
};
run_pure_shape(shape, ctx).await
}
pub(crate) fn resolve_pix_source(corpus_ref: &str, pix_ref: &str, ctx: &DispatchCtx) -> Option<String> {
let mut keys: Vec<String> = Vec::new();
if !corpus_ref.is_empty() {
keys.push(corpus_ref.to_string());
}
if !pix_ref.is_empty() {
keys.push(format!("__pix_{pix_ref}_source"));
keys.push(pix_ref.to_string());
}
for k in keys {
if let Some(v) = ctx.let_bindings.get(&k) {
if !v.trim().is_empty() {
return Some(v.clone());
}
}
}
None
}
pub async fn run_navigate(
node: &IRNavigateStep,
ctx: &mut DispatchCtx,
) -> Result<NodeOutcome, DispatchError> {
if ctx.cancel.is_cancelled() {
return Err(DispatchError::UpstreamCancelled);
}
let query = crate::exec_context::interpolate_vars(&node.query, &ctx.let_bindings);
if let Some(src) = ctx.mdn_store_sources.get(&node.pix_ref).cloned() {
let step_index = ctx.step_counter;
ctx.step_counter += 1;
let out_name = if node.output_name.is_empty() {
"Navigate".to_string()
} else {
node.output_name.clone()
};
emit_step_start(ctx, &out_name, step_index, "navigate")?;
let doc_rows = crate::flow_dispatcher::wire_integrations::read_all_store_rows(
ctx,
&src.doc_store,
&node.where_expr,
)
.await?;
let edge_rows = crate::flow_dispatcher::wire_integrations::read_all_store_rows(
ctx,
&src.edge_store,
&node.where_expr,
)
.await?;
let adaptive = ctx.mdn_adaptive.contains(&node.pix_ref);
let mut reinforcement: Vec<(String, String, String, f64)> = Vec::new();
let content = match (doc_rows, edge_rows) {
(Some(drows), Some(erows)) => {
let (docs, edges) =
crate::flow_dispatcher::wire_integrations::extract_corpus_rows(
&drows, &erows, &src,
);
match crate::mdn::Corpus::from_rows(&docs, &edges) {
Ok(corpus) => {
let seed = corpus
.documents()
.into_iter()
.find(|d| d.title == node.seed)
.map(|d| d.id)
.or_else(|| corpus.documents().into_iter().map(|d| d.id).min())
.unwrap_or(0);
let budget = crate::mdn::NavBudget {
max_docs: node.budget.map(|b| b.max(1) as usize).unwrap_or(5),
epsilon: 1e-6,
};
let gain = crate::mdn::LexicalGain::new(&corpus);
let r = crate::mdn::navigate_corpus(&corpus, &query, seed, &budget, &gain);
let trail = r
.trail
.iter()
.filter_map(|(id, g)| {
corpus.document(*id).map(|d| format!("{} (Δ={:.2})", d.title, g))
})
.collect::<Vec<_>>()
.join(" → ");
ctx.let_bindings
.insert(format!("__navigate_{out_name}_trail"), trail);
if adaptive {
let denom = r.selected.len().max(1) as f64;
let score = (r.total_gain / denom).clamp(0.0, 1.0);
let params = crate::mdn_memory::MemoryParams::default();
let s_bar = {
let mut hist = ctx.mdn_histories.lock().unwrap();
let h = hist.entry(node.pix_ref.clone()).or_default();
let t = h.outcomes.len() as u64;
h.record(crate::mdn_memory::Outcome {
query: query.clone(),
path: r.selected.clone(),
score,
timestamp: t,
});
h.mean_score()
};
reinforcement =
crate::flow_dispatcher::wire_integrations::plan_edge_reinforcements(
&corpus, &r.selected, &docs, score, s_bar, params.eta,
);
}
r.selected
.iter()
.filter_map(|id| corpus.document(*id))
.map(|d| d.title.clone())
.collect::<Vec<_>>()
.join("\n")
}
Err(_) => String::new(),
}
}
_ => String::new(),
};
if !node.output_name.is_empty() {
ctx.let_bindings.insert(node.output_name.clone(), content.clone());
}
if !reinforcement.is_empty() {
let eps = crate::mdn_memory::MemoryParams::default().epsilon;
crate::flow_dispatcher::wire_integrations::persist_reinforcements(
ctx,
&src.edge_store,
&src.edge_weight,
&src.edge_from,
&src.edge_to,
&src.edge_type,
&reinforcement,
eps,
)
.await?;
}
emit_step_complete(ctx, &out_name, step_index, &content, 0)?;
return Ok(NodeOutcome::Completed {
output: content,
tokens_emitted: 0,
step_index,
});
}
if let Some(corpora) = ctx.mdn_corpora.clone() {
if let Some(base) = corpora.get(&node.pix_ref) {
let step_index = ctx.step_counter;
ctx.step_counter += 1;
let out_name = if node.output_name.is_empty() {
"Navigate".to_string()
} else {
node.output_name.clone()
};
emit_step_start(ctx, &out_name, step_index, "navigate")?;
let adaptive = ctx.mdn_adaptive.contains(&node.pix_ref);
let effective: crate::mdn::Corpus = if adaptive {
let hist = ctx.mdn_histories.lock().unwrap();
let h = hist.get(&node.pix_ref).cloned().unwrap_or_default();
crate::mdn_memory::apply_memory(base, &h, &crate::mdn_memory::MemoryParams::default())
} else {
base.clone()
};
let seed = effective
.documents()
.into_iter()
.find(|d| d.title == node.seed)
.map(|d| d.id)
.or_else(|| effective.documents().into_iter().map(|d| d.id).min())
.unwrap_or(0);
let budget = crate::mdn::NavBudget {
max_docs: node.budget.map(|b| b.max(1) as usize).unwrap_or(5),
epsilon: 1e-6,
};
let gain = crate::mdn::LexicalGain::new(&effective);
let r = crate::mdn::navigate_corpus(&effective, &query, seed, &budget, &gain);
let content = r
.selected
.iter()
.filter_map(|id| effective.document(*id))
.map(|d| d.title.clone())
.collect::<Vec<_>>()
.join("\n");
if !node.output_name.is_empty() {
ctx.let_bindings.insert(node.output_name.clone(), content.clone());
}
let trail = r
.trail
.iter()
.filter_map(|(id, g)| effective.document(*id).map(|d| format!("{} (Δ={:.2})", d.title, g)))
.collect::<Vec<_>>()
.join(" → ");
ctx.let_bindings.insert(format!("__navigate_{out_name}_trail"), trail);
if adaptive {
let denom = r.selected.len().max(1) as f64;
let score = (r.total_gain / denom).clamp(0.0, 1.0);
let mut hist = ctx.mdn_histories.lock().unwrap();
let h = hist.entry(node.pix_ref.clone()).or_default();
let t = h.outcomes.len() as u64;
h.record(crate::mdn_memory::Outcome {
query: query.clone(),
path: r.selected.clone(),
score,
timestamp: t,
});
}
emit_step_complete(ctx, &out_name, step_index, &content, 0)?;
return Ok(NodeOutcome::Completed {
output: content,
tokens_emitted: 0,
step_index,
});
}
}
if let Some(source) = resolve_pix_source(&node.corpus_ref, &node.pix_ref, ctx) {
if let Ok(tree) = crate::pix_navigator::index_markdown(&source) {
let step_index = ctx.step_counter;
ctx.step_counter += 1;
let out_name = if node.output_name.is_empty() {
"Navigate".to_string()
} else {
node.output_name.clone()
};
emit_step_start(ctx, &out_name, step_index, "navigate")?;
let cfg = crate::pix_navigator::NavConfig::default();
let scorer = crate::pix_navigator::LexicalScorer::default();
let result = crate::pix_navigator::pix_navigate(&tree, &query, &cfg, &scorer);
let content = result
.leaves
.iter()
.map(|l| l.content.as_str())
.collect::<Vec<_>>()
.join("\n\n---\n\n");
if !node.output_name.is_empty() {
ctx.let_bindings.insert(node.output_name.clone(), content.clone());
}
let trail = crate::pix_navigator::pix_trail(&tree, &result).join(" | ");
ctx.let_bindings
.insert(format!("__navigate_{out_name}_trail"), trail);
if !node.pix_ref.is_empty() {
for l in &result.leaves {
let path_titles: Vec<String> = l
.path
.iter()
.filter_map(|id| tree.node(*id))
.filter(|n| n.title != "root")
.map(|n| n.title.to_lowercase())
.collect();
ctx.let_bindings.insert(
format!("__pix_{}_{}", node.pix_ref, path_titles.join(".")),
l.content.clone(),
);
}
}
emit_step_complete(ctx, &out_name, step_index, &content, 0)?;
return Ok(NodeOutcome::Completed {
output: content,
tokens_emitted: 0,
step_index,
});
}
}
let trail_clause = if node.trail_enabled { " (with trail)" } else { "" };
let shape = PureShapeStep {
name: if node.output_name.is_empty() {
"Navigate".to_string()
} else {
node.output_name.clone()
},
user_prompt: format!(
"Navigate corpus `{}` via PIX `{}` for query: {}{}",
node.corpus_ref, node.pix_ref, query, trail_clause
),
framing_addendum: Some(
"You are navigating a PIX retrieval index. Trace your reasoning path; surface the document regions you crossed.".into(),
),
kind_slug: "navigate",
tools: Vec::new(),
requires_context: None,
temperature: None,
now_tz: None,
};
run_pure_shape(shape, ctx).await
}
pub async fn run_corroborate(
node: &IRCorroborateStep,
ctx: &mut DispatchCtx,
) -> Result<NodeOutcome, DispatchError> {
let shape = PureShapeStep {
name: if node.output_name.is_empty() {
"Corroborate".to_string()
} else {
node.output_name.clone()
},
user_prompt: format!("Corroborate navigation result `{}`", node.navigate_ref),
framing_addendum: Some(
"You are corroborating. Cross-validate independently; surface agreement strength + disagreements.".into(),
),
kind_slug: "corroborate",
tools: Vec::new(),
requires_context: None,
temperature: None,
now_tz: None,
};
run_pure_shape(shape, ctx).await
}
fn emit_step_start(
ctx: &mut DispatchCtx,
step_name: &str,
step_index: usize,
step_type: &str,
) -> Result<(), DispatchError> {
ctx.tx
.send(FlowExecutionEvent::StepStart {
step_name: step_name.to_string(),
step_index,
step_type: step_type.to_string(),
branch_path: ctx.branch_path_string(),
timestamp_ms: now_ms(),
})
.map_err(|_| DispatchError::ChannelClosed)
}
fn emit_step_complete(
ctx: &mut DispatchCtx,
step_name: &str,
step_index: usize,
full_output: &str,
tokens_output: u64,
) -> Result<(), DispatchError> {
ctx.tx
.send(FlowExecutionEvent::StepComplete {
step_name: step_name.to_string(),
step_index,
success: true,
full_output: full_output.to_string(),
tokens_input: 0,
tokens_output,
branch_path: ctx.branch_path_string(),
timestamp_ms: now_ms(),
})
.map_err(|_| DispatchError::ChannelClosed)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cancel_token::CancellationFlag;
use crate::ir_nodes::*;
use crate::pem::InMemoryBackend;
use std::sync::Arc;
use tokio::sync::mpsc;
fn fresh_ctx() -> (
DispatchCtx,
mpsc::UnboundedReceiver<FlowExecutionEvent>,
) {
let (tx, rx) = mpsc::unbounded_channel();
let ctx = DispatchCtx::new(
"TestFlow",
"stub",
"",
CancellationFlag::new(),
tx,
);
(ctx, rx)
}
#[tokio::test]
async fn run_remember_literal_value_binds_to_let_bindings() {
let (mut ctx, _rx) = fresh_ctx();
let node = IRRememberStep {
node_type: "remember",
source_line: 0,
source_column: 0,
expression: "us-east-1".into(),
memory_target: "region".into(),
};
let outcome = run_remember(&node, &mut ctx).await.unwrap();
match outcome {
NodeOutcome::Completed { output, tokens_emitted, .. } => {
assert_eq!(output, "us-east-1");
assert_eq!(tokens_emitted, 0);
}
other => panic!("expected Completed, got {other:?}"),
}
assert_eq!(ctx.let_bindings.get("region").unwrap(), "us-east-1");
}
#[tokio::test]
async fn run_remember_resolves_expression_through_let_bindings() {
let (mut ctx, _rx) = fresh_ctx();
ctx.let_bindings.insert("upstream".into(), "computed-X".into());
let node = IRRememberStep {
node_type: "remember",
source_line: 0,
source_column: 0,
expression: "upstream".into(),
memory_target: "snapshot".into(),
};
run_remember(&node, &mut ctx).await.unwrap();
assert_eq!(ctx.let_bindings.get("snapshot").unwrap(), "computed-X");
}
#[tokio::test]
async fn run_remember_with_pem_persists_to_backend() {
let backend: Arc<dyn crate::pem::PersistenceBackend> =
Arc::new(InMemoryBackend::default());
let (tx, _rx) = mpsc::unbounded_channel();
let mut ctx = DispatchCtx::new(
"F",
"stub",
"",
CancellationFlag::new(),
tx,
)
.with_pem(backend.clone())
.with_session_id("session-1");
let node = IRRememberStep {
node_type: "remember",
source_line: 0,
source_column: 0,
expression: "persisted-value".into(),
memory_target: "key1".into(),
};
run_remember(&node, &mut ctx).await.unwrap();
let state = backend.restore("session-1").await.unwrap();
assert_eq!(state.short_term_memory.len(), 1);
assert_eq!(state.short_term_memory[0].key, "key1");
}
#[tokio::test]
async fn run_recall_from_let_bindings_when_no_pem() {
let (mut ctx, _rx) = fresh_ctx();
ctx.let_bindings.insert("region".into(), "us-east-1".into());
let node = IRRecallStep {
node_type: "recall",
source_line: 0,
source_column: 0,
query: "current_region".into(),
memory_source: "region".into(),
};
let outcome = run_recall(&node, &mut ctx).await.unwrap();
match outcome {
NodeOutcome::Completed { output, .. } => {
assert_eq!(output, "us-east-1");
}
other => panic!("expected Completed, got {other:?}"),
}
assert_eq!(
ctx.let_bindings.get("current_region").unwrap(),
"us-east-1"
);
}
#[tokio::test]
async fn run_recall_from_pem_when_backend_set() {
let backend: Arc<dyn crate::pem::PersistenceBackend> =
Arc::new(InMemoryBackend::default());
let (tx, _rx) = mpsc::unbounded_channel();
let mut ctx = DispatchCtx::new(
"F",
"stub",
"",
CancellationFlag::new(),
tx,
)
.with_pem(backend.clone())
.with_session_id("sess");
run_remember(
&IRRememberStep {
node_type: "remember",
source_line: 0,
source_column: 0,
expression: "value-from-pem".into(),
memory_target: "pem_key".into(),
},
&mut ctx,
)
.await
.unwrap();
let outcome = run_recall(
&IRRecallStep {
node_type: "recall",
source_line: 0,
source_column: 0,
query: "recalled".into(),
memory_source: "pem_key".into(),
},
&mut ctx,
)
.await
.unwrap();
match outcome {
NodeOutcome::Completed { output, .. } => {
assert_eq!(output, "value-from-pem");
}
other => panic!("expected Completed, got {other:?}"),
}
}
#[tokio::test]
async fn run_recall_missing_key_returns_empty_string() {
let (mut ctx, _rx) = fresh_ctx();
let node = IRRecallStep {
node_type: "recall",
source_line: 0,
source_column: 0,
query: "x".into(),
memory_source: "never_set".into(),
};
let outcome = run_recall(&node, &mut ctx).await.unwrap();
match outcome {
NodeOutcome::Completed { output, .. } => assert_eq!(output, ""),
other => panic!("expected Completed, got {other:?}"),
}
}
#[tokio::test]
async fn run_forge_fails_closed_on_derivative_output() {
let (mut ctx, _rx) = fresh_ctx();
let node = IRForgeBlock {
node_type: "forge",
name: "Artwork".into(),
seed: "aurora borealis over ancient ruins".into(),
output_type: "Visual".into(),
mode: "transformational".into(),
novelty: 0.85,
depth: 2,
branches: 3,
..Default::default()
};
let result = run_forge(&node, &mut ctx).await;
match result {
Err(DispatchError::BackendError { name, message }) => {
assert_eq!(name, "forge");
assert!(
message.contains("forge.novelty_floor_breached"),
"expected novelty-floor rejection, got: {message}"
);
}
other => panic!("expected a fail-closed forge rejection, got {other:?}"),
}
}
#[tokio::test]
async fn run_focus_emits_focus_slug() {
let (mut ctx, mut rx) = fresh_ctx();
let node = IRFocusStep {
node_type: "focus",
source_line: 0,
source_column: 0,
expression: "key_insight".into(),
};
let _ = run_focus(&node, &mut ctx).await.unwrap();
let ev = rx.try_recv().unwrap();
match ev {
FlowExecutionEvent::StepStart { step_type, .. } => {
assert_eq!(step_type, "focus");
}
e => panic!("expected StepStart, got {e:?}"),
}
}
#[tokio::test]
async fn run_associate_emits_associate_slug() {
let (mut ctx, mut rx) = fresh_ctx();
let node = IRAssociateStep {
node_type: "associate",
source_line: 0,
source_column: 0,
left: "A".into(),
right: "B".into(),
using_field: "id".into(),
};
run_associate(&node, &mut ctx).await.unwrap();
let ev = rx.try_recv().unwrap();
match ev {
FlowExecutionEvent::StepStart { step_type, .. } => {
assert_eq!(step_type, "associate");
}
e => panic!("expected StepStart, got {e:?}"),
}
}
#[tokio::test]
async fn run_aggregate_emits_aggregate_slug() {
let (mut ctx, mut rx) = fresh_ctx();
let node = IRAggregateStep {
node_type: "aggregate",
source_line: 0,
source_column: 0,
target: "events".into(),
group_by: vec!["region".into()],
alias: "by_region".into(),
};
run_aggregate(&node, &mut ctx).await.unwrap();
let ev = rx.try_recv().unwrap();
match ev {
FlowExecutionEvent::StepStart { step_type, .. } => {
assert_eq!(step_type, "aggregate");
}
e => panic!("expected StepStart, got {e:?}"),
}
}
#[tokio::test]
async fn run_explore_emits_explore_slug() {
let (mut ctx, mut rx) = fresh_ctx();
let node = IRExploreStep {
node_type: "explore",
source_line: 0,
source_column: 0,
target: "hypothesis_space".into(),
limit: Some(5),
};
run_explore(&node, &mut ctx).await.unwrap();
let ev = rx.try_recv().unwrap();
match ev {
FlowExecutionEvent::StepStart { step_type, .. } => {
assert_eq!(step_type, "explore");
}
e => panic!("expected StepStart, got {e:?}"),
}
}
#[tokio::test]
async fn run_ingest_emits_ingest_slug() {
let (mut ctx, mut rx) = fresh_ctx();
let node = IRIngestStep {
node_type: "ingest",
source_line: 0,
source_column: 0,
source: "external_api".into(),
target: "raw".into(),
};
run_ingest(&node, &mut ctx).await.unwrap();
let ev = rx.try_recv().unwrap();
match ev {
FlowExecutionEvent::StepStart { step_type, .. } => {
assert_eq!(step_type, "ingest");
}
e => panic!("expected StepStart, got {e:?}"),
}
}
#[tokio::test]
async fn run_navigate_mdn_graph_when_ref_is_a_corpus() {
use std::collections::HashMap;
use std::sync::Arc;
let corpus = crate::mdn::Corpus::from_declaration(
&[
"intro overview".to_string(),
"liability limitation cap".to_string(),
"termination notice".to_string(),
],
&[
("cite".into(), "intro overview".into(), "liability limitation cap".into(), 0.9),
("cite".into(), "intro overview".into(), "termination notice".into(), 0.9),
],
)
.unwrap();
let mut map = HashMap::new();
map.insert("Sessions".to_string(), corpus);
let (ctx, _rx) = fresh_ctx();
let mut ctx = ctx.with_mdn_corpora(Arc::new(map));
let node = IRNavigateStep {
node_type: "navigate",
source_line: 0,
source_column: 0,
pix_ref: "Sessions".into(),
corpus_ref: String::new(),
query: "liability cap".into(),
trail_enabled: true,
output_name: "hits".into(),
seed: "intro overview".into(),
budget: Some(3),
where_expr: String::new(),
};
let outcome = run_navigate(&node, &mut ctx).await.unwrap();
match outcome {
NodeOutcome::Completed { output, .. } => {
assert!(output.contains("liability limitation cap"), "got: {output}");
assert!(!output.contains("termination notice"), "uninformative doc not visited");
}
other => panic!("expected Completed, got {other:?}"),
}
assert!(ctx.let_bindings.get("hits").unwrap().contains("liability"));
assert!(ctx.let_bindings.contains_key("__navigate_hits_trail"));
assert!(ctx.mdn_histories.lock().unwrap().is_empty(), "non-adaptive records nothing");
}
#[tokio::test]
async fn run_navigate_store_sourced_degrades_gracefully_without_postgres() {
use std::collections::HashMap;
use std::sync::Arc;
let mut sources = HashMap::new();
sources.insert(
"LtmGraph".to_string(),
crate::ir_nodes::IRCorpusStoreSource {
doc_store: "LtmSummaries".into(),
doc_id: "id".into(),
doc_title: "summary".into(),
edge_store: "LtmEdges".into(),
edge_from: "from_id".into(),
edge_to: "to_id".into(),
edge_type: "etype".into(),
edge_weight: "weight".into(),
},
);
let (ctx, _rx) = fresh_ctx();
let mut ctx = ctx.with_mdn_store_sources(Arc::new(sources));
let node = IRNavigateStep {
node_type: "navigate",
source_line: 0,
source_column: 0,
pix_ref: "LtmGraph".into(),
corpus_ref: String::new(),
query: "anything".into(),
trail_enabled: true,
output_name: "hits".into(),
seed: String::new(),
budget: Some(5),
where_expr: String::new(),
};
let outcome = run_navigate(&node, &mut ctx).await.unwrap();
match outcome {
NodeOutcome::Completed { output, .. } => {
assert_eq!(output, "", "no Postgres backend → empty live graph");
}
other => panic!("expected Completed, got {other:?}"),
}
assert_eq!(ctx.let_bindings.get("hits").map(String::as_str), Some(""));
}
#[tokio::test]
async fn run_navigate_adaptive_corpus_accumulates_memory() {
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
let corpus = crate::mdn::Corpus::from_declaration(
&["intro overview".to_string(), "liability cap".to_string()],
&[("cite".into(), "intro overview".into(), "liability cap".into(), 0.5)],
)
.unwrap();
let mut map = HashMap::new();
map.insert("Mem".to_string(), corpus);
let mut adaptive = HashSet::new();
adaptive.insert("Mem".to_string());
let (ctx, _rx) = fresh_ctx();
let mut ctx = ctx
.with_mdn_corpora(Arc::new(map))
.with_mdn_adaptive(Arc::new(adaptive));
let node = IRNavigateStep {
node_type: "navigate",
source_line: 0,
source_column: 0,
pix_ref: "Mem".into(),
corpus_ref: String::new(),
query: "liability".into(),
trail_enabled: false,
output_name: "hits".into(),
seed: "intro overview".into(),
budget: Some(3),
where_expr: String::new(),
};
run_navigate(&node, &mut ctx).await.unwrap();
run_navigate(&node, &mut ctx).await.unwrap();
let hist = ctx.mdn_histories.lock().unwrap();
assert_eq!(
hist.get("Mem").map(|h| h.outcomes.len()),
Some(2),
"the adaptive corpus recorded both navigations"
);
assert!(hist.get("Mem").unwrap().outcomes[0].path.contains(&0));
}
#[tokio::test]
async fn run_navigate_emits_navigate_slug() {
let (mut ctx, mut rx) = fresh_ctx();
let node = IRNavigateStep {
node_type: "navigate",
source_line: 0,
source_column: 0,
pix_ref: "main_pix".into(),
corpus_ref: "law_corpus".into(),
query: "interpret_clause".into(),
trail_enabled: true,
output_name: "nav_result".into(),
seed: String::new(),
budget: None,
where_expr: String::new(),
};
run_navigate(&node, &mut ctx).await.unwrap();
let ev = rx.try_recv().unwrap();
match ev {
FlowExecutionEvent::StepStart { step_type, .. } => {
assert_eq!(step_type, "navigate");
}
e => panic!("expected StepStart, got {e:?}"),
}
}
#[tokio::test]
async fn run_navigate_real_indexes_and_retrieves_embeddings_free() {
let (mut ctx, _rx) = fresh_ctx();
ctx.let_bindings.insert(
"ContractDoc".into(),
"# Liability\n## Limitation\nLiability is capped at the contract value.\n\
# Termination\n## Notice\nEither party may terminate with thirty days notice."
.into(),
);
let node = IRNavigateStep {
node_type: "navigate",
source_line: 0,
source_column: 0,
pix_ref: "ContractIndex".into(),
corpus_ref: "ContractDoc".into(),
query: "what is the liability limitation cap".into(),
trail_enabled: true,
output_name: "sections".into(),
seed: String::new(),
budget: None,
where_expr: String::new(),
};
let outcome = run_navigate(&node, &mut ctx).await.unwrap();
match outcome {
NodeOutcome::Completed { output, .. } => {
assert!(
output.contains("capped at the contract value"),
"expected the Limitation section, got: {output}"
);
}
other => panic!("expected Completed, got {other:?}"),
}
assert!(ctx.let_bindings.get("sections").unwrap().contains("capped"));
assert!(ctx.let_bindings.contains_key("__navigate_sections_trail"));
}
#[tokio::test]
async fn run_corroborate_emits_corroborate_slug() {
let (mut ctx, mut rx) = fresh_ctx();
let node = IRCorroborateStep {
node_type: "corroborate",
source_line: 0,
source_column: 0,
navigate_ref: "nav_result".into(),
output_name: "validated".into(),
};
run_corroborate(&node, &mut ctx).await.unwrap();
let ev = rx.try_recv().unwrap();
match ev {
FlowExecutionEvent::StepStart { step_type, .. } => {
assert_eq!(step_type, "corroborate");
}
e => panic!("expected StepStart, got {e:?}"),
}
}
#[tokio::test]
async fn every_cognitive_handler_short_circuits_on_cancel() {
let cancel = CancellationFlag::new();
cancel.cancel();
let (tx, _rx) = mpsc::unbounded_channel();
let mut ctx = DispatchCtx::new("F", "stub", "", cancel, tx);
let r = IRRememberStep {
node_type: "remember",
source_line: 0,
source_column: 0,
expression: "x".into(),
memory_target: "y".into(),
};
assert!(matches!(
run_remember(&r, &mut ctx).await,
Err(DispatchError::UpstreamCancelled)
));
let r = IRRecallStep {
node_type: "recall",
source_line: 0,
source_column: 0,
query: "q".into(),
memory_source: "k".into(),
};
assert!(matches!(
run_recall(&r, &mut ctx).await,
Err(DispatchError::UpstreamCancelled)
));
assert!(matches!(
run_forge(
&IRForgeBlock {
node_type: "forge",
source_line: 0,
source_column: 0,
..Default::default()
},
&mut ctx,
)
.await,
Err(DispatchError::UpstreamCancelled)
));
assert!(matches!(
run_focus(
&IRFocusStep {
node_type: "focus",
source_line: 0,
source_column: 0,
expression: "x".into(),
},
&mut ctx,
)
.await,
Err(DispatchError::UpstreamCancelled)
));
}
}