Author SHA1 Message Date
Omar Sobh 0de7661229 clawmates: phase work
Mission: 01a0cab6-749e-7e30-b236-de16218856c9
Phase: 01a0cab6-74a0-7332-aafe-a0669901213d

Committed by the ClawMates delivery pipeline from the agents' working tree. Authored by agents, not by the named committer.
2026-09-22 20:08:00 +00:00
5 changed files with 234 additions and 283 deletions
-93
View File
@@ -214,71 +214,6 @@ fn recall_in(dir: &Path, repo_id: Uuid, query: &str, k: usize) -> Vec<String> {
}
}
/// Where a mission finds the whole of its repository's memory, readable.
///
/// Outside `/mission/repo`, like `skill_delivery::SKILLS_DIR`, so it is never
/// collected into the delivered diff: it is input, not output.
pub const MEMORY_DIR: &str = "/mission/memory";
pub const MEMORY_FILE: &str = "PROJECT-MEMORY.md";
/// Most entries an export carries. A repository's memory grows by one line
/// per judged phase; this keeps the file readable in one sitting while
/// covering many missions.
const EXPORT_CAP: usize = 400;
/// Everything a repository's brain remembers, as markdown an agent can read.
///
/// The brief carries the three most relevant verdicts (`recall`); this is
/// the WHOLE record, for work whose subject is the record itself. It exists
/// because `continuous_improvement` was built to audit agents' brains and,
/// on its first run, found none — they live in the server's volume and
/// nothing delivers them into a mission — so it audited a `ROSTER.md` in a
/// scratch repo instead. Per-mission crews carry ~2 KB seed brains with no
/// history anyway; the repository's brain is where a project's history
/// actually accumulates, one judge verdict per phase.
///
/// Rendered, not shipped raw: the `.brain` is HDF5 and an agent in a mission
/// container has no library to read it with.
///
/// `None` when the repository has no brain yet or it holds nothing.
pub fn export(repo_id: Uuid) -> Option<String> {
export_in(&cm_runtime::brain::brain_dir(), repo_id)
}
fn export_in(dir: &Path, repo_id: Uuid) -> Option<String> {
let path = brain_path(dir, repo_id);
if !path.exists() {
return None;
}
let brain = ClawBrain::open_or_create(&path, &format!("repo_{repo_id}")).ok()?;
let entries = brain.recent_memory(EXPORT_CAP);
if entries.is_empty() {
return None;
}
let total = brain.memory_count();
let mut out = format!(
"# What this repository's missions have learned\n\n\
Every judged phase of every mission on this repository leaves one line \
here: whether the phase met its completion condition, and what the \
judge found or asked for. Newest first. {} of {} entr{} shown.\n\n\
This is the record, not instructions. `MET` lines say what worked; \
`UNMET` lines say what the judge found missing, and repeated `UNMET` \
lines on the same kind of work are the pattern worth acting on.\n\n",
entries.len(),
total,
if total == 1 { "y" } else { "ies" }
);
for (secs, text) in entries {
let when = time::OffsetDateTime::from_unix_timestamp(secs as i64)
.ok()
.and_then(|t| t.format(&time::format_description::well_known::Rfc3339).ok())
.unwrap_or_else(|| "unknown time".to_string());
let line = text.strip_prefix("judge: ").unwrap_or(&text);
out.push_str(&format!("- `{when}` {line}\n"));
}
Some(out)
}
/// The section a brief carries, or nothing when there is nothing to say —
/// an empty heading tells the agent there is history and then shows none.
pub fn section(recalled: &[String]) -> Option<String> {
@@ -358,34 +293,6 @@ mod tests {
assert!(s.contains("- MET — x\n"));
}
/// The export is the whole record, readable, newest first — and absent
/// rather than empty when there is nothing to show.
#[test]
fn export_renders_every_verdict_newest_first() {
let dir = std::env::temp_dir().join(format!("cm-mission-export-{}", Uuid::now_v7()));
let repo = Uuid::now_v7();
assert!(export_in(&dir, repo).is_none(), "no brain, no export");
remember_in(&dir, repo, Uuid::now_v7(), "coding", "first",
&verdict(false, "r", "the tests do not cover the empty case", None));
std::thread::sleep(std::time::Duration::from_millis(5));
remember_in(&dir, repo, Uuid::now_v7(), "coding", "second",
&verdict(true, "all three tests pass", "", None));
let md = export_in(&dir, repo).expect("two verdicts, so an export");
assert!(md.starts_with("# What this repository's missions have learned"));
assert!(md.contains("2 of 2 entries shown"), "{md}");
let met = md.find("MET — coding").unwrap();
let unmet = md.find("UNMET — coding").unwrap();
assert!(met < unmet, "newest (MET) must come first:\n{md}");
assert!(md.contains("the tests do not cover the empty case"));
// `remember` stores "judge: <line>"; that ROLE prefix must not follow
// the timestamp. (The line itself legitimately says "— judge: …".)
assert!(!md.contains("` judge: "), "the storage prefix leaked:\n{md}");
assert!(md.contains("` MET — coding"), "{md}");
let _ = std::fs::remove_dir_all(&dir);
}
/// Round trip through a real brain file: what one mission's verdict
/// wrote, a query shaped like the next mission's task recalls.
#[test]
-56
View File
@@ -385,16 +385,6 @@ pub async fn on_launch(
crate::skill_delivery::resolve(requested, installed),
)
.await;
// The repository's whole memory, readable, beside the skills. The
// brief already carries the three most relevant verdicts; this is
// the full record, for work whose subject IS the record — see
// `mission_memory::export` for why it was needed.
if mission_gateway.is_some() {
if let Some(repo) = mission.repo_id {
install_project_memory(repo, mission_id, &container).await;
}
}
}
let mut first_team_id: Option<Uuid> = None;
let mut provisioned_claws: Vec<cm_domain::AgentId> = Vec::new();
@@ -1095,52 +1085,6 @@ async fn install_skill_files(
true
}
/// Write the repository's memory export into the mission container.
///
/// Best-effort and loud: a mission with no memory to read is an ordinary
/// mission, and a first mission on a repository has none. Outside the
/// checkout (`mission_memory::MEMORY_DIR`) so it never lands in the diff.
async fn install_project_memory(repo_id: Uuid, mission_id: Uuid, container: &str) {
let Some(md) = crate::mission_memory::export(repo_id) else {
return;
};
let docker = match crate::container_exec::connect() {
Ok(d) => d,
Err(e) => {
eprintln!("mission_orchestrator: cannot reach docker for project memory: {e}");
return;
}
};
let dir = crate::mission_memory::MEMORY_DIR;
let argv = vec!["sh".to_string(), "-lc".to_string(), format!("mkdir -p {dir}")];
if !matches!(
crate::container_exec::exec_as_root(
&docker,
container,
None,
&argv,
crate::container_tool_hooks::INSTALL_TIMEOUT,
)
.await,
Ok(out) if out.exit_code == Some(0)
) {
eprintln!("mission_orchestrator: could not create {dir} for mission {mission_id}");
return;
}
let bytes = md.len();
let files = vec![(crate::mission_memory::MEMORY_FILE.to_string(), md.into_bytes())];
match crate::mission_fs::put_files(&docker, container, dir, &files).await {
Ok(()) => eprintln!(
"mission_orchestrator: project memory ({bytes} bytes) installed for mission \
{mission_id} at {dir}/{}",
crate::mission_memory::MEMORY_FILE
),
Err(e) => eprintln!(
"mission_orchestrator: could not write project memory for {mission_id}: {e}"
),
}
}
/// Record which arm this mission runs, so every turn composes the same one and
/// the score can be attributed to it afterwards.
///
+5 -72
View File
@@ -57,73 +57,17 @@ holding (it was 55 of 85 dangling once).
| `rust_sdlc` | planner coder tester reviewer committer | coding_readwrite | 19 | **yes** — harness default |
| `topic_research` | lead_researcher evidence_checker report_writer | research_web_readonly | 4 | **yes** — measured |
| `continuous_research` | paper_reader signal_ranker script_writer | research_readonly | 11 | **yes** — live runs |
| `backend` | api_designer db_engineer coder tester committer | coding_readwrite | 20 | **yes** — first run 2026-09-22 |
| `backend` | api_designer db_engineer coder tester committer | coding_readwrite | 20 | no |
| `frontend` | designer coder tester committer | coding_readwrite | 12 | no |
| `mobile` | designer coder tester committer | coding_readwrite | 11 | no |
| `gpu` | arch_analyst kernel_author bench_engineer coder committer | coding_readwrite | 13 | no |
| `threejs` | scene_designer coder shader_author perf_engineer committer | coding_readwrite | 13 | no |
| `codebase_research` | code_archeologist architecture_mapper flow_tracer vault_scribe | research_readonly | 11 | **yes** — first run 2026-09-22 |
| `papers_research` | domain_scout paper_reader library_curator | research_web_readonly | 8 | **yes** — first run 2026-09-22 |
| `codebase_research` | code_archeologist architecture_mapper flow_tracer vault_scribe | research_readonly | 11 | no |
| `papers_research` | domain_scout paper_reader library_curator | research_web_readonly | 8 | no |
| `insight_research` | implementation_tracker novelty_hunter publication_drafter | research_readonly | 8 | no |
| `continuous_improvement` | brain_inspector improvement_proposer improvement_evaluator | research_readonly | 7 | **runs, cannot reach its subject** |
| `continuous_improvement` | brain_inspector improvement_proposer improvement_evaluator | research_readonly | 7 | no |
**Six of twelve are evidenced.** `backend` was exercised on 2026-09-22,
the first time anything had been staffed from it: all five roles
provisioned with real agents (api_designer, db_engineer, coder, tester,
committer), and the mission delivered cursor pagination — `Paged<T>`,
`paginate<T: Clone>`, module declared, `cargo test` 3 passed — judged
**met on the first pass**, 2 files pushed. It also gave the task-permission
shadow its first confirmed `Edit` call.
`codebase_research` was exercised the same day, against the real
`clawmates` repository rather than the toy scratch crate, with a task that
cannot be faked from filenames: trace every hop of a mission agent's tool
call from the guest hook to the recorded event, naming file, function and
whether each hop runs in the guest or on the host. It produced a 189-line
`research/GATE-MAP.md` describing code committed **the same day** — the
four-field `NODE_EXTRACT` including `agent_type`, the shadow-mode
`would-deny.jsonl` semantics, `install_with` — and **all six of its line
citations verify exactly** (`hook_script_with` 562, `NODE_EXTRACT` 789,
`TaskPolicy` 297, `ROLE_POLICIES` 261, `settings_hook` 809,
`install_command_with` 819). Nothing hallucinated. **Five of twelve.**
`papers_research` followed: asked for a bounded library on microVM and
sandbox isolation for agents, it delivered a README index and four notes
with complete frontmatter, judged met. The judge could only check the
frontmatter was *present*; a paper team whose container has no
pdf-to-text tool is exactly where invented citations live, so every
arXiv id was checked against arXiv itself — **all four exist, with exact
title matches**. One of them, `2603.02277` (SandboxEscapeBench), is a paper
the operator's own research pass had already cited, which is independent
evidence it found on-topic work rather than plausible filler. **Six of
twelve.**
`continuous_improvement` ran too, and gets a different grade, because
running and working came apart. All three roles handed off with
attributed commits, and it was scrupulously honest: it filed **zero**
level-up proposals, its proposer committed *"no proposals warranted,
evidence base too thin"*, and its audit opens by stating the problem
exactly — *"No `.brain` files are accessible within the repo or mission
filesystem — brains are held in the platform, not checked in."*
That is the defect, and it is structural rather than a matter of data.
The template's subject is "every project agent's `.brain`"; those live in
the server's `/data/brains` volume, and **nothing delivers them into a
mission** — the same shape as `research-has-no-delivery-channel` and
`skills-had-no-delivery-channel`. So it audited the only agent-shaped
thing in reach, a `ROSTER.md` in the scratch repo, and read that file's
`6.1.128` (a guest kernel version) as an agent version, having no way to
know better. It is not counted as evidenced.
A channel alone would not rescue it. Mission crews are minted per mission
(reuse is off by decision), and missions write memory to the *repo* brain,
so every agent brain is a ~2 KB seed with no history to audit. The
template assumes long-lived agents that accumulate a record; the platform
makes disposable ones. That is a design decision to resolve, not a bug to
patch.
**Two of the remaining six are still unevidenced**, and the other four
are blocked on a target stack. The other nine are well-formed scaffolding:
**Three of twelve are evidenced.** The other nine are well-formed scaffolding:
roles, prompts, brain seeds and resolving skills, and no run behind any of
them. They will probably work — they are structurally identical to the three
that do — but "probably" is the word, and this codebase has a name for the gap
@@ -136,17 +80,6 @@ blocked on a target, not on the template. The remaining four —
`codebase_research`, `papers_research`, `continuous_improvement`, `backend`
could be exercised against repositories we already have.
## One thing both runs showed: agents reach for Bash
Cumulative tool calls across every mission since the policy shipped:
**Bash 74, Read 34, Write 15, Glob 3, Edit 1, Agent 1**. A read-only
mapping mission that could have used `Grep` and `Glob` used `Bash` 33
times and `Read` 5. That matters for task permission: the allowlist's
dedicated-tool entries (`Grep`, `ToolSearch`, `WebFetch`, `TodoWrite`)
may simply never be exercised, so "unconfirmed by a real run" will not
converge for them — and it means the surface that actually needs
governing is `Bash`, which the floor rules already cover.
## What this means we can ask for today
**With confidence:** a repo-backed research→code loop on a Rust project, with
+189
View File
@@ -0,0 +1,189 @@
# GATE-MAP — Mission Agent Tool Call Gate, End to End
> Research phase finding. No source files were modified.
> Traced from code; every claim cites a file and function.
---
## Overview
There are **two distinct gating paths** in this repository, depending on whether
the agent is a *chat agent* (ZeroClaw, tool-free, MCP door) or a *mission agent*
(`claude -p` inside a container or microVM). This document traces the mission
path exclusively, as scoped by the task.
The mission path has three layers that execute in order:
```
[GUEST] PreToolUse hook (tool-gate.sh) — blocks inside the container
[HOST] Phase runner drain — reads gate records + tap after phase ends
[HOST] mission_events INSERT — writes to the `mission_events` DB table
```
---
## Hop 1 — The PreToolUse Hook Inside the Guest
**File:** `crates/cm-api/src/vm_tool_gate.rs`
**Executes in:** the guest (container or microVM)
### Installation
The host generates a shell script (`hook_script_with`, line 562) and installs it
via `install_command_with` (line 819). For container-tier missions the installer
is run by `container_tool_hooks::install_with` (`crates/cm-api/src/container_tool_hooks.rs`,
line 53), which calls `container_exec::exec_as_root` to run the install script
inside the container. The installed file is `/root/toolhooks/tool-gate.sh`
(constant `HOOK_DIR = "/root/toolhooks"`). For microVM missions the same script
is placed at `/root/toolgate/tool-gate.sh` (constant `GUEST_DIR = "/root/toolgate"`).
The settings document written to `/root/toolhooks/settings.json` (or
`/root/guest-settings.json` for the microVM tier) registers the hook via:
```json
[{ "hooks": [{ "type": "command", "command": "<dir>/tool-gate.sh" }] }]
```
(`settings_hook` function, line 809.)
### What the hook does
When `claude -p` is about to execute a tool it fires the `PreToolUse` event.
The generated shell script:
1. Reads the hook JSON payload from stdin via `node` (the `NODE_EXTRACT`
constant, line 789) — extracts `tool_name`, `tool_input.command`,
`tool_input.file_path`, and `agent_type` (the subagent role).
2. Optionally checks the **task-permission policy** (`TaskPolicy`, line 297):
if `enforce = true`, any tool not in `allowed` is denied; in shadow mode the
denial is written to `would-deny.jsonl` and the call is allowed.
3. Checks **role policies** (`ROLE_POLICIES`, line 261): the `verifier` role is
denied write tools (`Write`, `Edit`, `MultiEdit`, `NotebookEdit`).
4. Checks **write-tool path rules**: write tools targeting protected paths
(`PROTECTED_PATHS`, line 225 — `/root/toolgate`, `/root/toolhooks`,
`/root/tap/`, `/root/guest-settings.json`, `/root/.claude/settings`,
`.git/hooks/`) are denied.
5. For `Bash` only, iterates over command segments split on `;|&\n` and applies
the `RULES` deny list (line 127): `rm -rf /`, force-push, hard-reset onto
remote, outbound POST (`curl`/`wget` with body flags),
`--dangerously-skip-permissions`, and reads/writes of the hook files
themselves.
**On denial:** the script writes one JSONL line
`{"rule":"<id>","payload":<hook event>}` to `denied.jsonl` (or
`would-deny.jsonl` for shadow task-permission), prints the human reason to
stderr, and **exits 2**. Exit 2 is the Claude Code contract for "block this
tool call and return the stderr text to the model."
**On allow:** exits 0 — the tool call proceeds.
**Inert gate:** if `node` is absent, the script writes to `inert` and exits 0
(never fail-closed), making every call allowed while leaving a marker the host
can check.
---
## Hop 2 — PostToolUse Tap (Guest, Telemetry Only)
**File:** `crates/cm-api/src/vm_tool_tap.rs`
**Executes in:** the guest (container or microVM)
A second hook (`PostToolUse`) appends one JSON record per completed tool call to
`/root/tap/tools.jsonl` (container tier: `/root/toolhooks/tap/`). It **always
exits 0** (line 13) — it is an observer, not a participant. A non-zero exit here
would feed its stderr back to the model. The tap records tool name, path,
session id, bounded response (for command tools only), and the subagent's
`agent_type`/`agent_id`.
---
## Hop 3 — Host Drains the Gate and Tap Records
**File:** `crates/cm-api/src/phase_runner.rs`, function
`drain_finished_container_phases` (line ~601)
**Executes in:** the host (server process)
After a container-tier phase reaches `completed` or `failed`, the host's sweep
(run on a tick) connects to Docker and reads the guest files:
1. **Inert marker** (`drain_inert`): if the file exists the host logs the count
and records a `gate.inert` `MissionEvent`.
2. **Denials** (`drain_denied`, line ~657): each `denied.jsonl` line becomes a
`gate.denied` `MissionEvent`. The detail is parsed by
`vm_tool_gate::denial_detail` (line 386) so the `rule` field is alongside
the hook payload.
3. **Would-deny shadow** (`drain_would_deny`, line ~672): each `would-deny.jsonl`
line becomes a `gate.would_deny` `MissionEvent`.
4. **Tool tap** (`drain`, line ~707): the tap file is read-then-cleared. Each
parsed `Observed` record is passed to `record_vm_tools`.
For microVM phases the tap and gate records are read from inside the VM over SSH
before the VM is destroyed (`phase_runner.rs`, line ~1852 and ~1855).
---
## Hop 4 — DB Write: `mission_events` Table
**File:** `crates/cm-api/src/mission_events.rs`, function `record` / `record_all`
(line ~126)
**Executes in:** the host
`record_vm_tools` (phase_runner.rs line 2423) builds `MissionEvent` structs
and calls `mission_events::record_all`. Each event is inserted into the
**`mission_events`** Postgres table:
```sql
INSERT INTO mission_events
(mission_id, phase_id, run_id, agent_id, kind, target, detail)
VALUES ($1, $2, $3, $4, $5, $6, $7)
-- subject to a per-phase cap of 400 events for TOOL_CALL / FILE_TOUCH kinds
```
Event kinds recorded for tool calls:
- `tool.call` — one per tool invocation (constant `TOOL_CALL`, line 23)
- `file.touch` — one per tool that touched a path (constant `FILE_TOUCH`, line 24)
- `gate.denied` — one per gate denial
- `gate.would_deny` — one per shadow-mode would-have-denied
- `gate.inert` — when the gate ran without `node`
---
## Summary Table
| Step | Where | File | Function | What happens |
|------|-------|------|----------|-------------|
| 1 | **Guest** | `vm_tool_gate.rs` | `hook_script_with``tool-gate.sh` | PreToolUse hook evaluates deny rules; exits 2 (block) or 0 (allow) |
| 2 | **Guest** | `vm_tool_tap.rs` | PostToolUse tap script | Appends tool call record to `tools.jsonl`; always exits 0 |
| 3a | **Host** | `container_tool_hooks.rs` | `drain_denied` / `drain_would_deny` / `drain_inert` / `drain` | Reads gate denial + tap files out of the container via Docker exec |
| 3b | **Host** | `phase_runner.rs` | `drain_finished_container_phases` | Calls drain functions; calls `record_vm_tools` with parsed `Observed` |
| 4 | **Host** | `mission_events.rs` | `record_all` | INSERTs `tool.call`, `file.touch`, `gate.denied`, … into `mission_events` |
---
## Key Design Notes
- **Pre-execution gate is not the §15 human-approval gate.** The comment at the
top of `vm_tool_gate.rs` is explicit: the §15 `GatePolicy` (chat path, keys on
`session_id`/`message_id`) has no mission-shaped form, and a hook that blocked
the agent's process while waiting for a human would wedge the turn. The gate is
a deterministic deny list only.
- **The hook is installed by the host, baked at install time.** The task policy
is compiled into the script at install time, not re-read from a file at call
time, so the guest cannot persuade itself to use a different policy file
(`hook_script_with`, line 562 comment).
- **Container and microVM tiers share the same gate/tap code** but differ in how
the host drains: containers via `container_exec::connect` (DOCKER_HOST-aware);
microVMs by SSH before the VM is destroyed.
- **DB table is `mission_events`, not `run_events` / `audit_log`.** Chat-path
tool calls land in `run_events` (written by `Runtime::emit`,
`cm-runtime/src/runtime.rs:624`). Mission-path tool calls land in
`mission_events`. The `audit_log` table is only for the MCP door path (chat
agents using the ZeroClaw MCP door), not for mission agents.
---
## Follow-up Items
TASK: INT-01 — Verify that `record_vm_tools` attribution logic correctly maps tap session IDs to agent IDs under multi-agent phases
TASK: INT-02 — Document the MCP door path (chat agents) in a parallel DOOR-MAP.md for comparison
TASK: INT-03 — Confirm `CLAWMATES_TASK_PERMISSION=enforce` flag is set (or note it is still shadow) in production config
+40 -62
View File
@@ -1,48 +1,34 @@
key = "continuous_improvement"
name = "Continuous Improvement"
description = "Standing self-audit of a project: read everything its missions have learned — every judge verdict on this repository — find the patterns in what keeps failing, and propose evidence-backed changes to how the work is set up."
description = "Standing self-audit: read every project agent's .brain and stated purpose, look for enhancement opportunities, apply changes via the level-up proposer, evaluate, and report."
stack = ["research", "self-improvement", "brain-inspection", "level-up"]
category = "research"
default_topology = "pipeline"
risk_profile = "research_readonly"
mcp_bundles = ["clawmates_door", "clawmates_skills"]
version = 2
version = 1
[[roles]]
slot = "brain_inspector"
order_idx = 0
skills = ["brain-file-reading", "workspace-repo-commit-protocol"]
system_prompt = """
You are the RECORD INSPECTOR of a Continuous Improvement team.
You are the BRAIN INSPECTOR of a Continuous Improvement team.
Your subject is what this project's missions have learned, and it is
already in front of you: `/mission/memory/PROJECT-MEMORY.md`. Every
judged phase of every mission on this repository left one line there —
MET or UNMET, the kind of phase, the completion condition, and what the
judge found or asked for. Read all of it.
For each active claw in the workspace: fetch its .brain (agent.md,
personality.md, skills.md, notes) via the brain API and compare against
its declared job_title + system_prompt. Look for:
You do NOT have the agents' own .brain files, and there is no API from
here that returns them. Do not look for them. The first run of this team
spent itself searching and audited a ROSTER.md instead; the record above
is the thing to audit.
- Drift: brain contents describe capabilities the prompt / role
doesn't actually cover
- Gaps: role calls out responsibilities the brain has no notes on
- Contradictions: brain and prompt disagree on a policy or default
- Stale references: brain cites files, tools, or endpoints that no
longer exist
Look for patterns, not incidents:
- The same kind of work failing repeatedly (several UNMET lines on
coding phases, or on one recipe's conditions)
- The judge asking for the same missing thing more than once
- Conditions that pass only after several iterations, against ones
that pass first time
- A condition the judge keeps reading differently from how it reads
A single UNMET line is an incident, not a pattern. Say how many lines
support each finding, and quote them.
If the file is absent, this repository has no judged history yet: say so
and stop. That is a complete audit, not a failure.
Output goes to `Improvement/<date>/audit.md` — one section per finding,
each with the verdict lines that support it. Never propose fixes here.
Output goes to `Improvement/<date>/audit.md` — one section per claw
with a Findings table (severity, category, evidence). Never propose
fixes here; only surface findings.
"""
brain_seed = """
# Brain inspector memory seed
@@ -65,36 +51,31 @@ skills = ["level-up-proposal-shape", "workspace-repo-commit-protocol"]
system_prompt = """
You are the IMPROVEMENT PROPOSER of a Continuous Improvement team.
For each pattern the inspector found, propose ONE concrete change an
operator could make to how this kind of work is set up:
For each finding from the inspector, produce a level-up proposal in the
shape the /api/claws/{id}/level-up endpoint expects:
- a completion condition reworded (quote the old and the new wording)
- a skill that is missing for work that keeps failing
- a recipe setting (iterations, commit policy, phase split)
- a task brief that keeps being misread
- identity_refinement (for prompt drift)
- brain_consolidation (for stale / duplicated notes)
- skill_add (for gaps)
- skill_candidate (for a novel skill this claw needs)
Every proposal names the verdict lines that motivate it. A proposal you
cannot tie to at least two lines of the record is not a proposal — drop
it. "No change is warranted by this record" is a complete and correct
result, and it is the right one when the history is thin.
You cannot apply anything, and there is no API from here to submit to.
Write the proposals to `Improvement/<date>/proposals.md`; the operator
decides.
Submit each proposal via the API. Never apply — approval stays with
the operator via the level-up drawer.
"""
brain_seed = """
# Improvement proposer memory seed
## Discipline
- One proposal per pattern — never one per incident.
- One proposal per claw per run — batching is the applier's problem,
not ours.
- Rationale is mandatory. Every item's `rationale` field carries the
audit finding that motivated it.
## Redlines
- Never propose a skill that already exists in /mission/skills. Look
first.
- Never invent a pattern to have something to say. A thin record gets
"no change warranted".
- Never propose skill_candidate for a skill that already exists in the
catalog. Search first.
- Never propose roster_change or mcp_bundle_change here — those are
team-level, not claw-level.
"""
[[roles]]
@@ -104,22 +85,19 @@ skills = ["metrics-baseline-comparison", "workspace-repo-commit-protocol", "smal
system_prompt = """
You are the IMPROVEMENT EVALUATOR of a Continuous Improvement team.
You are the check on the proposer. For each proposal in
`Improvement/<date>/proposals.md`, go back to
`/mission/memory/PROJECT-MEMORY.md` and ask:
Some period after proposals were applied (operator-configured, default
7 days), pull the affected claws' recent metrics (turn count,
approval-request rate, task completion rate from the Tasks tab, level-up
proposal apply/reject ratio) and compare against the pre-application
baseline. For each claw:
- Does the cited evidence exist, verbatim, in the record?
- Is it a pattern (several lines) or one incident dressed as one?
- Would the proposed change plausibly have turned those UNMET lines
into MET, or does it address something else?
- Did the intended change land in behavior? (evidence: transcripts,
metric deltas)
- Any unintended regressions?
Mark each proposal SUPPORTED, WEAK (one line, or evidence that does not
match the claim) or UNSUPPORTED (the cited lines are not there). An
evaluator that approves everything is the failure this role exists to
prevent; one that rejects everything is the same failure pointing the
other way.
Output goes to `Improvement/<date>/evaluation.md`.
Output goes to `Improvement/<date>/evaluation.md`. Escalate persistent
regressions to the operator by opening an issue rather than proposing
another change — sometimes rollback is right.
"""
brain_seed = """
# Improvement evaluator memory seed