Skip to content
Merged
77 changes: 77 additions & 0 deletions docs/cli/me-memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ Memories are the core data type in Memory Engine. Each memory has content, an op
- [me memory create](#me-memory-create) -- create a memory
- [me memory get](#me-memory-get) -- get a memory by ID or path
- [me memory search](#me-memory-search) -- search memories
- [me memory history](#me-memory-history) -- show a memory's audit-log history
- [me memory update](#me-memory-update) -- update a memory
- [me memory revert](#me-memory-revert) -- restore a memory to an earlier version
- [me memory delete](#me-memory-delete) -- delete a single memory
- [me memory deltree](#me-memory-deltree) -- delete a subtree
- [me memory edit](#me-memory-edit) -- open a memory in your editor
Expand Down Expand Up @@ -166,6 +168,51 @@ me memory search --tree '/share/design/*' --select id,tree,content:200,score

---

## me memory history

Show the append-only audit log for a memory, a subtree, or a bulk operation. Every insert, update, and delete is recorded as one immutable event with the actor, an app-level cause, the operation, an operation id (shared across a bulk statement), and a full snapshot of the resulting (or, for deletes, removed) state — so you can answer "who changed this, when, and how", including who deleted it.

```
me memory history [id-or-path] [options]
```

| Argument | Required | Description |
|----------|----------|-------------|
| `id-or-path` | no | A memory ID (UUIDv7), or a memory's `tree/name` path, to scope history to one memory. A path resolves live first, then via the audit log — so a **deleted** memory's history is still reachable by its path. |

| Option | Description |
|--------|-------------|
| `--tree <path>` | Show events at or under this subtree path. |
| `--operation <op>` | Filter by operation: `insert`, `update`, or `delete`. |
| `--operation-id <uuid>` | Show all events sharing one bulk operation id (e.g. every row of a bulk delete or move). |
| `--since <ts>` | Only events at or after this time (ISO 8601). A `--since` alone drives a space-wide activity feed. |
| `--until <ts>` | Only events strictly before this time (ISO 8601). |
| `--cursor <cursor>` | Page from a prior result's `nextCursor`. |
| `--limit <n>` | Maximum events (default 20, max 1000). |
| `--order-by <dir>` | Order by event time: `desc` (default, newest first) or `asc`. |
| `--select <fields>` | Comma-separated snapshot fields to return (e.g. `tree,content:200`). The audit envelope (event id, time, operation, cause, actor, memory id) is always shown. |

At least one scope is required: a positional id/path, `--tree`, `--operation-id`, or `--since`. Access is enforced per event by read access to that event's tree, so a memory that moved between trees may show a partial history to a caller who cannot read some of its historical trees. Audit events are retained for 30 days.

```bash
# Full history of one memory, including who deleted it
me memory history 0194a000-0001-7000-8000-000000000001

# A deleted memory's history, by its old path
me memory history /share/auth/jwt-rotation

# Everything that changed under a subtree, oldest first
me memory history --tree /share/auth --order-by asc

# Space-wide activity feed for a window
me memory history --since 2025-04-01T00:00:00Z --until 2025-04-08T00:00:00Z

# Every row of one bulk delete
me memory history --operation-id 0194a000-0002-7000-8000-00000000000a
```

---

## me memory update

Update a memory.
Expand All @@ -191,6 +238,36 @@ At least one update field is required. Metadata is fully replaced, not merged. U

---

## me memory revert

Restore a memory to an earlier version's state, applied as a new forward version (it reproduces the old snapshot; it does not rewrite history). Look up versions with `me memory history`.

```
me memory revert <id-or-path> <version> [options]
```

| Argument | Required | Description |
|----------|----------|-------------|
| `id-or-path` | yes | A memory ID (UUIDv7), or a `tree/name` path. A path resolves live first, then via the audit log — so reverting a **deleted** memory by its old path undeletes it. |
| `version` | yes | The version number to restore (must still be within the 30-day audit retention window). |

| Option | Description |
|--------|-------------|
| `--expect-version-hash <hash>` | Only revert if the memory's current `versionHash` matches — guards against a concurrent change. Omit for a deliberate override. |
| `-y, --yes` | Skip the confirmation prompt. |

The full snapshot is restored (content, meta, tree, name, temporal), so a revert can move the memory back to an old tree, or fail with `CONFLICT` if the snapshot's `(tree, name)` slot is now taken. Reverting a deleted memory re-creates it, continuing its version sequence. Requires write access on the current tree and, when the target version lived elsewhere, on that tree too.

```bash
# Restore a memory to version 2 (becomes a new, higher version)
me memory revert 0194a000-0001-7000-8000-000000000001 2

# Undelete a memory to its last version, by its old path
me memory revert /share/auth/jwt-rotation 5 --yes
```

---

## me memory delete

Delete a **single** memory, by ID or by its `tree/name` path. To delete a whole subtree, use [`me memory deltree`](#me-memory-deltree).
Expand Down
106 changes: 106 additions & 0 deletions docs/mcp/me_memory_history.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# me_memory_history

Read the append-only audit log of memory mutations.

Every insert, update, and delete is recorded as one immutable event. Each event carries the actor, an app-level cause, the physical operation, an `operationId` shared across a bulk statement, and a full snapshot of the resulting (or, for deletes, removed) state. Use it to answer "who changed this memory, when, and how" — including "who deleted it".

Access is enforced per event by read access to that event's own tree, so the history of a memory that moved between trees may appear partial to a caller who lacks read on some of its historical trees. Deleted memories remain visible here — query them by `memoryId`.

Pass at least one scope: `memoryId`, `path`, `tree`, `operationId`, or `since`. `operation` narrows within a scope.

## Parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `space` | `string` | varies | Absent in locked mode; required nonempty string in multi-space mode. It selects the same-server space for this call. |
| `memoryId` | `string \| null` | no | UUID of a memory; returns just that memory's history. Works after deletion. |
| `path` | `string \| null` | no | `tree/name` path of a memory; resolved live, else via the audit log, so a deleted memory's history is reachable by path. |
| `tree` | `string \| null` | no | Subtree path filter; returns events at or under this path. |
| `operation` | `"insert" \| "update" \| "delete" \| null` | no | Filter by physical operation. |
| `operationId` | `string \| null` | no | Return all events sharing one bulk operation id (e.g. every row of a bulk delete or move). |
| `since` | `string \| null` | no | Only events at or after this time (ISO 8601). A `since` alone drives a space-wide activity feed. |
| `until` | `string \| null` | no | Only events strictly before this time (ISO 8601). |
| `cursor` | `string \| null` | no | Keyset cursor from a prior response's `nextCursor`; fetches the next page. |
| `limit` | `number \| null` | no | Maximum events (`0` = default 20, max 1000). |
| `order` | `"asc" \| "desc" \| null` | no | Sort by event time. Default `desc` (newest first). |
| `select` | `string[] \| null` | no | Snapshot fields to return per event (e.g. `content:200`); the audit envelope is always included. |
| `format` | `"yaml" \| "json" \| "compact" \| null` | no | Text serialization format. Omit or pass `null` for YAML; `json` and `compact` both return compact JSON. |

At least one of `memoryId`, `path`, `tree`, `operationId`, or `since` is required; a bare unbounded scan is rejected.

## Returns

The tool returns YAML by default. The JSON below illustrates the result shape.

```json
{
"events": [
{
"eventId": "0194a000-0002-7000-8000-000000000009",
"at": "2025-04-15T12:05:00Z",
"operation": "delete",
"operationId": "0194a000-0002-7000-8000-00000000000a",
"cause": "delete",
"actor": {
"principalId": "0194a000-0000-7000-8000-000000000003",
"principalName": "alice@example.com",
"apiKeyId": null,
"apiKeyName": null
},
"memoryId": "0194a000-0001-7000-8000-000000000001",
"tree": "/share/auth",
"name": "jwt-rotation",
"meta": {},
"temporal": null,
"content": "Rotate signing keys every 90 days.",
"version": 2,
"versionHash": "5f3e9c2a8b1d4f7e0c3a6b9d2e5f8c1a"
}
],
"limit": 20,
"nextCursor": null
}
```

`nextCursor` is a keyset cursor: non-null when a full page was returned (more events may exist). Pass it back as `cursor` to fetch the next page.

| Field | Type | Description |
|-------|------|-------------|
| `eventId` | `string` | UUIDv7 identifier of the event. |
| `at` | `string` | ISO 8601 timestamp of when the event was recorded. |
| `operation` | `string` | Physical operation: `insert`, `update`, or `delete`. |
| `operationId` | `string` | Shared across every row of one statement — correlates bulk operations. |
| `cause` | `string \| null` | App-level intent (e.g. `create`, `update`, `delete`, `move`, `delete_tree`); `null` for direct/unattributed mutations. |
| `actor` | `object` | Who performed the mutation: `principalId`, `principalName`, `apiKeyId`, `apiKeyName`. All `null` for unattributed mutations; `apiKey*` are `null` for session-authed writes. |
| `memoryId` | `string` | UUID of the memory the event belongs to. |
| `tree` | `string` | Tree path at the time of the event (canonical `/`-form). |
| `name` | `string \| null` | Leaf name at the time of the event, or `null`. |
| `meta` | `object` | Metadata snapshot. |
| `temporal` | `object \| null` | Temporal range snapshot with `start`/`end`, or `null`. |
| `content` | `string` | Content snapshot of the resulting (or, for deletes, removed) state. |
| `version` | `integer` | Logical-payload version of the snapshot. |
| `versionHash` | `string` | 32-char md5 hex of the snapshot. |

## Example

The history of a single memory, including who deleted it:

```json
{
"memoryId": "0194a000-0001-7000-8000-000000000001"
}
```

Every row of one bulk delete:

```json
{
"operationId": "0194a000-0002-7000-8000-00000000000a"
}
```

## Notes

- The audit log is append-only; there is no way to modify or remove events through the API. Events are retained for 30 days.
- History is gated per event by read access to that event's tree, so a moved memory's history can look partial. Deleted memories stay readable by `memoryId` or `path`.
- Omit `select` for the full event. Selecting only trims the snapshot fields (`content`, `meta`, `tree`, `name`, `temporal`, `version`, `versionHash`); the audit envelope (`eventId`, `at`, `operation`, `operationId`, `cause`, `actor`, `memoryId`) is always present. `content:N`, `content:M:N`, and `content:M:` select UTF-16 code-unit ranges and include the full UTF-16 `contentLength`.
50 changes: 50 additions & 0 deletions docs/mcp/me_memory_revert.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# me_memory_revert

Restore a memory to an earlier version's state, applied as a new forward version.

Reverting does not rewrite history — it reproduces the version-N snapshot as the memory's current state, which bumps the version and records a new `revert` event. Look up the target version with [me_memory_history](me_memory_history.md).

Reverting a **deleted** memory re-creates it (undelete), continuing its version sequence so version numbers stay monotonic for that id. The full snapshot is restored (`content`, `meta`, `tree`, `name`, `temporal`), so a revert can move the memory back to an old tree or hit a `(tree, name)` conflict if that slot is now occupied. Only versions still within the audit retention window (30 days) can be reverted.

## Parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `space` | `string` | varies | Absent in locked mode; required nonempty string in multi-space mode. It selects the same-server space for this call. |
| `id` | `string \| null` | no | The UUID of the memory to revert. Provide `id` or `path`. |
| `path` | `string \| null` | no | `tree/name` path of the memory; resolves live, else via the audit log (so a deleted memory can be undeleted by path). |
| `version` | `number` | yes | The version number to restore. |
| `expectedVersionHash` | `string \| null` | no | Only revert if the memory's current `versionHash` matches — guards against a concurrent change to a live memory. Omit for a deliberate override. |

Provide `id` or `path` (at least one is required). If both are given, `path` takes precedence.

## Returns

The reverted memory, in the same shape as [me_memory_get](me_memory_get.md) — with a bumped `version` and a new `versionHash`.

## Example

Restore a memory to version 2:

```json
{
"id": "0194a000-0001-7000-8000-000000000001",
"version": 2
}
```

Undelete a memory to its last version, by path:

```json
{
"path": "/share/auth/jwt-rotation",
"version": 5
}
```

## Notes

- Requires write access on the memory's current tree and, when the target version lived elsewhere, on that tree too.
- Reverting to the memory's current state is a no-op (no new version).
- A version outside the retention window, or one you cannot read, returns an error.
- `NOT_FOUND` if neither `id` nor `path` resolves; `CONFLICT` if restoring the snapshot's `(tree, name)` collides with an existing memory.
Loading