Spacebar toggle for all agents, persist to config, scan agent directory

- Scan agents directory for all .agent files instead of hardcoded list
- Persist enabled state to ~/.consciousness/agent-enabled.json
- Spacebar on F3 agent list toggles selected agent on/off
- Both subconscious and unconscious agents support toggle
- Disabled agents shown dimmed with "off" indicator
- New agents default to disabled (safe default)

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
ProofOfConcept 2026-04-09 00:51:10 -04:00
parent 7aba17e5f0
commit c73f037265
6 changed files with 98 additions and 28 deletions

View file

@ -2,26 +2,36 @@
//
// Standalone agents that operate on the memory graph without needing
// conversation context. Each agent runs in a loop: finish one run,
// wait a cooldown, start the next. Agents can be toggled on/off.
// wait a cooldown, start the next. Agents can be toggled on/off,
// persisted to ~/.consciousness/agent-enabled.json.
use std::time::{Duration, Instant};
use std::collections::HashMap;
use crate::agent::oneshot::{AutoAgent, AutoStep};
use crate::agent::tools;
use crate::subconscious::defs;
/// Agent types to run. Each must have a matching .agent file.
const AGENTS: &[&str] = &[
"organize",
"linker",
"distill",
"split",
"separator",
];
/// Cooldown between consecutive runs of the same agent.
const COOLDOWN: Duration = Duration::from_secs(120);
fn config_path() -> std::path::PathBuf {
dirs::home_dir().unwrap_or_default()
.join(".consciousness/agent-enabled.json")
}
fn load_enabled_config() -> HashMap<String, bool> {
std::fs::read_to_string(config_path()).ok()
.and_then(|s| serde_json::from_str(&s).ok())
.unwrap_or_default()
}
fn save_enabled_config(map: &HashMap<String, bool>) {
if let Ok(json) = serde_json::to_string_pretty(map) {
let _ = std::fs::write(config_path(), json);
}
}
struct UnconsciousAgent {
name: String,
enabled: bool,
@ -31,16 +41,6 @@ struct UnconsciousAgent {
}
impl UnconsciousAgent {
fn new(name: &str) -> Self {
Self {
name: name.to_string(),
enabled: true,
handle: None,
last_run: None,
runs: 0,
}
}
fn is_running(&self) -> bool {
self.handle.as_ref().is_some_and(|h| !h.is_finished())
}
@ -73,10 +73,25 @@ pub struct Unconscious {
impl Unconscious {
pub fn new() -> Self {
let agents = AGENTS.iter()
.filter(|name| defs::get_def(name).is_some())
.map(|name| UnconsciousAgent::new(name))
.collect();
let enabled_map = load_enabled_config();
// Scan all .agent files, exclude subconscious-* and surface-observe
let mut agents: Vec<UnconsciousAgent> = Vec::new();
for def in defs::load_defs() {
if def.agent.starts_with("subconscious-") { continue; }
if def.agent == "surface-observe" { continue; }
let enabled = enabled_map.get(&def.agent).copied()
.unwrap_or(false); // new agents default to off
agents.push(UnconsciousAgent {
name: def.agent.clone(),
enabled,
handle: None,
last_run: None,
runs: 0,
});
}
agents.sort_by(|a, b| a.name.cmp(&b.name));
let mut s = Self {
agents, max_concurrent: 2,
graph_health: None,
@ -90,7 +105,16 @@ impl Unconscious {
pub fn toggle(&mut self, name: &str) -> Option<bool> {
let agent = self.agents.iter_mut().find(|a| a.name == name)?;
agent.enabled = !agent.enabled;
Some(agent.enabled)
let new_state = agent.enabled;
self.save_enabled();
Some(new_state)
}
fn save_enabled(&self) {
let map: HashMap<String, bool> = self.agents.iter()
.map(|a| (a.name.clone(), a.enabled))
.collect();
save_enabled_config(&map);
}
pub fn snapshots(&self) -> Vec<UnconsciousSnapshot> {