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
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "open-second-brain",
"version": "1.37.0",
"version": "1.38.0",
"description": "Plugin-first second brain package for AI agents and humans.",
"author": {
"name": "Open Second Brain contributors"
Expand Down
2 changes: 1 addition & 1 deletion .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "open-second-brain",
"version": "1.37.0",
"version": "1.38.0",
"description": "Plugin-first second brain package for Codex, Hermes, Claude Code, OpenClaw, and other agent runtimes.",
"skills": "./skills",
"hooks": "./hooks/hooks.json",
Expand Down
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.38.0] - 2026-07-21

A semantic-health baselining release ([#147](https://github.com/itechmeat/open-second-brain/issues/147)): an optional acknowledge-before watermark lets an operator declare "I have seen and accepted the state up to this date", so a one-time bulk seed of preferences no longer pins the health verdict at `watch` forever through the `concept-gap` and `batch-concept-inflation` advisories. Detection and storage are unchanged; the watermark only filters which findings are surfaced, the verdict is computed from the surfaced set, and suppression is always reported explicitly. With no watermark set, every output is byte-identical to 1.37.0.

### Added

- Semantic-health baseline watermark: an optional `health.silence_before` date (date-only or full ISO-8601 timestamp) in `_brain.yaml` suppresses advisory findings whose underlying entries are entirely older than the watermark - a `batch-concept-inflation` burst whose window ended before it, a `concept-gap` term whose corpus mentions all predate it. Conservative by construction: an entry with no parseable timestamp counts as newer than any watermark, and a concept-gap term with even one post-baseline mention surfaces with its full frequency. An invalid value is an explicit config error, never silently ignored.
- `o2b brain health-baseline set <date>|now`, `get`, and `clear` record, show, and remove the watermark without hand-editing `_brain.yaml`; the upsert preserves unrelated config content byte-for-byte under the repository's file-lock plus atomic-rename convention. Usage errors exit 2 and `--json` operational failures emit `{ok:false}` envelopes.
- Explicit suppression reporting: whenever the watermark hides at least one finding, `o2b brain health` prints `suppressed: N finding(s) older than baseline <date>` and the report carries an additive `suppressed` object with per-detector counts and the baseline date, mirrored on MCP `brain_health` as `suppressed: { concept_gaps, batch_inflation, baseline }`; the field is absent whenever nothing is hidden.

### Changed

- The four detector-local ISO timestamp parsers (stale-claim, batch-inflation, thesis, and the new baseline code) consolidated into one shared `iso-time` helper.

## [1.37.0] - 2026-07-19

A retrieval quality and context delivery wave: nine units that make search answer relationship-shaped and summary-shaped questions deterministically, explain and plan retrieval without touching live policy, and put the right context in front of the agent while stale operational state stays out. Relationship queries traverse typed edges as a fourth RRF arm, dedup and search respect composite scopes, operational state moves to an overwrite-only lane with a retrieval-time staleness barrier, prompts gain a cadence-controlled navigation tier, an opt-in strict hook redirects the first raw vault read to the search index, and codegraph partnering covers every project in the workspace. Two shared seams carry the wave: a composite scope-key module and an index-admission predicate. The kernel still calls no LLM, and every new surface is byte-identical when its flag or param is omitted.
Expand Down Expand Up @@ -6623,6 +6637,7 @@ plugin config (vault field)`, and exits with a clear
- Sandbox vault and plugin manifest fixtures for tests.
- GitHub release workflow for tag-based and manually dispatched releases.

[1.38.0]: https://github.com/itechmeat/open-second-brain/compare/v1.37.0...v1.38.0
[1.37.0]: https://github.com/itechmeat/open-second-brain/compare/v1.36.0...v1.37.0
[1.36.0]: https://github.com/itechmeat/open-second-brain/compare/v1.35.0...v1.36.0
[1.35.0]: https://github.com/itechmeat/open-second-brain/compare/v1.34.0...v1.35.0
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ Open Second Brain plugs into [Hermes Agent](https://github.com/NousResearch/herm

## What is new

Open Second Brain 1.37.0 is a retrieval quality and context delivery release. Search now answers relationship questions natively: a typed-edge relational arm (opt-in, vocabulary-driven, bounded depth 2) joins hybrid RRF, query plans route summary-shaped questions to the summary surface deterministically, and dedup plus search respect composite owner/session/project scopes so identical text in two contexts is never wrongly collapsed. Retrieval also explains itself: `o2b search rerank-fit` tells you when a reranker is out-of-domain or score-inverted on your vault, and the read-only `brain_retrieval_plan` MCP tool lays out per question the source strategy, token allocation, reliability with p95 latency, and a marginal-value stop, without touching live ranking policy. On the delivery side, a cadence-controlled navigation tier can inject a fenced vault map on top of the always-on kernel, an opt-in strict hook redirects the first raw vault read of a session to the search index and then steps back, operational state moves into an overwrite-only `o2b brain state` lane that is excluded from semantic layers and barred from resurfacing stale values through recall, and codegraph partnering covers every project in the workspace instead of the first one found. The kernel still calls no LLM.
Open Second Brain 1.38.0 adds baselining for semantic-health advisories. When a vault is bulk-seeded with many preferences at once, the `concept-gap` and `batch-concept-inflation` advisories fire permanently and pin the health verdict at `watch`, with no way to acknowledge the burst short of deleting real memory. Now an optional `health.silence_before` watermark in `_brain.yaml` (managed by `o2b brain health-baseline set <date>|now`, `get`, `clear`) declares the state accepted up to that date: findings whose underlying entries are entirely older than the watermark are filtered from the surfaced report, the verdict reflects only what is shown, and the report always says exactly how many findings the baseline hid. Detection and storage are untouched, an entry without a parseable timestamp is never suppressed, and with no watermark set every output is byte-identical to 1.37.0.

Previous release, 1.36.0, was a knowledge intake and consolidation wave: an inbound Telegram capture bot with a report-then-apply inbox drain, keyed Brave and Tavily research providers, a count-triggered fact rollup ladder, subject diarization, an evidence-identity gate on synthesis findings, a capped idempotent link-graph repair lane, and a deterministic skill-proposal verifier. Details live in the [CHANGELOG](CHANGELOG.md).
Previous release, 1.37.0, was a retrieval quality and context delivery wave: a typed-edge relational retrieval arm, composite owner/session/project scope keys, an overwrite-only `o2b brain state` lane with a retrieval-time staleness barrier, a deterministic summary-search router, `o2b search rerank-fit`, the read-only `brain_retrieval_plan` MCP tool, a cadence-controlled navigation tier, an opt-in strict orientation hook, and workspace-wide codegraph partnering. Details live in the [CHANGELOG](CHANGELOG.md).

## Why

Expand Down
20 changes: 20 additions & 0 deletions docs/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,26 @@ status across every discovered code project, threading `project_path`
per query when supported (feature-detected) and degrading with an
explicit note when not.

### Semantic-health baselining (since v1.38.0)

```text
o2b brain health-baseline set <date>|now | get | clear - record, show, or remove the health.silence_before watermark in _brain.yaml without hand-editing it; set accepts a date-only or full ISO-8601 value (or the literal now) and rejects anything else with exit 2; the upsert preserves unrelated config content byte-for-byte under a file lock with an atomic rename
```

The watermark acknowledges vault history up to a date: `o2b brain health`
suppresses advisory findings whose underlying entries are entirely older
than `health.silence_before` - a `batch-concept-inflation` burst whose
window ended before it, a `concept-gap` term whose corpus mentions all
predate it - and computes the verdict from the surfaced findings only.
An entry without a parseable timestamp counts as newer than any
watermark, so it is never suppressed, and a concept-gap term with even
one post-baseline mention surfaces with its full frequency. Whenever the
watermark hides at least one finding, the report prints
`suppressed: N finding(s) older than baseline <date>` and carries an
additive `suppressed` object with per-detector counts; nothing is ever
hidden silently. An invalid config value is an explicit error at load,
and with no watermark set every output is byte-identical to v1.37.0.

## Stability and trust (since v1.0.0)

```text
Expand Down
7 changes: 7 additions & 0 deletions docs/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -564,3 +564,10 @@ Both servers reuse the same backing CLI (`o2b mcp --scope writer` vs the default
results byte-identical) and its outcome carries an advisory `surface`
field when the deterministic router selects the summary surface;
non-summary queries are unchanged.
- Since v1.38.0 `brain_health` additively carries a
`suppressed: { concept_gaps, batch_inflation, baseline }` object when
the optional `health.silence_before` watermark hides at least one
advisory finding whose underlying entries are entirely older than the
baseline date; the verdict is computed from the surfaced findings, the
key is absent whenever nothing is hidden, and with no watermark set
the output is byte-identical to v1.37.0.
2 changes: 1 addition & 1 deletion openclaw.plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"id": "open-second-brain",
"name": "Open Second Brain",
"description": "Second brain for AI agents using Obsidian-compatible Markdown vaults.",
"version": "1.37.0",
"version": "1.38.0",
"activation": { "onStartup": true },
"skills": ["./skills"],
"contracts": {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "open-second-brain",
"version": "1.37.0",
"version": "1.38.0",
"private": false,
"description": "Second brain for AI agents using Obsidian-compatible Markdown vaults. Works with Hermes, Claude Code, Codex, and OpenClaw.",
"keywords": [
Expand Down
2 changes: 1 addition & 1 deletion plugin.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: open-second-brain
version: "1.37.0"
version: "1.38.0"
description: "Open Second Brain - native Hermes memory provider backed by an Obsidian-compatible Markdown vault."
author: "Open Second Brain contributors"
memory_provider: true
Expand Down
2 changes: 1 addition & 1 deletion plugins/codex/.codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "open-second-brain",
"version": "1.37.0",
"version": "1.38.0",
"description": "Plugin-first second brain package for Codex, Hermes, Claude Code, OpenClaw, and other agent runtimes.",
"author": {
"name": "Open Second Brain contributors",
Expand Down
2 changes: 1 addition & 1 deletion plugins/hermes/plugin.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: open-second-brain
version: "1.37.0"
version: "1.38.0"
description: "Open Second Brain - native Hermes memory provider backed by an Obsidian-compatible Markdown vault."
author: "Open Second Brain contributors"
memory_provider: true
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ build-backend = "setuptools.build_meta"
# CLI entry points (those moved to `package.json` `bin`).
[project]
name = "open-second-brain"
version = "1.37.0"
version = "1.38.0"
description = "Hermes Python shim for Open Second Brain. Most of the project (CLI, MCP server, OpenClaw plugin) is TypeScript on Bun; see package.json."
readme = "README.md"
requires-python = ">=3.11"
Expand Down
3 changes: 3 additions & 0 deletions src/cli/brain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
cmdBrainAnticipate,
cmdBrainWatchdog,
cmdBrainHealth,
cmdBrainHealthBaseline,
cmdBrainStatus,
cmdBrainHistory,
cmdBrainActivation,
Expand Down Expand Up @@ -231,6 +232,8 @@ export async function handleBrainSubcommand(argv: ReadonlyArray<string>): Promis
return await cmdBrainWatchdog(rest);
case "health":
return await cmdBrainHealth(rest);
case "health-baseline":
return await cmdBrainHealthBaseline(rest);
case "status":
return await cmdBrainStatus(rest);
case "history":
Expand Down
8 changes: 8 additions & 0 deletions src/cli/brain/help-text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Brain verbs (observing memory):
anticipate Inspect or refresh the anticipatory context cache (--session)
watchdog Probe Brain health and plan safe recovery (--remediate, --restore, --force-restore)
health Semantic-health report: contradictions, concept gaps, stale claims
health-baseline Acknowledge-before watermark for advisories (set <date>|now / get / clear)
history Render a preference's edit-history timeline
activation Activation event store: status and sweep
truth Claim ledger: ingest, slots, conflicts, aggregate, collisions, sweep
Expand Down Expand Up @@ -331,6 +332,13 @@ export const VERB_HELP: Record<string, string> = {
"Semantic-health report: contradictory confirmed preferences, recurring\n" +
"concepts with no dedicated preference, and confirmed preferences on stale\n" +
"evidence, plus a clean/watch/investigate verdict. Read-only.\n",
"health-baseline":
"usage: o2b brain health-baseline <set <date>|set now|get|clear> [--vault <path>] [--json]\n" +
"Acknowledge-before watermark (health.silence_before in _brain.yaml). set\n" +
"records that state up to a date is seen and accepted, so concept-gap and\n" +
"batch-inflation advisories entirely older than it stop pinning the verdict\n" +
"at watch; get prints the current value; clear removes it. Detection and\n" +
"stored memory are untouched - only what the report surfaces changes.\n",
status:
"usage: o2b brain status [--vault <path>] [--json]\n" +
"Unified operator status snapshot. Composes doctor, semantic health,\n" +
Expand Down
106 changes: 106 additions & 0 deletions src/cli/brain/verbs/health-baseline.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import {
HealthBaselineError,
readHealthBaseline,
writeHealthBaseline,
} from "../../../core/brain/health-baseline.ts";
import { isValidIsoInstant } from "../../../core/brain/health/iso-time.ts";
import {
brainVerbContext,
fail,
normalizeFlagString,
ok,
okJson,
parse,
usageError,
} from "../helpers.ts";

/**
* `o2b brain health-baseline` - operate the acknowledge-before watermark
* (`health.silence_before` in `_brain.yaml`).
*
* Subcommands:
* set <date>|now record the watermark (date-only or full ISO instant)
* get print the current watermark
* clear remove the watermark
*
* The date is a positional argument (not a `--value` flag as in
* `brain state`) because a single required value reads cleaner as
* `set 2026-01-01` than `set --date 2026-01-01`. Setting only changes
* what the semantic-health report surfaces; detection and stored memory
* are untouched.
*/
export async function cmdBrainHealthBaseline(argv: string[]): Promise<number> {
const sub = argv[0];
const { flags, positional } = parse(argv.slice(1), {
vault: { type: "string" },
json: { type: "boolean" },
});
const json = flags["json"] === true;

if (sub === "get") {
const { vault } = brainVerbContext(flags);
try {
const baseline = readHealthBaseline(vault);
if (json) okJson(baseline === null ? { present: false } : { present: true, baseline });
else ok(baseline === null ? "no health baseline set" : baseline);
return 0;
} catch (exc) {
return handleBaselineError("get", exc, json);
}
}

if (sub === "set") {
const raw = normalizeFlagString(positional[0]);
if (raw === null) return usageError("brain health-baseline set requires a <date> or 'now'");
const value = raw === "now" ? new Date().toISOString() : raw;
if (!isValidIsoInstant(value)) {
return usageError(
`brain health-baseline set: not an ISO-8601 date (YYYY-MM-DD) or timestamp: ${raw}`,
);
}
const { vault } = brainVerbContext(flags);
try {
await writeHealthBaseline(vault, value);
if (json) okJson({ baseline: value });
else ok(`health baseline set to ${value}`);
return 0;
} catch (exc) {
return handleBaselineError("set", exc, json);
}
}

if (sub === "clear") {
const { vault } = brainVerbContext(flags);
try {
const existed = readHealthBaseline(vault) !== null;
await writeHealthBaseline(vault, null);
if (json) okJson({ cleared: existed });
else ok(existed ? "health baseline cleared" : "no health baseline set");
return 0;
} catch (exc) {
return handleBaselineError("clear", exc, json);
}
}

return usageError(
`brain health-baseline: unknown subcommand '${sub ?? ""}' (expected set|get|clear)`,
);
}

/**
* Surface a {@link HealthBaselineError} (or a config-load failure) as an
* operational failure - the `{ ok: false, message }` envelope under
* `--json`, otherwise a plain `fail()` line. Any other error is a genuine
* bug and rethrown.
*/
function handleBaselineError(op: string, exc: unknown, json: boolean): number {
if (exc instanceof HealthBaselineError || exc instanceof Error) {
const message = `health-baseline ${op} failed: ${exc.message}`;
if (json) {
okJson({ ok: false, message });
return 1;
}
return fail(message);
}
throw exc;
}
6 changes: 6 additions & 0 deletions src/cli/brain/verbs/health.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ export async function cmdBrainHealth(argv: string[]): Promise<number> {
}

process.stdout.write(`verdict: ${sh.verdict}\n`);
if (sh.suppressed) {
const hidden = sh.suppressed.conceptGaps + sh.suppressed.batchInflation;
process.stdout.write(
`suppressed: ${hidden} finding(s) older than baseline ${sh.suppressed.baseline}\n`,
);
}
for (const c of sh.contradictions) {
process.stdout.write(
`[contradiction] ${c.aId} (${c.aSign}) vs ${c.bId} (${c.bSign})` +
Expand Down
1 change: 1 addition & 0 deletions src/cli/brain/verbs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export { cmdBrainRollback } from "./rollback.ts";
export { cmdBrainDoctor } from "./doctor.ts";
export { cmdBrainWatchdog } from "./watchdog.ts";
export { cmdBrainHealth } from "./health.ts";
export { cmdBrainHealthBaseline } from "./health-baseline.ts";
export { cmdBrainStatus } from "./status.ts";
export { cmdBrainHistory } from "./history.ts";
export { cmdBrainActivation } from "./activation.ts";
Expand Down
18 changes: 16 additions & 2 deletions src/core/brain/doctor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,7 @@ interface SignalSignRecord {
readonly id: string;
readonly sign: import("./types.ts").BrainSignalSign;
readonly principle: string;
readonly created_at: string;
}

/**
Expand All @@ -531,7 +532,12 @@ function readAllSignalRecords(vault: string): ReadonlyArray<SignalSignRecord> {
if (!name.endsWith(".md") || !name.startsWith("sig-")) continue;
try {
const sig = parseSignal(join(dir, name));
out.push({ id: sig.id, sign: sig.signal, principle: sig.principle });
out.push({
id: sig.id,
sign: sig.signal,
principle: sig.principle,
created_at: sig.created_at,
});
} catch {
// schema error - reported by checkSignals
}
Expand Down Expand Up @@ -560,14 +566,22 @@ function checkSemanticHealth(
...signals.map((s) => s.principle),
...prefRecords.map((r) => r.pref.principle),
];
// Aligned index-for-index with corpusPrinciples: the authored date of
// each entry, so the baseline watermark can tell a concept gap that is
// entirely old from one still mentioned in fresh memory.
const corpusPrincipleDates: (string | null)[] = [
...signals.map((s) => s.created_at),
...prefRecords.map((r) => r.pref.created_at),
];
const coveredTopics = prefRecords.map((r) => r.pref.topic);

const report = reconcileSemanticHealth(
{ preferences, signSignById, corpusPrinciples, coveredTopics },
{ preferences, signSignById, corpusPrinciples, corpusPrincipleDates, coveredTopics },
{
contradictionJaccard: health.contradiction_jaccard,
conceptGapMinFrequency: health.concept_gap_min_frequency,
staleClaimMaxAgeDays: health.stale_claim_max_age_days,
...(health.silence_before !== null ? { silenceBefore: health.silence_before } : {}),
now,
},
);
Expand Down
Loading
Loading