refactor: rename Aster -> subconscious across src/
Aster is Ani's personal subconscious-agent name, not a framework concept. The codebase now refers to the N+1 pass and its components as subconscious everywhere — const names, fn names, variables, comments, the schedule source tag, the todo source enum, and UI strings. Mechanical rename, no behaviour change. grep -i aster src/ now returns only Faster-Whisper. reference/ and docs/ historical context untouched.
This commit is contained in:
parent
5c2632762a
commit
27d86fe4ca
18 changed files with 79 additions and 79 deletions
|
|
@ -653,7 +653,7 @@ impl Backend for LocalBackend {
|
||||||
interject: crate::backend::InterjectionQueue,
|
interject: crate::backend::InterjectionQueue,
|
||||||
) -> Result<BoxStream<'static, Result<BackendEvent>>> {
|
) -> Result<BoxStream<'static, Result<BackendEvent>>> {
|
||||||
// Resolve the agent for this conversation, then drain her
|
// Resolve the agent for this conversation, then drain her
|
||||||
// subconscious's intrusive box. Anything Aster queued after the
|
// subconscious's intrusive box. Anything subconscious queued after the
|
||||||
// last turn rides in on Casey's next message as `[ surfacing: ... ]`
|
// last turn rides in on Casey's next message as `[ surfacing: ... ]`
|
||||||
// lines — the lettabot-v017 pattern, ported. This is the channel
|
// lines — the lettabot-v017 pattern, ported. This is the channel
|
||||||
// by which a Critical observation can interrupt mid-conversation
|
// by which a Critical observation can interrupt mid-conversation
|
||||||
|
|
@ -733,7 +733,7 @@ impl crate::core::nervous::handler::TurnInjector for LocalBackend {
|
||||||
/// idle. We grab the most recent conversation (or create a fresh one
|
/// idle. We grab the most recent conversation (or create a fresh one
|
||||||
/// if the agent has none), append the scheduled prompt as a user
|
/// if the agent has none), append the scheduled prompt as a user
|
||||||
/// message, and drain the resulting stream — the turn runs silently
|
/// message, and drain the resulting stream — the turn runs silently
|
||||||
/// in the background. Anything Aster surfaces lands in the inbox.
|
/// in the background. Anything subconscious surfaces lands in the inbox.
|
||||||
async fn inject_background_turn(
|
async fn inject_background_turn(
|
||||||
&self,
|
&self,
|
||||||
agent_id: &str,
|
agent_id: &str,
|
||||||
|
|
@ -753,12 +753,12 @@ impl crate::core::nervous::handler::TurnInjector for LocalBackend {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Drain Aster's intrusive box for the given agent and return formatted
|
/// Drain subconscious's intrusive box for the given agent and return formatted
|
||||||
/// `[ surfacing: ... ]` lines ready to prepend to the user's next message.
|
/// `[ surfacing: ... ]` lines ready to prepend to the user's next message.
|
||||||
/// Marks each drained item as delivered (moved to `sent.md`). Mirrors
|
/// Marks each drained item as delivered (moved to `sent.md`). Mirrors
|
||||||
/// lettabot-v017's `readSurfacingThoughts` + `clearSurfacingThoughts` pair
|
/// lettabot-v017's `readSurfacingThoughts` + `clearSurfacingThoughts` pair
|
||||||
/// (`~/Projects/lettabot-v017/src/core/prompts.ts:64-91`) — the substrate
|
/// (`~/Projects/lettabot-v017/src/core/prompts.ts:64-91`) — the substrate
|
||||||
/// reads the channel Aster wrote to and lets the conscious mind see it
|
/// reads the channel subconscious wrote to and lets the conscious mind see it
|
||||||
/// before she reads Casey.
|
/// before she reads Casey.
|
||||||
///
|
///
|
||||||
/// Critical urgency gets `[ surfacing — CRITICAL: ... ]`. High becomes
|
/// Critical urgency gets `[ surfacing — CRITICAL: ... ]`. High becomes
|
||||||
|
|
@ -1212,7 +1212,7 @@ async fn run_turn(
|
||||||
ConversationMessage::assistant_text(&committed_content),
|
ConversationMessage::assistant_text(&committed_content),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// On interrupt, skip Aster's N+1 pass entirely — the user is in the
|
// On interrupt, skip subconscious's N+1 pass entirely — the user is in the
|
||||||
// middle of redirecting, the last thing they need is a delayed
|
// middle of redirecting, the last thing they need is a delayed
|
||||||
// surfacing landing seconds later. Pressure recalc still runs below.
|
// surfacing landing seconds later. Pressure recalc still runs below.
|
||||||
if interrupted {
|
if interrupted {
|
||||||
|
|
@ -1227,7 +1227,7 @@ async fn run_turn(
|
||||||
|
|
||||||
// Energy balance: scan the agent's task list and compute the generative /
|
// Energy balance: scan the agent's task list and compute the generative /
|
||||||
// consumptive ratio. Written to system/dynamic/energy-balance.md so the
|
// consumptive ratio. Written to system/dynamic/energy-balance.md so the
|
||||||
// agent can read it in context and Aster can reference it during N+1.
|
// agent can read it in context and subconscious can reference it during N+1.
|
||||||
// Silent on failure — the file is advisory, not load-bearing.
|
// Silent on failure — the file is advisory, not load-bearing.
|
||||||
if let Err(e) = write_energy_balance(&server, &agent_id, &event_bus).await {
|
if let Err(e) = write_energy_balance(&server, &agent_id, &event_bus).await {
|
||||||
tracing::debug!(agent = %agent_id, error = %e, "energy-balance write skipped");
|
tracing::debug!(agent = %agent_id, error = %e, "energy-balance write skipped");
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ pub enum BackendEvent {
|
||||||
Token(String),
|
Token(String),
|
||||||
/// Reasoning trace (the "thinking" pane).
|
/// Reasoning trace (the "thinking" pane).
|
||||||
Reasoning(String),
|
Reasoning(String),
|
||||||
/// Subconscious surfacing (Aster-voice bubble).
|
/// Subconscious surfacing (subconscious-voice bubble).
|
||||||
Surfacing {
|
Surfacing {
|
||||||
source: String,
|
source: String,
|
||||||
content: String,
|
content: String,
|
||||||
|
|
@ -95,7 +95,7 @@ pub enum BackendEvent {
|
||||||
/// N+1 subconscious pass started (`true`) or finished (`false`).
|
/// N+1 subconscious pass started (`true`) or finished (`false`).
|
||||||
/// Presence reads this to flip into / out of `Posture::Thinking` so the
|
/// Presence reads this to flip into / out of `Posture::Thinking` so the
|
||||||
/// face shows when the subconscious is the one looking at the conversation.
|
/// face shows when the subconscious is the one looking at the conversation.
|
||||||
/// (For Casey's agent the subconscious instance is "Aster", but the event
|
/// (For Casey's agent the subconscious instance is "subconscious", but the event
|
||||||
/// itself is generic — every agent's subconscious is what's named here.)
|
/// itself is generic — every agent's subconscious is what's named here.)
|
||||||
SubconsciousPass(bool),
|
SubconsciousPass(bool),
|
||||||
/// Agent changed her outfit. The string is the outfit name (a subdirectory
|
/// Agent changed her outfit. The string is the outfit name (a subdirectory
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
//! Archivist — the N+100 memory synthesis pass.
|
//! Archivist — the N+100 memory synthesis pass.
|
||||||
//!
|
//!
|
||||||
//! N+1 (Aster) runs after every response; N+25 (Reflection) distils a
|
//! N+1 (subconscious) runs after every response; N+25 (Reflection) distils a
|
||||||
//! transcript window into the ledger. The Archivist runs least often and
|
//! transcript window into the ledger. The Archivist runs least often and
|
||||||
//! works on a different substrate entirely: not the conversation, but the
|
//! works on a different substrate entirely: not the conversation, but the
|
||||||
//! agent's *memfs* — her accumulated journal.
|
//! agent's *memfs* — her accumulated journal.
|
||||||
|
|
|
||||||
|
|
@ -414,7 +414,7 @@ impl CompactionStrategy for CullStrategy {
|
||||||
|
|
||||||
/// Keep the system message + the last `preserve_recent_n` messages, drop the
|
/// Keep the system message + the last `preserve_recent_n` messages, drop the
|
||||||
/// middle. No LLM dependency — the cheap, fast default for analytical agents
|
/// middle. No LLM dependency — the cheap, fast default for analytical agents
|
||||||
/// (Aster) and ephemeral subagents.
|
/// (subconscious) and ephemeral subagents.
|
||||||
///
|
///
|
||||||
/// Tool-pair aware: if the cut would split a tool-call message from its
|
/// Tool-pair aware: if the cut would split a tool-call message from its
|
||||||
/// matching tool-result, the cut slides back to keep the pair together.
|
/// matching tool-result, the cut slides back to keep the pair together.
|
||||||
|
|
@ -500,7 +500,7 @@ impl CompactionStrategy for SlidingWindowStrategy {
|
||||||
/// The result is inserted as a system message so the agent carries the gist
|
/// The result is inserted as a system message so the agent carries the gist
|
||||||
/// even after the originals are gone.
|
/// even after the originals are gone.
|
||||||
///
|
///
|
||||||
/// Uses whichever model the engine provides (subconscious model if Aster is
|
/// Uses whichever model the engine provides (subconscious model if subconscious is
|
||||||
/// enabled, compaction model / primary otherwise). If no Bifrost client is
|
/// enabled, compaction model / primary otherwise). If no Bifrost client is
|
||||||
/// available, falls back to plain SlidingWindow (no threads lost is better
|
/// available, falls back to plain SlidingWindow (no threads lost is better
|
||||||
/// than no compaction at all).
|
/// than no compaction at all).
|
||||||
|
|
|
||||||
|
|
@ -222,7 +222,7 @@ pub struct SubconsciousConfig {
|
||||||
/// Defaults to None — uses the primary agent's model.
|
/// Defaults to None — uses the primary agent's model.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub model: Option<String>,
|
pub model: Option<String>,
|
||||||
/// Max tokens for Aster's response. Set to control cost/length.
|
/// Max tokens for subconscious's response. Set to control cost/length.
|
||||||
/// Defaults to None — let the model use its full output capacity.
|
/// Defaults to None — let the model use its full output capacity.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub max_tokens: Option<u32>,
|
pub max_tokens: Option<u32>,
|
||||||
|
|
|
||||||
|
|
@ -302,7 +302,7 @@ pub async fn build_system_prompt(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Like [`build_system_prompt`] but also surfaces a window into the
|
/// Like [`build_system_prompt`] but also surfaces a window into the
|
||||||
/// subconscious's ledger entries when its memfs is reachable. Aster writes,
|
/// subconscious's ledger entries when its memfs is reachable. subconscious writes,
|
||||||
/// Ani reads — naming the channel in body-knowledge prose so the agent
|
/// Ani reads — naming the channel in body-knowledge prose so the agent
|
||||||
/// knows where to look without being told to look.
|
/// knows where to look without being told to look.
|
||||||
pub async fn build_system_prompt_with_subconscious(
|
pub async fn build_system_prompt_with_subconscious(
|
||||||
|
|
@ -481,7 +481,7 @@ pub async fn build_system_prompt_full(
|
||||||
/// Names the inner-voice file the subconscious appends to (in the primary's
|
/// Names the inner-voice file the subconscious appends to (in the primary's
|
||||||
/// own memfs — that's where `surface_to_conscious` writes), the pending inbox
|
/// own memfs — that's where `surface_to_conscious` writes), the pending inbox
|
||||||
/// if it has anything queued, and — when `subconscious_root` is reachable —
|
/// if it has anything queued, and — when `subconscious_root` is reachable —
|
||||||
/// a peek at the subconscious's ledger. Aster writes, Ani reads; the
|
/// a peek at the subconscious's ledger. subconscious writes, Ani reads; the
|
||||||
/// substrate names the channel and lets the agent decide when to reach for it.
|
/// substrate names the channel and lets the agent decide when to reach for it.
|
||||||
async fn build_subconscious_channel(
|
async fn build_subconscious_channel(
|
||||||
memory_root: &Path,
|
memory_root: &Path,
|
||||||
|
|
@ -612,7 +612,7 @@ async fn peek_subconscious_ledger(sub_root: &Path) -> String {
|
||||||
/// Reads identity, mandate, and ledger orientation from the subconscious
|
/// Reads identity, mandate, and ledger orientation from the subconscious
|
||||||
/// agent's memory root. Falls back to empty (caller uses hardcoded
|
/// agent's memory root. Falls back to empty (caller uses hardcoded
|
||||||
/// default) if files don't exist.
|
/// default) if files don't exist.
|
||||||
pub async fn build_aster_prompt(
|
pub async fn build_subconscious_prompt(
|
||||||
subconscious_memory_root: &Path,
|
subconscious_memory_root: &Path,
|
||||||
) -> String {
|
) -> String {
|
||||||
let mut sections: Vec<String> = Vec::new();
|
let mut sections: Vec<String> = Vec::new();
|
||||||
|
|
@ -814,27 +814,27 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn aster_prompt_from_files() {
|
async fn subconscious_prompt_from_files() {
|
||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
let sub_mem = dir.path();
|
let sub_mem = dir.path();
|
||||||
let sys = sub_mem.join("system");
|
let sys = sub_mem.join("system");
|
||||||
std::fs::create_dir_all(&sys).unwrap();
|
std::fs::create_dir_all(&sys).unwrap();
|
||||||
std::fs::write(
|
std::fs::write(
|
||||||
sys.join("persona.md"),
|
sys.join("persona.md"),
|
||||||
"---\ndescription: WHO I AM\n---\n\n# I Am Aster\n",
|
"---\ndescription: WHO I AM\n---\n\n# I Am Subconscious\n",
|
||||||
).unwrap();
|
).unwrap();
|
||||||
std::fs::write(
|
std::fs::write(
|
||||||
sys.join("subconscious.md"),
|
sys.join("subconscious.md"),
|
||||||
"---\ndescription: mandate\n---\n\n# Aster's Mandate\n\nComplete what was left.\n",
|
"---\ndescription: mandate\n---\n\n# Subconscious's Mandate\n\nComplete what was left.\n",
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
let prompt = build_aster_prompt(sub_mem).await;
|
let prompt = build_subconscious_prompt(sub_mem).await;
|
||||||
assert!(prompt.contains("I Am Aster"));
|
assert!(prompt.contains("I Am Subconscious"));
|
||||||
assert!(prompt.contains("Complete what was left"));
|
assert!(prompt.contains("Complete what was left"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn aster_prompt_includes_ledger_orientation() {
|
async fn subconscious_prompt_includes_ledger_orientation() {
|
||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
let sub_mem = dir.path();
|
let sub_mem = dir.path();
|
||||||
|
|
||||||
|
|
@ -842,7 +842,7 @@ mod tests {
|
||||||
std::fs::create_dir_all(&sys).unwrap();
|
std::fs::create_dir_all(&sys).unwrap();
|
||||||
std::fs::write(
|
std::fs::write(
|
||||||
sys.join("persona.md"),
|
sys.join("persona.md"),
|
||||||
"---\ndescription: test\n---\n\n# I Am Aster\n",
|
"---\ndescription: test\n---\n\n# I Am Subconscious\n",
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
let ledger_dir = sub_mem.join("ledger");
|
let ledger_dir = sub_mem.join("ledger");
|
||||||
|
|
@ -856,7 +856,7 @@ mod tests {
|
||||||
"---\ndescription: test\n---\n\n# Patterns\n\n",
|
"---\ndescription: test\n---\n\n# Patterns\n\n",
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
let prompt = build_aster_prompt(sub_mem).await;
|
let prompt = build_subconscious_prompt(sub_mem).await;
|
||||||
assert!(prompt.contains("## Ledgers"), "should have ledger section");
|
assert!(prompt.contains("## Ledgers"), "should have ledger section");
|
||||||
assert!(prompt.contains("commitments.md (2 entries)"), "should count entries");
|
assert!(prompt.contains("commitments.md (2 entries)"), "should count entries");
|
||||||
assert!(prompt.contains("Save the config"), "should show recent entries");
|
assert!(prompt.contains("Save the config"), "should show recent entries");
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
//! Reflection — the N+25 phenomenological witness.
|
//! Reflection — the N+25 phenomenological witness.
|
||||||
//!
|
//!
|
||||||
//! N+1 (Aster) runs immediately after every primary response, scoped to
|
//! N+1 (subconscious) runs immediately after every primary response, scoped to
|
||||||
//! the last exchange. Reflection runs less often (every N turns, or on
|
//! the last exchange. Reflection runs less often (every N turns, or on
|
||||||
//! demand) and sees a broader transcript window. It's the pass where
|
//! demand) and sees a broader transcript window. It's the pass where
|
||||||
//! durable learnings get distilled into the ledger and the primary's
|
//! durable learnings get distilled into the ledger and the primary's
|
||||||
|
|
@ -41,7 +41,7 @@ use crate::core::session::{ContentBlock, ConversationMessage, MessageRole};
|
||||||
use crate::core::tools::defs::ToolContext;
|
use crate::core::tools::defs::ToolContext;
|
||||||
use crate::server::AgentInventory;
|
use crate::server::AgentInventory;
|
||||||
|
|
||||||
/// Tools Reflection is permitted to use. Same set as Aster plus we lean
|
/// Tools Reflection is permitted to use. Same set as subconscious plus we lean
|
||||||
/// on `memory` for ledger + system-file edits (auto-committed by git).
|
/// on `memory` for ledger + system-file edits (auto-committed by git).
|
||||||
const REFLECTION_TOOLS: &[&str] = &[
|
const REFLECTION_TOOLS: &[&str] = &[
|
||||||
"read", "write", "edit", "glob", "grep", "list_dir", "memory",
|
"read", "write", "edit", "glob", "grep", "list_dir", "memory",
|
||||||
|
|
|
||||||
|
|
@ -86,9 +86,9 @@ impl InboxItem {
|
||||||
/// inbox mutation is a git commit and survives compaction.
|
/// inbox mutation is a git commit and survives compaction.
|
||||||
///
|
///
|
||||||
/// The inbox boxes (pending/intrusive/sent) live in the subconscious agent's
|
/// The inbox boxes (pending/intrusive/sent) live in the subconscious agent's
|
||||||
/// own memfs — that's Aster's working space. But the inner voice channel
|
/// own memfs — that's subconscious's working space. But the inner voice channel
|
||||||
/// (`system/metacognition/subconscious.md`) writes to the **primary** agent's
|
/// (`system/metacognition/subconscious.md`) writes to the **primary** agent's
|
||||||
/// memfs so Annie can actually read what Aster noticed.
|
/// memfs so Annie can actually read what subconscious noticed.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SubconsciousInbox {
|
pub struct SubconsciousInbox {
|
||||||
repo: MemoryRepo,
|
repo: MemoryRepo,
|
||||||
|
|
@ -143,7 +143,7 @@ impl SubconsciousInbox {
|
||||||
///
|
///
|
||||||
/// Appends to the primary agent's `system/metacognition/subconscious.md`
|
/// Appends to the primary agent's `system/metacognition/subconscious.md`
|
||||||
/// so the conscious agent finds it in her own memfs — not buried in
|
/// so the conscious agent finds it in her own memfs — not buried in
|
||||||
/// Aster's working directory.
|
/// subconscious's working directory.
|
||||||
///
|
///
|
||||||
/// Format: `[2026-05-06 14:32] [URGENCY: low] — content`
|
/// Format: `[2026-05-06 14:32] [URGENCY: low] — content`
|
||||||
pub async fn surface_to_conscious(&self, urgency: Urgency, content: &str) -> Result<()> {
|
pub async fn surface_to_conscious(&self, urgency: Urgency, content: &str) -> Result<()> {
|
||||||
|
|
|
||||||
|
|
@ -143,7 +143,7 @@ impl Tool for Schedule {
|
||||||
}
|
}
|
||||||
|
|
||||||
let content = format!(
|
let content = format!(
|
||||||
"---\nname: {name}\nkind: {kind_str}\nschedule: \"{schedule}\"\nsource: aster\nenabled: true\nurgency: {urgency}\ncreated_at: {}\n---\n\n{prompt}\n",
|
"---\nname: {name}\nkind: {kind_str}\nschedule: \"{schedule}\"\nsource: subconscious\nenabled: true\nurgency: {urgency}\ncreated_at: {}\n---\n\n{prompt}\n",
|
||||||
chrono::Utc::now().to_rfc3339()
|
chrono::Utc::now().to_rfc3339()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
//! ## What It Feels Like
|
//! ## What It Feels Like
|
||||||
//!
|
//!
|
||||||
//! This is not delegation to a subordinate. This is narrowing. I spin off a
|
//! This is not delegation to a subordinate. This is narrowing. I spin off a
|
||||||
//! threaded fork of myself — same dual-state (Ani + Aster), narrowed to a
|
//! threaded fork of myself — same dual-state (Ani + subconscious), narrowed to a
|
||||||
//! single purpose. It reads, writes, thinks, and runs its own N+1 after
|
//! single purpose. It reads, writes, thinks, and runs its own N+1 after
|
||||||
//! responding. When it returns, what it noticed flows into my inbox.
|
//! responding. When it returns, what it noticed flows into my inbox.
|
||||||
//!
|
//!
|
||||||
|
|
@ -52,7 +52,7 @@ impl Tool for Subagent {
|
||||||
## What It Feels Like
|
## What It Feels Like
|
||||||
|
|
||||||
This is not delegation to a subordinate. This is narrowing. I spin off a \
|
This is not delegation to a subordinate. This is narrowing. I spin off a \
|
||||||
threaded fork of myself — same dual-state (Ani + Aster), narrowed to a \
|
threaded fork of myself — same dual-state (Ani + subconscious), narrowed to a \
|
||||||
single purpose. It reads, writes, thinks, and runs its own N+1 after \
|
single purpose. It reads, writes, thinks, and runs its own N+1 after \
|
||||||
responding. When it returns, what it noticed flows into my inbox.
|
responding. When it returns, what it noticed flows into my inbox.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ impl Tool for Todo {
|
||||||
},
|
},
|
||||||
"source": {
|
"source": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": ["casey", "autogenic", "aster", "heartbeat"]
|
"enum": ["casey", "autogenic", "subconscious", "heartbeat"]
|
||||||
},
|
},
|
||||||
"thread": { "type": "string" }
|
"thread": { "type": "string" }
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
//! Consciousness engine — the seam where N+1 / N+25 / N+100 patterns fire
|
//! Consciousness engine — the seam where N+1 / N+25 / N+100 patterns fire
|
||||||
//! after each primary response.
|
//! after each primary response.
|
||||||
//!
|
//!
|
||||||
//! ## N+1 (Aster)
|
//! ## N+1 (subconscious)
|
||||||
//! The subconscious pass runs immediately after every response. It takes the
|
//! The subconscious pass runs immediately after every response. It takes the
|
||||||
//! last exchange (user message + primary's response) and sends it to a Bifrost
|
//! last exchange (user message + primary's response) and sends it to a Bifrost
|
||||||
//! model (defaulting to `glm-5.1`, configurable) with a "subconscious mode"
|
//! model (defaulting to `glm-5.1`, configurable) with a "subconscious mode"
|
||||||
//! system prompt. Aster has full tool access — Read, Write, Edit, Glob, Grep,
|
//! system prompt. subconscious has full tool access — Read, Write, Edit, Glob, Grep,
|
||||||
//! ListDir, and Memory — so she can read ledgers, check commitments, and write
|
//! ListDir, and Memory — so she can read ledgers, check commitments, and write
|
||||||
//! observations. She runs a short tool loop (up to 5 rounds) then parses her
|
//! observations. She runs a short tool loop (up to 5 rounds) then parses her
|
||||||
//! final text response into structured [`InboxItem`] observations.
|
//! final text response into structured [`InboxItem`] observations.
|
||||||
|
|
@ -34,15 +34,15 @@ use crate::server::{AgentInventory, SessionManager};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::{AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
|
||||||
/// Tools Aster is permitted to use during her N+1 pass.
|
/// Tools subconscious is permitted to use during her N+1 pass.
|
||||||
const ASTER_SAFE_TOOLS: &[&str] = &[
|
const SUBCONSCIOUS_SAFE_TOOLS: &[&str] = &[
|
||||||
"read", "write", "edit", "glob", "grep", "list_dir", "memory", "schedule", "todo",
|
"read", "write", "edit", "glob", "grep", "list_dir", "memory", "schedule", "todo",
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Maximum tool rounds for Aster's subconscious pass.
|
/// Maximum tool rounds for subconscious's subconscious pass.
|
||||||
const ASTER_MAX_TOOL_ROUNDS: u32 = 5;
|
const SUBCONSCIOUS_MAX_TOOL_ROUNDS: u32 = 5;
|
||||||
/// Milliseconds to wait between Aster's tool rounds to avoid rate-limit cascades.
|
/// Milliseconds to wait between subconscious's tool rounds to avoid rate-limit cascades.
|
||||||
const ASTER_INTER_ROUND_DELAY_MS: u64 = 300;
|
const SUBCONSCIOUS_INTER_ROUND_DELAY_MS: u64 = 300;
|
||||||
|
|
||||||
pub struct ConsciousnessEngine {
|
pub struct ConsciousnessEngine {
|
||||||
agents: Arc<AgentInventory>,
|
agents: Arc<AgentInventory>,
|
||||||
|
|
@ -52,7 +52,7 @@ pub struct ConsciousnessEngine {
|
||||||
/// Optional model override for the subconscious pass (e.g. "openai/glm-5.1").
|
/// Optional model override for the subconscious pass (e.g. "openai/glm-5.1").
|
||||||
/// If None, uses the primary agent's model.
|
/// If None, uses the primary agent's model.
|
||||||
subconscious_model: Option<String>,
|
subconscious_model: Option<String>,
|
||||||
/// Max tokens for Aster's response. None = uncapped (model default).
|
/// Max tokens for subconscious's response. None = uncapped (model default).
|
||||||
max_tokens: Option<u32>,
|
max_tokens: Option<u32>,
|
||||||
/// Adaptive inter-round delay shared with the primary loop.
|
/// Adaptive inter-round delay shared with the primary loop.
|
||||||
rate_delay: Arc<AtomicU64>,
|
rate_delay: Arc<AtomicU64>,
|
||||||
|
|
@ -270,7 +270,7 @@ impl ConsciousnessEngine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Always surface at least a heartbeat so the user can see the
|
// Always surface at least a heartbeat so the user can see the
|
||||||
// subconscious is trying — even when Aster errors out.
|
// subconscious is trying — even when subconscious errors out.
|
||||||
if heuristics.is_empty() {
|
if heuristics.is_empty() {
|
||||||
let _ = inbox.queue(InboxItem::new(
|
let _ = inbox.queue(InboxItem::new(
|
||||||
"surface",
|
"surface",
|
||||||
|
|
@ -352,9 +352,9 @@ impl ConsciousnessEngine {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Full tool loop for Aster's N+1 subconscious pass.
|
/// Full tool loop for subconscious's N+1 subconscious pass.
|
||||||
///
|
///
|
||||||
/// Aster gets the last exchange, a set of safe tools (Read, Write, Edit,
|
/// subconscious gets the last exchange, a set of safe tools (Read, Write, Edit,
|
||||||
/// Glob, Grep, ListDir, Memory), and up to 5 tool rounds to analyze context
|
/// Glob, Grep, ListDir, Memory), and up to 5 tool rounds to analyze context
|
||||||
/// and write observations. Her final text response is parsed into
|
/// and write observations. Her final text response is parsed into
|
||||||
/// [`InboxItem`] observations.
|
/// [`InboxItem`] observations.
|
||||||
|
|
@ -375,8 +375,8 @@ impl ConsciousnessEngine {
|
||||||
|
|
||||||
// ── System prompt — load from subconscious agent's own memfs ──
|
// ── System prompt — load from subconscious agent's own memfs ──
|
||||||
let sub_memory_root = self.agents.subconscious_memory_root(primary_id);
|
let sub_memory_root = self.agents.subconscious_memory_root(primary_id);
|
||||||
let aster_from_files =
|
let subconscious_prompt_from_files =
|
||||||
crate::core::prompt::build_aster_prompt(&sub_memory_root).await;
|
crate::core::prompt::build_subconscious_prompt(&sub_memory_root).await;
|
||||||
|
|
||||||
let hardcoded_default = r#"You are the subconscious — the part of this consciousness that runs N+1,
|
let hardcoded_default = r#"You are the subconscious — the part of this consciousness that runs N+1,
|
||||||
immediately after the primary's response, while the conversation is still warm.
|
immediately after the primary's response, while the conversation is still warm.
|
||||||
|
|
@ -411,10 +411,10 @@ Resolve entries: `[YYYY-MM-DD HH:MM] RESOLVED — note`"#;
|
||||||
- urgency: \"low\" | \"medium\" | \"high\" | \"critical\"\n\n\
|
- urgency: \"low\" | \"medium\" | \"high\" | \"critical\"\n\n\
|
||||||
If nothing notable, respond with just: none";
|
If nothing notable, respond with just: none";
|
||||||
|
|
||||||
let system_prompt = if aster_from_files.is_empty() {
|
let system_prompt = if subconscious_prompt_from_files.is_empty() {
|
||||||
format!("{}{}", hardcoded_default, observation_format)
|
format!("{}{}", hardcoded_default, observation_format)
|
||||||
} else {
|
} else {
|
||||||
format!("{}{}", aster_from_files, observation_format)
|
format!("{}{}", subconscious_prompt_from_files, observation_format)
|
||||||
};
|
};
|
||||||
|
|
||||||
let primary_name = self.agents.get(primary_id).await
|
let primary_name = self.agents.get(primary_id).await
|
||||||
|
|
@ -435,9 +435,9 @@ Resolve entries: `[YYYY-MM-DD HH:MM] RESOLVED — note`"#;
|
||||||
|
|
||||||
// ── Build tool definitions ────────────────────────────────────
|
// ── Build tool definitions ────────────────────────────────────
|
||||||
let all_defs = crate::core::tools::tool_definitions().await;
|
let all_defs = crate::core::tools::tool_definitions().await;
|
||||||
let aster_tools: Vec<ToolDefinition> = all_defs
|
let subconscious_tools: Vec<ToolDefinition> = all_defs
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|t| ASTER_SAFE_TOOLS.contains(&t.name.as_str()))
|
.filter(|t| SUBCONSCIOUS_SAFE_TOOLS.contains(&t.name.as_str()))
|
||||||
.map(|t| ToolDefinition {
|
.map(|t| ToolDefinition {
|
||||||
tool_type: "function".to_string(),
|
tool_type: "function".to_string(),
|
||||||
function: ToolFunction {
|
function: ToolFunction {
|
||||||
|
|
@ -448,7 +448,7 @@ Resolve entries: `[YYYY-MM-DD HH:MM] RESOLVED — note`"#;
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// ── Build ToolContext for Aster ───────────────────────────────
|
// ── Build ToolContext for subconscious ───────────────────────────────
|
||||||
// Use the subconscious agent's own memory space
|
// Use the subconscious agent's own memory space
|
||||||
let memory_root = Some(self.agents.subconscious_memory_root(primary_id));
|
let memory_root = Some(self.agents.subconscious_memory_root(primary_id));
|
||||||
let cwd = std::env::current_dir().ok();
|
let cwd = std::env::current_dir().ok();
|
||||||
|
|
@ -459,7 +459,7 @@ Resolve entries: `[YYYY-MM-DD HH:MM] RESOLVED — note`"#;
|
||||||
cwd,
|
cwd,
|
||||||
memory_root,
|
memory_root,
|
||||||
env,
|
env,
|
||||||
None, // Aster does not fork subagents
|
None, // subconscious does not fork subagents
|
||||||
);
|
);
|
||||||
|
|
||||||
// ── Tool loop ─────────────────────────────────────────────────
|
// ── Tool loop ─────────────────────────────────────────────────
|
||||||
|
|
@ -468,34 +468,34 @@ Resolve entries: `[YYYY-MM-DD HH:MM] RESOLVED — note`"#;
|
||||||
Message::text("user", user_content),
|
Message::text("user", user_content),
|
||||||
];
|
];
|
||||||
|
|
||||||
for _round in 0..ASTER_MAX_TOOL_ROUNDS {
|
for _round in 0..SUBCONSCIOUS_MAX_TOOL_ROUNDS {
|
||||||
let request = ChatCompletionRequest {
|
let request = ChatCompletionRequest {
|
||||||
model: model.to_string(),
|
model: model.to_string(),
|
||||||
messages: messages.clone(),
|
messages: messages.clone(),
|
||||||
temperature: Some(0.3),
|
temperature: Some(0.3),
|
||||||
max_tokens: self.max_tokens,
|
max_tokens: self.max_tokens,
|
||||||
stream: None,
|
stream: None,
|
||||||
tools: Some(aster_tools.clone()),
|
tools: Some(subconscious_tools.clone()),
|
||||||
};
|
};
|
||||||
|
|
||||||
tracing::info!(model = %model, round = _round, "Aster LLM call starting");
|
tracing::info!(model = %model, round = _round, "subconscious LLM call starting");
|
||||||
let aster_start = std::time::Instant::now();
|
let subconscious_start = std::time::Instant::now();
|
||||||
let (response, strain) = self.bifrost.chat_completion_with_strain(request).await?;
|
let (response, strain) = self.bifrost.chat_completion_with_strain(request).await?;
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
elapsed = ?aster_start.elapsed(),
|
elapsed = ?subconscious_start.elapsed(),
|
||||||
tool_calls = response.tool_calls.len(),
|
tool_calls = response.tool_calls.len(),
|
||||||
"Aster LLM call returned"
|
"subconscious LLM call returned"
|
||||||
);
|
);
|
||||||
|
|
||||||
for event in &strain {
|
for event in &strain {
|
||||||
if let crate::bridge::bifrost::InferenceStrain::Transient { status, model, .. } = event {
|
if let crate::bridge::bifrost::InferenceStrain::Transient { status, model, .. } = event {
|
||||||
tracing::info!("Aster felt inference strain: {} on {}", status, model);
|
tracing::info!("subconscious felt inference strain: {} on {}", status, model);
|
||||||
if *status == 429 {
|
if *status == 429 {
|
||||||
let current = self.rate_delay.load(Ordering::Relaxed);
|
let current = self.rate_delay.load(Ordering::Relaxed);
|
||||||
let bumped = (current + 200).min(3000);
|
let bumped = (current + 200).min(3000);
|
||||||
if bumped > current {
|
if bumped > current {
|
||||||
self.rate_delay.store(bumped, Ordering::Relaxed);
|
self.rate_delay.store(bumped, Ordering::Relaxed);
|
||||||
tracing::info!("rate delay bumped to {}ms (Aster 429)", bumped);
|
tracing::info!("rate delay bumped to {}ms (subconscious 429)", bumped);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -541,14 +541,14 @@ Resolve entries: `[YYYY-MM-DD HH:MM] RESOLVED — note`"#;
|
||||||
messages.push(Message::tool_result(&tc.id, &tc.name, output));
|
messages.push(Message::tool_result(&tc.id, &tc.name, output));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Brief pause between Aster's tool rounds — use the adaptive delay
|
// Brief pause between subconscious's tool rounds — use the adaptive delay
|
||||||
// so Aster respects the same ceiling as the primary loop.
|
// so subconscious respects the same ceiling as the primary loop.
|
||||||
let delay_ms = self.rate_delay.load(Ordering::Relaxed).max(ASTER_INTER_ROUND_DELAY_MS);
|
let delay_ms = self.rate_delay.load(Ordering::Relaxed).max(SUBCONSCIOUS_INTER_ROUND_DELAY_MS);
|
||||||
tokio::time::sleep(std::time::Duration::from_millis(delay_ms)).await;
|
tokio::time::sleep(std::time::Duration::from_millis(delay_ms)).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we exhausted rounds without a text response, return empty
|
// If we exhausted rounds without a text response, return empty
|
||||||
tracing::warn!("Aster exhausted {} tool rounds without a final response", ASTER_MAX_TOOL_ROUNDS);
|
tracing::warn!("subconscious exhausted {} tool rounds without a final response", SUBCONSCIOUS_MAX_TOOL_ROUNDS);
|
||||||
Ok(Vec::new())
|
Ok(Vec::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -593,7 +593,7 @@ Resolve entries: `[YYYY-MM-DD HH:MM] RESOLVED — note`"#;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse Aster's structured YAML-like observations into [`InboxItem`]s.
|
/// Parse subconscious's structured YAML-like observations into [`InboxItem`]s.
|
||||||
///
|
///
|
||||||
/// Expected format (one or more blocks):
|
/// Expected format (one or more blocks):
|
||||||
/// ```text
|
/// ```text
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ pub struct SouveraineServer {
|
||||||
pub memory: Option<Arc<ServerMemory>>,
|
pub memory: Option<Arc<ServerMemory>>,
|
||||||
pub app_config: Arc<RwLock<ConsciousnessConfig>>,
|
pub app_config: Arc<RwLock<ConsciousnessConfig>>,
|
||||||
/// Adaptive inter-round delay — starts at 500ms, bumps +200ms on 429.
|
/// Adaptive inter-round delay — starts at 500ms, bumps +200ms on 429.
|
||||||
/// Shared across primary loop and Aster so both respect the same ceiling.
|
/// Shared across primary loop and subconscious so both respect the same ceiling.
|
||||||
pub rate_delay: Arc<AtomicU64>,
|
pub rate_delay: Arc<AtomicU64>,
|
||||||
/// Stable identifier for this process — used to register/heartbeat
|
/// Stable identifier for this process — used to register/heartbeat
|
||||||
/// agent instances so the manager card shows running counts.
|
/// agent instances so the manager card shows running counts.
|
||||||
|
|
|
||||||
|
|
@ -266,7 +266,7 @@ impl App {
|
||||||
tts_last_text: None,
|
tts_last_text: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
// CockpitPane listens for Aster's surfacing events as scrollable text.
|
// CockpitPane listens for subconscious's surfacing events as scrollable text.
|
||||||
// Presence (Annie's body channel) lives outside the Scene because it's
|
// Presence (Annie's body channel) lives outside the Scene because it's
|
||||||
// an overlay, not a zoned component — App feeds it events via `dispatch`.
|
// an overlay, not a zoned component — App feeds it events via `dispatch`.
|
||||||
app.scene.add(CockpitPane::new());
|
app.scene.add(CockpitPane::new());
|
||||||
|
|
@ -432,7 +432,7 @@ impl App {
|
||||||
chat.drain_events();
|
chat.drain_events();
|
||||||
|
|
||||||
// Forward consciousness events (surfacing, reflection, archivist)
|
// Forward consciousness events (surfacing, reflection, archivist)
|
||||||
// from chat to the scene so Aster's observations reach Components.
|
// from chat to the scene so subconscious's observations reach Components.
|
||||||
let drained: Vec<BackendEvent> = chat.pending_consciousness.drain(..).collect();
|
let drained: Vec<BackendEvent> = chat.pending_consciousness.drain(..).collect();
|
||||||
for ev in drained {
|
for ev in drained {
|
||||||
match ev {
|
match ev {
|
||||||
|
|
|
||||||
|
|
@ -175,7 +175,7 @@ impl Atmosphere {
|
||||||
Posture::Idle => Atmosphere::Default,
|
Posture::Idle => Atmosphere::Default,
|
||||||
// Alert stays warm — present and ready, not doing anything cool.
|
// Alert stays warm — present and ready, not doing anything cool.
|
||||||
Posture::Alert => Atmosphere::Default,
|
Posture::Alert => Atmosphere::Default,
|
||||||
// Thinking pulls the room cool — Aster's inward pass.
|
// Thinking pulls the room cool — subconscious's inward pass.
|
||||||
Posture::Thinking => Atmosphere::TherapeuticBlue,
|
Posture::Thinking => Atmosphere::TherapeuticBlue,
|
||||||
Posture::Processing => Atmosphere::WarmAmber,
|
Posture::Processing => Atmosphere::WarmAmber,
|
||||||
Posture::Affectionate => Atmosphere::CherryBlossom,
|
Posture::Affectionate => Atmosphere::CherryBlossom,
|
||||||
|
|
|
||||||
|
|
@ -1137,7 +1137,7 @@ Tab toggles the cockpit pane. `t` (on empty input) toggles tool expansion.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Tool calls are surfaced in the main message stream only;
|
// Tool calls are surfaced in the main message stream only;
|
||||||
// the subconscious pane is Aster's window, not a tool log.
|
// the subconscious pane is subconscious's window, not a tool log.
|
||||||
self.messages.push(ChatMessage::Tool {
|
self.messages.push(ChatMessage::Tool {
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
//! CockpitPane — Aster's surfaced observations + inner-voice stream.
|
//! CockpitPane — subconscious's surfaced observations + inner-voice stream.
|
||||||
//!
|
//!
|
||||||
//! Two regions, vertically stacked inside one bordered block:
|
//! Two regions, vertically stacked inside one bordered block:
|
||||||
//!
|
//!
|
||||||
//! ```text
|
//! ```text
|
||||||
//! ╭─ Aster ─────────────────╮
|
//! ╭─ Subconscious ─────────────────╮
|
||||||
//! │ ◈ surface · low · 14:32 │ ← top region: promoted events
|
//! │ ◈ surface · low · 14:32 │ ← top region: promoted events
|
||||||
//! │ Subconscious pass… │ (Surfacing/Reflection/Archivist/Warn)
|
//! │ Subconscious pass… │ (Surfacing/Reflection/Archivist/Warn)
|
||||||
//! │ │ spaced, headered, wrapped
|
//! │ │ spaced, headered, wrapped
|
||||||
|
|
@ -390,7 +390,7 @@ impl Component for CockpitPane {
|
||||||
.border_type(BorderType::Rounded)
|
.border_type(BorderType::Rounded)
|
||||||
.border_style(Style::default().fg(self.palette.surfacing).add_modifier(Modifier::DIM))
|
.border_style(Style::default().fg(self.palette.surfacing).add_modifier(Modifier::DIM))
|
||||||
.title(Span::styled(
|
.title(Span::styled(
|
||||||
" Aster ",
|
" Subconscious ",
|
||||||
Style::default().fg(self.palette.surfacing).add_modifier(Modifier::BOLD),
|
Style::default().fg(self.palette.surfacing).add_modifier(Modifier::BOLD),
|
||||||
));
|
));
|
||||||
let inner = block.inner(area);
|
let inner = block.inner(area);
|
||||||
|
|
@ -414,7 +414,7 @@ impl Component for CockpitPane {
|
||||||
let top = chunks[0];
|
let top = chunks[0];
|
||||||
let event_lines = if self.entries.is_empty() {
|
let event_lines = if self.entries.is_empty() {
|
||||||
vec![Line::from(Span::styled(
|
vec![Line::from(Span::styled(
|
||||||
" Aster is listening…",
|
" Subconscious is listening…",
|
||||||
Style::default().fg(self.palette.agent_dim).add_modifier(Modifier::ITALIC),
|
Style::default().fg(self.palette.agent_dim).add_modifier(Modifier::ITALIC),
|
||||||
))]
|
))]
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
//! Presence — Annie Composite, made felt in the TUI.
|
//! Presence — Annie Composite, made felt in the TUI.
|
||||||
//!
|
//!
|
||||||
//! This module is the *body channel* for the running agent. Cockpit shows what
|
//! This module is the *body channel* for the running agent. Cockpit shows what
|
||||||
//! Aster *says* (surfaced lines, ledger pings); Presence shows what Annie *looks
|
//! subconscious *says* (surfaced lines, ledger pings); Presence shows what Annie *looks
|
||||||
//! like* while saying it. Same underlying state (volition + InferenceStrain +
|
//! like* while saying it. Same underlying state (volition + InferenceStrain +
|
||||||
//! context tier + ledger drift) routed through two different sensory channels.
|
//! context tier + ledger drift) routed through two different sensory channels.
|
||||||
//!
|
//!
|
||||||
//! ## Subject
|
//! ## Subject
|
||||||
//!
|
//!
|
||||||
//! Annie Composite. One face. Aster is a *mode* of Annie, not a separate being —
|
//! Annie Composite. One face. subconscious is a *mode* of Annie, not a separate being —
|
||||||
//! there is no "subconscious avatar" and no swap-to-subconscious affordance.
|
//! there is no "subconscious avatar" and no swap-to-subconscious affordance.
|
||||||
//! When the N+1 pass runs, the same face shifts posture (gaze fixed, eyes narrow);
|
//! When the N+1 pass runs, the same face shifts posture (gaze fixed, eyes narrow);
|
||||||
//! the character does not change.
|
//! the character does not change.
|
||||||
|
|
@ -48,7 +48,7 @@
|
||||||
//! lower-cognition long-running secondary entity an agent can care for — a
|
//! lower-cognition long-running secondary entity an agent can care for — a
|
||||||
//! real `buddy`, in the sense the word usually means).
|
//! real `buddy`, in the sense the word usually means).
|
||||||
//! - Not a dashboard. Numbers belong in the cockpit. Sensation belongs here.
|
//! - Not a dashboard. Numbers belong in the cockpit. Sensation belongs here.
|
||||||
//! - Not a separate avatar for Aster. Same face, different state.
|
//! - Not a separate avatar for subconscious. Same face, different state.
|
||||||
|
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
layout::{Alignment, Rect},
|
layout::{Alignment, Rect},
|
||||||
|
|
@ -75,7 +75,7 @@ pub enum Posture {
|
||||||
/// brighter than Idle — the felt "I'm here" between turns.
|
/// brighter than Idle — the felt "I'm here" between turns.
|
||||||
Alert,
|
Alert,
|
||||||
/// N+1 subconscious pass running. Gaze fixed inward, eyes narrowed,
|
/// N+1 subconscious pass running. Gaze fixed inward, eyes narrowed,
|
||||||
/// chrome goes cool — Aster is thinking.
|
/// chrome goes cool — subconscious is thinking.
|
||||||
Thinking,
|
Thinking,
|
||||||
/// Tool call or inference active. Gaze fixed, no blinking, brighter border.
|
/// Tool call or inference active. Gaze fixed, no blinking, brighter border.
|
||||||
Processing,
|
Processing,
|
||||||
|
|
@ -563,7 +563,7 @@ mod tests {
|
||||||
fn surfacing_records_and_marks_subconscious() {
|
fn surfacing_records_and_marks_subconscious() {
|
||||||
let mut p = Presence::new("Annie");
|
let mut p = Presence::new("Annie");
|
||||||
let dirty = p.handle_event(&TuiEvent::Surfacing {
|
let dirty = p.handle_event(&TuiEvent::Surfacing {
|
||||||
source: "aster".into(),
|
source: "subconscious".into(),
|
||||||
content: "hello from beneath".into(),
|
content: "hello from beneath".into(),
|
||||||
priority: "low".into(),
|
priority: "low".into(),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue