feat(app): add Electron desktop UI and evolve memory/retrieval layer
Introduce an Electron app with session browser, memory list, and usage views (vanilla JS + Vue scaffolding). On the data layer: add content_type and is_meta to messages for transcript control-plane filtering, introduce FTS5-backed memory recall with safe tokenization, support memory archival via forget() through the renamed --attune runtime, and expose anchors on memory records.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# Obelisk Query Patterns
|
||||
|
||||
These are copyable CodeAct patterns for `runtime.mjs --query` scripts, plus one
|
||||
`--remember` registration pattern. They are not new APIs. Adapt them to the
|
||||
These are copyable CodeAct patterns for `runtime.mjs --query` scripts plus
|
||||
`--attune` memory mutation patterns. They are not new APIs. Adapt them to the
|
||||
user's scope and return compact evidence.
|
||||
|
||||
Read this before the first query for broad synthesis, progress summaries,
|
||||
@@ -43,14 +43,17 @@ return {
|
||||
memories: map.current_project.memories.map(m => ({
|
||||
id: m.id,
|
||||
path: m.path,
|
||||
anchors: m.anchors,
|
||||
summary: m.summary?.slice(0, 240),
|
||||
})),
|
||||
},
|
||||
prior_memories: memories({ ...scoped, query: topic, limit: 5 }).map(m => ({
|
||||
id: m.id,
|
||||
path: m.path,
|
||||
anchors: m.anchors,
|
||||
session_id: m.session_id,
|
||||
created_at: m.created_at,
|
||||
rank: m.rank,
|
||||
summary: m.summary?.slice(0, 260),
|
||||
})),
|
||||
session_evidence: search(topic.replace(/[-_]/g, ' '), { ...scoped, limit: 8 })
|
||||
@@ -88,6 +91,7 @@ return {
|
||||
memories: map.current_project.memories.map(m => ({
|
||||
id: m.id,
|
||||
path: m.path,
|
||||
anchors: m.anchors,
|
||||
summary: m.summary?.slice(0, 240),
|
||||
})),
|
||||
},
|
||||
@@ -138,11 +142,13 @@ const prior_memories = memories({
|
||||
}).map(m => ({
|
||||
id: m.id,
|
||||
path: m.path,
|
||||
anchors: m.anchors,
|
||||
session_id: m.session_id,
|
||||
message_start: m.message_start,
|
||||
message_end: m.message_end,
|
||||
created_at: m.created_at,
|
||||
summary: m.summary?.slice(0, 260),
|
||||
rank: m.rank,
|
||||
}));
|
||||
|
||||
const session_evidence = search(ftsTopic, { project, limit: 8 })
|
||||
@@ -167,14 +173,14 @@ return {
|
||||
};
|
||||
```
|
||||
|
||||
## Register Approved Memory
|
||||
## Attune Approved Memory
|
||||
|
||||
Use this only after the user approves writing memory and the markdown file
|
||||
already exists. `remember()` validates the file and stores a normalized absolute
|
||||
path, so keep the script small and return the registered record.
|
||||
|
||||
Run this script with `runtime.mjs --remember <script>`. The `--remember` runtime
|
||||
exposes only `remember()`, not retrieval helpers.
|
||||
Run this script with `runtime.mjs --attune <script>`. The `--attune` runtime
|
||||
exposes only `remember()` and `forget()`, not retrieval helpers.
|
||||
|
||||
```js
|
||||
return remember({
|
||||
@@ -182,6 +188,7 @@ return remember({
|
||||
session_id: 'source-session-id',
|
||||
message_start: 'first-message-uuid',
|
||||
message_end: 'last-message-uuid',
|
||||
anchors: [{ kind: 'file', path: 'SKILL.md' }],
|
||||
summary: [
|
||||
'Decision: Obelisk uses one user-facing entry that queries both memory and raw sessions.',
|
||||
'Memory records are prior notes and must be identified naturally when they influence an answer.',
|
||||
@@ -190,6 +197,53 @@ return remember({
|
||||
});
|
||||
```
|
||||
|
||||
## Forget Approved Memory
|
||||
|
||||
Use this only after the user asks to archive an outdated or wrong memory. Identify
|
||||
the exact memory ID in a normal `--query` script first. If one candidate clearly
|
||||
matches the user's request, that request is approval to archive it; if several
|
||||
candidates match, ask which one to forget.
|
||||
|
||||
Run the mutation with `runtime.mjs --attune <script>`:
|
||||
|
||||
```js
|
||||
return forget({
|
||||
id: 'mem-id-to-delete',
|
||||
reason: 'Outdated by newer project guidance.',
|
||||
});
|
||||
```
|
||||
|
||||
`forget()` archives the record. Active recall through `memories()` will omit it,
|
||||
and the markdown file at `path` is left in place.
|
||||
|
||||
## Update Approved Memory
|
||||
|
||||
Use this when the user explicitly corrects an existing memory, or after the
|
||||
agent proposes a replacement and the user approves. An update is one combined
|
||||
operation: archive the old record and register the replacement markdown file.
|
||||
The new markdown file must already exist before running `--attune`.
|
||||
|
||||
```js
|
||||
const archived = forget({
|
||||
id: 'old-memory-id',
|
||||
reason: 'Replaced by updated memory from the current session.',
|
||||
});
|
||||
|
||||
const created = remember({
|
||||
path: '.obelisk/memories/updated-memory.md',
|
||||
session_id: 'current-session-id',
|
||||
message_start: 'first-message-uuid',
|
||||
message_end: 'last-message-uuid',
|
||||
anchors: [{ kind: 'file', path: 'src/path/to/file.ts' }],
|
||||
summary: 'Updated summary: concise English retrieval surface for the replacement memory.',
|
||||
});
|
||||
|
||||
return { archived, created };
|
||||
```
|
||||
|
||||
If the agent only suspects a memory is stale, do not run this pattern yet.
|
||||
Answer from current evidence and ask whether to archive or replace the memory.
|
||||
|
||||
## One-Shot Retrieval For Synthesis
|
||||
|
||||
Use this for conclusion, broad history, failure investigation, or file evolution
|
||||
|
||||
@@ -78,7 +78,8 @@ the needed join, grouping, or exact schema-level check better than helpers.
|
||||
|
||||
Ordering and context are semantic:
|
||||
|
||||
- `sessions()`, `memories()`, `summaries()`, `workflows()`, and `failures()` are newest first.
|
||||
- `sessions()`, `summaries()`, `workflows()`, and `failures()` are newest first.
|
||||
- `memories()` without `query` is newest first; `memories({ query })` is FTS-ranked over memory `summary`/`path`, with lower rank sorting earlier.
|
||||
- `fileHistory()` is oldest first.
|
||||
- `search().context` is temporal neighbors in one session, not causal context.
|
||||
- `context(uuid)` and `trace(uuid)` are for parent-chain/causal expansion.
|
||||
@@ -97,19 +98,44 @@ For semantic questions, build a task-local evidence view:
|
||||
{
|
||||
query_plan: { mode, scope, facets, limits },
|
||||
prior_memories: [
|
||||
{ id, path, session_id, created_at, summary }
|
||||
{ id, path, anchors, session_id, created_at, summary }
|
||||
],
|
||||
evidence: [
|
||||
{ type, id, session_id, timestamp, facet, snippet }
|
||||
{ type, id, session_id, timestamp, content_type, is_meta, facet, snippet }
|
||||
],
|
||||
omitted: 0
|
||||
}
|
||||
```
|
||||
|
||||
For message evidence, preserve `content_type` when projecting snippets.
|
||||
`text` can support user-visible claims; `thinking` is only trace/debug context;
|
||||
`tool_use` means follow `tool_calls` for structured details; `tool_result`
|
||||
means follow `tool_results` for structured output. Mixed or unfamiliar message
|
||||
surfaces remain `unknown`.
|
||||
|
||||
Preserve `is_meta` separately from `content_type`. Default message evidence
|
||||
should exclude `is_meta=1` rows because they are transcript control-plane
|
||||
content, not ordinary user intent or assistant conclusions. Include them only
|
||||
when investigating injected caveats, command envelopes, or transcript structure.
|
||||
When writing raw SQL for ordinary conversation evidence, add
|
||||
`COALESCE(m.is_meta,0)=0` to message filters unless meta rows are the subject of
|
||||
the investigation.
|
||||
|
||||
Memory recall is English-indexed: translate non-English user requests into
|
||||
concise English query terms before calling `memories({ query })`. Memory
|
||||
summaries registered with `remember()` are also English, regardless of the
|
||||
conversation language.
|
||||
`memories({ query })` uses safe FTS5 tokenization over memory `summary` and
|
||||
`path`, so hyphens and punctuation do not need raw `MATCH` escaping.
|
||||
`memories()` returns active memories only. For raw SQL memory recall, include
|
||||
`deleted_at IS NULL`; archived memory records are management/audit data.
|
||||
|
||||
The agent may decide whether to use, ignore, or verify a recalled memory for the
|
||||
current answer without user approval because no persistent state changes. If a
|
||||
user explicitly says a memory is wrong, outdated, should be forgotten, or should
|
||||
be replaced, that request is approval to mutate the exact matching memory. If
|
||||
the agent discovers the conflict without an explicit user request, it should
|
||||
answer from current evidence and ask before archiving or replacing the memory.
|
||||
|
||||
Then synthesize the conclusion in the final answer. Do not pretend the raw
|
||||
evidence view is itself a stored Obelisk entity.
|
||||
@@ -121,7 +147,11 @@ project conventions, abandoned alternatives, repeated failure causes, workflow
|
||||
patterns, and conclusions synthesized across multiple raw evidence points. Do
|
||||
not propose memory for one-off lookups, uncertain findings, or duplicate
|
||||
coverage. The offer is only a proposal: write the markdown file and run
|
||||
`--remember` only after user approval.
|
||||
`--attune` only after user approval.
|
||||
|
||||
Memory updates are archive-plus-write, not in-place edits: run `forget()` on the
|
||||
old record and `remember()` the replacement markdown file under the same user
|
||||
approval.
|
||||
|
||||
## Text Search Semantics
|
||||
|
||||
|
||||
+132
-25
@@ -41,6 +41,8 @@ CREATE TABLE messages (
|
||||
timestamp TEXT, -- ISO 8601
|
||||
role TEXT, -- "user" or "assistant" (from message payload)
|
||||
text TEXT, -- extracted text content (thinking + text blocks, truncated to 10k chars)
|
||||
content_type TEXT, -- "text", "thinking", "tool_use", "tool_result", or "unknown"
|
||||
is_meta INTEGER DEFAULT 0, -- 1 for injected/control-plane transcript messages
|
||||
model TEXT, -- model name (e.g. "claude-opus-4-6-20250529"), NULL for user messages
|
||||
is_sidechain INTEGER DEFAULT 0, -- 1 if this message is on a sidechain (retry/branch)
|
||||
agent_id TEXT, -- subagent or workflow agent UUID (NULL for main conversation)
|
||||
@@ -54,6 +56,22 @@ CREATE TABLE messages (
|
||||
|
||||
Indexes: `idx_messages_session(session_id)`, `idx_messages_agent(agent_id)`, `idx_messages_ts(session_id, timestamp)`.
|
||||
|
||||
`content_type` preserves the top-level Claude Code content block shape for the
|
||||
message row. Treat `text` as user/assistant visible language, `thinking` as
|
||||
trace/debug material, and `tool_use` as a marker that the assistant message
|
||||
contains tool calls. `tool_result` marks a tool-result message, but the
|
||||
structured payload remains in `tool_results`. Tool-call details remain in
|
||||
`tool_calls`. Messages whose top-level content is not one of these four raw
|
||||
message surfaces are `unknown`. Real user input is represented by `type='user'`
|
||||
and `content_type='text'`, not by a separate `user_message` content type.
|
||||
|
||||
`is_meta` marks transcript control-plane content: injected caveats, command
|
||||
envelopes such as `<command-name>/exit</command-name>`, and similar messages
|
||||
that may appear as user-role text but are not ordinary user intent. It is
|
||||
separate from `type`, `role`, and `content_type`. Default helpers hide meta
|
||||
messages from ordinary recall; use `includeMeta: true` or explicit SQL when
|
||||
investigating injected context, command messages, or transcript structure.
|
||||
|
||||
### messages_fts
|
||||
|
||||
FTS5 virtual table for full-text search over message text.
|
||||
@@ -70,6 +88,26 @@ CREATE VIRTUAL TABLE messages_fts USING fts5(
|
||||
|
||||
Queried via `MATCH` syntax. Rebuilt on each index pass.
|
||||
|
||||
### memories_fts
|
||||
|
||||
FTS5 virtual table for ranked memory recall over registered memory summaries
|
||||
and paths.
|
||||
|
||||
```sql
|
||||
CREATE VIRTUAL TABLE memories_fts USING fts5(
|
||||
id UNINDEXED, -- memory record ID, carried for inspection
|
||||
path, -- searchable memory file path
|
||||
summary, -- searchable compact memory summary
|
||||
content=memories,
|
||||
content_rowid=rowid,
|
||||
tokenize='unicode61 remove_diacritics 1'
|
||||
);
|
||||
```
|
||||
|
||||
`memories({ query })` queries this table with safe tokenization and joins back to
|
||||
`memories`, omitting archived rows. It is rebuilt during index finalization;
|
||||
`remember()` also inserts the new memory row into FTS immediately.
|
||||
|
||||
### tool_calls
|
||||
|
||||
Every tool invocation by the assistant. One row per `tool_use` content block.
|
||||
@@ -191,14 +229,22 @@ CREATE TABLE memories (
|
||||
message_start TEXT, -- first relevant message UUID, if known
|
||||
message_end TEXT, -- last relevant message UUID, if known
|
||||
path TEXT, -- normalized absolute markdown memory file path
|
||||
anchors TEXT, -- optional JSON array of recall anchors
|
||||
summary TEXT, -- retrieval summary of the memory
|
||||
created_at TEXT -- ISO 8601 registration time
|
||||
created_at TEXT, -- ISO 8601 registration time
|
||||
deleted_at TEXT, -- ISO 8601 archive time, if forgotten
|
||||
deleted_reason TEXT -- human/agent deletion reason, if forgotten
|
||||
);
|
||||
```
|
||||
|
||||
Indexes: `idx_memories_project(project)`,
|
||||
`idx_memories_session(session_id)`, `idx_memories_created(created_at)`.
|
||||
|
||||
Active memory means `deleted_at IS NULL`. Recall helpers return active memories
|
||||
only. Archived memories are management/audit data, not recall data. Query recall
|
||||
uses `memories_fts` joined back to `memories`; when using raw SQL for memory
|
||||
recall, include `deleted_at IS NULL`.
|
||||
|
||||
### Key Relationships
|
||||
|
||||
```
|
||||
@@ -221,8 +267,8 @@ workflows.run_id <-- workflow_agents.run_id
|
||||
|
||||
## 2. Query API Reference
|
||||
|
||||
Read helpers are available as globals inside `--query` scripts. Memory write
|
||||
helpers are available only inside `--remember` scripts. Scripts run in an async
|
||||
Read helpers are available as globals inside `--query` scripts. Memory mutation
|
||||
helpers are available only inside `--attune` scripts. Scripts run in an async
|
||||
IIFE with a 30-second timeout.
|
||||
|
||||
### Simple Layer
|
||||
@@ -240,6 +286,7 @@ Full-text search across all message text using FTS5.
|
||||
| `opts.after` | `string` | ISO 8601 lower bound on timestamp |
|
||||
| `opts.before` | `string` | ISO 8601 upper bound on timestamp |
|
||||
| `opts.cwd` | `string` | Filter by working directory (supports LIKE) |
|
||||
| `opts.includeMeta` | `boolean` | Include injected/control-plane messages (default `false`) |
|
||||
|
||||
**Scope note:** `sessions.project` is the stored Claude Code project slug,
|
||||
`sessions.project_path` is the absolute session path derived from message `cwd`
|
||||
@@ -248,15 +295,22 @@ Helper `project` filters are fuzzy `LIKE` filters over `sessions.project`. For
|
||||
exact project membership, use `sql()` with `s.project = ?` or
|
||||
`s.project_path = ?`.
|
||||
|
||||
**Returns:** `Array<{ message, session, rank, context }>` where `context` is the
|
||||
6 nearest messages by timestamp in the same session. It is temporal neighbor
|
||||
context, not a parent chain. `rank` is the FTS5 relevance score used by
|
||||
`ORDER BY rank`; lower values sort earlier, so treat the returned order as the
|
||||
relevance order unless you are deliberately using FTS5 ranking details.
|
||||
**Returns:** `Array<{ message, session, rank, context }>` where `message`
|
||||
includes `{ uuid, text, content_type, is_meta, role, timestamp, model, cwd }`
|
||||
and `context` is the 6 nearest non-meta messages by timestamp in the same
|
||||
session unless `includeMeta: true` is passed. It is temporal neighbor context,
|
||||
not a parent chain. `rank` is the FTS5 relevance score used by `ORDER BY rank`;
|
||||
lower values sort earlier, so treat the returned order as the relevance order
|
||||
unless you are deliberately using FTS5 ranking details.
|
||||
|
||||
```js
|
||||
const hits = search('MCTS exploration');
|
||||
return hits.map(h => ({ title: h.session.title, text: h.message.text?.slice(0, 200) }));
|
||||
return hits.map(h => ({
|
||||
title: h.session.title,
|
||||
content_type: h.message.content_type,
|
||||
is_meta: h.message.is_meta,
|
||||
text: h.message.text?.slice(0, 200),
|
||||
}));
|
||||
```
|
||||
|
||||
#### `context(uuid)`
|
||||
@@ -285,8 +339,8 @@ Read-only SQL with parameterized bindings. Returns an array of row objects.
|
||||
|
||||
**Returns:** `Array<Object>` -- each row as `{ column: value }`.
|
||||
|
||||
Write statements are rejected. Use `--remember` and `remember()` for memory
|
||||
registration after user approval.
|
||||
Write statements are rejected. Use `--attune` with `remember()` or `forget()`
|
||||
for memory mutation after user approval.
|
||||
|
||||
```js
|
||||
const rows = sql('SELECT id, title FROM sessions WHERE project = ? ORDER BY ended_at DESC LIMIT 5', 'Users-tomiya-Code-quiet-zero');
|
||||
@@ -306,9 +360,11 @@ const chain = trace('some-uuid');
|
||||
return chain.map(m => ({ role: m.role, text: m.text?.slice(0, 100) }));
|
||||
```
|
||||
|
||||
#### `thread(sessionId)`
|
||||
#### `thread(sessionId, opts?)`
|
||||
|
||||
All messages in a session, ordered by timestamp.
|
||||
Session messages ordered by timestamp. Meta messages are omitted by default;
|
||||
pass `{ includeMeta: true }` to include injected caveats, command envelopes, and
|
||||
other control-plane transcript rows.
|
||||
|
||||
**Returns:** `Array<message>`.
|
||||
|
||||
@@ -457,7 +513,7 @@ from `process.cwd()` against `sessions.project_path`, then from exact
|
||||
],
|
||||
memory_total,
|
||||
memories: [
|
||||
{ id, path, summary, session_id, project, created_at }
|
||||
{ id, path, anchors, summary, session_id, project, created_at }
|
||||
]
|
||||
} | null,
|
||||
projects: [
|
||||
@@ -491,6 +547,7 @@ return {
|
||||
memories: map.current_project?.memories.map(m => ({
|
||||
id: m.id,
|
||||
path: m.path,
|
||||
anchors: m.anchors,
|
||||
summary: m.summary,
|
||||
})),
|
||||
};
|
||||
@@ -522,12 +579,12 @@ return qz.map(s => ({ title: s.title, branch: s.git_branch, ended: s.ended_at })
|
||||
|
||||
#### `memories(opts?)`
|
||||
|
||||
Registered markdown memory records. Like other list helpers, passing a string
|
||||
is treated as `sessionId`, and passing a number is treated as `limit`.
|
||||
Active registered markdown memory records. Like other list helpers, passing a
|
||||
string is treated as `sessionId`, and passing a number is treated as `limit`.
|
||||
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `opts.query` | `string` | English term filter over `summary` and `path`; hyphens/underscores are treated as spaces |
|
||||
| `opts.query` | `string` | English FTS recall query over `summary` and `path`; hyphens/underscores/punctuation are safely tokenized |
|
||||
| `opts.project` | `string` | SQL `LIKE` pattern over `memories.project` |
|
||||
| `opts.sessionId` | `string` | Restrict to one source session |
|
||||
| `opts.sessions` | `string[]` | Restrict to a set of source session IDs |
|
||||
@@ -536,9 +593,13 @@ is treated as `sessionId`, and passing a number is treated as `limit`.
|
||||
| `opts.branch` | `string` | Filter by source session git branch (exact match) |
|
||||
| `opts.limit` | `number` | Max results (default 50) |
|
||||
|
||||
**Returns:** `Array<memory_row>` ordered by `created_at` descending.
|
||||
**Returns:** `Array<memory_row & { rank?: number }>` with archived memories
|
||||
omitted. Without `query`, results are ordered by `created_at` descending. With
|
||||
`query`, results are ordered by FTS rank first, then `created_at` descending;
|
||||
lower rank sorts earlier.
|
||||
|
||||
`query` is a lightweight English term filter, not FTS5 ranking. Translate
|
||||
`query` uses safe FTS5 tokenization rather than raw `MATCH`, so punctuation-only
|
||||
queries return no rows instead of broadening into all memories. Translate
|
||||
non-English user requests into concise English query terms before calling
|
||||
`memories()`. Use it to avoid pulling all recent memories, then read the
|
||||
markdown file at `path` when a memory looks relevant. The runtime rejects
|
||||
@@ -553,6 +614,7 @@ const prior = memories({
|
||||
return prior.map(m => ({
|
||||
id: m.id,
|
||||
path: m.path,
|
||||
anchors: m.anchors,
|
||||
session_id: m.session_id,
|
||||
summary: m.summary?.slice(0, 240),
|
||||
}));
|
||||
@@ -562,11 +624,11 @@ return prior.map(m => ({
|
||||
|
||||
Register a human-approved markdown memory file. This is a write helper, not a
|
||||
recall helper; use it only after the user has approved writing memory. It is
|
||||
available only in scripts run with `runtime.mjs --remember`.
|
||||
available only in scripts run with `runtime.mjs --attune`.
|
||||
|
||||
`--remember` exposes only `remember()`, not `search()`, `sql()`, `memories()`,
|
||||
or other retrieval helpers. If source IDs are unknown, find them first with a
|
||||
normal `--query` script.
|
||||
`--attune` exposes only `remember()` and `forget()`, not `search()`, `sql()`,
|
||||
`memories()`, or other retrieval helpers. If source IDs or memory IDs are
|
||||
unknown, find them first with a normal `--query` script.
|
||||
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
@@ -576,12 +638,14 @@ normal `--query` script.
|
||||
| `record.message_start` | `string` | First relevant source message UUID, if known |
|
||||
| `record.message_end` | `string` | Last relevant source message UUID, if known |
|
||||
| `record.project` | `string` | Project slug override. Defaults from `sessions.project` for `session_id` |
|
||||
| `record.anchors` | `array` or JSON `string` | Optional recall anchors stored as JSON text. Expected shape is an array of objects, such as `{ kind: 'file', path: 'src/index/builder.ts' }` |
|
||||
|
||||
`remember()` validates that `path` exists and is a regular file, and rejects
|
||||
obvious CJK text in `summary`. It stores the normalized absolute path in
|
||||
`memories.path`.
|
||||
`memories.path`. `anchors` is nullable; omit it or pass an empty array when the
|
||||
memory has no explicit file or object anchors.
|
||||
|
||||
**Returns:** `{ id, path, project, created_at }`.
|
||||
**Returns:** `{ id, path, project, anchors, created_at }`.
|
||||
|
||||
```js
|
||||
return remember({
|
||||
@@ -589,10 +653,53 @@ return remember({
|
||||
session_id: 'source-session-id',
|
||||
message_start: 'first-message-uuid',
|
||||
message_end: 'last-message-uuid',
|
||||
anchors: [{ kind: 'file', path: 'src/index/builder.ts' }],
|
||||
summary: 'Decision: keep Obelisk as one user-facing entry that queries both memory and raw session evidence. Memory is prior notes, not final authority.',
|
||||
});
|
||||
```
|
||||
|
||||
#### `forget(record)`
|
||||
|
||||
Archive a human-approved memory record. Use it when the user says a memory is
|
||||
outdated, wrong, or should be forgotten. It is available only in scripts run
|
||||
with `runtime.mjs --attune`.
|
||||
|
||||
`forget()` requires a precise memory ID. Do not pass a query string and let the
|
||||
helper choose. If the ID is unknown, first use a normal `--query` script with
|
||||
`memories()` to identify candidates. If exactly one candidate clearly matches
|
||||
the user's request, the request is approval to archive it. If multiple memories
|
||||
could match, ask the user which one to forget.
|
||||
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `record.id` | `string` | Memory record ID to archive |
|
||||
| `record.reason` | `string` | Required reason for audit and future management views |
|
||||
|
||||
`forget()` sets `deleted_at` and `deleted_reason`. It does not delete the
|
||||
markdown file at `path`. Active recall helpers omit archived memories.
|
||||
|
||||
**Returns:** `{ id, deleted_at, deleted_reason }`, or the same fields plus
|
||||
`already_deleted: true` if the record had already been forgotten.
|
||||
|
||||
```js
|
||||
return forget({
|
||||
id: 'mem-20260610-example',
|
||||
reason: 'Outdated by newer project guidance.',
|
||||
});
|
||||
```
|
||||
|
||||
#### Memory Mutation Approval
|
||||
|
||||
Agents may decide whether to use, ignore, or verify memory in a single answer
|
||||
without approval. Approval is required only for persistent memory mutations.
|
||||
When the user explicitly says a memory is wrong, outdated, should be forgotten,
|
||||
or should say something else, that utterance is approval to mutate the exact
|
||||
matching memory. If multiple memories could match, ask the user to choose.
|
||||
|
||||
Updating is not an in-place edit. Archive the old record with `forget()`, then
|
||||
write and register a replacement markdown file with `remember()` under the same
|
||||
approval.
|
||||
|
||||
---
|
||||
|
||||
## 3. Common Query Patterns
|
||||
|
||||
Reference in New Issue
Block a user