PixelDosa UI

Agent Memory

What the agent remembers about you, with what you said kept apart from what it guessed. Edit anything, forget anything, pause without erasing.

What it remembers

5 things

What you told it

  • Works in Europe/Berlin time

    From your settings

  • Wants summaries under five bullet points

    You said so on 2 Sep

What it picked up

Guesses from how you work. Worth checking.

  • Prefers TypeScript over JavaScript examples

    Noticed across 6 conversations

  • Manages a team of about eight people

    Picked up from a chat on 28 Aug

  • Doesn't take meetings on Fridays

    Noticed from your calendar replies

Installation

bash
npx shadcn@latest add @pixeldosa/agent-memory

Requires `cn` at `@/lib/utils`. `onForget` should really delete. The component keeps its own copy for Undo. When a user edits an inferred memory, store it as `stated`, because they have now said it.

Usage

"use client";

import { AgentMemory } from "@/components/ui/agent-memory";

<AgentMemory
  memories={memories}
  onEdit={(id, text) => memory.update(id, { text, origin: "stated" })}
  onForget={(id) => memory.delete(id)}
  onRestore={(item) => memory.restore(item)}
  onForgetAll={() => memory.clear()}
  paused={settings.memoryPaused}
  onPausedChange={(paused) => save({ memoryPaused: paused })}
/>

Why it exists

An agent that remembers you is only trustworthy if you can see what it remembers. And the risky memories aren't the ones you told it. They're the ones it inferred. A wrong guess like "manages a team of eight" quietly shapes every later answer, and you never see why.

Features

  • Stated and inferred, kept apart. What you said sits in one list. What it picked up sits in another, framed as "Guesses from how you work. Worth checking."
  • Where it came from. Each memory can show its source: "Noticed across 6 conversations".
  • Edit in place. Enter saves and Escape cancels. An edited guess becomes something you stated.
  • Forget means now. onForget fires immediately. The row stays put, struck through, with Undo, and focus moves to it.
  • Pause means pause. Nothing new is remembered, and the note says so. Existing memories are still used until you forget them.
  • Forget everything needs a second step that says it can't be undone.

Examples

Only ask about inferences, for a lighter check-in:

<AgentMemory memories={memories.filter((m) => m.origin === "inferred")} {...handlers} />

Props

PropTypeDescription
memories{ id, text, origin: "stated" | "inferred", source? }[]Required.
onEdit(id, text) => voidRequired.
onForget(id) => voidRequired. Really delete. The component keeps a copy for Undo.
onRestore(memory) => voidEnables Undo.
onForgetAll() => voidShows "Forget everything…".
pausedboolean
onPausedChange(paused) => voidShows the Remember new things switch.
headingLevel2 | 3 | 4 | 5 | 6Heading level, so the component fits your page's outline. Defaults to 3.

Accessibility

  • A labelled region, with Edit and Forget buttons named after the memory they act on.
  • The pause control is a real role="switch".
  • Focus moves to Undo after forgetting, and back to Forget after restoring.
  • Forgets and restores are announced by content in a polite live region.
  • Forget all's confirmation focuses Cancel, the safe choice.

When not to use

It isn't chat history, and it isn't a data export. It shows the facts the agent carries into future conversations. Use Autonomy Control for what the agent may do on its own.

why it's built this way

Engineering Notes

Built around the difference between stated and inferred memory. From the model side, an inferred preference that is wrong is worse than no memory, because it shapes answers without the user ever seeing why. So origin is a required field, the two kinds are listed in separate sections (the inferred list has a dashed border and reads 'Guesses from how you work'), and each memory can carry its source. Forgetting is honest about timing. `onForget` fires at once, so the caller can really delete it, and the component keeps a local snapshot with the original position so the row stays in place, struck through, with Undo. Focus moves to Undo after a forget and back to Forget after a restore, so keyboard users never lose their place. Pausing uses a real `role="switch"`, and its note says exactly what pausing does and doesn't do, because 'paused' is widely misread as 'deleted'. Forget all is the only irreversible action. It opens an inline confirmation with focus on Cancel and uses the destructive intent. A polite live region announces forgets and restores by content.

how it moves, and when it doesn't

Motion Notes

Still, apart from direct feedback. It is the user's turn, so nothing moves unless the user acts. The pause switch thumb slides and its track colour changes at `--pd-duration-fast` on the standard curve. Hover colour changes run at `--pd-duration-instant`. Forgetting doesn't collapse or fade the row. It swaps to its struck-through state instantly and keeps its height, so the list doesn't shift under the pointer and Undo appears exactly where the user clicked. Everything is instant under prefers-reduced-motion.