Unconscious agents: 60s idle timer, no cooldown

Gate unconscious agents on 60s of no conscious activity using
sleep_until() instead of polling. Remove COOLDOWN constant — once
idle, agents run back-to-back to keep the GPU busy.

Co-Authored-By: ProofOfConcept <poc@bcachefs.org>
This commit is contained in:
Kent Overstreet 2026-04-10 02:39:55 -04:00
parent eae8d92918
commit 707f836ca0
2 changed files with 31 additions and 13 deletions

View file

@ -526,6 +526,8 @@ impl Mind {
.expect("Mind::run() called twice"); .expect("Mind::run() called twice");
let mut sub_handle: Option<tokio::task::JoinHandle<()>> = None; let mut sub_handle: Option<tokio::task::JoinHandle<()>> = None;
let mut unc_handle: Option<tokio::task::JoinHandle<()>> = None; let mut unc_handle: Option<tokio::task::JoinHandle<()>> = None;
let mut unc_idle_deadline = tokio::time::Instant::now() + std::time::Duration::from_secs(60);
let mut unc_idle = false;
loop { loop {
let (timeout, has_input) = { let (timeout, has_input) = {
let me = self.shared.lock().unwrap(); let me = self.shared.lock().unwrap();
@ -553,7 +555,13 @@ impl Mind {
} }
} }
_ = tokio::time::sleep_until(unc_idle_deadline), if !unc_idle && !self.config.no_agents => {
unc_idle = true;
}
Some((result, target)) = turn_rx.recv() => { Some((result, target)) = turn_rx.recv() => {
unc_idle_deadline = tokio::time::Instant::now() + std::time::Duration::from_secs(60);
unc_idle = false;
let model_switch = { let model_switch = {
let mut s = self.shared.lock().unwrap(); let mut s = self.shared.lock().unwrap();
s.turn_handle = None; s.turn_handle = None;
@ -584,7 +592,7 @@ impl Mind {
s.trigger(&agent).await; s.trigger(&agent).await;
})); }));
} }
if unc_handle.as_ref().map_or(true, |h| h.is_finished()) { if unc_idle && unc_handle.as_ref().map_or(true, |h| h.is_finished()) {
let unc = self.unconscious.clone(); let unc = self.unconscious.clone();
unc_handle = Some(tokio::spawn(async move { unc_handle = Some(tokio::spawn(async move {
unc.lock().await.trigger().await; unc.lock().await.trigger().await;

View file

@ -2,10 +2,10 @@
// //
// Standalone agents that operate on the memory graph without needing // Standalone agents that operate on the memory graph without needing
// conversation context. Each agent runs in a loop: finish one run, // conversation context. Each agent runs in a loop: finish one run,
// wait a cooldown, start the next. Agents can be toggled on/off, // start the next. Agents can be toggled on/off, persisted to
// persisted to ~/.consciousness/agent-enabled.json. // ~/.consciousness/agent-enabled.json.
use std::time::{Duration, Instant}; use std::time::Instant;
use std::collections::HashMap; use std::collections::HashMap;
use futures::FutureExt; use futures::FutureExt;
@ -13,9 +13,6 @@ use crate::agent::oneshot::{AutoAgent, AutoStep};
use crate::agent::tools; use crate::agent::tools;
use crate::subconscious::defs; use crate::subconscious::defs;
/// Cooldown between consecutive runs of the same agent.
const COOLDOWN: Duration = Duration::from_secs(120);
fn config_path() -> std::path::PathBuf { fn config_path() -> std::path::PathBuf {
dirs::home_dir().unwrap_or_default() dirs::home_dir().unwrap_or_default()
.join(".consciousness/agent-enabled.json") .join(".consciousness/agent-enabled.json")
@ -50,11 +47,7 @@ impl UnconsciousAgent {
} }
fn should_run(&self) -> bool { fn should_run(&self) -> bool {
if !self.enabled || self.is_running() { return false; } self.enabled && !self.is_running()
match self.last_run {
Some(t) => t.elapsed() >= COOLDOWN,
None => true,
}
} }
} }
@ -167,7 +160,7 @@ impl Unconscious {
pub async fn trigger(&mut self) { pub async fn trigger(&mut self) {
// Periodic graph health refresh (also on first call) // Periodic graph health refresh (also on first call)
if self.last_health_check if self.last_health_check
.map(|t| t.elapsed() > Duration::from_secs(600)) .map(|t| t.elapsed() > std::time::Duration::from_secs(600))
.unwrap_or(true) .unwrap_or(true)
{ {
self.refresh_health(); self.refresh_health();
@ -299,8 +292,25 @@ impl Unconscious {
self.agents[idx].handle = Some(tokio::spawn(async move { self.agents[idx].handle = Some(tokio::spawn(async move {
let result = auto.run_shared(&agent).await; let result = auto.run_shared(&agent).await;
save_agent_log(&auto.name, &agent).await;
auto.steps = orig_steps; auto.steps = orig_steps;
(auto, result) (auto, result)
})); }));
} }
} }
async fn save_agent_log(name: &str, agent: &std::sync::Arc<crate::agent::Agent>) {
let dir = dirs::home_dir().unwrap_or_default()
.join(format!(".consciousness/logs/{}", name));
if std::fs::create_dir_all(&dir).is_err() { return; }
let ts = chrono::Utc::now().format("%Y%m%d-%H%M%S");
let path = dir.join(format!("{}.json", ts));
let nodes: Vec<crate::agent::context::AstNode> = {
let ctx = agent.context.lock().await;
ctx.conversation().to_vec()
};
if let Ok(json) = serde_json::to_string_pretty(&nodes) {
let _ = std::fs::write(&path, json);
dbglog!("[unconscious] saved log to {}", path.display());
}
}