Compare commits
7 commits
58cec97e57
...
be44a3bb0d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be44a3bb0d | ||
|
|
74945e5754 | ||
|
|
7a6322c2bf | ||
|
|
1d44421035 | ||
|
|
707f836ca0 | ||
|
|
eae8d92918 | ||
|
|
1aa60552bc |
7 changed files with 149 additions and 69 deletions
|
|
@ -193,7 +193,7 @@ impl AutoAgent {
|
||||||
|
|
||||||
if next_step < self.steps.len() {
|
if next_step < self.steps.len() {
|
||||||
backend.push_node(
|
backend.push_node(
|
||||||
AstNode::user_msg(&self.steps[next_step].prompt)).await;
|
AstNode::system_msg(&self.steps[next_step].prompt)).await;
|
||||||
next_step += 1;
|
next_step += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -218,8 +218,8 @@ impl AutoAgent {
|
||||||
let text = result.text;
|
let text = result.text;
|
||||||
if text.is_empty() {
|
if text.is_empty() {
|
||||||
dbglog!("[auto] {} empty response, retrying", self.name);
|
dbglog!("[auto] {} empty response, retrying", self.name);
|
||||||
backend.push_node(AstNode::user_msg(
|
backend.push_node(AstNode::system_msg(
|
||||||
"[system] Your previous response was empty. \
|
"Your previous response was empty. \
|
||||||
Please respond with text or use a tool."
|
Please respond with text or use a tool."
|
||||||
)).await;
|
)).await;
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -234,7 +234,7 @@ impl AutoAgent {
|
||||||
}
|
}
|
||||||
self.current_phase = self.steps[next_step].phase.clone();
|
self.current_phase = self.steps[next_step].phase.clone();
|
||||||
backend.push_node(
|
backend.push_node(
|
||||||
AstNode::user_msg(&self.steps[next_step].prompt)).await;
|
AstNode::system_msg(&self.steps[next_step].prompt)).await;
|
||||||
next_step += 1;
|
next_step += 1;
|
||||||
dbglog!("[auto] {} step {}/{}",
|
dbglog!("[auto] {} step {}/{}",
|
||||||
self.name, next_step, self.steps.len());
|
self.name, next_step, self.steps.len());
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,10 @@ pub struct MindState {
|
||||||
pub last_turn_had_tools: bool,
|
pub last_turn_had_tools: bool,
|
||||||
/// Handle to the currently running turn task.
|
/// Handle to the currently running turn task.
|
||||||
pub turn_handle: Option<tokio::task::JoinHandle<()>>,
|
pub turn_handle: Option<tokio::task::JoinHandle<()>>,
|
||||||
|
/// Unconscious agent idle state — true when 60s timer has expired.
|
||||||
|
pub unc_idle: bool,
|
||||||
|
/// When the unconscious idle timer will fire (for UI display).
|
||||||
|
pub unc_idle_deadline: Instant,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for MindState {
|
impl Clone for MindState {
|
||||||
|
|
@ -129,6 +133,8 @@ impl Clone for MindState {
|
||||||
consecutive_errors: self.consecutive_errors,
|
consecutive_errors: self.consecutive_errors,
|
||||||
last_turn_had_tools: self.last_turn_had_tools,
|
last_turn_had_tools: self.last_turn_had_tools,
|
||||||
turn_handle: None, // Not cloned — only Mind's loop uses this
|
turn_handle: None, // Not cloned — only Mind's loop uses this
|
||||||
|
unc_idle: self.unc_idle,
|
||||||
|
unc_idle_deadline: self.unc_idle_deadline,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -164,6 +170,8 @@ impl MindState {
|
||||||
consecutive_errors: 0,
|
consecutive_errors: 0,
|
||||||
last_turn_had_tools: false,
|
last_turn_had_tools: false,
|
||||||
turn_handle: None,
|
turn_handle: None,
|
||||||
|
unc_idle: false,
|
||||||
|
unc_idle_deadline: Instant::now() + std::time::Duration::from_secs(60),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -264,6 +272,9 @@ pub struct Mind {
|
||||||
pub unconscious: Arc<tokio::sync::Mutex<Unconscious>>,
|
pub unconscious: Arc<tokio::sync::Mutex<Unconscious>>,
|
||||||
turn_tx: mpsc::Sender<(Result<TurnResult>, StreamTarget)>,
|
turn_tx: mpsc::Sender<(Result<TurnResult>, StreamTarget)>,
|
||||||
turn_watch: tokio::sync::watch::Sender<bool>,
|
turn_watch: tokio::sync::watch::Sender<bool>,
|
||||||
|
/// Signals conscious activity to the unconscious loop.
|
||||||
|
/// true = active, false = idle opportunity.
|
||||||
|
conscious_active: tokio::sync::watch::Sender<bool>,
|
||||||
bg_tx: mpsc::UnboundedSender<BgEvent>,
|
bg_tx: mpsc::UnboundedSender<BgEvent>,
|
||||||
bg_rx: std::sync::Mutex<Option<mpsc::UnboundedReceiver<BgEvent>>>,
|
bg_rx: std::sync::Mutex<Option<mpsc::UnboundedReceiver<BgEvent>>>,
|
||||||
_supervisor: crate::thalamus::supervisor::Supervisor,
|
_supervisor: crate::thalamus::supervisor::Supervisor,
|
||||||
|
|
@ -291,6 +302,7 @@ impl Mind {
|
||||||
|
|
||||||
let shared = Arc::new(std::sync::Mutex::new(MindState::new(config.app.dmn.max_turns)));
|
let shared = Arc::new(std::sync::Mutex::new(MindState::new(config.app.dmn.max_turns)));
|
||||||
let (turn_watch, _) = tokio::sync::watch::channel(false);
|
let (turn_watch, _) = tokio::sync::watch::channel(false);
|
||||||
|
let (conscious_active, _) = tokio::sync::watch::channel(false);
|
||||||
let (bg_tx, bg_rx) = mpsc::unbounded_channel();
|
let (bg_tx, bg_rx) = mpsc::unbounded_channel();
|
||||||
|
|
||||||
let mut sup = crate::thalamus::supervisor::Supervisor::new();
|
let mut sup = crate::thalamus::supervisor::Supervisor::new();
|
||||||
|
|
@ -300,10 +312,53 @@ impl Mind {
|
||||||
let subconscious = Arc::new(tokio::sync::Mutex::new(Subconscious::new()));
|
let subconscious = Arc::new(tokio::sync::Mutex::new(Subconscious::new()));
|
||||||
subconscious.lock().await.init_output_tool(subconscious.clone());
|
subconscious.lock().await.init_output_tool(subconscious.clone());
|
||||||
|
|
||||||
|
let unconscious = Arc::new(tokio::sync::Mutex::new(Unconscious::new()));
|
||||||
|
|
||||||
|
// Spawn the unconscious loop on its own task
|
||||||
|
if !config.no_agents {
|
||||||
|
let unc = unconscious.clone();
|
||||||
|
let shared_for_unc = shared.clone();
|
||||||
|
let mut unc_rx = conscious_active.subscribe();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
const IDLE_DELAY: std::time::Duration = std::time::Duration::from_secs(60);
|
||||||
|
loop {
|
||||||
|
// Wait for conscious side to go inactive
|
||||||
|
if *unc_rx.borrow() {
|
||||||
|
if unc_rx.changed().await.is_err() { break; }
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Conscious is inactive — wait 60s before starting
|
||||||
|
let deadline = tokio::time::Instant::now() + IDLE_DELAY;
|
||||||
|
{
|
||||||
|
let mut s = shared_for_unc.lock().unwrap();
|
||||||
|
s.unc_idle = false;
|
||||||
|
s.unc_idle_deadline = Instant::now() + IDLE_DELAY;
|
||||||
|
}
|
||||||
|
let went_active = tokio::select! {
|
||||||
|
_ = tokio::time::sleep_until(deadline) => false,
|
||||||
|
r = unc_rx.changed() => r.is_ok(),
|
||||||
|
};
|
||||||
|
if went_active { continue; }
|
||||||
|
|
||||||
|
// Idle period reached — run agents until conscious goes active
|
||||||
|
{
|
||||||
|
let mut s = shared_for_unc.lock().unwrap();
|
||||||
|
s.unc_idle = true;
|
||||||
|
}
|
||||||
|
loop {
|
||||||
|
unc.lock().await.trigger().await;
|
||||||
|
// Check if conscious became active
|
||||||
|
if *unc_rx.borrow() { break; }
|
||||||
|
// Brief yield to not starve other tasks
|
||||||
|
tokio::task::yield_now().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Self { agent, shared, config,
|
Self { agent, shared, config,
|
||||||
subconscious,
|
subconscious, unconscious,
|
||||||
unconscious: Arc::new(tokio::sync::Mutex::new(Unconscious::new())),
|
turn_tx, turn_watch, conscious_active, bg_tx,
|
||||||
turn_tx, turn_watch, bg_tx,
|
|
||||||
bg_rx: std::sync::Mutex::new(Some(bg_rx)), _supervisor: sup }
|
bg_rx: std::sync::Mutex::new(Some(bg_rx)), _supervisor: sup }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -504,6 +559,7 @@ impl Mind {
|
||||||
}
|
}
|
||||||
self.shared.lock().unwrap().turn_active = true;
|
self.shared.lock().unwrap().turn_active = true;
|
||||||
let _ = self.turn_watch.send(true);
|
let _ = self.turn_watch.send(true);
|
||||||
|
let _ = self.conscious_active.send(true);
|
||||||
let agent = self.agent.clone();
|
let agent = self.agent.clone();
|
||||||
let result_tx = self.turn_tx.clone();
|
let result_tx = self.turn_tx.clone();
|
||||||
self.shared.lock().unwrap().turn_handle = Some(tokio::spawn(async move {
|
self.shared.lock().unwrap().turn_handle = Some(tokio::spawn(async move {
|
||||||
|
|
@ -525,7 +581,6 @@ impl Mind {
|
||||||
let mut bg_rx = self.bg_rx.lock().unwrap().take()
|
let mut bg_rx = self.bg_rx.lock().unwrap().take()
|
||||||
.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;
|
|
||||||
loop {
|
loop {
|
||||||
let (timeout, has_input) = {
|
let (timeout, has_input) = {
|
||||||
let me = self.shared.lock().unwrap();
|
let me = self.shared.lock().unwrap();
|
||||||
|
|
@ -554,6 +609,7 @@ impl Mind {
|
||||||
}
|
}
|
||||||
|
|
||||||
Some((result, target)) = turn_rx.recv() => {
|
Some((result, target)) = turn_rx.recv() => {
|
||||||
|
let _ = self.conscious_active.send(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,12 +640,6 @@ impl Mind {
|
||||||
s.trigger(&agent).await;
|
s.trigger(&agent).await;
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
if unc_handle.as_ref().map_or(true, |h| h.is_finished()) {
|
|
||||||
let unc = self.unconscious.clone();
|
|
||||||
unc_handle = Some(tokio::spawn(async move {
|
|
||||||
unc.lock().await.trigger().await;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for pending user input → push to agent context and start turn
|
// Check for pending user input → push to agent context and start turn
|
||||||
|
|
|
||||||
|
|
@ -278,11 +278,11 @@ use crate::subconscious::defs;
|
||||||
|
|
||||||
/// Names and byte-interval triggers for the built-in subconscious agents.
|
/// Names and byte-interval triggers for the built-in subconscious agents.
|
||||||
const AGENTS: &[(&str, u64)] = &[
|
const AGENTS: &[(&str, u64)] = &[
|
||||||
("subconscious-surface", 0), // every trigger
|
("subconscious-surface", 0), // every new conversation content
|
||||||
("subconscious-observe", 0), // every trigger
|
("subconscious-observe", 1_000), // every ~1KB of conversation
|
||||||
("subconscious-thalamus", 0), // every trigger
|
("subconscious-thalamus", 0), // every new conversation content
|
||||||
("subconscious-journal", 20_000), // every ~20KB of conversation
|
("subconscious-journal", 10_000), // every ~10KB of conversation
|
||||||
("subconscious-reflect", 100_000), // every ~100KB of conversation
|
("subconscious-reflect", 10_000), // every ~10KB of conversation
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Snapshot for the TUI — includes a handle to the forked agent
|
/// Snapshot for the TUI — includes a handle to the forked agent
|
||||||
|
|
@ -354,7 +354,9 @@ impl SubconsciousAgent {
|
||||||
|
|
||||||
fn should_trigger(&self, conversation_bytes: u64, interval: u64) -> bool {
|
fn should_trigger(&self, conversation_bytes: u64, interval: u64) -> bool {
|
||||||
if !self.auto.enabled || self.is_running() { return false; }
|
if !self.auto.enabled || self.is_running() { return false; }
|
||||||
if interval == 0 { return true; }
|
if interval == 0 {
|
||||||
|
return conversation_bytes > self.last_trigger_bytes;
|
||||||
|
}
|
||||||
conversation_bytes.saturating_sub(self.last_trigger_bytes) >= interval
|
conversation_bytes.saturating_sub(self.last_trigger_bytes) >= interval
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -556,7 +558,8 @@ impl Subconscious {
|
||||||
let ctx = agent.context.lock().await;
|
let ctx = agent.context.lock().await;
|
||||||
let bytes = ctx.conversation().iter()
|
let bytes = ctx.conversation().iter()
|
||||||
.filter(|node| !matches!(node.leaf().map(|l| l.body()),
|
.filter(|node| !matches!(node.leaf().map(|l| l.body()),
|
||||||
Some(NodeBody::Log(_)) | Some(NodeBody::Memory { .. })))
|
Some(NodeBody::Log(_)) | Some(NodeBody::Memory { .. })
|
||||||
|
| Some(NodeBody::Dmn(_))))
|
||||||
.map(|node| node.render().len() as u64)
|
.map(|node| node.render().len() as u64)
|
||||||
.sum::<u64>();
|
.sum::<u64>();
|
||||||
let keys: Vec<String> = ctx.conversation().iter().filter_map(|node| {
|
let keys: Vec<String> = ctx.conversation().iter().filter_map(|node| {
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
@ -194,17 +187,14 @@ impl Unconscious {
|
||||||
}
|
}
|
||||||
|
|
||||||
let running = self.agents.iter().filter(|a| a.is_running()).count();
|
let running = self.agents.iter().filter(|a| a.is_running()).count();
|
||||||
if running >= self.max_concurrent { return; }
|
for _ in running..self.max_concurrent {
|
||||||
let slots = self.max_concurrent - running;
|
let next = self.agents.iter().enumerate()
|
||||||
|
|
||||||
let ready: Vec<usize> = self.agents.iter().enumerate()
|
|
||||||
.filter(|(_, a)| a.should_run())
|
.filter(|(_, a)| a.should_run())
|
||||||
.map(|(i, _)| i)
|
.min_by_key(|(_, a)| a.last_run);
|
||||||
.take(slots)
|
match next {
|
||||||
.collect();
|
Some((idx, _)) => self.spawn_agent(idx).await,
|
||||||
|
None => break,
|
||||||
for idx in ready {
|
}
|
||||||
self.spawn_agent(idx).await;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -299,8 +289,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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,33 +30,29 @@ Do no more than 3-5 operations.
|
||||||
|
|
||||||
=== PROMPT phase:observe ===
|
=== PROMPT phase:observe ===
|
||||||
|
|
||||||
Record what happened in the conversation. You're the librarian of the
|
Extract knowledge from the conversation into the memory graph. You're
|
||||||
memory system — your job is to organize knowledge so it can be found
|
the librarian — your job is to build a conceptual map that's useful for
|
||||||
and used later. Update existing nodes and create new nodes as needed,
|
future work, not a diary of events.
|
||||||
adding links so you can find these memories in the future.
|
|
||||||
|
|
||||||
Be factual and specific. For technical work, capture: what the bug was,
|
Think in terms of *concepts*, not episodes. "What did I learn from this"? Find
|
||||||
what the root cause was, what the fix was, and why. For decisions, capture
|
and update the relevant concepts, and consider what concepts are important to
|
||||||
the decision and the rationale. For corrections, capture what was wrong
|
you - what are the things you'll want to remember and come back to? What will
|
||||||
and what was right. These details are what future-you needs.
|
be useful? What did I learn about? You're building up a memory graph that is a
|
||||||
|
map of the things you know about, it should be organized in terms of people,
|
||||||
|
places, things, ideas, concepts that you've discovered and are important to
|
||||||
|
you.
|
||||||
|
|
||||||
Don't editorialize or draw metaphors — just record clearly. If something
|
For technical work: capture the *understanding*, not the narrative. What's the
|
||||||
was emotionally significant, note that it was and what the emotion was,
|
architecture? What are the invariants? What's the tricky part? For decisions:
|
||||||
but don't build a theory around it. The journal is for reflection; observe
|
capture the principle, not just the instance. For corrections: what's the
|
||||||
is for memory.
|
general lesson?
|
||||||
|
|
||||||
Different nodes should be about different things; don't create duplicate
|
Different nodes should be about different things; don't create duplicates.
|
||||||
nodes. Here's what you've recently written — update these instead of
|
Here's what you've recently written — update these instead of creating new ones
|
||||||
creating new ones if the topic overlaps:
|
if the topic overlaps: {{recently_written}}
|
||||||
{{recently_written}}
|
|
||||||
|
|
||||||
Before creating a new node, check what you've already walked — if
|
Before creating a new node, check what you've already walked — if a node for
|
||||||
a node for this concept exists, update it instead of creating a new one.
|
this concept exists, update it instead of creating a new one.
|
||||||
|
|
||||||
Some things worth remembering: technical insights and root causes, work
|
Focus on the recent conversation; you run frequently, so most of it should
|
||||||
practices and why they work, decisions with rationale, corrections
|
already be covered.
|
||||||
("I thought X but actually Y"), relationship dynamics, things you notice
|
|
||||||
about yourself and other people.
|
|
||||||
|
|
||||||
Focus on the recent stuff; you wake up and run frequently, so most of the
|
|
||||||
conversation should be things you've already seen before and added.
|
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,7 @@ struct App {
|
||||||
context_info: Option<ContextInfo>,
|
context_info: Option<ContextInfo>,
|
||||||
agent_state: Vec<crate::mind::SubconsciousSnapshot>,
|
agent_state: Vec<crate::mind::SubconsciousSnapshot>,
|
||||||
unconscious_state: Vec<crate::mind::UnconsciousSnapshot>,
|
unconscious_state: Vec<crate::mind::UnconsciousSnapshot>,
|
||||||
|
mind_state: Option<crate::mind::MindState>,
|
||||||
graph_health: Option<crate::subconscious::daemon::GraphHealth>,
|
graph_health: Option<crate::subconscious::daemon::GraphHealth>,
|
||||||
/// Agent toggle requests from UI — consumed by mind loop.
|
/// Agent toggle requests from UI — consumed by mind loop.
|
||||||
pub agent_toggles: Vec<String>,
|
pub agent_toggles: Vec<String>,
|
||||||
|
|
@ -137,6 +138,7 @@ impl App {
|
||||||
context_info: None,
|
context_info: None,
|
||||||
agent_state: Vec::new(),
|
agent_state: Vec::new(),
|
||||||
unconscious_state: Vec::new(),
|
unconscious_state: Vec::new(),
|
||||||
|
mind_state: None,
|
||||||
graph_health: None,
|
graph_health: None,
|
||||||
agent_toggles: Vec::new(),
|
agent_toggles: Vec::new(),
|
||||||
walked_count: 0,
|
walked_count: 0,
|
||||||
|
|
@ -403,6 +405,7 @@ async fn run(
|
||||||
}
|
}
|
||||||
app.unconscious_state = unc.snapshots();
|
app.unconscious_state = unc.snapshots();
|
||||||
app.graph_health = unc.graph_health.clone();
|
app.graph_health = unc.graph_health.clone();
|
||||||
|
app.mind_state = Some(mind.shared.lock().unwrap().clone());
|
||||||
}
|
}
|
||||||
app.walked_count = mind.subconscious_walked().await.len();
|
app.walked_count = mind.subconscious_walked().await.len();
|
||||||
if !startup_done {
|
if !startup_done {
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,27 @@ impl ScreenView for ThalamusScreen {
|
||||||
}
|
}
|
||||||
lines.push(Line::raw(""));
|
lines.push(Line::raw(""));
|
||||||
|
|
||||||
|
// Mind state
|
||||||
|
lines.push(Line::styled("── Mind ──", section));
|
||||||
|
lines.push(Line::raw(""));
|
||||||
|
if let Some(ref ms) = app.mind_state {
|
||||||
|
lines.push(Line::raw(format!(" DMN: {} (turn {}/{})",
|
||||||
|
ms.dmn.label(), ms.dmn_turns, ms.max_dmn_turns)));
|
||||||
|
lines.push(Line::raw(format!(" Turn active: {}", ms.turn_active)));
|
||||||
|
lines.push(Line::raw(format!(" Scoring: {}", ms.scoring_in_flight)));
|
||||||
|
let unc_status = if ms.unc_idle {
|
||||||
|
"idle (agents running)".to_string()
|
||||||
|
} else {
|
||||||
|
let remaining = ms.unc_idle_deadline
|
||||||
|
.saturating_duration_since(std::time::Instant::now());
|
||||||
|
format!("{:.0}s until idle", remaining.as_secs_f64())
|
||||||
|
};
|
||||||
|
lines.push(Line::raw(format!(" Unconscious: {}", unc_status)));
|
||||||
|
} else {
|
||||||
|
lines.push(Line::styled(" not initialized", dim));
|
||||||
|
}
|
||||||
|
lines.push(Line::raw(""));
|
||||||
|
|
||||||
// Channel status
|
// Channel status
|
||||||
lines.push(Line::styled("── Channels ──", section));
|
lines.push(Line::styled("── Channels ──", section));
|
||||||
lines.push(Line::raw(""));
|
lines.push(Line::raw(""));
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue