Brain: inject identity into the live prompt + surface AGENTS.md in the command center
ci / gates (push) Failing after 11s
ci / rust (push) Has been skipped
ci / sandbox-k8s (push) Has been skipped
ci / frontend (push) Has been skipped
ci / e2e (push) Has been skipped

Gap 1 — the brain's identity sections were stored but UI-only. cm-runtime/brain.rs
`compose_system` now folds the brain's AGENTS.md ("## How I operate") + personality
into the live system prompt (after the Postgres-authoritative base, before skills +
memory; falls back to the brain's soul_md when the base is empty). Mirrors the
OpenClaw/ZeroClaw render order.

Mapping — expose `agent_md` on `GET /api/claws/{id}/brain` (ClawBrainResponse) and
render it as a collapsible "HOW I OPERATE · AGENTS.md" card in the command center's
BRAIN column (RawBrain gains agent_md; richBrain passes it through). So the section
that's now in the prompt is also visible in the UI.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
Omar Sobh
2026-06-26 12:59:17 -07:00
co-authored by Claude Opus 4.8
parent 11a1f22daa
commit 9fca3f6676
5 changed files with 39 additions and 9 deletions
+3
View File
@@ -148,6 +148,8 @@ pub struct ClawBrainResponse {
/// Whether a `.brain` file already existed before this request. /// Whether a `.brain` file already existed before this request.
pub exists: bool, pub exists: bool,
pub system_prompt: Option<String>, pub system_prompt: Option<String>,
/// AGENTS.md — "how I operate" (workflow/rules); folded into the live prompt.
pub agent_md: Option<String>,
pub personality: Option<String>, pub personality: Option<String>,
pub skills: Vec<BrainSkill>, pub skills: Vec<BrainSkill>,
pub tools: Vec<BrainTool>, pub tools: Vec<BrainTool>,
@@ -205,6 +207,7 @@ fn load_brain(agent: &Agent, skills: &[(String, String)]) -> ClawBrainResponse {
ClawBrainResponse { ClawBrainResponse {
exists: existed, exists: existed,
system_prompt: brain.system_prompt(), system_prompt: brain.system_prompt(),
agent_md: brain.agent_md(),
personality: brain.personality(), personality: brain.personality(),
skills: skills_v, skills: skills_v,
tools: tools_v, tools: tools_v,
+26 -6
View File
@@ -2,9 +2,9 @@
//! //!
//! Each turn we open the claw's local working `.brain` (cm-brain / ClawhDF5), //! 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 //! recall relevant memory, record the user's turn, and compose a system prompt
//! that injects the claw's **skills** and **recalled memory** on top of its //! that injects the claw's **identity** (AGENTS.md "how I operate" + personality),
//! Postgres-authoritative system prompt. Any failure falls back to the plain //! **skills**, and **recalled memory** on top of its Postgres-authoritative system
//! prompt — the brain must never break chat. //! prompt. Any failure falls back to the plain prompt — the brain must never break chat.
//! //!
//! The local file is a working cache of the claw's brain (canonical home is //! 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. //! ClawBrainHub); memory accrues here and is pushed back on save/publish.
@@ -20,8 +20,8 @@ fn brain_dir() -> PathBuf {
} }
/// Compose the system prompt for a claw, augmenting `base_prompt` with its /// Compose the system prompt for a claw, augmenting `base_prompt` with its
/// skills + recalled memory, and recording the user turn. Best-effort: returns /// identity (AGENTS.md + personality), skills, and recalled memory, and recording
/// `base_prompt` unchanged on any brain error. /// the user turn. Best-effort: returns `base_prompt` unchanged on any brain error.
pub fn compose_system( pub fn compose_system(
agent_id: &str, agent_id: &str,
base_prompt: &str, base_prompt: &str,
@@ -64,8 +64,28 @@ fn try_compose(
// Record the user's turn so future sessions can recall it (best-effort). // Record the user's turn so future sessions can recall it (best-effort).
let _ = brain.remember("user", user_text, session_label); let _ = brain.remember("user", user_text, session_label);
let mut out = String::with_capacity(base_prompt.len() + 256); let mut out = String::with_capacity(base_prompt.len() + 512);
// Base identity: the Postgres-authoritative system prompt, or — if it's empty
// (e.g. a brain pulled from the Hub that carries its identity in soul_md) —
// the brain's own system prompt (which falls back to soul_md).
if base_prompt.trim().is_empty() {
if let Some(sp) = brain.system_prompt() {
out.push_str(&sp);
}
} else {
out.push_str(base_prompt); out.push_str(base_prompt);
}
// Identity sections stored in the brain but previously UI-only — now folded
// into the live prompt (mirrors the OpenClaw/ZeroClaw AGENTS.md + persona
// render order): "how I operate", then personality.
if let Some(agent_md) = brain.agent_md() {
out.push_str("\n\n## How I operate\n");
out.push_str(&agent_md);
}
if let Some(persona) = brain.personality() {
out.push_str("\n\n## Personality\n");
out.push_str(&persona);
}
if !skills.is_empty() { if !skills.is_empty() {
out.push_str("\n\n## Your skills (apply them when relevant)\n"); out.push_str("\n\n## Your skills (apply them when relevant)\n");
for (name, body) in skills { for (name, body) in skills {
@@ -7,11 +7,11 @@
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { Activity, Brain, Camera, Cpu, Database, Drama, Settings, ShieldCheck, Wrench, Zap } from "lucide-react"; import { Activity, Brain, Camera, Cpu, Database, Drama, Settings, ShieldCheck, Workflow, Wrench, Zap } from "lucide-react";
import type { DemoAgent } from "@/lib/dashboard-demo"; import type { DemoAgent } from "@/lib/dashboard-demo";
import { useAgentTelemetry, useLiveEvent } from "@/lib/live/useClawmatesLive"; import { useAgentTelemetry, useLiveEvent } from "@/lib/live/useClawmatesLive";
import { AnatomyCard, PersonalityBody, SystemPromptCard, mono, tag, type RawBrain } from "./anatomy-cards"; import { AnatomyCard, MarkdownText, PersonalityBody, SystemPromptCard, mono, tag, type RawBrain } from "./anatomy-cards";
import { AvatarModal } from "./AvatarModal"; import { AvatarModal } from "./AvatarModal";
import { AddToolModal } from "./AddToolModal"; import { AddToolModal } from "./AddToolModal";
@@ -232,6 +232,11 @@ export function ClawCommandCenter({
{/* BRAIN */} {/* BRAIN */}
<div style={colStyle}> <div style={colStyle}>
<SystemPromptCard prompt={agent.systemPrompt} /> <SystemPromptCard prompt={agent.systemPrompt} />
{brain?.agent_md ? (
<AnatomyCard tint="#ff8a7a" label="HOW I OPERATE · AGENTS.md" icon={<Workflow size={15} />} collapsible>
<MarkdownText text={brain.agent_md} baseSize={13.5} />
</AnatomyCard>
) : null}
<AnatomyCard tint="#c98af0" label="PERSONALITY" icon={<Drama size={15} />} collapsible> <AnatomyCard tint="#c98af0" label="PERSONALITY" icon={<Drama size={15} />} collapsible>
<PersonalityBody raw={brain?.personality} fallback={c.personality} /> <PersonalityBody raw={brain?.personality} fallback={c.personality} />
</AnatomyCard> </AnatomyCard>
@@ -101,6 +101,7 @@ type RawApp = { id: string; name: string; category?: string };
type RawBrain = { type RawBrain = {
exists: boolean; exists: boolean;
system_prompt: string | null; system_prompt: string | null;
agent_md: string | null;
personality: string | null; personality: string | null;
skills: { name: string; body: string }[]; skills: { name: string; body: string }[];
tools: { name: string; state: string }[]; tools: { name: string; state: string }[];
@@ -13,6 +13,7 @@ export const mono = "'JetBrains Mono', ui-monospace, monospace";
export type RawBrain = { export type RawBrain = {
exists: boolean; exists: boolean;
system_prompt: string | null; system_prompt: string | null;
agent_md: string | null;
personality: string | null; personality: string | null;
skills: { name: string; body: string }[]; skills: { name: string; body: string }[];
tools: { name: string; state: string }[]; tools: { name: string; state: string }[];