perf(chat): index skills in the prompt instead of inlining every body
The chat path concatenated every installed skill's complete markdown into the system prompt on every turn. Bodies average ~3.5 KB (~900 tokens) and the count is unbounded, so this was by far the largest thing in the prompt and it scaled with how many skills a claw had installed -- a fixed toll paid whether or not any skill was relevant to the turn. The prompt now lists name + description, and a new `skills.read` tool fetches a body on demand. This is the contract the mission path already had: the `clawmates_skills` MCP server advertises description + when_to_use and lets the agent read what it needs. The two paths now agree. `compose_system` takes (title, description, body) rather than (title, body): the index needs the description, and first-touch brain seeding still needs the real body so the .brain stays a complete portable artifact. Not done here: filtering tool descriptors per agent, which the plan paired with this. The premise doesn't hold -- risk_profile governs the ZeroClaw tool namespace (file_edit, shell) on the mission path, while the chat path has its own registry (files.write, shell.exec) and no per-agent policy whatsoever; `risk_profile` appears nowhere in cm-runtime. Filtering there would invent a capability boundary rather than enforce one, silently revoking chat tools. Left for a deliberate decision. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
285d0c82f2
commit
81b93a5c25
@@ -1,10 +1,12 @@
|
||||
//! Best-effort brain augmentation for the chat path.
|
||||
//!
|
||||
//! Each turn we open the claw's local working `.brain` (cm-brain / ClawhDF5),
|
||||
//! recall relevant memory, record the user's turn, and compose a system prompt
|
||||
//! that injects the claw's **identity** (AGENTS.md "how I operate" + personality),
|
||||
//! **skills**, and **recalled memory** on top of its Postgres-authoritative system
|
||||
//! prompt. Any failure falls back to the plain prompt — the brain must never break chat.
|
||||
//! recall relevant memory, record the turn, and compose a system prompt on top
|
||||
//! of the claw's Postgres-authoritative one. Any failure falls back to the plain
|
||||
//! prompt — the brain must never break chat.
|
||||
//!
|
||||
//! Skills are **indexed, not inlined**: the prompt lists what the claw has and
|
||||
//! what each is for, and `skills.read` fetches a body on demand.
|
||||
//!
|
||||
//! The local file is a working cache of the claw's brain (canonical home is
|
||||
//! ClawBrainHub); memory accrues here and is pushed back on save/publish.
|
||||
@@ -25,7 +27,7 @@ fn brain_dir() -> PathBuf {
|
||||
pub fn compose_system(
|
||||
agent_id: &str,
|
||||
base_prompt: &str,
|
||||
skills: &[(String, String)], // (title, body)
|
||||
skills: &[(String, String, String)], // (title, description, body)
|
||||
user_text: &str,
|
||||
session_label: &str,
|
||||
) -> String {
|
||||
@@ -41,7 +43,7 @@ pub fn compose_system(
|
||||
fn try_compose(
|
||||
agent_id: &str,
|
||||
base_prompt: &str,
|
||||
skills: &[(String, String)],
|
||||
skills: &[(String, String, String)],
|
||||
user_text: &str,
|
||||
session_label: &str,
|
||||
) -> Result<String, cm_brain::BrainError> {
|
||||
@@ -55,7 +57,7 @@ fn try_compose(
|
||||
if !base_prompt.is_empty() {
|
||||
brain.set_system_prompt(base_prompt)?;
|
||||
}
|
||||
for (name, body) in skills {
|
||||
for (name, _description, body) in skills {
|
||||
brain.set_skill(name, body)?;
|
||||
}
|
||||
}
|
||||
@@ -86,10 +88,21 @@ fn try_compose(
|
||||
out.push_str("\n\n## Personality\n");
|
||||
out.push_str(&persona);
|
||||
}
|
||||
// Skills are indexed, not inlined. Bodies average ~3.5 KB (~900 tokens)
|
||||
// each and were previously concatenated in full on every turn, unbounded in
|
||||
// the number installed — by far the largest thing in the prompt. The claw
|
||||
// now sees what it has and what each is for, and calls `skills.read` for a
|
||||
// body when one is actually relevant. Same summary-and-fetch contract the
|
||||
// mission path already gets from the `clawmates_skills` MCP server.
|
||||
if !skills.is_empty() {
|
||||
out.push_str("\n\n## Your skills (apply them when relevant)\n");
|
||||
for (name, body) in skills {
|
||||
out.push_str(&format!("\n### {name}\n{body}\n"));
|
||||
out.push_str("\n\n## Your skills\n");
|
||||
out.push_str("Call `skills.read` with a skill's name to read it in full.\n");
|
||||
for (name, description, _body) in skills {
|
||||
if description.trim().is_empty() {
|
||||
out.push_str(&format!("- {name}\n"));
|
||||
} else {
|
||||
out.push_str(&format!("- {name} — {description}\n"));
|
||||
}
|
||||
}
|
||||
}
|
||||
if !recalled.is_empty() {
|
||||
|
||||
Reference in New Issue
Block a user