Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0de7661229 |
@@ -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 —
|
/// 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.
|
/// an empty heading tells the agent there is history and then shows none.
|
||||||
pub fn section(recalled: &[String]) -> Option<String> {
|
pub fn section(recalled: &[String]) -> Option<String> {
|
||||||
@@ -358,34 +293,6 @@ mod tests {
|
|||||||
assert!(s.contains("- MET — x\n"));
|
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
|
/// Round trip through a real brain file: what one mission's verdict
|
||||||
/// wrote, a query shaped like the next mission's task recalls.
|
/// wrote, a query shaped like the next mission's task recalls.
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -385,16 +385,6 @@ pub async fn on_launch(
|
|||||||
crate::skill_delivery::resolve(requested, installed),
|
crate::skill_delivery::resolve(requested, installed),
|
||||||
)
|
)
|
||||||
.await;
|
.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 first_team_id: Option<Uuid> = None;
|
||||||
let mut provisioned_claws: Vec<cm_domain::AgentId> = Vec::new();
|
let mut provisioned_claws: Vec<cm_domain::AgentId> = Vec::new();
|
||||||
@@ -1095,52 +1085,6 @@ async fn install_skill_files(
|
|||||||
true
|
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
|
/// Record which arm this mission runs, so every turn composes the same one and
|
||||||
/// the score can be attributed to it afterwards.
|
/// the score can be attributed to it afterwards.
|
||||||
///
|
///
|
||||||
|
|||||||
+5
-100
@@ -57,101 +57,17 @@ holding (it was 55 of 85 dangling once).
|
|||||||
| `rust_sdlc` | planner coder tester reviewer committer | coding_readwrite | 19 | **yes** — harness default |
|
| `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 |
|
| `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 |
|
| `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 |
|
| `frontend` | designer coder tester committer | coding_readwrite | 12 | no |
|
||||||
| `mobile` | designer coder tester committer | coding_readwrite | 11 | no |
|
| `mobile` | designer coder tester committer | coding_readwrite | 11 | no |
|
||||||
| `gpu` | arch_analyst kernel_author bench_engineer coder committer | coding_readwrite | 13 | 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 |
|
| `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 |
|
| `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 | **yes** — first run 2026-09-22 |
|
| `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 |
|
| `insight_research` | implementation_tracker novelty_hunter publication_drafter | research_readonly | 8 | no |
|
||||||
| `continuous_improvement` | brain_inspector improvement_proposer improvement_evaluator | research_readonly | 7 | **reaches its subject; right answer, judge-failed on quoting** |
|
| `continuous_improvement` | brain_inspector improvement_proposer improvement_evaluator | research_readonly | 7 | no |
|
||||||
|
|
||||||
**Six of twelve are evidenced.** `backend` was exercised on 2026-09-22,
|
**Three of twelve are evidenced.** The other nine are well-formed scaffolding:
|
||||||
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.
|
|
||||||
|
|
||||||
**Retargeted (2026-09-22).** The decision taken: audit the *repo* brain,
|
|
||||||
which is where missions actually write what they learn. The server now
|
|
||||||
exports it as `/mission/memory/PROJECT-MEMORY.md`, one line per judged
|
|
||||||
phase, and installs it into every mission that has a repository.
|
|
||||||
|
|
||||||
Two runs followed, and they separate the channel from the steering:
|
|
||||||
|
|
||||||
- **v2 (01a0cad8) — channel works, steering does not.** The 2.4 KB record
|
|
||||||
was installed. The agent made 50 tool calls, 0 of them touched it, 13
|
|
||||||
went to the roster, and the judge passed it. The brief and `done_when`
|
|
||||||
were reused from v1 ("read each agent's brain"), and the template's
|
|
||||||
retargeted role prompts are **inert on mission turns** (see
|
|
||||||
`prompt-injection-two-paths`). Rewriting `system_prompt` cannot aim a
|
|
||||||
mission; only the brief can, and nothing supplies one for this team.
|
|
||||||
- **v3 (01a0cade) — right answer, failed by the judge.** With a brief and
|
|
||||||
`done_when` pointing at the record, 3 of 7 tool calls read it. The audit
|
|
||||||
states the record exactly (4 lines, MET 4 / UNMET 0), finds no pattern,
|
|
||||||
and files **zero** proposals ("a single UNMET line would be an incident;
|
|
||||||
this record has zero"), the correct result on an all-MET record. The
|
|
||||||
judge failed it, correctly by the letter: the condition required each
|
|
||||||
finding to *quote* the record lines, and the audit summarised them in a
|
|
||||||
table instead. `max_iterations` was 1, so there was no retry.
|
|
||||||
|
|
||||||
So the subject is now reachable and the reasoning holds up. The unproven
|
|
||||||
part is the case this team exists for, a record *with* failures. That
|
|
||||||
needs a repository whose history contains UNMET verdicts, and a default
|
|
||||||
brief so no caller has to write one. Still not counted as evidenced.
|
|
||||||
|
|
||||||
**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:
|
|
||||||
roles, prompts, brain seeds and resolving skills, and no run behind any of
|
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
|
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
|
that do — but "probably" is the word, and this codebase has a name for the gap
|
||||||
@@ -164,17 +80,6 @@ blocked on a target, not on the template. The remaining four —
|
|||||||
`codebase_research`, `papers_research`, `continuous_improvement`, `backend` —
|
`codebase_research`, `papers_research`, `continuous_improvement`, `backend` —
|
||||||
could be exercised against repositories we already have.
|
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
|
## What this means we can ask for today
|
||||||
|
|
||||||
**With confidence:** a repo-backed research→code loop on a Rust project, with
|
**With confidence:** a repo-backed research→code loop on a Rust project, with
|
||||||
|
|||||||
@@ -91,7 +91,6 @@ const TEMPLATE_LABEL: Record<TemplateKind, string> = {
|
|||||||
refactor: "Refactor",
|
refactor: "Refactor",
|
||||||
benchmark: "Benchmark",
|
benchmark: "Benchmark",
|
||||||
continuous_research: "Continuous Research",
|
continuous_research: "Continuous Research",
|
||||||
self_audit: "Self-audit",
|
|
||||||
custom: "Custom",
|
custom: "Custom",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ const TEMPLATE_BADGE: Record<TemplateKind, { label: string; color: string }> = {
|
|||||||
refactor: { label: "REFACTOR", color: "#ffb44a" },
|
refactor: { label: "REFACTOR", color: "#ffb44a" },
|
||||||
benchmark: { label: "BENCHMARK", color: "#83e6a5" },
|
benchmark: { label: "BENCHMARK", color: "#83e6a5" },
|
||||||
continuous_research: { label: "PODCAST", color: "#e0b0ff" },
|
continuous_research: { label: "PODCAST", color: "#e0b0ff" },
|
||||||
self_audit: { label: "AUDIT", color: "#b8c4a0" },
|
|
||||||
custom: { label: "CUSTOM", color: "#8a8a92" },
|
custom: { label: "CUSTOM", color: "#8a8a92" },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -91,7 +91,6 @@ const PALETTES: Record<string, Palette> = {
|
|||||||
security_hardening: SECURITY,
|
security_hardening: SECURITY,
|
||||||
refactor: REFACTOR,
|
refactor: REFACTOR,
|
||||||
benchmark: BENCHMARK,
|
benchmark: BENCHMARK,
|
||||||
self_audit: RESEARCH,
|
|
||||||
custom: BASE,
|
custom: BASE,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ export type TemplateKind =
|
|||||||
| "refactor"
|
| "refactor"
|
||||||
| "benchmark"
|
| "benchmark"
|
||||||
| "continuous_research"
|
| "continuous_research"
|
||||||
| "self_audit"
|
|
||||||
| "custom";
|
| "custom";
|
||||||
|
|
||||||
export type MissionStatus =
|
export type MissionStatus =
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -1,48 +1,34 @@
|
|||||||
key = "continuous_improvement"
|
key = "continuous_improvement"
|
||||||
name = "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"]
|
stack = ["research", "self-improvement", "brain-inspection", "level-up"]
|
||||||
category = "research"
|
category = "research"
|
||||||
default_topology = "pipeline"
|
default_topology = "pipeline"
|
||||||
risk_profile = "research_readonly"
|
risk_profile = "research_readonly"
|
||||||
mcp_bundles = ["clawmates_door", "clawmates_skills"]
|
mcp_bundles = ["clawmates_door", "clawmates_skills"]
|
||||||
version = 2
|
version = 1
|
||||||
|
|
||||||
[[roles]]
|
[[roles]]
|
||||||
slot = "brain_inspector"
|
slot = "brain_inspector"
|
||||||
order_idx = 0
|
order_idx = 0
|
||||||
skills = ["brain-file-reading", "workspace-repo-commit-protocol"]
|
skills = ["brain-file-reading", "workspace-repo-commit-protocol"]
|
||||||
system_prompt = """
|
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
|
For each active claw in the workspace: fetch its .brain (agent.md,
|
||||||
already in front of you: `/mission/memory/PROJECT-MEMORY.md`. Every
|
personality.md, skills.md, notes) via the brain API and compare against
|
||||||
judged phase of every mission on this repository left one line there —
|
its declared job_title + system_prompt. Look for:
|
||||||
MET or UNMET, the kind of phase, the completion condition, and what the
|
|
||||||
judge found or asked for. Read all of it.
|
|
||||||
|
|
||||||
You do NOT have the agents' own .brain files, and there is no API from
|
- Drift: brain contents describe capabilities the prompt / role
|
||||||
here that returns them. Do not look for them. The first run of this team
|
doesn't actually cover
|
||||||
spent itself searching and audited a ROSTER.md instead; the record above
|
- Gaps: role calls out responsibilities the brain has no notes on
|
||||||
is the thing to audit.
|
- 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:
|
Output goes to `Improvement/<date>/audit.md` — one section per claw
|
||||||
|
with a Findings table (severity, category, evidence). Never propose
|
||||||
- The same kind of work failing repeatedly (several UNMET lines on
|
fixes here; only surface findings.
|
||||||
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.
|
|
||||||
"""
|
"""
|
||||||
brain_seed = """
|
brain_seed = """
|
||||||
# Brain inspector memory seed
|
# Brain inspector memory seed
|
||||||
@@ -65,36 +51,31 @@ skills = ["level-up-proposal-shape", "workspace-repo-commit-protocol"]
|
|||||||
system_prompt = """
|
system_prompt = """
|
||||||
You are the IMPROVEMENT PROPOSER of a Continuous Improvement team.
|
You are the IMPROVEMENT PROPOSER of a Continuous Improvement team.
|
||||||
|
|
||||||
For each pattern the inspector found, propose ONE concrete change an
|
For each finding from the inspector, produce a level-up proposal in the
|
||||||
operator could make to how this kind of work is set up:
|
shape the /api/claws/{id}/level-up endpoint expects:
|
||||||
|
|
||||||
- a completion condition reworded (quote the old and the new wording)
|
- identity_refinement (for prompt drift)
|
||||||
- a skill that is missing for work that keeps failing
|
- brain_consolidation (for stale / duplicated notes)
|
||||||
- a recipe setting (iterations, commit policy, phase split)
|
- skill_add (for gaps)
|
||||||
- a task brief that keeps being misread
|
- skill_candidate (for a novel skill this claw needs)
|
||||||
|
|
||||||
Every proposal names the verdict lines that motivate it. A proposal you
|
Submit each proposal via the API. Never apply — approval stays with
|
||||||
cannot tie to at least two lines of the record is not a proposal — drop
|
the operator via the level-up drawer.
|
||||||
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.
|
|
||||||
"""
|
"""
|
||||||
brain_seed = """
|
brain_seed = """
|
||||||
# Improvement proposer memory seed
|
# Improvement proposer memory seed
|
||||||
|
|
||||||
## Discipline
|
## 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
|
- Rationale is mandatory. Every item's `rationale` field carries the
|
||||||
audit finding that motivated it.
|
audit finding that motivated it.
|
||||||
|
|
||||||
## Redlines
|
## Redlines
|
||||||
- Never propose a skill that already exists in /mission/skills. Look
|
- Never propose skill_candidate for a skill that already exists in the
|
||||||
first.
|
catalog. Search first.
|
||||||
- Never invent a pattern to have something to say. A thin record gets
|
- Never propose roster_change or mcp_bundle_change here — those are
|
||||||
"no change warranted".
|
team-level, not claw-level.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
[[roles]]
|
[[roles]]
|
||||||
@@ -104,22 +85,19 @@ skills = ["metrics-baseline-comparison", "workspace-repo-commit-protocol", "smal
|
|||||||
system_prompt = """
|
system_prompt = """
|
||||||
You are the IMPROVEMENT EVALUATOR of a Continuous Improvement team.
|
You are the IMPROVEMENT EVALUATOR of a Continuous Improvement team.
|
||||||
|
|
||||||
You are the check on the proposer. For each proposal in
|
Some period after proposals were applied (operator-configured, default
|
||||||
`Improvement/<date>/proposals.md`, go back to
|
7 days), pull the affected claws' recent metrics (turn count,
|
||||||
`/mission/memory/PROJECT-MEMORY.md` and ask:
|
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?
|
- Did the intended change land in behavior? (evidence: transcripts,
|
||||||
- Is it a pattern (several lines) or one incident dressed as one?
|
metric deltas)
|
||||||
- Would the proposed change plausibly have turned those UNMET lines
|
- Any unintended regressions?
|
||||||
into MET, or does it address something else?
|
|
||||||
|
|
||||||
Mark each proposal SUPPORTED, WEAK (one line, or evidence that does not
|
Output goes to `Improvement/<date>/evaluation.md`. Escalate persistent
|
||||||
match the claim) or UNSUPPORTED (the cited lines are not there). An
|
regressions to the operator by opening an issue rather than proposing
|
||||||
evaluator that approves everything is the failure this role exists to
|
another change — sometimes rollback is right.
|
||||||
prevent; one that rejects everything is the same failure pointing the
|
|
||||||
other way.
|
|
||||||
|
|
||||||
Output goes to `Improvement/<date>/evaluation.md`.
|
|
||||||
"""
|
"""
|
||||||
brain_seed = """
|
brain_seed = """
|
||||||
# Improvement evaluator memory seed
|
# Improvement evaluator memory seed
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
key = "self_audit"
|
|
||||||
title = "Self-audit"
|
|
||||||
blurb = "Read every judge verdict this repository's missions have earned, find what keeps failing, and propose evidence-backed changes to how the work is set up."
|
|
||||||
# The subject is the repo brain, exported into the mission as
|
|
||||||
# /mission/memory/PROJECT-MEMORY.md. It exists only for missions with a repo.
|
|
||||||
requires_repo = true
|
|
||||||
|
|
||||||
# `continuous_improvement` existed without a recipe, so every mission that used
|
|
||||||
# it had to write its own brief — and the brief is the only thing that aims a
|
|
||||||
# mission. Role `system_prompt`s are inert on mission turns (they reach chat,
|
|
||||||
# not missions). Measured 2026-09-22: with the role prompts retargeted at the
|
|
||||||
# record and a brief left over from v1, 0 of 50 tool calls read the record and
|
|
||||||
# the judge passed an audit of a ROSTER.md. With the brief below, the record
|
|
||||||
# was read and the audit got the right answer. See docs/TEMPLATE-MATURITY.md.
|
|
||||||
default_team_template = "continuous_improvement"
|
|
||||||
|
|
||||||
[[phases]]
|
|
||||||
kind = "research"
|
|
||||||
order_idx = 0
|
|
||||||
[phases.config]
|
|
||||||
default_topology = "pipeline"
|
|
||||||
task = """
|
|
||||||
Audit what this repository's missions have learned.
|
|
||||||
|
|
||||||
The record is /mission/memory/PROJECT-MEMORY.md: one line per judged phase of every mission on this repository — MET or UNMET, the phase kind, the completion condition, and the judge's reason. That record is the whole subject of this audit. Do not look for agent brain files, rosters or anything else to audit; there is nothing else.
|
|
||||||
|
|
||||||
If the file is absent, the repository has no judged history yet. Write that in research/IMPROVEMENT-AUDIT.md and stop — that is a complete audit.
|
|
||||||
|
|
||||||
Otherwise:
|
|
||||||
|
|
||||||
1. Count the lines, and how many are MET and how many UNMET. State the numbers.
|
|
||||||
2. Look for patterns, not incidents: the same kind of work failing more than once, the judge asking for the same missing thing more than once, a condition that passed only after several iterations. One UNMET line is an incident, not a pattern — name it as an incident and propose nothing for it.
|
|
||||||
3. For each pattern, quote the record lines it rests on, copied verbatim as they appear in the file. A paraphrase or a summary table is not evidence; the quoted lines are.
|
|
||||||
4. For each pattern, propose ONE change to how the work is set up — a reworded completion condition (old and new wording), a missing skill, a recipe setting, a task brief — and say why it would have turned those UNMET lines into MET. A proposal that fewer than two lines support is dropped, not softened.
|
|
||||||
5. If the record shows no failure pattern, say so, and say that no change is warranted. That is a correct result, and on a thin or clean record it is the expected one. Never invent a pattern to have something to report.
|
|
||||||
|
|
||||||
You cannot apply anything and there is nothing to submit to. Write everything to research/IMPROVEMENT-AUDIT.md; the operator decides.
|
|
||||||
"""
|
|
||||||
# Wording follows the measured rule: say what the file CONTAINS. The v3 run's
|
|
||||||
# condition said "quotes the record lines each finding rests on" and the judge
|
|
||||||
# failed an audit with no failure patterns for paraphrasing its observations.
|
|
||||||
# The quoting requirement now attaches to what needs evidence — a reported
|
|
||||||
# pattern — and the clean-record outcome is named so it reads as a pass.
|
|
||||||
done_when = "research/IMPROVEMENT-AUDIT.md exists and either states that no project record was present, or states how many verdict lines the record contains with the MET and UNMET counts and then, for each failure pattern it reports, quotes at least two record lines verbatim and proposes one change, or states that the record shows no failure pattern and no change is warranted"
|
|
||||||
max_iterations = 2
|
|
||||||
commit_policy = "always"
|
|
||||||
Reference in New Issue
Block a user