use std::fmt::Write as _;
use super::planner::{plan_document_recipe, AgenticPlan, DocumentRecipe};
use super::{associative_learning, code_rewrite_learning, execution_learning, routing_learning};
use crate::associative_persistence::AssociativeMemory;
use crate::links_format::format_lino_value_verbatim;
use crate::memory::parse_links_notation;
use crate::protocol::ChatMessage;
pub static REPORTS: &[&LearningReport] = &[
&associative_learning::REPORT,
&routing_learning::REPORT,
&code_rewrite_learning::REPORT,
&execution_learning::REPORT,
&self_hosting_learning::REPORT,
];
pub mod self_hosting_learning;
#[must_use]
pub fn route(task: &str) -> Option<&'static LearningReport> {
REPORTS.iter().copied().find(|report| report.matches(task))
}
pub struct LearningReport {
pub head: &'static str,
pub issue: &'static str,
pub promotion_gate: Option<&'static str>,
pub path: &'static str,
pub task: &'static str,
pub memory: &'static str,
pub subject: &'static str,
}
impl LearningReport {
#[must_use]
pub fn render_document(&self) -> String {
self.render_document_from(self.memory)
}
#[must_use]
pub fn render_document_from(&self, memory_document: &str) -> String {
render(self, memory_document)
}
#[must_use]
pub fn matches(&self, prompt: &str) -> bool {
prompt.to_lowercase().contains(self.path)
}
pub(super) fn plan_step(&self, messages: &[ChatMessage], tool_names: &[&str]) -> AgenticPlan {
let document = self.render_document();
let final_answer = self.final_answer(&document);
plan_document_recipe(
messages,
tool_names,
DocumentRecipe {
path: self.path,
verify_command: format!("cat {}", self.path),
final_answer,
document,
},
)
}
#[must_use]
pub fn final_answer(&self, document: &str) -> String {
let expressions = document
.lines()
.filter(|line| line.trim_start().starts_with("learned_expression_"))
.count();
let Self { subject, path, .. } = self;
if self.promotion_gate.is_some() {
format!(
"Formal AI ranked {expressions} {subject}; the human-review-gated report is in {path}."
)
} else {
format!(
"Formal AI persisted and ranked {expressions} {subject} through the associative auto-learning pipeline; the reproducible Links Notation report is in {path}."
)
}
}
}
fn render(report: &LearningReport, memory_document: &str) -> String {
let events = parse_links_notation(memory_document);
let mut memory = AssociativeMemory::from_memory_events(&events);
let seed = memory
.retention_ranking()
.first()
.cloned()
.unwrap_or_default();
let recalled = memory.recall_related(&seed, 2);
let ranking = memory.retention_ranking();
let warning_count = memory
.expressions()
.values()
.map(|expression| expression.validation_issues.len())
.sum::<usize>();
let mut out = String::new();
let _ = writeln!(out, "{}", report.head);
field(&mut out, 2, "issue", report.issue);
if let Some(gate) = report.promotion_gate {
field(&mut out, 2, "decision", "awaiting_human_review");
field(&mut out, 2, "promotion_gate", gate);
}
out.push_str(" record_type \"agent_cli_auto_learning\"\n");
out.push_str(" substrate \"links_network\"\n");
out.push_str(" retention_formula \"reads + writes + incoming_links + outgoing_links\"\n");
let _ = writeln!(out, " expression_count \"{}\"", memory.len());
let _ = writeln!(out, " validation_warning_count \"{warning_count}\"");
field(&mut out, 2, "multi_hop_seed", &seed);
field(&mut out, 2, "multi_hop_recall", &recalled.join("|"));
for (index, id) in ranking.iter().enumerate() {
let Some(expression) = memory.get(id) else {
continue;
};
let _ = writeln!(out, " learned_expression_{:02}", index + 1);
field(&mut out, 4, "id", id);
field(&mut out, 4, "text", &expression.text);
field(&mut out, 4, "reads", &expression.reads.to_string());
field(&mut out, 4, "writes", &expression.writes.to_string());
field(
&mut out,
4,
"incoming_links",
&memory.in_degree(id).to_string(),
);
field(
&mut out,
4,
"outgoing_links",
&memory.out_degree(id).to_string(),
);
field(
&mut out,
4,
"retention_score",
&memory.retention_score(id).to_string(),
);
field(
&mut out,
4,
"qualifiers",
&expression
.qualifiers
.iter()
.map(|(name, value)| format!("{name}={value}"))
.collect::<Vec<_>>()
.join("|"),
);
field(
&mut out,
4,
"validation",
if expression.validation_issues.is_empty() {
"aligned"
} else {
"retained_with_warning"
},
);
}
out
}
fn field(out: &mut String, indent: usize, name: &str, value: &str) {
let _ = writeln!(
out,
"{}{name} {}",
" ".repeat(indent),
format_lino_value_verbatim(value)
);
}