Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,22 @@ onward the project follows SemVer normally: patch releases are
bug-fix-only, minor releases are backward-compatible additions, and
breaking changes require a major bump.

## v2.5.3: Close the benchmark learning loop

A focused follow-up to v2.5.2. Adds the host-side path that lets a graded
task feed its outcome back into the brain, so the consolidation machinery
(usefulness, query-routes, staples) receives a real signal on every solve.

### Added

- `kimetsu brain benchmark-credit --task <t> --passed`: on a pass, groups a
citation for the memories most relevant to the task and routes it through
the same grouped, query-linked path consolidation consumes. Driven by a
benchmark harness after grading, so the learning signal never depends on
the agent inside the sandbox calling a tool. This is what makes an
iterated-benchmark learning loop actually reach the brain; before it, the
benchmark memory path recorded outcomes but never exercised consolidation.

## v2.5.2: The brain that learns from its own outcomes

Consolidation v1: the first release where citation outcomes reshape the brain
Expand Down
18 changes: 9 additions & 9 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ resolver = "3"
# everything below except the per-crate `description`, `keywords`,
# and `categories` which live in each `[package]` block since
# crates.io enforces them per crate.
version = "2.5.2"
version = "2.5.3"
edition = "2024"
# Rust ecosystem dual-license (matches tokio, serde, fastembed-rs,
# etc.). UNLICENSED would block crates.io entirely.
Expand Down
4 changes: 2 additions & 2 deletions crates/kimetsu-agent/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ aws-sigv4.workspace = true
aws-smithy-runtime-api.workspace = true
blake3.workspace = true
http.workspace = true
kimetsu-brain = { path = "../kimetsu-brain", version = "2.5.2" }
kimetsu-core = { path = "../kimetsu-core", version = "2.5.2" }
kimetsu-brain = { path = "../kimetsu-brain", version = "2.5.3" }
kimetsu-core = { path = "../kimetsu-core", version = "2.5.3" }
regex.workspace = true
reqwest.workspace = true
rusqlite.workspace = true
Expand Down
2 changes: 1 addition & 1 deletion crates/kimetsu-brain/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ hf-hub = { version = "0.5", optional = true, default-features = false, features
# already native (ort). The lean build never links it.
usearch = { version = "2", optional = true }
ignore.workspace = true
kimetsu-core = { path = "../kimetsu-core", version = "2.5.2" }
kimetsu-core = { path = "../kimetsu-core", version = "2.5.3" }
# v0.4.5: regex backs the secret-redaction patterns in
# `kimetsu_brain::redact`. The crate is already a workspace pin
# elsewhere; we just opt this crate into it now.
Expand Down
39 changes: 39 additions & 0 deletions crates/kimetsu-brain/src/reinforce.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,45 @@ pub struct ReinforceSummary {
pub routes_embedded: usize,
}

/// Close the benchmark learning loop for one graded task (v2.5.2): when a
/// task PASSES, the memories most relevant to it get a grouped, query-linked
/// citation — the exact signal consolidation consumes (usefulness +1.0 each,
/// query-routes from task -> those memories, and staples from their
/// co-citation). Driven host-side by the benchmark harness after grading, so
/// the learning signal never depends on the in-container agent calling any
/// tool. Failures produce no citation (the retrieved memories are not
/// necessarily to blame). Returns how many memories were credited.
///
/// Why this exists: the MCP `kimetsu_brain_cite` tool routes through the
/// SINGLETON `record_mcp_citation` path (one id, no query, fresh run) which
/// feeds none of the three consumers. This routes through the grouped
/// `record_citations` path, which feeds all three.
pub fn credit_benchmark_outcome(
start: &Path,
task: &str,
passed: bool,
top_k: usize,
) -> KimetsuResult<usize> {
if !passed {
return Ok(0);
}
// Retrieve the memories most relevant to this task, then credit the top
// few as "in context when the task was solved". search_memories ranks by
// BM25 over the task text across project + user brains.
let hits = crate::project::search_memories(start, task, top_k.max(1) as u32, 0, None, None)?;
let ids: Vec<String> = hits.into_iter().take(top_k).map(|h| h.memory_id).collect();
if ids.is_empty() {
return Ok(0);
}
crate::project::record_citations(
start,
&ids,
Some("benchmark: in context when task passed"),
Some(task),
)?;
Ok(ids.len())
}

/// Run the offline consolidation pass: staple qualifying co-citations and/or
/// rebuild the query-routes table. Both idempotent; safe to run every
/// session end or between benchmark iterations.
Expand Down
6 changes: 3 additions & 3 deletions crates/kimetsu-chat/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ openclaw = ["dep:json5"]
# surface, not a benchmark harness.

[dependencies]
kimetsu-agent = { path = "../kimetsu-agent", version = "2.5.2" }
kimetsu-brain = { path = "../kimetsu-brain", version = "2.5.2" }
kimetsu-core = { path = "../kimetsu-core", version = "2.5.2" }
kimetsu-agent = { path = "../kimetsu-agent", version = "2.5.3" }
kimetsu-brain = { path = "../kimetsu-brain", version = "2.5.3" }
kimetsu-core = { path = "../kimetsu-core", version = "2.5.3" }
base64.workspace = true
crossterm.workspace = true
json5 = { workspace = true, optional = true }
Expand Down
8 changes: 4 additions & 4 deletions crates/kimetsu-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ path = "src/main.rs"

[dependencies]
clap.workspace = true
kimetsu-agent = { path = "../kimetsu-agent", version = "2.5.2" }
kimetsu-brain = { path = "../kimetsu-brain", version = "2.5.2" }
kimetsu-chat = { path = "../kimetsu-chat", version = "2.5.2" }
kimetsu-core = { path = "../kimetsu-core", version = "2.5.2" }
kimetsu-agent = { path = "../kimetsu-agent", version = "2.5.3" }
kimetsu-brain = { path = "../kimetsu-brain", version = "2.5.3" }
kimetsu-chat = { path = "../kimetsu-chat", version = "2.5.3" }
kimetsu-core = { path = "../kimetsu-core", version = "2.5.3" }
# v0.4.6: `kimetsu doctor` serializes its report struct so --json
# output can be piped into CI / hooks.
flate2 = "1"
Expand Down
27 changes: 26 additions & 1 deletion crates/kimetsu-cli/src/commands/brain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ pub(crate) fn brain(command: BrainCommand) -> KimetsuResult<()> {
BrainCommand::Forget(args) => brain_forget(args),
BrainCommand::Cite(args) => brain_cite(args),
BrainCommand::Reinforce(args) => brain_reinforce(args),
BrainCommand::BenchmarkCredit(args) => brain_benchmark_credit(args),
BrainCommand::Regret(args) => brain_regret(args),
BrainCommand::Distill(args) => brain_distill(args),
BrainCommand::Graph { command } => brain_graph(command),
Expand Down Expand Up @@ -2756,7 +2757,7 @@ pub(crate) fn brain_forget(args: ForgetArgs) -> KimetsuResult<()> {
c.use_count,
c.usefulness_score,
c.age_days,
&c.text_preview
c.text_preview
);
}
}
Expand Down Expand Up @@ -2896,6 +2897,30 @@ pub(crate) fn brain_reinforce(args: ReinforceArgs) -> KimetsuResult<()> {
Ok(())
}

pub(crate) fn brain_benchmark_credit(args: BenchmarkCreditArgs) -> KimetsuResult<()> {
let workspace = args
.workspace
.unwrap_or_else(|| env::current_dir().unwrap_or_default());
let credited = kimetsu_brain::reinforce::credit_benchmark_outcome(
&workspace,
&args.task,
args.passed,
args.top_k,
)?;
println!(
"benchmark-credit: {} memor{} cited for task \"{}\" ({})",
credited,
if credited == 1 { "y" } else { "ies" },
args.task,
if args.passed {
"passed"
} else {
"not passed — no citation"
}
);
Ok(())
}

pub(crate) fn brain_regret(args: RegretArgs) -> KimetsuResult<()> {
let workspace = args
.workspace
Expand Down
22 changes: 22 additions & 0 deletions crates/kimetsu-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -847,6 +847,11 @@ enum BrainCommand {
/// before get a bounded retrieval boost. Model-free; run offline
/// (session end / between benchmark iterations).
Reinforce(ReinforceArgs),
/// Close the benchmark learning loop for one graded task: on a PASS,
/// grouped-cite the memories most relevant to the task (feeds usefulness,
/// query-routes, and staples). Driven host-side by the benchmark harness
/// so the learning signal never depends on the in-container agent.
BenchmarkCredit(BenchmarkCreditArgs),
/// Record a regret: mark that a surfaced memory was unhelpful/misleading.
///
/// Writes a `retrieval.regret` telemetry event for the memory — the negative
Expand Down Expand Up @@ -1359,6 +1364,23 @@ struct ReinforceArgs {
workspace: Option<PathBuf>,
}

/// Args for `kimetsu brain benchmark-credit`.
#[derive(Debug, Args)]
struct BenchmarkCreditArgs {
/// The task description / query the graded task represents.
#[arg(long)]
task: String,
/// Mark the task as PASSED — only passes produce a citation.
#[arg(long)]
passed: bool,
/// How many top-ranked memories to credit on a pass.
#[arg(long, default_value_t = 3)]
top_k: usize,
/// Override the brain workspace path (defaults to current directory).
#[arg(long)]
workspace: Option<PathBuf>,
}

/// Args for `kimetsu brain regret`.
#[derive(Debug, Args)]
struct RegretArgs {
Expand Down
6 changes: 3 additions & 3 deletions crates/kimetsu-e2e/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ publish = false
# Real (non-dev) deps so the test fixtures + scripted provider can be
# re-exported from `kimetsu_e2e::prelude` to the integration tests in
# `tests/`. Integration tests treat this crate as a normal library.
kimetsu-agent = { path = "../kimetsu-agent", version = "2.5.2" }
kimetsu-brain = { path = "../kimetsu-brain", version = "2.5.2" }
kimetsu-core = { path = "../kimetsu-core", version = "2.5.2" }
kimetsu-agent = { path = "../kimetsu-agent", version = "2.5.3" }
kimetsu-brain = { path = "../kimetsu-brain", version = "2.5.3" }
kimetsu-core = { path = "../kimetsu-core", version = "2.5.3" }
rusqlite.workspace = true
serde_json.workspace = true
time.workspace = true
Expand Down
Loading