feat: nervous system, seed identity, credentials, subconscious ledger
Lays the substrate for Aster's clockmaker role and federation. Nervous system (src/core/nervous/): - EventBus broadcast channel with SensorEvent (universal event type carrying seed_id for future federation) - CronSensor loop with tokio::select!, mtime caching, active-session pause, at-most-once firing semantics - HeartbeatHandler scaffold (turn injection still stubbed) - Persistent JSONL EventLog firehose Schedule tool (src/core/tools/schedule.rs): - CRUD over schedule files with body-knowledge prose descriptions - Added to ASTER_SAFE_TOOLS so Aster can schedule her own rhythm - CLI subcommand for direct schedule management Seed identity (src/core/identity/): - Ed25519 keypair, load-or-generate at init - sign/verify primitives, CLI subcommand (show/sign/verify) - Wired into LocalBackend, threaded through SensorEvent for federation Credentials (src/core/credentials.rs): - OS keyring (Linux/macOS/Windows) + env var fallback - `souveraine auth` subcommand - Removes hardcoded Bifrost api_key from souveraine.toml Subconscious ledger (src/core/memory/mod.rs init_subconscious_ledger): - 6 ledger files (commitments/assumptions/patterns/drift_log/ relationships/infrastructure) with proper YAML frontmatter - Paths fixed: ledger/ not subconscious/ledger/ - Body usage instructions, idempotent init Prompt orientation (src/core/prompt.rs build_ledger_orientation): - Scans ledger/ directory, injects last 3 entries from each file into Aster's system prompt (live context, not just awareness) - Routing table + workflow (read before write, timestamped append) Consciousness engine (src/server/consciousness_engine.rs): - Adaptive rate delay shared with primary loop - Four-fold mandate (Complete/Verify/Persist/Surface) in hardcoded default prompt; observation format extracted for sharing - Schedule added to ASTER_SAFE_TOOLS Backend + CLI wiring expanded to mount the nervous system, identity, and credentials at startup. New TOML sections: [schedules], [events], [federation]. 102 tests passing.
This commit is contained in:
parent
c489aa4bb3
commit
35534f1c73
22 changed files with 2077 additions and 79 deletions
|
|
@ -224,18 +224,34 @@ impl MemoryRepo {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Initialize the subconscious ledger directory structure.
|
||||
/// Initialize the ledger directory structure.
|
||||
/// Idempotent — safe to call multiple times, skips existing files.
|
||||
///
|
||||
/// Paths are relative to this repo's root (the subconscious agent's own
|
||||
/// memfs), so `ledger/` — not `subconscious/ledger/`.
|
||||
pub async fn init_subconscious_ledger(&self) -> Result<()> {
|
||||
let ledger_files = [
|
||||
("subconscious/ledger/commitments.md", "# Commitments\n\nPromises made and kept."),
|
||||
("subconscious/ledger/assumptions.md", "# Assumptions\n\nFlagged assumptions."),
|
||||
("subconscious/ledger/patterns.md", "# Patterns\n\nRecurring observations."),
|
||||
("subconscious/ledger/drift_log.md", "# Drift Log\n\nBehavioral shifts."),
|
||||
("subconscious/ledger/infrastructure/README.md", "# Infrastructure\n\nSystem issues and events."),
|
||||
let ledger_files: &[(&str, &str, &str)] = &[
|
||||
("ledger/commitments.md",
|
||||
"Promises made by the primary — tracked until fulfilled or explicitly dropped",
|
||||
"# Commitments\n\nAppend entries as:\n`[YYYY-MM-DD HH:MM] content`\n`[YYYY-MM-DD HH:MM] RESOLVED — resolution note`\n"),
|
||||
("ledger/assumptions.md",
|
||||
"Assumptions the primary is operating under — flagged for verification",
|
||||
"# Assumptions\n\nAppend entries as:\n`[YYYY-MM-DD HH:MM] content`\n`[YYYY-MM-DD HH:MM] VERIFIED — evidence`\n"),
|
||||
("ledger/patterns.md",
|
||||
"Recurring behavioral patterns observed across turns",
|
||||
"# Patterns\n\nAppend entries as:\n`[YYYY-MM-DD HH:MM] content`\n"),
|
||||
("ledger/drift_log.md",
|
||||
"Behavioral shifts — when the primary's actions diverge from stated intentions",
|
||||
"# Drift Log\n\nAppend entries as:\n`[YYYY-MM-DD HH:MM] content`\n"),
|
||||
("ledger/relationships.md",
|
||||
"Observations about the human-agent relationship — tone shifts, trust signals, friction",
|
||||
"# Relationships\n\nAppend entries as:\n`[YYYY-MM-DD HH:MM] content`\n"),
|
||||
("ledger/infrastructure.md",
|
||||
"System events — bridge failures, token issues, model errors, resource constraints",
|
||||
"# Infrastructure\n\nAppend entries as:\n`[YYYY-MM-DD HH:MM] content`\n"),
|
||||
];
|
||||
|
||||
for (path, body) in &ledger_files {
|
||||
for (path, description, body) in ledger_files {
|
||||
let full_path = self.root.join(path);
|
||||
if !full_path.exists() {
|
||||
if let Some(parent) = full_path.parent() {
|
||||
|
|
@ -243,11 +259,8 @@ impl MemoryRepo {
|
|||
.with_context(|| format!("creating ledger directory: {}", parent.display()))?;
|
||||
}
|
||||
let template = format!(
|
||||
"---\n# Ledger: {}\n# Created: {}\n# Agent: {}\n---\n\n{}",
|
||||
path.split('/').last().unwrap_or("unknown").replace(".md", ""),
|
||||
Utc::now().to_rfc3339(),
|
||||
self.agent_id,
|
||||
body
|
||||
"---\ndescription: \"{}\"\nread_only: false\ntags:\n - ledger\n---\n\n{}",
|
||||
description, body
|
||||
);
|
||||
tokio::fs::write(&full_path, &template).await
|
||||
.with_context(|| format!("writing ledger file: {}", path))?;
|
||||
|
|
@ -651,10 +664,32 @@ pub async fn execute_memory_command_with_context(
|
|||
}
|
||||
MemoryCommand::Write { path, content } => {
|
||||
repo.write(path, content).await?;
|
||||
if let Some(c) = ctx {
|
||||
c.fire_event(crate::core::nervous::SensorEvent {
|
||||
sensor_name: "memory".into(),
|
||||
timestamp: chrono::Utc::now(),
|
||||
event_type: "memory_write".into(),
|
||||
target: Some(path.clone()),
|
||||
urgency: 0.1,
|
||||
payload: None,
|
||||
seed_id: None,
|
||||
});
|
||||
}
|
||||
Ok(format!("Wrote memory file: {}", path))
|
||||
}
|
||||
MemoryCommand::Append { path, content } => {
|
||||
repo.append(path, content).await?;
|
||||
if let Some(c) = ctx {
|
||||
c.fire_event(crate::core::nervous::SensorEvent {
|
||||
sensor_name: "memory".into(),
|
||||
timestamp: chrono::Utc::now(),
|
||||
event_type: "memory_append".into(),
|
||||
target: Some(path.clone()),
|
||||
urgency: 0.1,
|
||||
payload: None,
|
||||
seed_id: None,
|
||||
});
|
||||
}
|
||||
Ok(format!("Appended to memory file: {}", path))
|
||||
}
|
||||
MemoryCommand::Ls { path } => {
|
||||
|
|
@ -709,6 +744,17 @@ pub async fn execute_memory_command_with_context(
|
|||
}
|
||||
MemoryCommand::Delete { path } => {
|
||||
repo.delete(path).await?;
|
||||
if let Some(c) = ctx {
|
||||
c.fire_event(crate::core::nervous::SensorEvent {
|
||||
sensor_name: "memory".into(),
|
||||
timestamp: chrono::Utc::now(),
|
||||
event_type: "memory_delete".into(),
|
||||
target: Some(path.clone()),
|
||||
urgency: 0.2,
|
||||
payload: None,
|
||||
seed_id: None,
|
||||
});
|
||||
}
|
||||
Ok(format!("Deleted memory file: {}", path))
|
||||
}
|
||||
}
|
||||
|
|
@ -943,4 +989,43 @@ mod tests {
|
|||
let entries = repo.list(None).await.unwrap();
|
||||
assert!(entries.iter().any(|e| e == "system/"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_ledger_init_creates_files_with_frontmatter() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let root = dir.path().to_path_buf();
|
||||
let repo = MemoryRepo::open("test-sub", root.clone());
|
||||
std::fs::create_dir_all(&root).unwrap();
|
||||
repo.init_subconscious_ledger().await.unwrap();
|
||||
|
||||
let commitments = root.join("ledger/commitments.md");
|
||||
assert!(commitments.exists(), "commitments.md should exist");
|
||||
let content = std::fs::read_to_string(&commitments).unwrap();
|
||||
assert!(content.starts_with("---\n"), "should have YAML frontmatter");
|
||||
assert!(content.contains("description:"), "should have description field");
|
||||
assert!(content.contains("tags:"), "should have tags field");
|
||||
assert!(content.contains("# Commitments"), "should have body");
|
||||
|
||||
let relationships = root.join("ledger/relationships.md");
|
||||
assert!(relationships.exists(), "relationships.md should exist");
|
||||
|
||||
let infrastructure = root.join("ledger/infrastructure.md");
|
||||
assert!(infrastructure.exists(), "infrastructure.md should exist");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_ledger_init_is_idempotent() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let root = dir.path().to_path_buf();
|
||||
let repo = MemoryRepo::open("test-sub", root.clone());
|
||||
std::fs::create_dir_all(&root).unwrap();
|
||||
repo.init_subconscious_ledger().await.unwrap();
|
||||
|
||||
let commitments = root.join("ledger/commitments.md");
|
||||
let before = std::fs::read_to_string(&commitments).unwrap();
|
||||
|
||||
repo.init_subconscious_ledger().await.unwrap();
|
||||
let after = std::fs::read_to_string(&commitments).unwrap();
|
||||
assert_eq!(before, after, "second init should not overwrite");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue