# v0.10.8 design - retire `event_log_append` + JSONL log sidecar

Closes `Projects/OpenSecondBrain/Features/_summary.md` items §32 (full
removal of `event_log_append` from the agent-facing surface; unification
of all three runtimes on `Brain/log/` as the single source of truth)
and §23 (structured JSONL copy `Brain/log/<date>.jsonl` written next to
the existing markdown file, with discipline-report and future machine
consumers switched to read JSONL instead of parsing markdown). Shipped
together as v0.10.8.

The companion implementation plan lives at
`2026-05-19-v0.10.8-impl.md`.

## 1. Why this release

Two issues prevent agents from logging reliably:

1. The v0.9 design declared `Daily/` deprecated as an agent-facing
   write path, but four leftover surfaces still direct agents back into
   it: three identity-reminder templates, the Stop guardrail message,
   the hook event-name detector, and `hooks/README.md`. The MCP tool
   `event_log_append` itself has been absent from `buildToolTable()`
   since v0.9, yet the templates instruct agents to call it. Less
   trained models fall back to bash `vault-log`; better trained models
   skip logging entirely.
2. Machine consumers of `Brain/log/<date>.md` parse markdown ad-hoc.
   The discipline report (`src/core/discipline/log-counts.ts`) is the
   first such consumer; future tooling will compound the cost. A
   structured copy of the log next to the markdown is the natural fix.

v0.10.8 closes both at once because they touch the same module
(`src/core/brain/log.ts`) and the same operator-visible contract
("where do brain events live"). Splitting would force two passes over
the same surface within days of each other.

## 2. Goals

- All three runtimes (Claude Code, Codex, Hermes, plus OpenClaw)
  observe one and only one agent-facing log surface: `Brain/log/`.
- The Stop guardrail clears on any of `brain_feedback`,
  `brain_apply_evidence`, `brain_note` - no third path.
- A new `brain_note` MCP tool gives agents a Brain-native way to record
  narrative milestones that do not fit `feedback` or `apply-evidence`.
- Every write to `Brain/log/<date>.md` is mirrored to
  `Brain/log/<date>.jsonl` in the same atomic operation.
- `discipline-report` (and any future machine consumer) reads JSONL
  when present and falls back to markdown when not.
- No data migration. Existing markdown days without a sidecar JSONL
  keep working through the reader's fallback path.
- MEMORY housekeeping removes language-specific anchors from
  `/root/.claude/projects/-root/memory/` per the operator's standing
  rule "no hard-coded user language".

## 3. Non-goals

- No CLI verb `o2b brain note` in this release. Triggered later by a
  concrete need for shell-side note recording.
- No auto-watcher for stray `Daily/` writes from `@agent-identity`.
  The drift-check is operator-run (`o2b discipline report`).
- No migration of historical `Daily/` entries into `Brain/log/`.
  `Daily/` stays as a human-CLI surface (cron-jobs, ad-hoc scripts).
- No bidirectional MEMORY <-> Brain sync. The existing one-way bridge
  (v0.10.7 §E) is enough; reverse is deferred per §E Deferred entry.
- No `--include-archived` flag for snapshots. Snapshot already covers
  the whole `Brain/` tree, JSONL is included automatically.

## 4. Subtrack breakdown

The subtracks map to `_summary.md` §32A through §32H plus §23. They
ship as one PR; the order below is the order in which a reviewer should
read the diff, not necessarily the order of commits.

### 4.1 §32A - lightweight verification gate

The literal wording of §32A in `_summary.md` asks for "≥1 brain event
per day per known_agent for one week after v0.10.7". v0.10.7 shipped
2026-05-18; v0.10.8 cannot wait seven days without violating the
"no dates in planning" rule and stalling the release. We replace the
seven-day calendar gate with a triggered gate:

- Before the operator ships v0.10.8, run `o2b discipline report` on
  the production vault for the window between v0.10.7's ship moment
  and now.
- Pass condition: for every agent name in `_brain.yaml:
  discipline_report.known_agents` that has any activity in the window
  (git activity, mtime walk, vault delta), there is at least one
  `brain_feedback`, `brain_apply_evidence`, or `brain_note` event
  attributed to that agent.
- Fail condition: an agent has activity but zero brain events. Stop;
  investigate; do not remove the old surface.
- No code asserts. This is a release-checklist item documented in
  `2026-05-19-v0.10.8-impl.md` and the v0.10.8 CHANGELOG migration
  block.
- Rationale for replacing the calendar gate: the original "one week"
  threshold is a proxy for "agents have proved they use the new
  channel". The proxy survives translation to "observed brain events
  per agent per window" without losing meaning, and it is not bound
  to a date the planning rules forbid.

### 4.2 §32B - `brain_note` MCP tool

#### 4.2.1 Contract

```
brain_note({
  text: string,         // required
  agent?: string        // optional override
})
```

Writes one event block to `Brain/log/<today>.md`:

```
## HH:MM:SSZ - note
- text: <redacted, newline-collapsed message>
- agent: <resolved agent>
```

#### 4.2.2 Resolution and hygiene rules

- `agent` follows the same resolver chain as `brain_feedback`:
  `normalizeAgentArgument(args.agent) ?? resolveAgentName(config)`.
- `text` is run through `redactText` (the secret-stripper from
  `src/core/event-log.ts`, lifted out to a shared module - see
  §4.7 "Shared sanitizer").
- Newlines (`\r?\n`) collapse to single spaces - one event = one line
  in the markdown bullet and one line in the JSONL row.
- Length cap: 4096 characters after redaction; the writer raises
  `Error("brain_note: text exceeds 4096 chars")` on overflow.
- Empty / whitespace-only `text` raises `Error("brain_note: text is
  required")`. Mirrors `event_log_append`'s old behaviour.

#### 4.2.3 Plumbing

- New event kind constant:
  `BRAIN_LOG_EVENT_KIND.note = "note"` in `src/core/brain/types.ts`.
- New typed event interface:
  ```ts
  export interface BrainNoteLogEvent extends BrainLogEventBase {
    readonly kind: typeof BRAIN_LOG_EVENT_KIND.note;
    readonly text: string;
    readonly agent: string;
  }
  ```
  added to the `BrainLogEvent` discriminated union.
- New tool handler `toolBrainNote` in `src/mcp/brain-tools.ts`
  (lives with the other brain tools, not in `tools.ts`).
- New definition in `BRAIN_TOOLS`:
  ```ts
  {
    name: "brain_note",
    description: "Append one narrative-milestone line to today's Brain log.",
    inputSchema: { ... },
    handler: toolBrainNote,
  }
  ```
- `WRITER_TOOL_NAMES` in `src/mcp/tools.ts` gains `"brain_note"` so
  the always-loaded writer scope advertises three tools instead of
  two.
- `buildInstructions` writer branch updated to name three tools, not
  two.

#### 4.2.4 Discipline-report integration

`src/core/discipline/log-counts.ts:AgentCounts` gains one field:

```ts
export interface AgentCounts {
  readonly feedback: number;
  readonly apply_evidence: number;
  readonly note: number;       // new
  readonly other: number;
  readonly total: number;
}
```

The renderer (`src/core/discipline/render.ts`) shows `note` next to
`feedback` and `apply_evidence`. Telegram and JSON output reflect the
new key.

### 4.3 §32C - drop `event_log_append` from the hook surface

Files touched:

- `hooks/lib/detect.ts`
  - `BRAIN_EVENT_NAME_SUFFIX` regex changes from
    `(event_log_append|brain_feedback|brain_apply_evidence)` to
    `(brain_feedback|brain_apply_evidence|brain_note)`.
  - `BRAIN_EVENT_BASH_NEEDLES` loses `"o2b append-event"` and
    `"vault-log "`. It gains nothing in this release. The
    `o2b brain feedback` / `o2b brain apply-evidence` entries stay.
  - Comments updated to drop the `event_log_append` paragraph.
- `hooks/lib/messages.ts`
  - `stopGuardrailReason` body no longer mentions `event_log_append`.
    The "pick whichever fits this turn" block lists three triggers:
    `brain_feedback`, `brain_apply_evidence`, `brain_note`.
  - `stopGuardrailCadenceLine` for `codex` replaces
    `"call \`event_log_append\` now"` with `"call \`brain_feedback\`
    / \`brain_apply_evidence\` / \`brain_note\` now"`.
- Existing tests in `tests/hooks/detect.test.ts`,
  `tests/hooks/stop-log-guardrail.test.ts` are updated to assert the
  new behaviour (no event_log_append clear, three new triggers do
  clear).

### 4.4 §32D - rewrite identity-reminder templates

All three templates rewritten in lock-step:

- `templates/identity-reminder.txt` (common, single source of truth).
- `templates/identity-reminder.hermes.txt` (preserves "Hermes turns
  are short; do not batch across turns").
- `templates/identity-reminder.openclaw.txt` (preserves "OpenClaw has
  no session boundary; log immediately").

New body (common, single line per template by convention; runtime
variants substitute the cadence note):

> [open-second-brain] Identity: @{agent}. After a durable artifact
> (code, config, instruction-file edit, content artifact, research
> finding) call `brain_feedback` if the turn surfaced a new user
> preference, `brain_apply_evidence` (`result: applied | violated |
> outdated`) if an active preference in `Brain/preferences/` scopes
> to the artifact, or `brain_note` for narrative milestones that fit
> neither. Skip pure discussion, exploration, read-only queries, and
> planning without an artifact.

The Hermes Python shim (`plugins/hermes/__init__.py`) caches the
template per process; a `hermes gateway restart` after install
refreshes it.

`tests/fixtures/identity-reminder/{hermes,openclaw}.txt` and the
common-template tests are updated to the new wording. The fixture
files are byte-pinned so future drift is caught by
`tests/core/identity-reminder.test.ts`.

### 4.5 §32E - clean up `hooks/README.md`

The v0.8-era table and prose are rewritten to describe v0.10.8 reality:

- Brain/log/ is the single agent-facing log; Daily/ is human-CLI.
- PostToolUse reminder lists three triggers
  (`brain_feedback` / `brain_apply_evidence` / `brain_note`).
- Stop guardrail clears on any of those three.
- One paragraph announces the JSONL sidecar - what it is, where it
  lives, why machine consumers should read it instead of parsing
  markdown.

No code change accompanies this section; documentation only.

### 4.6 §32F - `cmdAppendEvent` resolves agent through config

`src/cli/main.ts:313-341`. Current behaviour falls back to the literal
string `"agent"` when neither `--as` nor `VAULT_AGENT_NAME` is set;
this is the source of corrupted Daily entries with `@agent` instead of
`@claude-vps-agent`.

Replacement:

```ts
async function cmdAppendEvent(argv: string[]): Promise<number> {
  const { flags, positional } = parseFlags(argv, {
    vault: { type: "string" },
    as: { type: "string" },           // no default
    date: { type: "string" },
    time: { type: "string" },
    config: { type: "string" },
  });
  if (positional.length < 1) {
    process.stderr.write("error: append-event requires a message argument\n");
    return 2;
  }
  const message = positional[0]!;
  const config = (flags["config"] as string | undefined) ?? defaultConfigPath();
  const vault = requireVault(flags["vault"] as string | undefined, config);
  const tz = resolveTimezone(config);
  const explicit = flags["as"] as string | undefined;
  const agent = explicit ?? resolveAgentName(config);
  // ... appendEvent call unchanged
}
```

`resolveAgentName` already exists (it powers every other CLI verb that
needs an identity). The env-var fallback `VAULT_AGENT_NAME` is part of
its chain, so cron-jobs that rely on the env keep working.

New test: `tests/cli/append-event.test.ts`: "Without --as and without
env, config `agent_name: xyz` produces a Daily entry with `@xyz`."

### 4.7 §32G - drop the old handler from all runtimes

A full kill, not a hidden-CLI keep. The MCP tool is gone from every
agent-facing surface; the underlying `appendEvent` function stays for
the CLI verb and any future shell-side use.

Files touched:

- `src/openclaw/index.ts` - remove the
  `api.registerTool({ name: "event_log_append", ... })` block (~50
  lines).
- `src/mcp/tools.ts`
  - Remove `toolEventLogAppend` (the standalone function).
  - Remove the corresponding comment block that explains "intentionally
    not advertised".
  - Remove `appendEvent` and `validateEventTime` imports if no longer
    used (the file still uses `resolveAgentName` and friends for the
    other tools).
- `plugins/hermes/__init__.py` - docstring update: drop
  `event_log_append` from the per-tool list in the module-level
  comment. No functional change.
- `tests/openclaw/bundle.test.ts` - drop the assertion that the
  OpenClaw bundle registers `event_log_append`.
- `tests/mcp/mcp.test.ts` - already does not advertise
  `event_log_append`; only comment cleanup if any.

The shared `appendEvent` function in `src/core/event-log.ts` is
preserved (cron-jobs, `o2b append-event` CLI verb, future scripts).

### 4.8 §32H - drift-check

Operator-run, not automated:

1. Right after v0.10.8 ships: `o2b discipline report --window 1d`.
   Verify status is `ok` or `info`, not `alert`.
2. Sustained check (operator's eyes, no code): grep `Daily/<date>.md`
   for `@agent` literal or `@<known_agent>` entries written by an LLM
   runtime. None expected.
3. Trigger to revert §32C/D: any of the above produces an event from
   an agent name in `known_agents` that did not come from a human CLI
   (cron, shell). Document the case and re-open §32.

Section in the v0.10.8 CHANGELOG migration block describes the
trigger; no code lands for this subtrack.

### 4.9 §23 - JSONL sidecar

#### 4.9.1 File layout

For each event block in `Brain/log/<date>.md`, one corresponding line
in `Brain/log/<date>.jsonl`. Both files live in the same directory;
both are append-only; both are written by the same function in the
same atomic step.

#### 4.9.2 JSONL row schema

```jsonc
{
  "ts":      "<ISO-8601 UTC>",   // e.g. "2026-05-19T13:42:00Z"
  "kind":    "<BrainLogEventKind value>",
  "payload": { ... }              // every bullet from the markdown body
}
```

- `ts` matches the markdown `## HH:MM:SSZ` heading (reconstructed back
  to a full ISO timestamp using the day file's date).
- `kind` is one of `BRAIN_LOG_EVENT_KIND.*`.
- `payload` is a JSON object where each markdown bullet `- key: value`
  becomes a key/value pair. Array bullets (`- key:\n  - item\n  - item`)
  become a JSON array `["item", "item"]`. No flattening, no key
  renaming - the projection is one-to-one with the markdown body.

Multiple events on the same UTC second produce multiple JSONL lines
in the order of `appendLogEvent` calls (matches the markdown stability
guarantee).

#### 4.9.3 Writer changes (`src/core/brain/log.ts`)

The current `appendLogEvent` reads the markdown file, appends a block,
writes back atomically. The new version:

1. Acquires a lock on `Brain/log/` (via `proper-lockfile`'s `lockSync`
   wrapped in a small ELOCKED-only retry loop because the sync API
   forbids built-in retries; same scope as `src/core/event-log.ts`).
2. Reads `<date>.jsonl` if present.
3. Builds the new JSONL row via `renderJsonlLine(event)` and writes
   `<date>.jsonl` atomically (`atomicWriteFileSync`). JSONL is the
   machine source of truth, so it is written first.
4. Reads `<date>.md` if present.
5. Appends the new event block to the markdown and writes
   `<date>.md` atomically.
6. Releases the lock.

Failure model: if step 3 fails the call throws and nothing is written;
both files stay at their previous (consistent) state. If step 5 fails
after step 3 succeeded the JSONL is one event ahead of the markdown;
the reader (§4.9.4) prefers JSONL and returns the correct count, so
machine consumers (discipline-report, future tooling) keep working
without manual reconciliation. The human Obsidian view lags by one
event until the next successful append rewrites the markdown. The
caller sees the exception from `atomicWriteFileSync(mdPath, …)` and
can investigate. We deliberately do **not** claim "self-healing":
markdown is not automatically rebuilt from the JSONL on subsequent
appends (the writer only reads + appends to whatever markdown is on
disk). Re-syncing the markdown view, if needed, is an operator step
(`o2b brain digest` regeneration / hand re-render from JSONL).

Why not a single combined write: the two files have different formats
and different append semantics. Combining them into one transactional
file format buys nothing - the lock already serialises writers, and
JSONL-first ordering bounds the visible-to-machine-consumer asymmetry
to "never" (the consumer always sees the latest event or it has not
been written at all).

#### 4.9.4 Reader (`src/core/brain/log-jsonl.ts`)

New module, single public function:

```ts
export interface ReadLogDayResult {
  readonly entries: ReadonlyArray<BrainLogEntry>;
  readonly source: "jsonl" | "markdown-fallback";
  readonly warnings: ReadonlyArray<BrainLogParseWarning>;
}

export function readLogDay(vault: string, date: string): ReadLogDayResult;
```

Behaviour:
- If `<date>.jsonl` exists: parse line by line via `JSON.parse`,
  validate the three top-level keys, coerce to `BrainLogEntry`,
  return `source: "jsonl"`. Malformed lines surface as warnings;
  the rest of the file still returns.
- If `<date>.jsonl` is absent: call `parseLogDay` (markdown reader)
  and return its result with `source: "markdown-fallback"`.
- If both are absent: empty array, `source: "jsonl"` (no day file
  for that date).

#### 4.9.5 Discipline-report switch

`src/core/discipline/log-counts.ts:countBrainEvents` switches from
`parseLogDay` to `readLogDay`. No public-API change; counters and
return shape are identical apart from the new `note` field added by
§4.2.4.

#### 4.9.6 Backfill policy

None. Historical `<date>.md` files without a sidecar JSONL are
served by the reader fallback. Once any agent appends an event on a
date, both files exist from that point. No one-off migration script,
no eager backfill at install.

Why: backfill costs operator complexity (when to run, snapshot before,
idempotency) for no measurable gain - the reader handles the
asymmetry. If a future tool genuinely needs JSONL for old days, that
tool can run a one-shot backfill on demand.

#### 4.9.7 Snapshot / manifest / protect

- `o2b brain snapshot`: archives all of `Brain/`; the new `.jsonl`
  files are included automatically.
- `o2b brain manifest` (sha256 walk): `.jsonl` participates in the
  manifest; rollback drift-check picks up sidecar changes alongside
  markdown.
- `o2b brain protect`: both Claude Code (`permissions.deny`) and
  Codex (`osb_protected.filesystem` path list) protect the
  `Brain/log/` directory through a glob / directory rule; the new
  `.jsonl` files inherit protection. The implementation plan verifies
  this against `src/core/brain/protect.ts` before merge.
- `o2b brain rollback`: archives + manifest cover the new file. No
  code change.

#### 4.9.8 Testing

- `tests/core/brain.log.test.ts` extended:
  - Writer produces both files; bytes equal after multiple appends.
  - Writer survives a missing JSONL file (creates it).
  - Writer survives a missing markdown file (creates both).
- `tests/core/brain.log-jsonl-reader.test.ts` (new):
  - JSONL present -> uses JSONL, `source: "jsonl"`.
  - JSONL absent -> uses markdown fallback, `source:
    "markdown-fallback"`.
  - Malformed JSON line -> warning + remaining lines returned.
  - Mixed (both present, JSONL shorter by one event) -> reader returns
    JSONL content; the operator sees nothing degraded.
- `tests/core/discipline/log-counts.test.ts`:
  - Counts via JSONL.
  - Counts via markdown fallback.
  - Both produce identical results for the same content.

### 4.10 Shared sanitizer

`redactText` currently lives in `src/core/event-log.ts`. Move it (with
its `SECRET_ASSIGNMENT_RE`) to `src/core/sanitize.ts` and import from
both `event-log.ts` and the new `brain_note` handler. The Pay Memory
redactor already has a similar regex; no consolidation in this PR -
that is a separate cleanup track.

### 4.11 MEMORY housekeeping

Operator-owned files under `/root/.claude/projects/-root/memory/`:

1. `feedback_daily_log_strict_russian.md` - **delete**. The rule
   couples two failure modes: it calls a tool that no longer exists
   (after §32G) and it hard-codes a language ("messages in Russian"),
   which violates the standing "no hard-coded user language" rule.
   Index line in `MEMORY.md` removed in the same edit.
2. `feedback_always_russian.md` - **rewrite**. Core principle becomes
   "reply in the user's current dialog language; for this user the
   default is Russian (observed fact, not encoded rule)". The
   "Russian" appears as observation, not as principle.
3. `feedback_no_em_dashes.md` - **rewrite**. Core principle becomes
   "use typographic conventions of the user's dialog language; em-dash
   is replaced with hyphen-with-spaces in languages where em-dash is
   not idiomatic (Russian for this user)". Rule reads as typography,
   not as Russian-specific.
4. `project_o2b_log_language.md` - **delete**. The hybrid policy it
   describes targeted the old `agent-event-log` SKILL and
   `event_log_append` tool; after §32 the localisation policy lives in
   `skills/brain-memory/SKILL.md`.

`MEMORY.md` index updated in the same edit: removed lines for deleted
files; descriptions adjusted for rewritten ones.

A Brain `brain_feedback` signal `sig-2026-05-19-no-hardcoded-user-
language` records the underlying principle so the dream pass can
promote it into `Brain/preferences/` if it recurs.

## 5. Architecture diagrams

### 5.1 Write path

```
brain_feedback / brain_apply_evidence / brain_note (MCP)
                       |
                       v
            appendLogEvent(event)        <-- src/core/brain/log.ts
                       |
        +--------------+--------------+
        |  acquire Brain/log/ lock    |
        +-----------------------------+
                       |
        +--------------+--------------+
        |  read .md, read .jsonl      |
        +-----------------------------+
                       |
        +--------------+--------------+
        |  atomic write .md           |
        |  atomic write .jsonl        |
        +-----------------------------+
                       |
                  release lock
```

### 5.2 Read path

```
caller (discipline-report, future tool)
                       |
                       v
              readLogDay(vault, date)    <-- src/core/brain/log-jsonl.ts
                       |
              .jsonl exists?
                /             \
              yes              no
               |                |
        parse line-by-line   parseLogDay (markdown)
               |                |
        source: "jsonl"   source: "markdown-fallback"
```

## 6. SOLID / KISS / DRY compliance

- **Single Responsibility.** `appendLogEvent` writes both files
  because they are two encodings of the same event; nothing else
  produces brain-log content. The reader is split out (`readLogDay`)
  because there are multiple consumers.
- **Open/Closed.** New event kind `note` is added by extending the
  `BRAIN_LOG_EVENT_KIND` `as const` map and the `BrainLogEvent`
  union, not by branching existing code paths.
- **DRY.** One sanitizer (`redactText`) shared across event-log and
  brain-note handlers. One reader (`readLogDay`) for every machine
  consumer. One event-kind constant set. The three identity-reminder
  templates differ only in their per-runtime cadence sentence; the
  common payload is the same.
- **KISS.** No combined .md+.jsonl file format; no migration; no
  in-process JSONL backfill at startup. The reader handles asymmetry
  transparently.

## 7. Risks and mitigations

| Risk | Mitigation |
|---|---|
| JSONL write succeeds, markdown write fails (or vice versa) | Lock + atomic write under JSONL-first ordering: machine-side readers (which prefer JSONL) keep returning the correct count even after a partial failure; the markdown side may lag by one event until the next successful append. Re-syncing the markdown view from the JSONL is an explicit operator step (`o2b brain digest` regeneration / hand re-render), never automatic. |
| Hand-edited markdown drifts from JSONL | Reader prefers JSONL, returns warnings on malformed lines. Deleting the JSONL is **not** a safe rebuild path on its own — the next append would create a new JSONL containing only the new event, not the historical ones. To force a rebuild, regenerate `<date>.jsonl` from the parsed markdown (out-of-band script, not part of `appendLogEvent`) before the next write. |
| Agent still calls `event_log_append` from training memory | Tool is gone from every MCP registration; runtime returns "unknown tool". Identity-reminder + Stop guardrail point at the new tools. |
| `cmdAppendEvent` fix breaks cron-jobs relying on the `"agent"` default | The env-var path (`VAULT_AGENT_NAME`) is preserved via `resolveAgentName`. Cron-jobs that already set `VAULT_AGENT_NAME` are unaffected; ones that relied on `"agent"` literal get the configured identity instead (which is the bug fix). |
| MEMORY rewrite removes context Sergey relied on | The reformulated files preserve the operational intent (language-of-dialog, typography conventions, mirror-preserved English in repo artifacts); only the "Russian as principle" framing is removed. |

## 8. Release-checklist gate

A v0.10.8 ship is allowed only if all of the following hold:

1. Tests pass: `bun test`.
2. `o2b discipline report` on `/root/vault` shows status `ok` or
   `info` for the window since v0.10.7; every `known_agent` with
   activity has at least one brain event.
3. Smoke check: writing a `brain_feedback` MCP call lands one entry
   in `Brain/log/<today>.md` AND one line in `Brain/log/<today>.jsonl`
   that describe the same event (same `ts`, same `kind`, same payload
   keys / values). The two encodings are not byte-equal - the markdown
   is human-formatted, the JSONL is machine-formatted - the smoke
   check confirms semantic equivalence, not byte equivalence.
4. Manual MCP probe: `event_log_append` is not advertised by any of
   `bun src/mcp/main.ts` (full scope), `bun src/mcp/main.ts --scope
   writer`, the OpenClaw bundle, and the Hermes plugin tool list.

A failure on (2) blocks §32C/D landing (per §4.1 above).

## 9. Out-of-scope items, deferred

These items came up during design but are intentionally not in
v0.10.8. Each entry carries a trigger to revisit:

- **CLI verb `o2b brain note`.** Trigger: a cron-job or shell-script
  use-case for appending a narrative milestone outside MCP.
- **Auto-watcher for stray `Daily/` writes from agent identities.**
  Trigger: §32H drift-check finds repeated cases the operator-run
  check missed.
- **One-shot JSONL backfill of historical markdown days.** Trigger:
  a machine consumer materially benefits from JSONL on days older
  than v0.10.8 ship.
- **Shared Pay Memory + brain-log sanitizer.** Trigger: a third
  consumer of secret redaction joins the codebase.
- **Bidirectional MEMORY <-> Brain sync.** Trigger as in v0.10.7 §E
  Deferred entry.

`_summary.md` will record these in `## Deferred work` with the same
triggers.

## 10. Changes to `_summary.md`

The summary doc is updated in the same release:

- §32 (`_summary.md:275-303`) marked shipped in v0.10.8 with the
  per-subtrack status (§32A lightweight gate, §32B brain_note,
  §32C-G shipped, §32H operator drift-check).
- §23 (`_summary.md:223-227`) marked shipped in v0.10.8.
- `## Deferred work` extended with the five entries from §9 above,
  each with its trigger.
- Reference from §30 to §32 added in the §30 paragraph (the v0.10.7
  card) clarifying that §32 retires the old channel §30 augmented.

No version-history dates appear in `_summary.md` beyond the existing
shipped-in markers; per the "no dates in planning" rule the deferred
entries are trigger-based only.

## 11. Implementation plan reference

The bite-sized implementation plan is `2026-05-19-v0.10.8-impl.md` -
written next, after this spec is approved.
