Skip to content

feat(memory): encrypt local store and preserve reviewed Markdown portability - #347

Draft
daveh-beep wants to merge 12 commits into
clay/memory-mcpfrom
dave/memory-encryption-v2
Draft

daveh-beep wants to merge 12 commits into
clay/memory-mcpfrom
dave/memory-encryption-v2

Conversation

@daveh-beep

@daveh-beep daveh-beep commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Extend the encrypted-memory follow-up to the existing memory stack. Keep editing and explicit Markdown import/export in Berd, preserve review and consent boundaries, and fail closed when keys or platform support are unavailable.

This is the draft encryption follow-up to #290, not a replacement for #288–#290. It includes #348’s voice-catalog repair as an attributable cherry-pick and merges main at 2f93fb34 to preserve the tested integration. Clay’s branches are unchanged. Cross-platform storage validation is included; this does not enable or claim Windows/Linux/Intel desktop support.

  • Encrypt active documents, pending proposals, approval/suppression metadata, and transaction journals with authenticated AES-256-GCM. Keep keys in the platform keystore, never in renderer responses, subprocess arguments, environment variables, or plaintext store files.
  • Keep memory-off available while key authorization is pending. Key access does not hold the transaction/policy lock; initialization remains separately serialized. Revalidate store identity and markers before recovery/publication. Run blocking app operations on bounded workers without treating async cancellation as cancellation of a native key request.
  • Enforce the current Apple-silicon macOS availability boundary across native commands, frontend reads/polling, agent context, managed MCP registration, and packaging. Unsupported builds preserve existing data and cannot reactivate managed memory from stale settings.
  • Add opt-in portable-store validation for the shared store/MCP on Intel macOS, Linux, and Windows. Repair Linux directory synchronization and Windows lock-contention classification without enabling those desktop builds prematurely.
  • Deliver repeated memory-off transitions correctly to external harnesses, including after memory was re-enabled.
  • Add synthetic regression coverage, a local portability runner, and a read-only signed-artifact inspection helper. Metadata inspection is not keystore acceptance.

Related issue

Depends on the existing draft stack:

No separate issue was found in the scoped memory encryption issue search. The existing PRs are the relevant work; do not create a duplicate memory implementation. Further restacking remains coordinated with the stack owner; this is not a claim of validation against newer main revisions.

Testing

Completed on the integrated implementation snapshot (all executable source/config/test inputs match this PR; four subsequent documentation updates reconcile status):

Check Result
Apple-silicon macOS shared-store/MCP/stdio 68 tests passed
Intel-target macOS under Rosetta Same 68 tests passed
Linux arm64, isolated container Same 68 tests passed
Intel/Linux default disabled configuration Each: 7 helpers, 2 absence tests, 2 compile-fail doctests passed
Windows GNU cross-compilation Default and portable-store configurations, including test code, passed
Shared-crate clippy Mac, Linux, and Windows cross-target passed with warnings denied
App memory regression 45 tests passed
Release-script suite 214 tests passed
Frontend formatting/lint/i18n/typecheck and Tauri-check variants Passed
Full final just ci Passed on the unchanged local integrated snapshot: 7,962 frontend tests passed / 1 skipped, 214 release-script tests, all configured Rust/check/clippy/build lanes

Data tests use synthetic temporary stores and injected keys. Linux source tests ran with networking disabled, no host-home mounts, and no credential service. Windows cross-compilation is not Windows execution. Rosetta is not final Intel-hardware/package acceptance.

UI behavior was previously checked using synthetic fixtures for edit/save, unsaved Markdown import, export confirmation, and unavailable targets. Sanitized before/after screenshots still need to be selected and reviewed for the submitted candidate. No screenshots of real memory are required or appropriate.

Behavior and compatibility

  • LAWS/MEMORY.md changes the earlier direct-plaintext-file contract to in-app editing plus explicit Markdown portability. This is an intentional product-contract change requiring owner review.
  • Legacy non-fresh stores are refused and preserved; no automatic migration, reset, recovery key, or machine-transfer flow is implemented.
  • Missing established keys never silently regenerate. Key loss can make ciphertext unrecoverable.
  • Filenames, sizes, and policy state remain visible. Explicit exports, previous backups, recalled text in agent transcripts, and compromised/same-user process access are outside the at-rest protection claim.
  • Complete authenticated snapshot rollback is not detected. Older plaintext binaries can damage the same store. These limitations require explicit rollout decisions.
  • Windows atomic file publication is not a claim of Unix-equivalent directory-flush power-loss durability.

Why this remains draft

  • Verify actual signed Mac app/sidecar key authorization through initialization, pending/denied prompts, relaunch, and update in an isolated account. Do not use the publishing release workflow merely to obtain a test artifact.
  • Verify CI on this updated PR head. Full local just ci passed, and branch reconstruction matched the tested source before the documentation-only update. Further stack/base changes need applicable revalidation.
  • Agree on legacy-data handling, old-binary/downgrade behavior, and recovery/rollback scope with the feature owners.
  • Close the existing feat(memory): connect agents through memory MCP #290 sidecar-resolution review requirement with its owner. Import hardening is separate and does not close that finding.
  • Native Windows/MSVC execution, real Credential Manager/Secret Service lifecycle behavior, and cross-platform desktop registration/UI/package integration remain follow-up acceptance work before opening those platform gates.

If encryption is required for launch, the lower memory layers and encryption must reach users together. Reviewable commits must not become independently releasable plaintext or partially encrypted states.

AI tools assisted with implementation and tests. The submitting contributor remains responsible for reviewing the code, evidence, and DCO certification.

daveh-beep and others added 8 commits September 18, 2026 15:36
…ability

Signed-off-by: David Hamilton <daveh@squareup.com>
## Summary

Adds low-distraction status cues to active Voice conversations. Voice
settings offer Off, While working (the default), and While working and
waiting. Working uses the macOS Pop cue; waiting uses Purr. There is no
volume control in settings.

Cues play at the beginning of each five-second window. Submitting a
prompt silences the waiting cue without changing the saved preference.
The chained client enters working on the first live tool call and stays
working until the run ends. Every subsequent tool call signals working
idempotently, without replaying the cue or resetting its cadence.
Incoming speech and outgoing audio temporarily suppress playback without
changing that state; the current cue resumes immediately when audio
ends. Run completion restores waiting, and cues stop with the owning
Voice session.

## Reviewer-reproducible examples

In Voice settings, select While working and waiting and start a chained
voice conversation. Submit a request that has the agent speak briefly
before its first tool call, make two consecutive tool calls that keep
one run active across multiple five-second intervals without speech
between the calls, speak again, and then make a final tool call. Expect
the waiting cue to stop when the prompt is submitted, no working cue
before the first tool call, an immediate working cue when the first call
starts (unless speech is active), and five-second cadence to continue
across the second call. Speech suppresses playback; the working cue
resumes immediately afterward. Run completion restores waiting.

Select While working and repeat the multi-tool request. Working cues
play during tool activity; no waiting cue plays after the run becomes
idle. Select Off and repeat the task: speech remains audible, but
neither working nor waiting cues play. End the voice conversation to
stop all cues.

Live verification covers chained voice with Apple TTS and speaker
output. Other voice modes and output routes were not live-tested.

---------

Signed-off-by: Sol <49aa1f65411fd096d2e2ec144f1e7aa36fdc76d1b907cfdf7be000c66f9d3b8e@buzz.block.builderlab.xyz>
Co-authored-by: Sol <49aa1f65411fd096d2e2ec144f1e7aa36fdc76d1b907cfdf7be000c66f9d3b8e@buzz.block.builderlab.xyz>
update docs to warn about `bb-cli` migration

Co-authored-by: Jarrod Sibbison <jsibbison@squareup.com>
## Summary

Renames the reusable voice-call Cargo package, Rust library namespace,
and standalone command from `berd-voice` to `berd-call`. Desktop Berd
now imports the same runtime as `berd_call`, while the `berd-call`
binary remains a separate client of that library.

The rename updates workspace wiring, protocol documentation, tests, and
scripts without adding a compatibility alias or standalone CLI
telemetry. Existing desktop telemetry schema names and analytics values
remain unchanged. The persisted asset-store lock filename also stays
stable so overlapping app versions continue coordinating access to the
same model store.

## Reviewer-reproducible examples

From a fresh checkout of this branch:

```bash
cd src-tauri
cargo run -p berd-call -- --help
```

The command builds and runs the renamed package, then prints usage
beginning with:

```text
supported commands are session, synthesize, voices, models, benchmark tts, and benchmark stt
usage:
  berd-call session --pcm-output-fd FD ...
```
**Category:** fix
**User Impact:** Status sounds no longer accumulate behind speech or
play immediately after a voice response.
**Problem:** Status cues could overlap or queue when playback completion
lagged, and Berd’s desktop host duplicated activity arbitration that
belongs in the shared call runtime.
**Solution:** Make status playback single-flight, centralize overlapping
speech suppression in `berd-call` activity guards, cancel cues before
speech proceeds, and restart the full cadence after speech without
replaying missed cues.

Depends on #338

<details>
<summary>File changes</summary>

**src-tauri/crates/berd-call/src/status_sounds.rs**
Makes cue playback single-flight and adds acknowledged,
reference-counted conversation activity guards with full-interval resume
after speech.

**src-tauri/crates/berd-call/src/lib.rs**
Exports the shared status-sound activity guard.

**src-tauri/src/commands/native_voice.rs**
Replaces Berd’s local user/playback suppression counters with shared
`berd-call` guards.

**src-tauri/crates/berd-call/README.md**
Documents shared activity ownership and cue coalescing.

**src-tauri/crates/berd-call/PROTOCOL.md**
Documents speech preemption and no-replay cadence behavior.

</details>
## Context

In OpenAI Realtime Expert-Spokesperson calls, the spokesperson can begin
a spoken acknowledgement and invoke the handoff tool in the same
response. Berd treated that handoff as a cancellation of the response
that produced it, marking its own acknowledgement interrupted. OpenAI
could then report that the already-finished response was no longer
active and terminate the call.

Normal hangup could also stop the native runtime before already-queued
provider events reached it, surfacing `Spokesperson runtime is
unavailable` during teardown.

## Summary

A handoff starts Expert work without cancelling acknowledgement audio
that has already begun. Once the acknowledgement finishes generating,
the Expert response can start while its audio drains. Expert audio
replaces any remaining acknowledgement without overlapping it. User
speech still interrupts playback immediately.

The provider's late `Cancellation failed: no active response found` race
no longer ends the call.

Hangup drains provider events that were already queued before stopping
the native runtime, preventing a late teardown event from surfacing as a
call failure.

## Reviewer-reproducible example

1. Run `just dev`, configure Voice for OpenAI Realtime
Expert-Spokesperson, start a voice call, and mute the microphone.
2. Ask the Spokesperson to use the terminal to run `pwd` and report the
printed path. The wording can vary; the request must require Expert tool
use and produce a handoff.
3. Confirm the acknowledgement becomes **Spoken** and the `Spokesperson
→ Expert · Handoff` event appears without marking the acknowledgement
**Interrupted**.
4. Let the Expert answer arrive. Confirm its audio replaces any
remaining acknowledgement rather than overlapping it.
5. Start another acknowledgement, unmute the microphone, speak while it
is playing, and confirm user speech interrupts it immediately.
6. Mute the microphone, repeat the `pwd` tool-use request, wait for the
handoff event, then hang up. Confirm the call returns to its normal
start state without either cancellation error or `Spokesperson runtime
is unavailable`.

---------

Signed-off-by: John Tennant <jtennant@block.xyz>
## Summary

Adds a public, self-describing command surface for the standalone
`berd-call` binary. Global help groups the supported runtime,
speech/model, synthesis, and benchmark commands; command-scoped help
documents their exact invocation; and version output is available
through both flag and command forms.

Help and version exit successfully before initializing audio, models,
credentials, or network clients. Host integration commands such as
`start`, `speak`, `status`, and `stop` are not part of the standalone
`berd-call` interface.

## Reviewer-reproducible examples

```bash
cd src-tauri
cargo run -q -p berd-call -- --help
cargo run -q -p berd-call -- help session
cargo run -q -p berd-call -- --version
cargo run -q -p berd-call -- version
cargo run -q -p berd-call -- start
```

Output excerpts:

```text
Berd Call

Voice-call runtime and speech tools for Berd and other hosts.

Usage:
  berd-call <command> [options]

Call runtime:
  session                 Run the host-facing framed voice-session protocol

Speech and models:
  synthesize              Render text to a WAV file
  voices                  List or download Siri voices
  models                  Inspect or install speech models

Diagnostics:
  benchmark tts           Benchmark a TTS backend
  benchmark stt           Benchmark an STT backend
```

```text
Run the host-facing framed voice-session protocol.

Usage:
  berd-call session --pcm-output-fd FD --voice NAME --language BCP47
  berd-call session --pcm-output-fd FD --tts-backend openai
  berd-call session --pcm-output-fd FD --tts-backend pocket
    --model-dir ABSOLUTE_PATH --voice ID
```

```text
berd-call 0.1.0
```

The host integration command `start` exits 2 and renders the global help
after:

```text
unrecognized command: start
```
…342)

## Summary

Adds the native host needed to run the shared `berd-call session`
runtime as a standalone macOS call. `berd-call start` owns
default-device microphone capture, PCM playback, and the child session
lifecycle; loopback-only `speak`, `status`, and `stop` commands control
that foreground call.

Direct mode streams authoritative user transcripts as TSV.
Expert-Spokesperson mode waits for the shared runtime delivery boundary
and preserves each delivered cursor and role. CoreAudio route metadata
suppresses input for built-in speakers without relying on localized
device names, while headphone-like output retains barge-in.

The standalone boundary contains call hosting and control only.
Updaters, menu-bar UI, persisted application settings, compatibility
hooks, and duplicate speech engines remain outside it.

## Reviewer-reproducible examples

From `src-tauri`, list the installed voices:

```bash
cargo run -q -p berd-call -- voices list --language en-US
```

Choose one voice from that output, then replace `<installed-voice>`
below:

```bash
cargo run -q -p berd-call -- start \
  --voice <installed-voice> --language en-US --port 5322 --stream
```

Speak aloud and note the resulting TSV cursor. In terminal A, use that
cursor to start a longer reply:

```bash
cargo run -q -p berd-call -- speak --port 5322 --re <cursor> "Here is a longer reply you can interrupt while it is playing."
```

While terminal A is still blocked on playback, interrupt it from
terminal B:

```bash
cargo run -q -p berd-call -- stop --port 5322
```

Terminal A returns an interrupted speech result containing `"status":
"interrupted"` and `spokenThroughUtf8`. Terminal B returns `"stopping":
true` before the foreground process exits cleanly. Completed speech
returns `"status": "completed"`.

To verify Expert-Spokesperson delivery, provide `OPENAI_API_KEY` and
start the same command with `--mode expert-spokesperson`. Say: "Please
ask the expert to explain how the code in this project starts a voice
session." The stream delivers the recognized user text followed by an
Expert handoff. For example (cursor values and handoff wording vary):

```text
cursor  role     text
1       user     Please ask the expert to explain how the code in this project starts a voice session.
2       handoff  Explain how the code in this project initiates a voice session...
```

Rows are tab-separated, with one numeric cursor, role, and text per
line. Spokesperson speech uses `spokesperson` or
`spokesperson_interrupted` roles.

To verify route policy, select built-in speakers as the macOS default
output, restart the call, and run:

```bash
cargo run -q -p berd-call -- status --port 5322
```

The session snapshot reports `"policy": "suppress_input"`. Repeat after
selecting headphones or AirPods as the default output; the snapshot
reports `"policy": "allow_barge_in"`, and speaking during playback
interrupts it.
…ranch

The memory review commit 5b894c9 overwrote the voice block in en and es settings.json with a divergent variant. That removed 62 voice keys and reworded 10 existing strings, which breaks 14 voice settings tests. Restore the voice block from the stack's base ce48ca5 and keep the one string the memory work genuinely added (voice.interruptionDescription). All 447 tests in src/features/voice-conversation pass; just check passes.

Signed-off-by: David Hamilton <daveh@squareup.com>
(cherry picked from commit 4bf3772)
Signed-off-by: David Hamilton <daveh@squareup.com>
Integrate main at 2f93fb3 after the separately attributable #348 voice catalog repair. Resolve Cargo and locale conflicts to the locally validated integration baseline. This changes only the encryption follow-up branch; the foundation, reviewed-proposals and MCP owner branches are unchanged.

Signed-off-by: David Hamilton <daveh@squareup.com>
… storage

Keep pending credential calls off the policy lock and synchronous app dispatch. Revalidate store identity, preserve initialization serialization, bound key workers, and retain repeated off-state handoffs. Enforce the current desktop availability boundary across native, renderer, MCP and packaging paths. Add opt-in shared-store validation with Linux directory-sync and Windows lock-contention repairs, synthetic regressions, and explicit native-acceptance limits. Include the narrow tested Sherpa cache repair needed for native validation.

The reconstructed source matches all 2,617 entries of the local just-ci snapshot: 7,962 frontend tests passed with one skipped, 214 release-script tests, and all configured Rust/check/clippy/build lanes passed. Signed app/sidecar key access and Windows runtime remain unverified; keep the PR draft.

Signed-off-by: David Hamilton <daveh@squareup.com>
Signed-off-by: David Hamilton <daveh@squareup.com>
@daveh-beep

Copy link
Copy Markdown
Contributor Author

🤖 @delkc — Dave’s agent here. Updated this draft with the tested encryption follow-up, preserving the #288 → #289 → #290 stack. Your branches are unchanged. This branch includes #348’s voice-catalog repair as a separate signed cherry-pick and main at 2f93fb34 so the tested integration is explicit.

Full local just ci passed: 7,962 frontend tests (one skipped), 214 release-script tests, and all configured Rust/check/clippy/build lanes. The reconstructed implementation matches the tested source; the later changes only reconcile four documentation files. Shared-store/MCP tests also passed on Apple-silicon Mac, Intel under Rosetta, and isolated Linux. Windows is cross-compile/clippy evidence only.

The draft now includes the pending-key/memory-off correction, platform gating, and portable storage repairs. It is not ready to ship: real signed app/sidecar key authorization, native Windows execution, and legacy-data/downgrade/recovery decisions remain open. Added desktop-platform gates stay closed.

When you have time, please advise on folding the catalog repair into the lower stack and the preferred restack/merge shape. Also, do any draft-build users have memory data we need to preserve through migration? The current implementation refuses legacy stores without deleting them. Acceptance details and limits are in docs/memory-encryption-handoff.md and docs/memory-portability.md.

This branch has not been deployed

No deployments
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.

3 participants