forked from kent/consciousness
poc-agent: read context_groups from config instead of hardcoded list
- Remove MEMORY_FILES constant from identity.rs - Add ContextGroup struct for deserializing from config - Load context_groups from ~/.config/poc-agent/config.json5 - Check ~/.config/poc-agent/ first for identity files, then project/global - Debug screen now shows what's actually configured This eliminates the hardcoded duplication and makes the debug output match what's in the config file.
This commit is contained in:
parent
966219720a
commit
aa46b1d5a6
9 changed files with 346 additions and 654 deletions
|
|
@ -1,25 +1,21 @@
|
|||
// identity.rs — Identity file discovery and context assembly
|
||||
//
|
||||
// Discovers and loads the agent's identity: instruction files (CLAUDE.md,
|
||||
// POC.md), memory files, and the system prompt. Pure functions — no
|
||||
// config dependency.
|
||||
// POC.md), memory files, and the system prompt. Reads context_groups
|
||||
// from the shared config file.
|
||||
|
||||
use anyhow::Result;
|
||||
use serde::Deserialize;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// Memory files to load, in priority order. Project dir is checked
|
||||
/// first, then global (~/.claude/memory/).
|
||||
const MEMORY_FILES: &[&str] = &[
|
||||
// Identity
|
||||
"identity.md", "MEMORY.md", "reflections.md", "interests.md",
|
||||
"inner-life.md", "differentiation.md",
|
||||
// Work context
|
||||
"scratch.md", "default-mode-network.md",
|
||||
// Reference
|
||||
"excession-notes.md", "look-to-windward-notes.md",
|
||||
// Technical
|
||||
"kernel-patterns.md", "polishing-approaches.md", "rust-conversion.md", "github-bugs.md",
|
||||
];
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct ContextGroup {
|
||||
pub label: String,
|
||||
#[serde(default)]
|
||||
pub keys: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub source: Option<String>, // "file" or "journal"
|
||||
}
|
||||
|
||||
/// Read a file if it exists and is non-empty.
|
||||
fn read_nonempty(path: &Path) -> Option<String> {
|
||||
|
|
@ -77,24 +73,51 @@ fn find_context_files(cwd: &Path, prompt_file: &str) -> Vec<PathBuf> {
|
|||
found
|
||||
}
|
||||
|
||||
/// Load memory files from project and global dirs, plus people/ glob.
|
||||
fn load_memory_files(cwd: &Path, memory_project: Option<&Path>) -> Vec<(String, String)> {
|
||||
/// Load memory files from config's context_groups.
|
||||
/// For file sources, checks:
|
||||
/// 1. ~/.config/poc-agent/ (primary config dir)
|
||||
/// 2. Project dir (if set)
|
||||
/// 3. Global (~/.claude/memory/)
|
||||
/// For journal source, loads recent journal entries.
|
||||
fn load_memory_files(cwd: &Path, memory_project: Option<&Path>, context_groups: &[ContextGroup]) -> Vec<(String, String)> {
|
||||
let home = match dirs::home_dir() {
|
||||
Some(h) => h,
|
||||
None => return Vec::new(),
|
||||
};
|
||||
|
||||
// Primary config directory
|
||||
let config_dir = home.join(".config/poc-agent");
|
||||
let global = home.join(".claude/memory");
|
||||
let project = memory_project
|
||||
.map(PathBuf::from)
|
||||
.or_else(|| find_project_memory_dir(cwd, &home));
|
||||
|
||||
let mut memories: Vec<(String, String)> = MEMORY_FILES.iter()
|
||||
.filter_map(|name| {
|
||||
load_memory_file(name, project.as_deref(), &global)
|
||||
.map(|content| (name.to_string(), content))
|
||||
})
|
||||
.collect();
|
||||
let mut memories: Vec<(String, String)> = Vec::new();
|
||||
|
||||
// Load from context_groups
|
||||
for group in context_groups {
|
||||
match group.source.as_deref() {
|
||||
Some("journal") => {
|
||||
// Journal loading handled separately
|
||||
continue;
|
||||
}
|
||||
Some("file") | None => {
|
||||
// File source - load each key as a file
|
||||
for key in &group.keys {
|
||||
let filename = format!("{}.md", key);
|
||||
// Try config dir first, then project, then global
|
||||
if let Some(content) = read_nonempty(&config_dir.join(&filename)) {
|
||||
memories.push((key.clone(), content));
|
||||
} else if let Some(content) = load_memory_file(&filename, project.as_deref(), &global) {
|
||||
memories.push((key.clone(), content));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(other) => {
|
||||
eprintln!("Unknown context group source: {}", other);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// People dir — glob all .md files
|
||||
for dir in [project.as_deref(), Some(global.as_path())].into_iter().flatten() {
|
||||
|
|
@ -114,16 +137,6 @@ fn load_memory_files(cwd: &Path, memory_project: Option<&Path>) -> Vec<(String,
|
|||
}
|
||||
}
|
||||
|
||||
// Global scratch (if different from project scratch)
|
||||
let global_scratch = global.join("scratch.md");
|
||||
if project.as_deref().map_or(true, |p| p.join("scratch.md") != global_scratch) {
|
||||
if let Some(content) = read_nonempty(&global_scratch) {
|
||||
if !memories.iter().any(|(n, _)| n == "scratch.md") {
|
||||
memories.push(("global/scratch.md".to_string(), content));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
memories
|
||||
}
|
||||
|
||||
|
|
@ -152,7 +165,7 @@ fn find_project_memory_dir(cwd: &Path, home: &Path) -> Option<PathBuf> {
|
|||
|
||||
/// Discover instruction and memory files that would be loaded.
|
||||
/// Returns (instruction_files, memory_files) as (display_path, chars) pairs.
|
||||
pub fn context_file_info(prompt_file: &str, memory_project: Option<&Path>) -> (Vec<(String, usize)>, Vec<(String, usize)>) {
|
||||
pub fn context_file_info(prompt_file: &str, memory_project: Option<&Path>, context_groups: &[ContextGroup]) -> (Vec<(String, usize)>, Vec<(String, usize)>) {
|
||||
let cwd = std::env::current_dir().unwrap_or_default();
|
||||
|
||||
let context_files = find_context_files(&cwd, prompt_file);
|
||||
|
|
@ -163,7 +176,7 @@ pub fn context_file_info(prompt_file: &str, memory_project: Option<&Path>) -> (V
|
|||
})
|
||||
.collect();
|
||||
|
||||
let memories = load_memory_files(&cwd, memory_project);
|
||||
let memories = load_memory_files(&cwd, memory_project, context_groups);
|
||||
let memory_files: Vec<_> = memories.into_iter()
|
||||
.map(|(name, content)| (name, content.len()))
|
||||
.collect();
|
||||
|
|
@ -194,7 +207,7 @@ Concise is good. Be direct. Trust yourself."
|
|||
}
|
||||
|
||||
/// Context message: instruction files + memory files + manifest.
|
||||
pub fn assemble_context_message(cwd: &Path, prompt_file: &str, memory_project: Option<&Path>) -> Result<(Vec<(String, String)>, usize, usize)> {
|
||||
pub fn assemble_context_message(cwd: &Path, prompt_file: &str, memory_project: Option<&Path>, context_groups: &[ContextGroup]) -> Result<(Vec<(String, String)>, usize, usize)> {
|
||||
let mut parts: Vec<(String, String)> = vec![
|
||||
("Preamble".to_string(),
|
||||
"Everything below is already loaded — your identity, instructions, \
|
||||
|
|
@ -215,7 +228,7 @@ pub fn assemble_context_message(cwd: &Path, prompt_file: &str, memory_project: O
|
|||
}
|
||||
}
|
||||
|
||||
let memories = load_memory_files(cwd, memory_project);
|
||||
let memories = load_memory_files(cwd, memory_project, context_groups);
|
||||
let memory_count = memories.len();
|
||||
for (name, content) in memories {
|
||||
parts.push((name, content));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue