2026-06-18 01:52:44 +08:00
|
|
|
# Obelisk -- Helper API Reference
|
|
|
|
|
|
|
|
|
|
Detailed reference for globals available inside `runtime.mjs --query` and
|
|
|
|
|
`runtime.mjs --attune` scripts.
|
|
|
|
|
|
|
|
|
|
- Use `references/schema.md` for raw SQL table/field/join checks.
|
|
|
|
|
- Use `references/query-patterns.md` for copyable retrieval plans.
|
|
|
|
|
- Use `references/retrieval-semantics.md` for query design and scope choices.
|
|
|
|
|
- Use `references/pitfalls.md` after runtime errors or confusing row shapes.
|
|
|
|
|
|
|
|
|
|
Query scripts run inside an async IIFE with a 30-second timeout. Use `return` to
|
|
|
|
|
emit JSON. `--query` scripts are read-only. `--attune` scripts expose only
|
|
|
|
|
memory mutation helpers.
|
|
|
|
|
|
|
|
|
|
## Query API Reference
|
|
|
|
|
|
|
|
|
|
### Read Helpers
|
|
|
|
|
|
|
|
|
|
These globals are available only in `runtime.mjs --query` scripts:
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
sql, search, context, trace, thread, raw,
|
|
|
|
|
overview, sessions, recent, summaries, memories,
|
|
|
|
|
subagents, workflows, workflowTree, fileHistory, failures
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
All list helpers accept bounded `limit` options. Many helpers also accept
|
|
|
|
|
`project`, `sessionId`, `sessions`, `after`, `before`, `branch`, and `source`
|
|
|
|
|
when the underlying table can express that scope. Passing a string to many list
|
|
|
|
|
helpers is treated as `sessionId`; passing a number is treated as `limit`.
|
|
|
|
|
|
|
|
|
|
### Mutation Helpers
|
|
|
|
|
|
|
|
|
|
These globals are available only in `runtime.mjs --attune` scripts:
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
remember, forget
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`--attune` does not expose `search()`, `sql()`, `memories()`, or other read
|
|
|
|
|
helpers. If you need IDs, discover them first with a normal `--query` script.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Core Helpers
|
|
|
|
|
|
|
|
|
|
#### `search(text, opts?)`
|
|
|
|
|
|
|
|
|
|
Full-text search across all indexed message text using FTS5.
|
|
|
|
|
|
|
|
|
|
| Param | Type | Description |
|
|
|
|
|
| --- | --- | --- |
|
|
|
|
|
| `text` | `string` | FTS5 query string |
|
|
|
|
|
| `opts.limit` | `number` | Max results, default 20 |
|
|
|
|
|
| `opts.sessionId` | `string` | Restrict to one session |
|
|
|
|
|
| `opts.project` | `string` | SQL `LIKE` pattern over `sessions.project` |
|
|
|
|
|
| `opts.after` | `string` | ISO lower bound on message timestamp |
|
|
|
|
|
| `opts.before` | `string` | ISO upper bound on message timestamp |
|
|
|
|
|
| `opts.cwd` | `string` | SQL `LIKE` filter over `messages.cwd` |
|
|
|
|
|
| `opts.source` | `string` | `"claude"`, `"codex"`, or omitted/all |
|
|
|
|
|
| `opts.includeMeta` | `boolean` | Include `is_meta=1` rows, default false |
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
Array<{
|
|
|
|
|
message: { uuid, text, content_type, is_meta, role, timestamp, model, cwd, source },
|
|
|
|
|
session: { id, title, project, started_at, source },
|
|
|
|
|
rank,
|
|
|
|
|
context
|
|
|
|
|
}>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`context` is temporal neighbor context in the same session, not a parent chain.
|
|
|
|
|
Use `context(uuid)` or `trace(uuid)` for causal/parent-chain expansion. Lower
|
|
|
|
|
FTS rank sorts earlier; prefer returned order unless deliberately inspecting
|
|
|
|
|
FTS ranking.
|
|
|
|
|
|
2026-07-08 16:35:10 +08:00
|
|
|
Valid FTS5 syntax in `text` is honored. Input that FTS5 would reject as
|
|
|
|
|
malformed (for example a hyphenated term like `foo-bar`) does not error: it
|
|
|
|
|
falls back to safe per-token quoting — the same tokenization `memories()` uses —
|
|
|
|
|
so ordinary text never crashes the query.
|
|
|
|
|
|
2026-06-18 01:52:44 +08:00
|
|
|
#### `context(uuid)`
|
|
|
|
|
|
|
|
|
|
Full indexed context around one message.
|
|
|
|
|
|
|
|
|
|
| Param | Type | Description |
|
|
|
|
|
| --- | --- | --- |
|
|
|
|
|
| `uuid` | `string` | Message UUID |
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
{ message, parentChain, session, subagent, workflow } | null
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`parentChain` contains ancestors, not temporal neighbors. If the message belongs
|
|
|
|
|
to a subagent or workflow agent, `subagent` or `workflow` is populated when the
|
|
|
|
|
metadata exists.
|
|
|
|
|
|
|
|
|
|
#### `sql(query, ...params)`
|
|
|
|
|
|
|
|
|
|
Read-only SQL helper with positional `?` bindings.
|
|
|
|
|
|
|
|
|
|
| Param | Type | Description |
|
|
|
|
|
| --- | --- | --- |
|
|
|
|
|
| `query` | `string` | `SELECT` or `WITH` statement |
|
|
|
|
|
| `...params` | `any` | Bind values |
|
|
|
|
|
|
|
|
|
|
Returns `Array<object>`.
|
|
|
|
|
|
|
|
|
|
Write statements are rejected. Use `references/schema.md` before non-trivial SQL
|
|
|
|
|
joins, and use `--attune` with `remember()` / `forget()` for memory mutation.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Orientation And Lists
|
|
|
|
|
|
|
|
|
|
#### `overview(opts?)`
|
|
|
|
|
|
|
|
|
|
Compact orientation map for choosing retrieval scope. It is not evidence: it
|
|
|
|
|
does not return snippets, full messages, or markdown memory contents.
|
|
|
|
|
|
|
|
|
|
Passing a string is treated as `project`. Passing a number is treated as
|
|
|
|
|
`limit`.
|
|
|
|
|
|
|
|
|
|
| Param | Type | Description |
|
|
|
|
|
| --- | --- | --- |
|
|
|
|
|
| `opts.project` | `string` | Project slug or SQL `LIKE` pattern to use as current scope |
|
|
|
|
|
| `opts.limit` | `number` | Max recent sessions in `current_project.sessions`, default 8 |
|
|
|
|
|
| `opts.projectLimit` | `number` | Max global project rows, default 20 |
|
|
|
|
|
| `opts.memoryLimit` | `number` | Max memories in `current_project.memories`, default 100 |
|
|
|
|
|
|
|
|
|
|
If `opts.project` is absent, `overview()` tries to identify the current project
|
|
|
|
|
from `process.cwd()` against `sessions.project_path`, then from exact
|
|
|
|
|
`messages.cwd` matches. It does not guess the current session.
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
{
|
|
|
|
|
current: {
|
|
|
|
|
cwd,
|
|
|
|
|
project: {
|
|
|
|
|
project,
|
|
|
|
|
project_path,
|
|
|
|
|
source: 'opts' | 'cwd_project_path' | 'cwd_messages',
|
|
|
|
|
confidence: 'exact' | 'inferred' | 'unknown'
|
|
|
|
|
} | null
|
|
|
|
|
},
|
|
|
|
|
current_project: {
|
|
|
|
|
project,
|
|
|
|
|
project_path,
|
|
|
|
|
session_total,
|
|
|
|
|
sessions: [
|
|
|
|
|
{ id, title, project, project_path, started_at, ended_at, git_branch, message_count, source }
|
|
|
|
|
],
|
|
|
|
|
memory_total,
|
|
|
|
|
memories: [
|
|
|
|
|
{ id, path, anchors, summary, session_id, project, created_at }
|
|
|
|
|
]
|
|
|
|
|
} | null,
|
|
|
|
|
projects: [
|
|
|
|
|
{
|
|
|
|
|
project,
|
|
|
|
|
project_path,
|
|
|
|
|
session_count,
|
|
|
|
|
memory_count,
|
|
|
|
|
last_session_at,
|
|
|
|
|
last_memory_at,
|
|
|
|
|
recent_branches
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
totals: {
|
|
|
|
|
projects,
|
|
|
|
|
sessions,
|
|
|
|
|
memories,
|
|
|
|
|
sources: [{ source: 'claude' | 'codex', session_count, last_session_at }]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Confirm facts with `memories()`, `search()`, other helpers, or `sql()`.
|
|
|
|
|
|
|
|
|
|
#### `sessions(opts?)`
|
|
|
|
|
|
|
|
|
|
Session rows ordered by `ended_at` descending. Passing a number is treated as
|
|
|
|
|
`limit`.
|
|
|
|
|
|
|
|
|
|
| Param | Type | Description |
|
|
|
|
|
| --- | --- | --- |
|
|
|
|
|
| `opts.project` | `string` | SQL `LIKE` pattern over `sessions.project` |
|
|
|
|
|
| `opts.after` | `string` | ISO lower bound on `started_at` |
|
|
|
|
|
| `opts.before` | `string` | ISO upper bound on `started_at` |
|
|
|
|
|
| `opts.limit` | `number` | Max rows, default 50 |
|
|
|
|
|
| `opts.branch` | `string` | Exact git branch |
|
|
|
|
|
| `opts.source` | `string` | `"claude"`, `"codex"`, or omitted/all |
|
|
|
|
|
| `opts.sessionId` | `string` | Exact session ID |
|
|
|
|
|
| `opts.sessions` | `string[]` | Restrict to session IDs |
|
|
|
|
|
|
|
|
|
|
Returns `Array<session_row>`.
|
|
|
|
|
|
|
|
|
|
#### `recent(n?)`
|
|
|
|
|
|
|
|
|
|
Shorthand for `sessions({ limit: n })`. Default `n` is 10.
|
|
|
|
|
|
|
|
|
|
Returns `Array<session_row>`.
|
|
|
|
|
|
|
|
|
|
#### `summaries(opts?)`
|
|
|
|
|
|
|
|
|
|
Session summary rows ordered by summary `timestamp` descending. Passing a string
|
|
|
|
|
is treated as `sessionId`; passing a number is treated as `limit`.
|
|
|
|
|
|
|
|
|
|
| Param | Type | Description |
|
|
|
|
|
| --- | --- | --- |
|
|
|
|
|
| `opts.sessionId` | `string` | Restrict to one session |
|
|
|
|
|
| `opts.sessions` | `string[]` | Restrict to session IDs |
|
|
|
|
|
| `opts.project` | `string` | SQL `LIKE` pattern over source session project |
|
|
|
|
|
| `opts.after` | `string` | ISO lower bound on summary timestamp |
|
|
|
|
|
| `opts.before` | `string` | ISO upper bound on summary timestamp |
|
|
|
|
|
| `opts.branch` | `string` | Exact source session branch |
|
|
|
|
|
| `opts.source` | `string` | Provider filter through joined session |
|
|
|
|
|
| `opts.limit` | `number` | Max rows, default 100 |
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
Array<summary_row & { session_title, project }>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`summaries.source` is the summary kind, such as `away_summary`; it is not the
|
|
|
|
|
provider source.
|
|
|
|
|
|
|
|
|
|
#### `memories(opts?)`
|
|
|
|
|
|
|
|
|
|
Active registered markdown memory records. Passing a string is treated as
|
|
|
|
|
`sessionId`; passing a number is treated as `limit`.
|
|
|
|
|
|
|
|
|
|
| Param | Type | Description |
|
|
|
|
|
| --- | --- | --- |
|
|
|
|
|
| `opts.query` | `string` | English recall query over `summary` and `path` |
|
|
|
|
|
| `opts.project` | `string` | SQL `LIKE` pattern over `memories.project` |
|
|
|
|
|
| `opts.sessionId` | `string` | Restrict to one source session |
|
|
|
|
|
| `opts.sessions` | `string[]` | Restrict to source session IDs |
|
|
|
|
|
| `opts.after` | `string` | ISO lower bound on `created_at` |
|
|
|
|
|
| `opts.before` | `string` | ISO upper bound on `created_at` |
|
|
|
|
|
| `opts.branch` | `string` | Exact source session branch |
|
|
|
|
|
| `opts.source` | `string` | Provider filter through the source session |
|
|
|
|
|
| `opts.limit` | `number` | Max rows, default 50 |
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
Array<memory_row & { rank?: number }>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Archived memories are omitted. Without `query`, rows are newest first. With
|
|
|
|
|
`query`, rows are ordered by safe FTS rank first, then `created_at` descending.
|
|
|
|
|
Lower rank sorts earlier. Translate non-English requests into concise English
|
|
|
|
|
query terms before calling `memories()`. Read the markdown file at `path` for
|
|
|
|
|
full content.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Structural Expansion Helpers
|
|
|
|
|
|
|
|
|
|
#### `trace(uuid)`
|
|
|
|
|
|
|
|
|
|
Walk the `parent_uuid` chain from a message to the conversation root.
|
|
|
|
|
|
|
|
|
|
Returns `Array<message>` ordered root-first.
|
|
|
|
|
|
|
|
|
|
#### `thread(sessionId, opts?)`
|
|
|
|
|
|
|
|
|
|
Messages in a session ordered by timestamp.
|
|
|
|
|
|
|
|
|
|
| Param | Type | Description |
|
|
|
|
|
| --- | --- | --- |
|
|
|
|
|
| `sessionId` | `string` | Session ID |
|
|
|
|
|
| `opts.includeMeta` | `boolean` | Include injected/control-plane rows, default false |
|
|
|
|
|
|
|
|
|
|
Returns `Array<message>`. Use `thread()` as a last resort; prefer targeted
|
|
|
|
|
search/context or compact SQL projections.
|
|
|
|
|
|
|
|
|
|
#### `raw(uuid, opts?)`
|
|
|
|
|
|
|
|
|
|
Windowed access to the original JSONL line for one indexed message. Use this
|
|
|
|
|
when indexed text, tool inputs, or tool results were truncated and you need the
|
|
|
|
|
raw source.
|
|
|
|
|
|
|
|
|
|
| Param | Type | Description |
|
|
|
|
|
| --- | --- | --- |
|
|
|
|
|
| `uuid` | `string` | Message UUID |
|
|
|
|
|
| `opts.offset` | `number` | Character offset into the JSONL line, default 0 |
|
|
|
|
|
| `opts.limit` | `number` | Max characters, default 10000 |
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
{ text, totalLength, offset, limit, hasMore } | null
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`raw()` resolves main-session, subagent, workflow-agent, and Codex JSONL paths
|
|
|
|
|
from indexed metadata.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Agent And Workflow Helpers
|
|
|
|
|
|
|
|
|
|
#### `subagents(opts?)`
|
|
|
|
|
|
|
|
|
|
Subagent metadata plus message counts. Passing a string is treated as
|
|
|
|
|
`sessionId`.
|
|
|
|
|
|
|
|
|
|
| Param | Type | Description |
|
|
|
|
|
| --- | --- | --- |
|
|
|
|
|
| `opts.sessionId` | `string` | Restrict to one session |
|
|
|
|
|
| `opts.project` | `string` | SQL `LIKE` pattern over source session project |
|
|
|
|
|
| `opts.source` | `string` | Provider filter |
|
|
|
|
|
| `opts.limit` | `number` | Max rows, default 100 |
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
Array<{ ...subagent_row, messageCount }>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### `workflows(opts?)`
|
|
|
|
|
|
|
|
|
|
Workflow run rows ordered newest first. Passing a string is treated as
|
|
|
|
|
`sessionId`.
|
|
|
|
|
|
|
|
|
|
| Param | Type | Description |
|
|
|
|
|
| --- | --- | --- |
|
|
|
|
|
| `opts.sessionId` | `string` | Restrict to one session |
|
|
|
|
|
| `opts.project` | `string` | SQL `LIKE` pattern over source session project |
|
|
|
|
|
| `opts.after` | `string` | ISO lower bound on workflow timestamp |
|
|
|
|
|
| `opts.before` | `string` | ISO upper bound on workflow timestamp |
|
|
|
|
|
| `opts.source` | `string` | Provider filter |
|
|
|
|
|
| `opts.limit` | `number` | Max rows, default 100 |
|
|
|
|
|
|
|
|
|
|
Returns `Array<workflow_row>`.
|
|
|
|
|
|
|
|
|
|
#### `workflowTree(runId)`
|
|
|
|
|
|
|
|
|
|
Lightweight execution tree for one workflow run. It parses `result_json` and
|
|
|
|
|
adds per-agent message counts. It does not load agent messages.
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
{ ...workflow_row, result: object | null, agents: Array<{ ...workflow_agent_row, messageCount }> } | null
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Evidence Helpers
|
|
|
|
|
|
|
|
|
|
#### `fileHistory(filePath, opts?)`
|
|
|
|
|
|
|
|
|
|
Tool calls that touched one file, ordered oldest first. Includes `Read` rows as
|
|
|
|
|
well as `Edit`/`Write`.
|
|
|
|
|
|
|
|
|
|
| Param | Type | Description |
|
|
|
|
|
| --- | --- | --- |
|
|
|
|
|
| `filePath` | `string` | Absolute file path |
|
|
|
|
|
| `opts.after` | `string` | ISO lower bound |
|
|
|
|
|
| `opts.before` | `string` | ISO upper bound |
|
|
|
|
|
| `opts.source` | `string` | Provider filter |
|
|
|
|
|
| `opts.limit` | `number` | Max rows, default 200 |
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
|
|
```js
|
2026-07-08 16:35:10 +08:00
|
|
|
Array<{
|
|
|
|
|
toolCall: { id, message_uuid, name, input_json },
|
|
|
|
|
session: { id, title, project },
|
|
|
|
|
timestamp
|
|
|
|
|
}>
|
2026-06-18 01:52:44 +08:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Use raw SQL with `ORDER BY m.timestamp DESC` when you need newest-first file
|
|
|
|
|
history.
|
|
|
|
|
|
|
|
|
|
#### `failures(opts?)`
|
|
|
|
|
|
|
|
|
|
Failed tool results with tool/session context and the next three messages after
|
|
|
|
|
the failure. Passing a string is treated as `sessionId`.
|
|
|
|
|
|
|
|
|
|
| Param | Type | Description |
|
|
|
|
|
| --- | --- | --- |
|
|
|
|
|
| `opts.sessionId` | `string` | Restrict to one session |
|
|
|
|
|
| `opts.project` | `string` | SQL `LIKE` pattern over source session project |
|
|
|
|
|
| `opts.after` | `string` | ISO lower bound on result message timestamp |
|
|
|
|
|
| `opts.before` | `string` | ISO upper bound on result message timestamp |
|
|
|
|
|
| `opts.source` | `string` | Provider filter |
|
|
|
|
|
| `opts.limit` | `number` | Max rows, default 50 |
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
Array<{ toolCall, result, session, nextMessages }>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Use SQL for precise counts and grouping; treat `failures()` as compact evidence,
|
|
|
|
|
not a counting primitive.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Memory Mutation Helpers
|
|
|
|
|
|
|
|
|
|
#### `remember(record)`
|
|
|
|
|
|
|
|
|
|
Register a human-approved markdown memory file. Available only in
|
|
|
|
|
`runtime.mjs --attune` scripts.
|
|
|
|
|
|
|
|
|
|
| Param | Type | Description |
|
|
|
|
|
| --- | --- | --- |
|
|
|
|
|
| `record.path` | `string` | Existing markdown file path |
|
|
|
|
|
| `record.summary` | `string` | Required English retrieval summary |
|
|
|
|
|
| `record.session_id` | `string` | Source session ID, if known |
|
|
|
|
|
| `record.message_start` | `string` | First relevant source message UUID |
|
|
|
|
|
| `record.message_end` | `string` | Last relevant source message UUID |
|
|
|
|
|
| `record.project` | `string` | Project slug override |
|
|
|
|
|
| `record.anchors` | `array` or JSON `string` | Optional recall anchors |
|
|
|
|
|
|
|
|
|
|
Relative paths resolve against the source session `project_path` when
|
|
|
|
|
`session_id` is provided, otherwise against the runtime cwd. `remember()`
|
|
|
|
|
validates that `path` exists and is a regular file, rejects obvious CJK text in
|
|
|
|
|
`summary`, stores the normalized absolute path, and accepts nullable `anchors`.
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
{ id, path, project, anchors, created_at }
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### `forget(record)`
|
|
|
|
|
|
|
|
|
|
Archive a human-approved memory record. Available only in
|
|
|
|
|
`runtime.mjs --attune` scripts.
|
|
|
|
|
|
|
|
|
|
| Param | Type | Description |
|
|
|
|
|
| --- | --- | --- |
|
|
|
|
|
| `record.id` | `string` | Exact memory ID |
|
|
|
|
|
| `record.reason` | `string` | Required archive reason |
|
|
|
|
|
|
|
|
|
|
`forget()` sets `deleted_at` and `deleted_reason`. It does not delete the
|
|
|
|
|
markdown file at `path`. Active recall helpers omit archived rows.
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
{ id, deleted_at, deleted_reason } |
|
|
|
|
|
{ id, deleted_at, deleted_reason, already_deleted: true }
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 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 in-place: archive the old record with `forget()`, then write and
|
|
|
|
|
register a replacement markdown file with `remember()` under the same approval.
|