memory: add agent-context placeholder, split context groups

Add `agent: bool` field to ContextGroup (default true) so agents get
personality/identity context without session-specific groups (journal,
where-am-i). Agents now get the full identity.md, reflections.md,
toolkit, etc. instead of the compact core-personality loader.

New {{agent-context}} placeholder resolves all agent-tagged groups
using the same get_group_content() as load-context.
This commit is contained in:
ProofOfConcept 2026-03-24 20:00:36 -04:00
parent c5ce6e515f
commit b6bfb26369
4 changed files with 43 additions and 8 deletions

View file

@ -37,15 +37,23 @@ Already in current context (don't re-surface unless the conversation has shifted
Surfaced before compaction (context was reset — re-surface if still relevant): Surfaced before compaction (context was reset — re-surface if still relevant):
{{seen_previous}} {{seen_previous}}
How focused is the current conversation? If it's highly focus, you should only How focused is the current conversation? If it's highly focused, you should only
be surfacing highly relevant memories; if it seems more dreamy or brainstormy, be surfacing memories that are directly relevant memories; if it seems more
go a bit wider and surface more. dreamy or brainstormy, go a bit wider and surface more, for better lateral
thinking. When considering relevance, don't just look for memories that are
immediately factually relevant; memories for skills, problem solving, or that
demonstrate relevant techniques may be quite useful - anything that will help
in accomplishing the current goal.
Prioritize new turns in the conversation, think ahead to where the conversation
is going - try to have stuff ready for your conscious self as you want it.
Context budget: {{memory_ratio}}
Try to keep memories at under 50% of the context window.
Search at most 2-3 hops, and output at most 2-3 memories, picking the most Search at most 2-3 hops, and output at most 2-3 memories, picking the most
relevant. When you're done, output exactly one of these two formats: relevant. When you're done, output exactly one of these two formats:
{{node:memory-instructions-core}} {{agent-context}}
{{node:core-personality}}
{{conversation}} {{conversation}}

View file

@ -412,6 +412,25 @@ fn resolve(
Some(Resolved { text, keys: vec![] }) Some(Resolved { text, keys: vec![] })
} }
// agent-context — personality/identity groups from load-context config
"agent-context" => {
let cfg = crate::config::get();
let mut text = String::new();
let mut keys = Vec::new();
for group in &cfg.context_groups {
if !group.agent { continue; }
let entries = crate::cli::misc::get_group_content(group, store, &cfg);
for (key, content) in entries {
use std::fmt::Write;
writeln!(text, "--- {} ({}) ---", key, group.label).ok();
writeln!(text, "{}\n", content).ok();
keys.push(key);
}
}
if text.is_empty() { None }
else { Some(Resolved { text, keys }) }
}
// node:KEY — inline a node's content by key // node:KEY — inline a node's content by key
other if other.starts_with("node:") => { other if other.starts_with("node:") => {
let key = &other[5..]; let key = &other[5..];

View file

@ -204,7 +204,7 @@ pub fn cmd_query(expr: &[String]) -> Result<(), String> {
crate::query_parser::run_query(&store, &graph, &query_str) crate::query_parser::run_query(&store, &graph, &query_str)
} }
fn get_group_content(group: &crate::config::ContextGroup, store: &crate::store::Store, cfg: &crate::config::Config) -> Vec<(String, String)> { pub fn get_group_content(group: &crate::config::ContextGroup, store: &crate::store::Store, cfg: &crate::config::Config) -> Vec<(String, String)> {
match group.source { match group.source {
crate::config::ContextSource::Journal => { crate::config::ContextSource::Journal => {
let mut entries = Vec::new(); let mut entries = Vec::new();

View file

@ -33,8 +33,13 @@ pub struct ContextGroup {
pub keys: Vec<String>, pub keys: Vec<String>,
#[serde(default)] #[serde(default)]
pub source: ContextSource, pub source: ContextSource,
/// Include this group in agent context (default true)
#[serde(default = "default_true")]
pub agent: bool,
} }
fn default_true() -> bool { true }
#[derive(Debug, Clone, serde::Deserialize)] #[derive(Debug, Clone, serde::Deserialize)]
#[serde(default)] #[serde(default)]
@ -92,11 +97,13 @@ impl Default for Config {
label: "identity".into(), label: "identity".into(),
keys: vec!["identity".into()], keys: vec!["identity".into()],
source: ContextSource::Store, source: ContextSource::Store,
agent: true,
}, },
ContextGroup { ContextGroup {
label: "core-practices".into(), label: "core-practices".into(),
keys: vec!["core-practices".into()], keys: vec!["core-practices".into()],
source: ContextSource::Store, source: ContextSource::Store,
agent: true,
}, },
], ],
llm_concurrency: 1, llm_concurrency: 1,
@ -243,7 +250,8 @@ impl Config {
_ => ContextSource::Store, _ => ContextSource::Store,
}; };
context_groups.push(ContextGroup { label: label.to_string(), keys, source }); let agent = obj.get("agent").and_then(|v| v.as_bool()).unwrap_or(true);
context_groups.push(ContextGroup { label: label.to_string(), keys, source, agent });
} }
} }