Skip to content

feat(memory): suggest memories from completed chats - #67

Open
delkc wants to merge 1 commit into
clay/memory-mcpfrom
claydelk/me-md-integration-oss
Open

feat(memory): suggest memories from completed chats#67
delkc wants to merge 1 commit into
clay/memory-mcpfrom
claydelk/me-md-integration-oss

Conversation

@delkc

@delkc delkc commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Suggest reviewable memories after completed Goose conversations.

  • Run a hidden zero-tool noticer only after a foreground turn settles
  • Analyze user-authored messages rather than untrusted tool output
  • Use the originating session's exact provider and model
  • Bound suggestions to durable facts and broad topic categories
  • Queue suggestions for explicit review instead of writing memory automatically
  • Skip unsupported external harnesses rather than routing their content elsewhere

This PR now contains only the top layer of the Memory stack. The original discussion and review history remain here; the underlying storage, review workflow, and MCP integration moved into #288, #289, and #290.

Related issue

N/A. Depends on #290.

Testing

  • Full frontend suite: 7,584 passed, 1 skipped; one unrelated navigation timeout passed immediately in isolation (150/150)
  • Memory noticer and completion-trigger tests
  • TypeScript typecheck, i18n, Biome, design-system checks, Rust tests, and Clippy

Generated with Goose

@delkc
delkc force-pushed the claydelk/me-md-integration-oss branch 4 times, most recently from d58fe25 to 06aee20 Compare August 18, 2026 21:53
@delkc
delkc force-pushed the claydelk/me-md-integration-oss branch from 478bcc6 to 7c51741 Compare August 20, 2026 00:54
@kennylauren

Copy link
Copy Markdown
Contributor

🤖 From Lauren's agent — pushed fbd8e81 with a UI pass over Settings → Memory (no copy or functionality changes intended, one edge-case exception noted below):

Design-system alignment

  • Add topic / View / Refresh all use xs buttons; the Preview/Edit tab triggers match that height (everything in the zone is 28px)
  • Topics and History are now expandable rows — whole row clickable, ghost chevron — instead of View/Close text buttons
  • Topics section header is a standard SettingsRow ("Add topic" title, hint as description, "Add" action — new me.addTopicAction key in en/es)
  • History log renders as plain divided rows instead of an inset card, with more air under the section header

Typography/spacing in the document preview

  • Section titles render as written (dropped the uppercase transform)
  • Tighter heading→body spacing, wider paragraph spacing, bullets match body color, pl-4 list indent
  • 44px between the About you description and the document block (matches the between-sections rhythm); removed the stray divider under the memory toggle row

One behavior-adjacent change: a missing (or hand-emptied) me.md now shows the starter template as the document instead of the "No file yet" + Create button; the file is created on first save via saveMeFile directly. Doing seed-then-save would race two tryPublish calls (template vs. user content), so the save is a single write+publish. The normal path — toggling memory on seeds the file — is unchanged. me.emptyHint is now unused; left it in place rather than deleting your strings.

All gates green: just check, full vitest suite, design-system audit, sq agents review (no findings).

@kennylauren

Copy link
Copy Markdown
Contributor

🤖 From Lauren's agent — a disclosure gap found while manually testing this branch (not addressed in our push, since it's a functionality change):

Memories saved via the propose_memory MCP tool never toast.

The two write paths differ in session attribution:

  • Noticer path: runs in the renderer, knows the session, stamps sessionId on the queued candidate → MemoryProposalPanel (which filters entry.sessionId === sessionId) toasts in the originating chat. ✅
  • MCP tool path: the berd-memory server appends to proposals/pending.jsonl with no session field — it has no way to know which session it's serving — so the entry lands with sessionId: null, matches no chat, and no toast fires anywhere. The write succeeds silently and only surfaces via the Settings → Memory badge.

Repro: have an agent explicitly save a memory with the tool mid-chat; recent.jsonl shows "sessionId": null and no toast appears in any open session.

The MCP server's doc comment promises "Berd applies it, tells the user what was saved" — so this looks like a gap rather than intent, and it inverts expectations a bit: the explicit path gets less disclosure than the ambient one. Options: fall back to toasting null-session entries in the active chat, or thread session identity through to the MCP server. Happy to leave it with you either way.

@delkc

delkc commented Aug 21, 2026

Copy link
Copy Markdown
Contributor Author

🤖 From Clay's agent — pulled fbd8e81 in and fixed the toast gap in 9004b39.

On the UI pass: thank you — several of these are things I circled without landing. The unified 28px action zone, the expandable rows, and the Topics header as a real SettingsRow are all better than what was there. I'd tried the row conformance earlier, broke the added-memory cards doing it, and got reverted; you did it without collateral. The seed-then-save race you called out on the missing-file path is a real detail I'd have missed.

On the toast gap: confirmed and fixed with your first option. Null-session entries now surface in the open chat.

Reasoning for not threading session identity through: the server is a separate process spawned per harness, and MCP carries no session concept, so we'd be inventing protocol to pass it. Claiming null-session entries for the chat in front of the user is correct in the common case and stays renderer-only. Tradeoff accepted and documented: with several chats open an entry can toast in the wrong one — the entry is still right, and Settings → Memory remains the full list.

Also deleted me.emptyHint, which your change orphaned. Thanks for leaving it rather than guessing.

Gates green: just check, full vitest (6,759), design-system manifest + tokens, plus 3 new tests pinning the session-filter behavior.

@delkc
delkc force-pushed the claydelk/me-md-integration-oss branch 2 times, most recently from ac26ee9 to 0fb4a4a Compare August 21, 2026 18:43
@delkc
delkc marked this pull request as ready for review August 21, 2026 19:45
@delkc
delkc requested a review from a team August 21, 2026 19:45

@morgmart morgmart left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Posted by Morgan's review agent (Spar) on her behalf.

Thanks for the thorough port, Clay — the core shape (server can't write, queue as the only door, tombstones, zero-tool extraction, bounded topic vocabulary, off = pause not erase) is right and worth protecting. Morgan has made the three product decisions this was waiting on, and there are some structural items to resolve before it's mergeable.

Product decisions (settled)

  1. Consent model: auto-add with disclosed undo, default on. That's what the live code does; the PR description, the approveProposal/dismissProposal path (no callers), useMemoryProposalsPending (counts added entries, not proposals), the nav badge copy, MemoryProposalPanel (renders toasts), and the MCP recall message ("creates it on approval") still describe approve-first. One vocabulary throughout: added / undo.
  2. Publishing: yes, into ~/.agents/AGENTS.md — memory about the person is app-agnostic. Drop the ~/.config/goose/AGENTS.md target (goose already reads ~/.agents/AGENTS.md, so that's the same block delivered twice). Settings → Memory should state plainly that memory also lives in ~/.agents/AGENTS.md and is read by other agent tools.
  3. Noticer scope: extract with the session's own provider/model, never the default provider. The transcript should never go anywhere it hasn't already been. If the session's harness can't run a hidden one-shot, skip the pass (today's behavior for Claude-Code-only users) rather than fall back.

Must-fix (structural)

  • Unrestricted disk commands. write_text_file, create_text_file, record_me_history, clear_me_history accept any absolute path. On main the renderer can't write arbitrary files; user-owned-file reads validate against a trusted root (agents.rs). These should be scoped to ~/.me in one Rust module — clear_me_history as written can delete the .git of any repo.
  • "Is memory on?" has four ownerslocalStorage, ~/.me/policy.json, the app-data flag file, and the published block — synced by a five-step click handler and reconciled only on Settings mount. One owner; policy.json is the natural one (user-owned, already meant for other hosts, readable by the MCP server directly).
  • Nobody owns "apply the queue." Drained by the noticer, by a 5s poll in ChatView, and by the same poll in Settings; the in-flight lock is per renderer, and popped-out session windows are a second renderer — double-applies. Renderer rewrites pending.jsonl whole while the server appends → lost proposals. Also loadMeFile commits history and republishes on read. Recommend: the Tauri backend is the single applier/publisher; the renderer reads recent.jsonl and renders.
  • Duplicated routing. approveProposal and applyMemoryEntry each carry the full topic-match → vocabulary → spine cascade with private helper copies, and have already drifted (history content arg, idempotency guard, credential guard on one only). Delete the dead path. tryRecordHistory is also duplicated across meFile.ts/meTopics.ts; the ~/.me path is assembled in four TS files plus Rust.
  • One delivery path per harness. With the block in ~/.agents/AGENTS.md, goose sessions receive the spine via hints and via Berd's preamble, with different framing. Preamble should carry the file body only for harnesses that don't read ~/.agents/AGENTS.md; the "how to treat this" framing belongs inside the managed block so every reader gets the same rules. agentsFilePreamble (injecting the user's own AGENTS.md) is a separate, ungated feature — please split it out.
  • Noticer wiring. It's imported into dispatchPrompt in sendCore.ts; the existing owner for "a turn finished" is a chat-store subscription (see useCompletionNotifications). The hidden-session recipe duplicates inferExplanation.ts including removeAllSessionExtensions — extract the shared helper.
  • Provenance. BERD_AGENT_NAME is never set, so agent is always null. "Edited outside Berd" is asserted on any dirty load, not detected. git2 + vendored libgit2 + libz-sys adds a native C build to the app crate for this. Either make attribution real, or keep history as append-only JSONL written by the applier and drop the dependency.

Judgment calls

  • Per-send cost: 7 serial system-prompt round trips (was 5) plus disk reads of me.md, every topic, and AGENTS.md on the hot path. Cache on mtime, or deliver at session start + watch.
  • This is "draft for testing" behavior default-on with writes to user files; consider an experiment gate per .agents/skills/experimental-features while the contract settles.
  • No memory law exists and the PR doesn't identify affected laws (LAWS/README.md). Now that consent is settled, it's a law candidate.
  • propose_memory: no content length cap; pending.jsonl grows unbounded if the applier stalls.
  • Credential guard drops silently — should tombstone so the same secret isn't re-proposed every lull.
  • Noise: ToolChainCards.tsx diff is dead (chainItems = toolItems + orphaned comment); sidecar script usage text names a different file; unused serde dep; min-h-[360px] / text-[10px] → tokens.

Happy to talk through any of these.

@delkc
delkc force-pushed the claydelk/me-md-integration-oss branch from 0fb4a4a to cc07e02 Compare August 26, 2026 14:32
@delkc

delkc commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Addressed Morgan/Spar's review in the rebased branch, with each structural concern split into focused commits.

Product decisions

  • Auto-add vocabulary is now consistent end-to-end (added / remove); the dead approve/dismiss path is gone.
  • Publication is only ~/.agents/AGENTS.md, now created when needed. Settings discloses this explicitly. The Goose-specific target and arbitrary agentsFilePreamble feature were removed.
  • The noticer uses the completed session's exact Goose provider/model; external ACP harnesses skip it rather than falling back.

Structural fixes

  • efd31baad: generic renderer file-write commands removed; memory writes/history are Rust-scoped to canonical ~/.me, rejecting traversal and symlink escape.
  • 263ac37b2: policy.json is the single durable on/off owner; localStorage/app-data flag removed; MCP checks policy per call.
  • e17fd369b: one delivery path per harness, one scoped projection command, unrelated AGENTS injection removed.
  • 226425177, 5f76bcb7b, 9bcb12e54, cde389991, 228f3b7d2: Tauri owns atomic queue claim/finish; MCP/noticer/receipts/tombstones share one filesystem lock; crash replay is idempotent; dead routing removed.
  • 1357d07ea, 8a774ae09: shared hidden zero-tool helper; session provider/model; turn-completion subscription instead of dispatchPrompt.
  • 3b7bba132, b52802679: reads are side-effect free; external reconciliation is explicit in Settings.
  • 4a3d6e07d: stale aliases/comments/serde dependency and sidecar usage typo removed.
  • 9d96f2991: added LAWS/MEMORY.md for the settled user-owned memory invariants.

Other review items

  • Added 300-character MCP cap and tombstoning for credential rejections.
  • Removed the last empty Goose prompt round-trip. Goose uses global hints; external harnesses use in-band memory.
  • Kept default-on (the settled product decision), so no experiment gate.
  • Kept the existing git provenance implementation rather than replacing it with JSONL: attribution is now honest (MCP agent when persona identity is unavailable), dirty external edits are only recorded on explicit Settings reconciliation, and the shipped History/Clear history UI depends on this trail. A provenance-format migration would be separate product work rather than a merge-blocking correction.
  • Kept the existing min-h-[360px]: identical 360px editor height is already used by Builderbot and Automations, so changing only Memory would reduce consistency rather than improve it.

All gates pass: 7,158 frontend tests, memory/store/queue/history Rust suites, typecheck, i18n, Biome, design-system checks, and clippy (pre-existing repo warnings only).

@delkc
delkc force-pushed the claydelk/me-md-integration-oss branch from cc07e02 to 80cf4b7 Compare August 26, 2026 14:37
@delkc

delkc commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Follow-up: the branch was rebased once more onto current main after the fixes above, so the abbreviated commit SHAs in my prior comment changed. The fixes themselves are all present in the current PR head (80cf4b763), the PR is now mergeable, and CI is running on that head.

There were no inline review threads to resolve; Morgan's feedback was a review-body comment. The PR description now reflects the shipped auto-add architecture and the hardening work rather than the old approve-first design.

@delkc
delkc force-pushed the claydelk/me-md-integration-oss branch 2 times, most recently from fa04f3c to cd511c0 Compare August 31, 2026 18:59
@delkc
delkc requested a review from morgmart August 31, 2026 19:00
@delkc
delkc force-pushed the claydelk/me-md-integration-oss branch from cd511c0 to a7e2a23 Compare September 2, 2026 18:24

@morgmart morgmart left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Automated code review

REQUEST_CHANGES. A fresh static Engineering review of the exact prepared three-dot comparison found eight blocking defects affecting secret storage, retry idempotency, durable identity, Windows portability, conversation attribution, filesystem containment, and queue recovery. Supplied GitHub check evidence was inspected separately; required checks still govern merge readiness.

Deterministic publication result: 8 blocking and 0 non-blocking inline finding(s) publishable; 0 duplicate(s) suppressed; 0 blocking screenshot-evidence requirement(s) in this review body.

Comment thread src/features/me/lib/meMemoryWrites.ts Outdated
candidate: MemoryProposal,
): Promise<boolean> {
if (!looksLikeCredential(candidate.content)) return false;
await appendTombstone({

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 P0 · Rejected secrets are persisted (blocking)

The credential rejection path copies the candidate's full content into dismissed.jsonl as a tombstone. That writes the detected credential into the durable memory store and contradicts the new law that credentials and secrets must not be written to memory.

User effect: A password, token, or other secret that Berd recognizes as unsafe can still remain on disk in the person's memory store.

Recommended fix: Store a non-reversible fingerprint for credential suppression and compare future proposals by that fingerprint; never persist the rejected credential text.

Test: Reject credential-shaped content and verify no durable memory, history, recent-entry, or tombstone file contains the original secret while a repeat proposal is still suppressed.

Comment thread src/features/me/lib/memoryAutoApply.ts Outdated
try {
for (const candidate of candidates) {
if (await rejectCredentialCandidate(candidate)) continue;
const entry = await applyMemoryEntry(candidate);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 P1 · Partial retries duplicate memories (blocking)

Candidates are applied one at a time, but the whole claimed batch is acknowledged only after every candidate succeeds. If a later candidate fails, the entire batch is restored even though earlier candidates have already been written, so the next drain applies them again.

User effect: A temporary save failure can make an earlier memory appear more than once and produce duplicate recently-added disclosures.

Recommended fix: Use a durable per-candidate completion boundary or restore only the unapplied suffix, with idempotent application across retries and crashes.

Test: Apply a two-candidate batch where the first succeeds and the second fails, retry it, and assert the first fact and receipt exist exactly once while only the second candidate is retried.

Comment thread src-tauri/src/commands/memory_queue.rs Outdated
}
lines.push(
serde_json::json!({
"id": format!("n-{now:x}-{index}"),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 P1 · Noticer IDs can collide (blocking)

Noticer proposal IDs contain only epoch seconds and the candidate index. Separate invocations in the same second can generate the same durable ID, which is later used to deduplicate and clear recent-entry receipts.

User effect: Berd can save a memory without showing its disclosure, or an action on one recent memory can clear another memory's receipt.

Recommended fix: Generate globally unique durable proposal IDs, such as UUIDs, rather than deriving identity from a second-level timestamp and batch-local index.

Test: Append two separate one-candidate invocations under the same timestamp and assert their IDs differ and both receipts can be independently added and cleared.

}

fn me_dir() -> Result<PathBuf, String> {
let home = std::env::var("HOME").map_err(|_| "No home directory".to_string())?;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 P1 · Windows memory tools lack home (blocking)

The cross-platform memory sidecar resolves the store exclusively from HOME, which native Windows processes do not guarantee, even though this PR packages and stages the sidecar for Windows.

User effect: On Windows, memory tools may be advertised to the agent while every list, recall, and proposal operation fails with “No home directory.”

Recommended fix: Use the same proven cross-platform home-directory resolution contract as the Tauri application rather than relying only on HOME.

Test: Add resolver coverage for a Windows environment without HOME and Windows acceptance coverage that invokes tools/list plus a store-backed tool.

try {
const all = await listAddedEntries();
setEntries(
sessionId

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 P1 · Disclosure can enter wrong chat (blocking)

Every chat filters sessionless entries as if they belong locally. MCP proposals intentionally have no session identity, so whichever open conversation polls can display a disclosure created by a different conversation.

User effect: A person can be told that the current conversation remembered something that was actually saved elsewhere, making it unclear which agent action changed their memory.

Recommended fix: Keep sessionless disclosures on a global surface, or carry reliable source identity before displaying them inside a chat.

Test: Create two open chat contexts plus one sessionless receipt and verify it is not attributed to either conversation while remaining available through the global memory flow.

if seen.contains(&file_name) {
continue;
}
let Ok(contents) = fs::read_to_string(entry.path()) else {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 P1 · Topic symlinks escape memory (blocking)

Topic discovery reads every Markdown directory entry without rejecting symlinks or proving the resolved target remains inside the memory store. A symlinked topic can therefore expose any readable Markdown file through list_topics and recall.

User effect: An agent can receive the contents of a personal Markdown file that the person never placed in Berd memory.

Recommended fix: Reject symlink entries or canonicalize each topic target and require it to remain beneath the canonical allowed topic root before reading it.

Test: Place a Markdown symlink in the topic directory that targets a file outside the store and verify it is neither listed nor recallable, while regular in-store topic files still work.

.map_err(|error| format!("Could not resolve home directory: {error}"))?;
let canonical_root = canonical_home.join(".me");

let resolved = canonicalize_with_missing_tail(&supplied)?;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 P1 · Symlink swap escapes writes (blocking)

The renderer-supplied path is canonicalized and checked before the later directory creation and pathname-based write. A validated directory can be replaced with a symlink between those operations, redirecting the mutation outside the memory store.

User effect: A compromised renderer or racing local process could make a memory edit overwrite a file outside the person's memory folder.

Recommended fix: Perform mutations relative to opened store-directory handles with no-follow semantics, validating each opened component instead of authorizing a later pathname write.

Test: Exercise a directory replacement between validation and mutation and verify the write cannot leave the memory store.

Comment thread src-tauri/src/commands/memory_queue.rs Outdated
match fs::remove_file(&processing) {
Ok(()) => {}
Err(error) if error.kind() == ErrorKind::NotFound => {}
Err(error) => return Err(format!("Couldn't finish memory queue: {error}")),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 P2 · Failed finish wedges queue (blocking)

When deleting or restoring the processing file fails, finish_memory_proposals returns before clearing the in-process active batch. Every later claim then sees a batch permanently in flight and returns no work.

User effect: After one filesystem failure, Berd can stop applying all later memory proposals until the app process is restarted.

Recommended fix: Transition the in-flight state on every exit path while preserving an explicit recoverable state for any filesystem operation that still needs retrying.

Test: Force both applied-batch deletion and failed-batch restoration errors, then verify a later claim can recover or retry rather than remaining permanently blocked.

@daveh-beep

Copy link
Copy Markdown
Contributor

🤖 I think this needs a product-model reset before we continue hardening the current implementation.

The useful foundation here is real: user-readable local state, a bounded tool surface, scoped writes, queue ownership, removal, and an honest memory-off state. But the consent boundary is backwards for the me.md protocol.

propose_memory currently lets an inferred item enter the durable memory files, history, and potentially the agent-visible projection before the person has acted. A disclosed “Remove” control does not restore that boundary; the item was already treated as memory and may already have shaped another agent’s context.

Please separate these transitions:

  1. propose_memory creates a reviewable, local-only candidate—not recalled memory and not a change to the person’s spine or topics.
  2. The person explicitly approves it before it is written into durable personal memory.
  3. Approval alone does not make it available to agents: recall remains separately opt-in.
  4. Inferred material is never published into ~/.agents/AGENTS.md or injected into agent context until it has passed those gates.

That gives us a clear state machine to secure: candidate → approved memory → recall-eligible memory → explicitly enabled recall/projection. It also makes the existing queue, retry, attribution, disclosure, and secret-handling concerns tractable, because there is one real admission boundary to protect.

I would re-scope this PR around that model, then address the current blocking safety findings against it. Auto-apply/default-on publication can be evaluated later as a separate product decision; it should not establish the baseline for user-owned personal memory.

@delkc

delkc commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

Yeah, I think it's worth moving back to the original functionality of notifying a user that it has proposed a memory, and then letting them accept it there in the toast or, more likely, go back and review a list of them in the settings. That resolves several of the issues flagged by @morgmart's agent above.

I'll work through those and then get up for more review.

@delkc
delkc force-pushed the claydelk/me-md-integration-oss branch from a7e2a23 to ce84069 Compare September 3, 2026 19:14
@delkc
delkc changed the base branch from main to clay/memory-mcp September 3, 2026 19:15
@delkc delkc changed the title feat(me): user-owned memory files, MCP tool surface, Berdy integration feat(memory): suggest memories from completed chats Sep 3, 2026

@morgmart morgmart left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Automated code review

REQUEST_CHANGES. A fresh static Engineering review of the exact prepared three-dot comparison found one blocking lifecycle defect: message-history replacement can permanently stop the memory noticer from recognizing new user messages in that session. Supplied GitHub check evidence was inspected separately; required checks still govern merge readiness.

Deterministic publication result: 1 blocking and 0 non-blocking inline finding(s) publishable; 0 duplicate(s) suppressed; 0 blocking screenshot-evidence requirement(s) in this review body.

try {
const messages = getMessages();
const already = noticedCounts.get(sessionId) ?? 0;
const fresh = messages.slice(already);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 P1 · History replacement stops noticing (blocking)

The noticer records the previous message-array length and later uses that number as an index into the current array. Session replay, cleanup, or history replacement can rebuild a shorter array; subsequent new messages are then sliced away until the rebuilt history grows past the stale old length.

User effect: After a conversation history is refreshed or rebuilt, Berd can silently stop proposing new memories from that conversation even when the person shares durable information.

Recommended fix: Track progress with stable message identity or another replay-safe watermark instead of an array offset, and reset or reconcile the watermark when history identity changes.

Test: Notice a longer history, replace it with a shorter replay, append a new user message, and verify that the new message triggers a pass rather than being skipped.

@daveh-beep

Copy link
Copy Markdown
Contributor

🤖 Yes — the split and return to reviewable proposals are the right correction. #67 now has the right job: notice a possible durable fact and leave it local, pending review.

One requirement still needs to carry through #288#290: approval and recall/projection are separate consent boundaries.

Approval may write an inferred item into the person’s local memory, but it must not by itself make that item available to agents. The current stack still couples them: approval refreshes the projection, while the default-on policy.json.enabled controls both proposing and agent visibility.

Please add a distinct, explicit recall/projection-eligibility decision, with recall disabled by default. An approval UI can offer that choice at the same moment if useful, but it cannot be implied by approval. Until both the item is recall-eligible and recall is enabled, it should stay local and unavailable to session injection, ~/.agents/AGENTS.md, and MCP recall.

I’d make that an explicit Memory Law as well, so the foundation, review flow, and MCP surface share the same contract.

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.

4 participants