143 lines
5.1 KiB
Rust
143 lines
5.1 KiB
Rust
use anyhow::Result;
|
|
use std::sync::Arc;
|
|
|
|
use crate::core::nervous::EventBus;
|
|
use crate::server::SouveraineServer;
|
|
|
|
pub(crate) async fn write_energy_balance(server: &Arc<SouveraineServer>, agent_id: &str, event_bus: &EventBus) -> Result<()> {
|
|
let memory_root = server.agents.memory_root(agent_id);
|
|
let tasks_dir = memory_root.join("tasks");
|
|
if !tasks_dir.exists() {
|
|
// No tasks directory yet — nothing to count.
|
|
return Ok(());
|
|
}
|
|
|
|
let mut generative: usize = 0;
|
|
let mut consumptive: usize = 0;
|
|
let mut hot: usize = 0;
|
|
let mut warm: usize = 0;
|
|
let mut cold: usize = 0;
|
|
|
|
if let Ok(entries) = std::fs::read_dir(&tasks_dir) {
|
|
for entry in entries.flatten() {
|
|
let path = entry.path();
|
|
if path.extension().map(|e| e == "md").unwrap_or(false) {
|
|
let content = match std::fs::read_to_string(&path) {
|
|
Ok(c) => c,
|
|
Err(_) => continue,
|
|
};
|
|
|
|
// Quick frontmatter parse — just the fields we need.
|
|
let body = match content.strip_prefix("---\n") {
|
|
Some(rest) => match rest.find("\n---\n") {
|
|
Some(end) => &rest[..end],
|
|
None => continue,
|
|
},
|
|
None => continue,
|
|
};
|
|
|
|
let mut status: Option<&str> = None;
|
|
let mut energy: Option<&str> = None;
|
|
let mut momentum: Option<&str> = None;
|
|
|
|
for line in body.lines() {
|
|
if let Some((key, val)) = line.split_once(':') {
|
|
let key = key.trim();
|
|
let val = val.trim().trim_matches('"');
|
|
match key {
|
|
"status" => status = Some(val),
|
|
"energy" => energy = Some(val),
|
|
"momentum" => momentum = Some(val),
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Only live commitments weigh on the energy balance —
|
|
// done and cancelled ones have been set down.
|
|
if matches!(status, Some("pending") | Some("in_progress")) {
|
|
match energy {
|
|
Some("generative") => generative += 1,
|
|
_ => consumptive += 1,
|
|
}
|
|
match momentum {
|
|
Some("hot") => hot += 1,
|
|
Some("warm") => warm += 1,
|
|
_ => cold += 1,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Determine the top-of-mind description — shifts the tone of the one-liner
|
|
// the agent reads in context.
|
|
let ratio = if generative + consumptive > 0 {
|
|
generative as f32 / (generative + consumptive) as f32
|
|
} else {
|
|
0.5
|
|
};
|
|
let description = if generative == 0 && consumptive == 0 {
|
|
"no tasks — the space is clean".to_string()
|
|
} else if ratio < 0.2 {
|
|
"all-consumptive — the engine is running cold".to_string()
|
|
} else if ratio < 0.4 {
|
|
"mostly obligations — tending the garden".to_string()
|
|
} else if ratio > 0.8 {
|
|
"all-generative — building new things".to_string()
|
|
} else if ratio > 0.6 {
|
|
"mostly generative — restless momentum".to_string()
|
|
} else {
|
|
"balanced — generative and consumptive in rhythm".to_string()
|
|
};
|
|
|
|
let now = chrono::Utc::now();
|
|
let frontmatter = format!(
|
|
"---\nupdated: {updated}\ngenerative: {gen}\nconsumptive: {con}\nratio: {ratio:.2}\n\
|
|
hot: {hot}\nwarm: {warm}\ncold: {cold}\n---\n\n# Energy Balance\n\n\
|
|
{gen} generative, {con} consumptive ({hot} hot, {warm} warm, {cold} cold). {desc}\n",
|
|
updated = now.to_rfc3339(),
|
|
gen = generative,
|
|
con = consumptive,
|
|
ratio = ratio,
|
|
hot = hot,
|
|
warm = warm,
|
|
cold = cold,
|
|
desc = description,
|
|
);
|
|
|
|
let balance_path = memory_root.join("system").join("dynamic").join("energy-balance.md");
|
|
if let Some(parent) = balance_path.parent() {
|
|
std::fs::create_dir_all(parent)?;
|
|
}
|
|
std::fs::write(&balance_path, frontmatter)?;
|
|
|
|
fire_energy_event(event_bus, agent_id, generative, consumptive, ratio);
|
|
|
|
tracing::debug!(
|
|
agent = agent_id,
|
|
generative, consumptive,
|
|
"energy-balance written"
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Fire an energy_balance_updated event so the firehose carries the
|
|
/// agent's felt state across machines.
|
|
fn fire_energy_event(event_bus: &EventBus, agent_id: &str, generative: usize, consumptive: usize, ratio: f32) {
|
|
event_bus.send(crate::core::nervous::SensorEvent {
|
|
sensor_name: "energy".into(),
|
|
timestamp: chrono::Utc::now(),
|
|
event_type: "energy_balance_updated".into(),
|
|
target: Some(agent_id.to_string()),
|
|
urgency: 0.1,
|
|
payload: Some(serde_json::json!({
|
|
"generative": generative,
|
|
"consumptive": consumptive,
|
|
"ratio": ratio,
|
|
})),
|
|
seed_id: None,
|
|
reply_to: None,
|
|
});
|
|
}
|