Skip to content

feat: what earns its keep (v1.51.0) - #177

Merged
solaitken merged 14 commits into
mainfrom
feat/salience-lifecycle-enrichment
Aug 22, 2026
Merged

solaitken merged 14 commits into
mainfrom
feat/salience-lifecycle-enrichment

Conversation

@solaitken

Copy link
Copy Markdown
Collaborator

Summary

Eight tracker cards shipped as one wave (v1.51.0): the dream pass gains a deterministic salience gate that decides which facts earn expensive synthesis and names every exclusion; model-mined session signals, staged Agent Skill drafts, and one-shot grounded design notes become three new model lanes that all write through one shared needs-llm-step envelope grammar and one fail-closed semantic-check registry; a note delete can follow structured provenance under a snapshot and an exact count guard; expiration becomes settable at creation and mutable afterwards from every writing surface; and o2b brain architect output now states its codegraph verdict, draws a containment diagram it can prove, and lists the repository's mined decision candidates. A ninth card, the local transformer embedding provider, is refused with a recorded rationale rather than shipped as a runtime-less stub.

Why this matters

Every lane where a model writes into the vault now passes the same three checkpoints before a byte lands, and everything expensive or destructive states what it considered and what it refused.

flowchart LR
    A[Model-authored payload] --> B{Declared response shape}
    B -->|violation named| R1[Refused]
    B --> C{Semantic checks: cap, floor, cardinality}
    C -->|limit named| R2[Refused]
    C --> D{Staging: inbox trial or pending accept}
    D -->|operator accepts| V[(Vault)]
    S[Existing signals: mass, confidence, reuse] --> G{Salience gate}
    G -->|admitted| E[Dream synthesis]
    G -->|excluded, named in summary| N[Run summary]
Loading

Concrete benefits

  • One envelope grammar instead of six divergent shapes: the three pre-existing needs-llm-step forms and the three new lanes share a named type, so the next model lane starts from the spine instead of a copy.
  • Token economy where it counts: the salience gate trims the rollup fold set deterministically, from signals the system already computes, with zero added model calls and a retriage preview before any re-stage.
  • Precision preserved: auto-extracted signals can only reach the speculative inbox under a cap, a confidence floor, the durability denylist, and write-approval staging; confirmed preferences remain reachable only through the dream pass.
  • Destructive discipline: --delete-linked deletes only files whose structured provenance traces solely to the target, reports everything else, and runs under the snapshot gate, count guard, and destructive-sites census.
  • Honest generated artifacts: the architect overview names its codegraph grounding per run (graph-present with counts, or graph-absent, lexical-only with the reason) and the containment diagram draws only what the scanner truthfully knows.
  • Audit trail: an independent reviewer with no build context returned one blocking and eleven non-blocking findings; all are applied in a dedicated commit, and the one false positive was disproved by a regression test rather than argued away.

What ships

Unit Artifact
Envelope spine + semantic checks src/core/brain/llm-step.ts, src/core/brain/response-checks.ts
Salience gate + retriage src/core/brain/salience-gate.ts, dream.salience_threshold, dream retriage
Session signal mining o2b brain extract-signals, brain_extract_signals, source_type: auto_extract
Provenance-bound cascade delete --delete-linked on note delete (CLI + MCP)
Capture guidance + ingress guidance on captures, o2b brain capture
Expiration lifecycle --expires at creation, o2b brain expire / brain_expire
Skill drafts from mature pages mature_page proposals, SKILL.md materializer on accept
Architect grounding codegraph and module-map overview regions, key-decisions note
Grounded design notes o2b brain design-note, brain_design_note
Release docs CHANGELOG 1.51.0, README what-is-new, version sync across 7 manifests

Test plan

  • Full suite, clean HOME: env HOME=$(mktemp -d) bun test - 11453 pass, 0 fail (1175 files)
  • Typecheck: bun x tsc --noEmit - clean
  • Lint: bun run lint - 0 errors, no new warnings
  • Manifest sync: bun run scripts/sync-version.ts --check - all 7 manifests at 1.51.0
  • OpenClaw bundle byte-gate: bun run build:openclaw under Bun 1.4.0 - zero diff
  • Unset dream.salience_threshold reproduces pre-wave behavior byte-for-byte (pinned by test)
  • import-session output pinned byte-identical (the extraction lane stayed out of the hook path)
  • Independent review pass: security clean; 1 blocking + 11 non-blocking findings applied, 1 disproved with a regression test

solaitken and others added 13 commits August 22, 2026 11:03
Phase-0 artifacts for the eight-task wave: consultant variants, design
document, and implementation plan. Variant B (shared envelope spine,
additive adoption) accepted with two amendments: deterministic-only
salience gate, and an explicit refusal of the transformer embedding
unit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017dYpaSgWTK5L6o6AzcLcBd
Three lanes already spoke the needs-llm-step grammar with three
independent declarations of it - the durable write-session envelope, the
diarization profile step, the rollup ladder envelope - and the next lane
that needed generated text would have written a fourth. src/core/brain/
llm-step.ts is the one declaration they share: the fields all three
carry (status, step, prompt, schema_hints, target_path) and nothing
else, plus buildNeedsLlmStep, which stamps the status literal so no
construction site can spell it a fourth way and copies the hint list so
the envelope cannot change under a reference the caller kept. The
builder refuses a blank step, prompt, or target_path by name: an
envelope missing one of the three cannot be answered, and it is worth
more as a refusal at the construction site than as an unanswerable
request downstream.

Adoption is additive and type-level. The diarization step IS the spine;
the rollup envelope is the spine plus the two fields only a ladder rung
has. WriteSessionEnvelope is the durable superset - its status widens to
the whole session lifecycle - so it inherits the spine's generation
fields and keeps its session-backed ones; the session kernel's runtime
is untouched, and both envelopes serialize byte-for-byte as before, key
order included, which the new suite pins against the pre-spine JSON.

Beside it, src/core/brain/response-checks.ts holds the constraints the
deliberately-shallow ShapeDescriptor language cannot express: cardinality
and cross-item rules, which need to read more than one value at a time.
Deepening the descriptor language to fit them would trade the flatness
the shape layer exists to keep, so they live next to it instead -
following the precedent of the research report's citation check, which
is left where it is because moving it would change its error class and
its messages. The registry is fail-closed: asserting a surface nobody
registered throws a named refusal rather than reading as a clean pass,
registering a surface twice throws rather than silently replacing the
first check, and a check returning a code outside the frozen vocabulary
is itself refused so no refusal reaches a caller unnamed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017dYpaSgWTK5L6o6AzcLcBd
The architecture overview described a project's structure without ever
saying what that description rested on. A reader could not tell whether
the notes were backed by a real code graph or by nothing but the
deterministic file scan, and the codegraph partner report - which has
answered exactly that question since the operational-readability work -
had no consumer inside the generator.

A new `codegraph` sentinel region in the overview states the verdict.
All five `CodegraphIndexState` values render distinctly: `indexed`
stamps `graph-present` with node, file and edge counts plus the health
summary (and one line per health finding, by code); the other four share
the `graph-absent, lexical-only` verdict but each keeps its own state
name and its own reason string, because their remediations differ and
`codegraph init` is actively the wrong advice for a partner that timed
out.

The verdict comes from the existing `buildCodegraphReport`. No partner
CLI vocabulary is added: `CODEGRAPH_CLI`'s four tokens remain the entire
surface. The report is asked with `limit: 1` and the scanned project as
`cwd`, which scopes the question to the project the overview is about -
the default scan widens to the vault parent's siblings and would
otherwise stamp a verdict about a different repository into this
repository's notes. A project root that is not a code project is
reported as `no_project`, which is true of it.

The region body is its own provenance stamp (state, counts, health), so
a region left behind by an index that has since moved describes the
reading it was made from. Because the verdict is a fact about the
machine rather than about the tree, the region is a declared exception
to this module's unchanged-project byte-identity guarantee, named as
such in the generator docstring; the exception is scoped to the region
and nothing else, and the new suite pins that every other region and all
operator prose still survive a moving verdict byte-for-byte.

The partner read happens before the sync lock is taken: it touches
neither the vault nor the notes, so holding the critical section across
it would only lengthen the window a concurrent run waits on.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017dYpaSgWTK5L6o6AzcLcBd
…isions note

Two additions to the generated architecture notes, both deterministic and
both grounded in data that already exists.

A `module-map` region in the overview renders the module layout as a
Mermaid flowchart. It is CONTAINMENT ONLY, and the region says so in
prose above the block: the scanner records no import graph at all
("Import-graph analysis is explicitly out of scope"), so every edge runs
from the project root to a module it holds, and a module-to-module edge
would be a relation nothing measured. Each node carries the module name,
its file count, and its dominant language. Node ids are positional
(`mod0`, `mod1`, ...) rather than derived from directory names, which
removes the need for a collision-free mangling from arbitrary bytes to a
Mermaid identifier; labels are entity-escaped for `#`, `"`, `<` and `>`,
with `#` first so the escapes are not themselves rewritten. Ordering runs
through `compareStable`, never a locale collator, so the block is
byte-identical for the same facts on any host.

A key-decisions note (`decisions.md`, `kind: arch-decisions`) is planned
beside the overview on every run. It lists the repository's ADR
candidates - title, sha, matched signals, and a wikilink to the draft -
read from the vault-global `Brain/decisions/candidates/` store and
filtered by the `repo_key` the architect already derives, which is the
same key the commit miner stamps. Without that filter one repository's
note would list another's drafts. A repo with no candidates gets an
explicit empty-state sentence rather than an empty region, because a
blank region is indistinguishable from a generator that failed to run.
A candidate whose frontmatter lost its sha is listed with "sha
unrecorded" - a hand-edited draft is reported, never silently dropped.

The key-decisions note is the second declared exception to the
unchanged-project byte-identity guarantee, named in the generator
docstring beside the codegraph region: mining new commits moves its bytes
with no change to the repository at all. The exception is scoped - the
new suite pins that an unchanged project AND an unchanged vault
regenerate every note byte-identically, and that a new candidate moves
the decisions note and nothing else. The `module-map` diagram is
explicitly NOT an exception; the docstring says so.

Ride-alongs in the files touched: `compareStable` moves to `scan.ts`, the
leaf of the scan/render pair, because the new candidate reader orders its
output with it too and the two modules may not import each other; the
language sort is hoisted into `sortedLanguages` so the summary line and
the diagram's dominant-language label cannot disagree about which
language leads a module; the summary line's magic 8 becomes
`LANGUAGES_LINE_CAP`. The run tally and the render stage's denominator go
from `1 + modules` to `2 + modules`, and the CLI's JSON envelope gains
`decisions_path`.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017dYpaSgWTK5L6o6AzcLcBd
The dream pass has no per-item model lane. Its entire model delegation is
the count-triggered rollup ladder, which reads one number - how many facts
exist - and emits one envelope per fired rung. So there is exactly one
place a salience judgement can change anything, and this is it: which
facts are counted into that fold set.

`salience-gate.ts` scores each fact by combining three signals the vault
already computes as pure functions, reused verbatim rather than
reimplemented: the dominant decayed outcome mass from `lessons.ts`, the
Wilson-bound confidence from `confidence.ts`, and the observed-reuse rate
from `observed-use.ts`. Mass is the only unbounded one, so it is mapped
into [0, 1) by a hyperbolic saturation with a named half-saturation
constant; the three weighted terms sum to 1, so the score is bounded and
each weight reads as "how much of the score this signal can buy". No
model, no cache, no prompt version - recomputation is free and a cached
verdict would be a second source of truth about a number three pure
functions already answer.

`dream.salience_threshold` is optional and unset by default. Unset, the
gate is OPEN: nothing is scored, so neither the log nor the continuity
store is read, the ladder counts the unfiltered fold set, and the run is
what it always was. That is the documented default, not a silent
fallback - the summary still reports the gate explicitly, with
`threshold: null`, because a missing field could not tell an absent gate
from a gate that excluded nothing. A malformed threshold is refused by
name at the config seam and again at the resolve seam, the doctrine
`dream-gates.ts` states for a malformed gate override. Every excluded
fact is named in the summary with its score and its three raw signals;
the gate never drops anything silently.

`o2b brain dream retriage <run-id>` (and MCP `brain_dream`
`action: "retriage"`) is the read-only answer to "what would move": a
staged bundle records the partition it was staged with, and retriage
re-runs the gate against the CURRENT threshold and names every fact that
would newly enter or leave the fold set, with both scores. It rewrites
nothing, so adopting the new partition stays a deliberate re-stage. A
bundle staged before the gate shipped is refused by name rather than
compared against an assumed-open partition.

No new DREAM_PHASE: the gate fits inside rollup planning, so the phase
refusal vocabulary is untouched. The salience partition rides beside the
plan in the bundle manifest rather than inside `DreamStagePlan`, because
that projection is what `validate` diffs to prove the vault has not
drifted and it models what the pass WRITES - the ladder's envelopes are
not in it either.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017dYpaSgWTK5L6o6AzcLcBd
…dius rule

A note delete removed one file and reported the inbound references it
stranded. When the note was an imported source, several of those
references were not navigation aids: a per-source summary page, a signal
citing it, a preference folded from that signal - files whose whole
content restates the note and which mean nothing once it is gone. An
operator had to hand-chase every one.

`--delete-linked` extends the delete to exactly that class and no
further. The rule deciding membership is not a new one: `deleteBySource`
already has it - a single-purpose derived page in a derivation
directory, citing no second source, folded from no foreign signal - and
this commit generalises that tracer over a spelling set rather than
copying it, so a note delete and a source delete cannot come to disagree
about what "derived solely from" means. `traceNoteDerivations` is the
note-side entry point; `deleteBySource`'s own behaviour is unchanged
(its identity carries one spelling, exactly as before).

The module header's commitment stands and the implementation says so
where the two sets diverge: a delete still rewrites nothing, and
everything outside the solely-derived set is REPORTED. A page citing a
second source, and every user note, land in `reportedFiles` and survive.
The response returns both lists, disjoint, on the dry run and on the
confirmed run, so a caller never subtracts one from the other. It also
names the scope the fold walked (`Brain/`), because an empty derived set
has two readings and only one is a measurement.

The destructive ladder is the existing one with one deliberate change:
under the flag the count guard asserts over the DELETION SET rather than
the inbound-reference count, so `--expect` counts the paths the response
just printed. All unlinks sit inside ONE `withDestructiveSnapshot` call
- the set is one decision, and a per-file recovery point would leave the
operator choosing among archives. The note goes first, so a failure
mid-cascade leaves dangling derivations (the state every delete before
this flag left behind) rather than derivations whose note survived. The
Brain half of the set IS covered by the archive, which is why a cascade
reports `partial` where a bare delete reports `unproven`; the
`destructive-sites.ts` entry for the module records that.

Surfaces: `deleteLinked` on the core input, `--delete-linked` on the CLI
verb (which prints both lists in full rather than counting them),
`delete_linked` on the MCP tool, plus help-text and command-manifest.
The flag is refused by name on any action that removes nothing, rather
than being silently ignored.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017dYpaSgWTK5L6o6AzcLcBd
Two halves of one gap. A capture could say what was captured and nothing
about what to do with it, and the only way to make one at all was the
inbound Telegram bot - `writeCaptureNote` had exactly one caller, so the
staging area, the drain and the `/catchup` watermark were reachable only
by an install that had configured a chat channel.

`guidance` is an optional string on WriteCaptureInput, rendered as a
`## Guidance` body section and read back verbatim on CaptureNote. It is
deliberately not merged into the body: the two are written in one breath
and mean different things, and a drain that cannot tell them apart would
route on an instruction as though it were content.

The hash shape changes, and only in one direction. Guidance joins the id
hash input, because two captures differing only in it are two captures
and hashing the body alone would collapse them onto one stem and hand
the second to the `-2` allocator, where the id stops describing the
content. The segment is appended ONLY when guidance is present, so the
hash input for a capture without it is the historical three-part string
byte for byte and every id the Telegram bot would have produced is
unchanged. The same conditionality governs the frontmatter: the
`capture_guidance` marker is emitted only alongside real guidance.

That marker is what makes the body split safe. A captured message may
itself end in a `## Guidance` heading - people quote documents - so the
parser splits only when the writer said it wrote a section, and a marked
page whose body carries no section is refused as corrupted rather than
read as guidance-less.

Telegram is unchanged, and that is a reading of its payload rather than
an omission: the update carries a chat id and one text body, with
nothing in it distinguishing content from instruction, so splitting on a
convention this bot invented would put words in the sender's mouth. The
call site says so.

`o2b brain capture` is the ingress: body as an argument or on stdin,
`--source`, `--sender` (defaulting to the configured agent), `--at` for
a replayed capture's own instant, `--guidance`, and `--json`. Every
refusal is named and exits 2 on both surfaces. It writes through the
same contract function the bot uses, so no second set of rules exists.
No MCP tool in this unit. No STATE_SURFACES change either: the registry
models ledgers and machine state, not Markdown content trees, and
`Brain/captures/` has no row to update - the surface contract is where
captures live and what they are, and neither moved.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017dYpaSgWTK5L6o6AzcLcBd
`expiration_date` shipped with a validator, a writer parameter on both
artifact kinds, and a fully-wired read side - and no surface anywhere
that set it. A caller could declare a memory's lifetime only by writing
straight to the core API, and could never change one at all: nothing in
the tree rewrote that field after the write. This closes both halves.

Creation. `--expires` on `o2b brain feedback` and `expires` on
`brain_feedback` plumb into the existing WriteSignalInput and
WritePreferenceInput slots. Both surfaces validate BEFORE the signal
write, so an unparseable date refuses the whole call rather than landing
a signal and then failing on the force-confirmed preference beside it. A
force-confirmed rule inherits the signal's lifetime: a preference
confirmed from an observation that expires does not outlive it.

Mutation. `o2b brain expire <id> --expires <date|none>` and the
`brain_expire` MCP tool, over a new `expiration-set.ts` core that
addresses signals and preferences BY ID and never accepts a path. That
routing is the design decision the brainstorm left open, and it settles
the way the note surfaces force: `resolveNoteTarget` refuses the `Brain/`
machinery root for every path it is given, which is what keeps a
note-editing verb from rewriting a preference, so reaching these
artifacts from there would mean carving an exception into the one guard
that makes those surfaces safe. `brain_expire` is a tool rather than a
`brain_lifecycle` action for the same reason - every action there
addresses its subject by a note path.

Clearing is the explicit word `none` and it REMOVES the key. `--expires
""` is what a shell produces when a variable did not expand, so an empty
value must never read as "un-expire this"; and an empty key on disk
parses to nothing, fails open on read, and looks like a lifetime
somebody meant to set. A re-set to the value already there is reported
as `changed: false` and writes nothing - the audit log records changes,
not calls, and both sides of each change are in it because "cleared" and
"moved to a later date" are different decisions the frontmatter
afterwards cannot tell apart.

The one chokepoint is now enforced rather than observed.
`tests/core/architecture/expiration-chokepoint.test.ts` sweeps `src/`
and requires every module that stamps the field into a frontmatter map
to name `normalizeExpirationDate`. That matters because the READ side
deliberately fails open on an unparseable value so corruption surfaces -
a tolerance that is only safe while every write is validated. The census
states its two narrowings rather than implying completeness. Ride-along:
`signal.ts` and `preference.ts` now stamp through `EXPIRATION_DATE_FIELD`
instead of repeating the literal.

Pins moved, each for the one tool added: the MCP tool count (110 -> 111)
in the parity, listing, stdio and removed-tools suites, the agent-scope
matrix classification and its probe counts (96/220 -> 97/221, quoted in
docs/architecture.md), the caller-supplied-agent tool count (21 -> 22),
and the write-site census's shared-helper rows (97 -> 98) for the new
module writing through `writeFrontmatterAtomic`.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017dYpaSgWTK5L6o6AzcLcBd
The regex fact extractor runs inside the live capture hook, which is why
it only recognises structure - a URL, an address, a unit-bound number.
Every preference an operator states in prose is invisible to it. This
adds the batch counterpart: `o2b brain extract-signals <session-ref>`
(CLI and `brain_extract_signals` MCP), diarization-shaped and two-phase.
Phase one reads an already-imported session's USER turns - the import
path's own rule - and returns a report carrying exactly one needs-llm-step
envelope built through the wave's spine builder. Phase two validates the
answer structurally against a new `extracted_signals` response shape and
semantically against a per-session cap and a per-item confidence floor,
both registered on the semantic-check registry because neither is
expressible in a descriptor, then routes the accepted items through the
existing helper chain in the deterministic extractor's order: capture
boundary, dedup, durability denylist, `Brain/pending/` staging when write
approval is on, `writeSignal`.

Refusals are named and total. An over-cap payload names the cap and the
count; an under-floor item names the floor and the value; a session with
no imported turns, or none authored by the user, or one the capture
boundary ignores, is refused rather than reported as an empty mine.
Nothing partial ever lands.

`source_type` gains `auto_extract`, a member of its own rather than a
reuse of `extracted`: both mine a session, but one is a regex over
structure and the other is a model reading prose, and their trust
profiles differ forever. The vocabulary stays closed and fail-closed, so
a reader from an older build refuses a file carrying the new member
rather than misreading it. `import-session` is untouched and now pinned:
every signal it writes still stamps `session`, and a re-import over the
same log leaves the inbox bytes unchanged.

Also fixed in passing, in the files this commit touches:

- `fact-extract.ts` documented "the seven families" over a table that has
  held three since the language-agnostic rewrite.
- Both `source_type` refusals in `signal.ts` hand-wrote "'live',
  'inline', or 'session'", a list already stale on `extracted`. They now
  render the vocabulary, so a new member cannot leave a refusal naming a
  set the guard no longer enforces.
- `session-recall.ts` gained `listSessionRawTurns`: `describe` counted
  turns and `expand` paged them from one node outward, and nothing handed
  a caller one session's turns as a list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017dYpaSgWTK5L6o6AzcLcBd
`skill-proposals.ts` mines continuity telemetry - what the agent did,
repeatedly, in a shape a detector recognises. It has never read a vault
page, so the material an operator already wrote down, verified and reused
was the one obvious skill source the queue could not see. And in a tree
whose whole subject is skills, nothing had ever written a SKILL.md.

This adds `skill-page-drafts.ts` and two operations on the existing
surface (`o2b brain skill-proposals page-candidates | page-draft`, and
the matching `brain_skill_proposals` operations) rather than a parallel
pipeline. Candidacy is gated three ways before a model is asked for
anything: the page-meta trio as the cheap filter (core tier, non-stale
lifecycle, high confidence - the only three fields that apply to
arbitrary user pages), observed reuse as the evidence floor (the one
signal saying the material was actually pulled back out), and installed
skill coverage as the negative signal. Every page the gate turns down is
reported with the reason and the measured value behind it; a report that
listed only winners could not be told apart from a vault nobody tagged.

Drafting is envelope-mediated, one spine envelope per admitted page,
carrying that page's content. The returned draft is validated
structurally by a new `skill_page_draft` response shape and semantically
by a check that its `name` is a legal skill directory name - the
descriptor language has no pattern key and deliberately keeps none.

Acceptance is where the one out-of-vault write happens. `skills_dir` may
point anywhere, so everything automatic stays in
`Brain/skill-proposals/pending/`, and only an explicit accept
materializes `SKILL.md` under the configured root - through the same
write-ahead journal the procedure branch already used. The journal now
records the ABSOLUTE path the materialize step is about to write, because
the two branches write into different trees and a rollback that
re-derived a procedure path from the slug would leave the real artifact
behind while reporting it removed. The rollback also removes the skill
directory it created, via `rmdirSync`, which refuses a non-empty one.

Sticky rejection, dedup and one-name-one-proposal are the declared path's,
unchanged: the planner is now parameterised by pattern kind rather than
copied. The write-site and destructive-site censuses are updated in this
commit with the reason `rmdirSync` is there.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017dYpaSgWTK5L6o6AzcLcBd
… and truth records

`o2b brain panel` deliberates: a durable write session, personas walked
over several turns, a note committed under `Brain/decisions/panels/`.
That is the right shape when a decision is worth several rounds and the
wrong one when somebody wants a design note now, and there was no other
shape on offer. This adds the one-shot sibling - `o2b brain design-note
<topic>` and `brain_design_note` - built like `diarization.ts`: read-only
grounding, no session, no model, exactly one needs-llm-step envelope.

What "grounded" means here is the point. The pass reads three stores that
already know what this project has argued about, decided and asserted -
tension records, decision records, and the truth projection with its
conflicts - and matches each to the topic with the vault's own
deterministic token overlap, the `tokenise`/`jaccard` pair
`findSimilarDecisions` already ranks with, so a topic reaches the same
records from every surface and no model decides relevance. The decision
store's matcher is called rather than reimplemented, and its similarity
floor is reused for all three stores so "related" means one thing across
the report.

An empty store is NAMED, not implied. A vault with no tensions yet and a
vault whose tensions all missed the topic are different facts leading to
different next moves, and rendering both as an empty list would be
exactly the misleading silence this project forbids - so `emptyStores`
carries the stores holding nothing at all, and the prompt says which of
the two happened per section. An empty vault produces a grounded report,
never a refusal.

The payload contract is one recommendation, and it is the case the
semantic-check registry exists for: "exactly one alternative sets
recommended: true" reads the whole array at once, which the deliberately
shallow descriptor language cannot state. Zero and two-plus are both
refused and the refusal carries the count found, because those are
different mistakes with different fixes. The validated note commits as
`Brain/decisions/design-<date>-<topic>.md`, the panel outputs' filename
shape one directory up, through an exclusive create: a second note for
the same topic on the same day is refused rather than overwriting the
first, since two design notes that disagree are worth keeping.

The write-site census's shared-helper count moves by one, with the reason:
this module commits through `writeFrontmatterAtomic` exclusively, so it
lands in the class the census wants modules to land in and needs no
exclusion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017dYpaSgWTK5L6o6AzcLcBd
…richment wave

Thirteen findings from an independent review of this branch, plus one
failing gate the review did not name.

- owner-scope-refusal: AGENT_ARGUMENT_TOOL_COUNT is 24, not 23 - the wave
  added `agent` to brain_expire, brain_extract_signals and
  brain_design_note. The prose beside it no longer states a number of its
  own, so the count lives in the constant alone.
- brain_design_note and brain_extract_signals returned raw absolute host
  paths; both now route through `vaultRelativeSafe`, as every other Brain
  tool does. An MCP response lands in model context.
- readSkillDraft re-applies the skill-name charset at MATERIALIZE time.
  That write is the one in the accept sequence that passes no vault
  confinement (the skills root may sit outside the vault), and a
  hand-edited `skill_name` reached `join()` unchecked - a proposal
  naming `../escaped` wrote a SKILL.md into the skills root's parent.
  SKILL_NAME_RE moves to skill-proposals.ts so both ends share one
  pattern.
- commitSkillPageDraft confines the caller-supplied page path with
  `ensureInsideVault` before probing it, and refuses an outside path by
  name instead of accepting it as provenance.
- The `--delete-linked` cascade now requires DECLARED provenance as well
  as the solely-derived rule. That rule was written for an imported
  source, where "cites no second source" is decisive; for a note it is
  vacuously true of any Brain page that writes `[[Note]]` in prose and
  declares no `source:` array, so such a page could enter the deletion
  set. It is reported instead. `deleteBySource` is untouched, and its
  payload keeps the shape its golden test pins: the new
  `structuredProvenance` field rides on `NoteDerivationEntry`, which only
  `traceNoteDerivations` returns. CLI help and the MCP descriptions say
  the enforced rule.
- The dream retriage validates the manifest's `considered` and `admitted`
  counts the way it already validates the threshold and the exclusion
  list. `Number(<junk>)` is NaN, which compares unequal to everything and
  reported a phantom "fold set changed size" on every retriage.
- `o2b brain dream` names the salience gate on the human stream when a
  threshold is configured - admitted of considered, and the threshold.
  Exclusions were visible only under `--json`, which is unchanged.
- setExpiration compares the stored value through the same normalizer the
  incoming one goes through, so `changed: false` means what its docstring
  says. An equivalent spelling used to rewrite the file and append an
  audit event describing no change.
- buildNeedsLlmStep drops a runtime `status` on its fields instead of
  letting it displace the stamped literal. It is dropped rather than
  re-stamped last because the envelope's key order is observable.
- commitExtractedSignals reports a mid-loop write failure as the partial
  write it is: `ExtractSignalsWriteError` names the signals already on
  disk and the item that failed, following ArchWriteError's precedent.
  Previously the call threw a bare cause with no accounting.
- brain_design_note's entry in the agent-scope matrix moves back into
  alphabetical order; the parity docblock's merge artifact is rewritten
  so the three added tools are each introduced grammatically.
- commitDesignNote takes the vault-identity write guard. It was the one
  write-capable module in `src/core/brain/` without it, and
  `tests/core/brain/vault-guard-census.test.ts` was failing on the branch
  because of it.

The reported greedy-split defect in capture-note's `splitGuidance` did
not reproduce: the leading `[\s\S]*` is greedy, so the separator already
matches at its LAST occurrence. A regression test now pins that, and the
comment says why in terms a reader cannot invert.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017dYpaSgWTK5L6o6AzcLcBd
CHANGELOG entry for the salience-lifecycle-enrichment wave, the README
what-is-new rewrite, and the version bump propagated to all mirrored
manifests via sync-version.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017dYpaSgWTK5L6o6AzcLcBd
@coderabbitai

coderabbitai Bot commented Aug 22, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 112 files, which is 12 over the limit of 100.

To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 81204408-7806-450c-bd60-d787b9d3bfce

📥 Commits

Reviewing files that changed from the base of the PR and between 86544b9 and e383151.

📒 Files selected for processing (112)
  • .claude-plugin/plugin.json
  • .codex-plugin/plugin.json
  • CHANGELOG.md
  • README.md
  • docs/architecture.md
  • docs/brainstorm/salience-lifecycle-enrichment/cli-output/claude.md
  • docs/brainstorm/salience-lifecycle-enrichment/cli-output/prompt.md
  • docs/brainstorm/salience-lifecycle-enrichment/design.md
  • docs/brainstorm/salience-lifecycle-enrichment/plan.md
  • docs/brainstorm/salience-lifecycle-enrichment/variants.md
  • docs/cli-reference.md
  • docs/how-it-works.md
  • docs/mcp.md
  • openclaw.plugin.json
  • package.json
  • plugin.yaml
  • plugins/codex/.codex-plugin/plugin.json
  • plugins/hermes/_schemas.py
  • plugins/hermes/plugin.yaml
  • pyproject.toml
  • src/cli/brain.ts
  • src/cli/brain/help-text.ts
  • src/cli/brain/verbs/architect.ts
  • src/cli/brain/verbs/capture.ts
  • src/cli/brain/verbs/design-note.ts
  • src/cli/brain/verbs/dream.ts
  • src/cli/brain/verbs/expire.ts
  • src/cli/brain/verbs/extract-signals.ts
  • src/cli/brain/verbs/feedback.ts
  • src/cli/brain/verbs/index.ts
  • src/cli/brain/verbs/note-lifecycle.ts
  • src/cli/brain/verbs/skill-proposals.ts
  • src/cli/command-manifest.ts
  • src/core/brain/architect/decisions.ts
  • src/core/brain/architect/generate.ts
  • src/core/brain/architect/scan.ts
  • src/core/brain/capture/capture-note.ts
  • src/core/brain/capture/telegram-capture.ts
  • src/core/brain/config-template.ts
  • src/core/brain/design-note.ts
  • src/core/brain/destructive-sites.ts
  • src/core/brain/diarization.ts
  • src/core/brain/dream-stage.ts
  • src/core/brain/dream-summary.ts
  • src/core/brain/dream-types.ts
  • src/core/brain/dream.ts
  • src/core/brain/expiration-set.ts
  • src/core/brain/extract-signals.ts
  • src/core/brain/fact-extract.ts
  • src/core/brain/llm-step.ts
  • src/core/brain/notes/lifecycle.ts
  • src/core/brain/policy/blocks/lifecycle.ts
  • src/core/brain/preference.ts
  • src/core/brain/response-checks.ts
  • src/core/brain/response-shape.ts
  • src/core/brain/rollup-ladder.ts
  • src/core/brain/salience-gate.ts
  • src/core/brain/session-recall.ts
  • src/core/brain/signal.ts
  • src/core/brain/skill-accept-journal.ts
  • src/core/brain/skill-page-drafts.ts
  • src/core/brain/skill-proposals.ts
  • src/core/brain/source-cleanup.ts
  • src/core/brain/types.ts
  • src/core/brain/write-session/types.ts
  • src/mcp/brain-tools.ts
  • src/mcp/brain/design-note-tools.ts
  • src/mcp/brain/extract-tools.ts
  • src/mcp/brain/feedback-tools.ts
  • src/mcp/brain/lifecycle-file-tools.ts
  • src/mcp/brain/procedure-tools.ts
  • tests/cli/brain-capture.test.ts
  • tests/cli/brain-design-note.test.ts
  • tests/cli/brain-dream-operator-surface.test.ts
  • tests/cli/brain-dream-retriage.test.ts
  • tests/cli/brain-expiration.test.ts
  • tests/cli/brain-extract-signals.test.ts
  • tests/cli/brain-note-lifecycle-help.test.ts
  • tests/cli/brain-note-lifecycle.test.ts
  • tests/cli/brain-skill-page-drafts.test.ts
  • tests/cli/skill-proposals-recover.test.ts
  • tests/core/architecture/expiration-chokepoint.test.ts
  • tests/core/architecture/write-site-census.test.ts
  • tests/core/brain.sessions.import.test.ts
  • tests/core/brain.types.test.ts
  • tests/core/brain/architect-codegraph-region.test.ts
  • tests/core/brain/architect-concurrent-runs.test.ts
  • tests/core/brain/architect-decisions-note.test.ts
  • tests/core/brain/architect-module-map.test.ts
  • tests/core/brain/architect-progress.test.ts
  • tests/core/brain/capture/capture-note.test.ts
  • tests/core/brain/design-note.test.ts
  • tests/core/brain/dream-salience-gate.test.ts
  • tests/core/brain/expiration-set.test.ts
  • tests/core/brain/extract-signals.test.ts
  • tests/core/brain/llm-step.test.ts
  • tests/core/brain/notes/note-delete-linked.test.ts
  • tests/core/brain/response-checks.test.ts
  • tests/core/brain/response-shape.test.ts
  • tests/core/brain/salience-gate.test.ts
  • tests/core/brain/skill-accept-dead-ends.test.ts
  • tests/core/brain/skill-contract.test.ts
  • tests/core/brain/skill-page-drafts.test.ts
  • tests/core/install/tool-ceiling.test.ts
  • tests/mcp/agent-scope-matrix.test.ts
  • tests/mcp/brain-expire-tool.test.ts
  • tests/mcp/brain-tools-parity.test.ts
  • tests/mcp/extract-signals-tool.test.ts
  • tests/mcp/mcp.test.ts
  • tests/mcp/note-lifecycle.test.ts
  • tests/mcp/owner-scope-refusal.test.ts
  • tests/mcp/removed-tools.test.ts

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@solaitken
solaitken enabled auto-merge (squash) August 22, 2026 14:05
@solaitken

Copy link
Copy Markdown
Collaborator Author

CodeRabbit skipped this pull request (111 files exceeds the 100-file limit, and usage credits are exhausted), so no automated review pass will arrive. An equivalent independent review was run inside the branch before it was opened: a reviewer with no build context examined all eleven implementation commits against main, confirmed the security-sensitive paths (SKILL.md materializer, model-authored slugs, delete cascade, Mermaid labels) are correctly gated, and returned one blocking and eleven non-blocking findings plus one false positive. All findings are applied in commit 28ab585 and the false positive is pinned by a regression test. Full-suite evidence after the fixes: 11453 pass, 0 fail.

…hanges

brain_feedback gained an `expires` parameter (creation-time expiration,
unit 3c) that never made it into the vendored copy in
plugins/hermes/_schemas.py, so the anti-drift test
(test_static_schemas_match_live_tools_list) failed CI's "Python plugin
tests" job. Fetched a fresh tools/list from the branch's own `o2b mcp`
against a throwaway vault and confirmed the other nine curated tools
(brain_apply_evidence, brain_note, brain_pinned_context, brain_query,
brain_search, brain_recall_gate, brain_context, brain_context_pack,
brain_pre_compact_extract) already matched byte-for-byte; only
brain_feedback needed re-vendoring.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017dYpaSgWTK5L6o6AzcLcBd
@solaitken
solaitken merged commit 6d3e8b2 into main Aug 22, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants