Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
8e919d8
feat(recall): add bounded atomic memory manifest
Tech-Circle-48 Oct 22, 2026
6c3e4a9
chore(desktop): align generated files with upstream
Tech-Circle-48 Oct 22, 2026
2fa557f
fix: reconcile atomic memory processing changes
Tech-Circle-48 Oct 22, 2026
dbf15ed
Merge branch 'master' into s3
Tech-Circle-48 Oct 22, 2026
85e0303
fix(memory): preserve model-free writes and integration compatibility
Tech-Circle-48 Oct 22, 2026
7ce6f73
fix(memory): reject model-supplied creation metadata
Tech-Circle-48 Oct 22, 2026
3909595
fix(runtime): preserve injected generation components
Tech-Circle-48 Oct 22, 2026
73a645b
fix(migrations): recognize ALTER-added memory descriptions
Tech-Circle-48 Oct 22, 2026
7284c0c
fix(evals): synchronize recall manifest tool catalog
Tech-Circle-48 Oct 22, 2026
36a446a
fix(desktop): regenerate API and IPC contracts
Tech-Circle-48 Oct 22, 2026
1da7621
fix(tags): unwrap atomic memory HTTP tag filters
Tech-Circle-48 Oct 22, 2026
530b99c
fix(recall): bound manifest cursor size
Tech-Circle-48 Oct 23, 2026
ec937dc
Merge branch 'master' into s3
Tech-Circle-48 Oct 23, 2026
6744130
fix(kimi): regenerate MCP tool profiles
Tech-Circle-48 Oct 23, 2026
dd056ea
fix(kimi): include descriptions in model fixtures
Tech-Circle-48 Oct 23, 2026
6c26029
fix(kimi): declare recall manifest capability
Tech-Circle-48 Oct 23, 2026
f30bc4c
fix(dsh): include descriptions in model fixtures
Tech-Circle-48 Oct 23, 2026
30a35db
fix(desktop): synchronize contract fingerprints
Tech-Circle-48 Oct 23, 2026
8f44c20
fix(recall): preserve exact Unicode filters in cursor fingerprints
Tech-Circle-48 Oct 24, 2026
7cb9043
Merge branch 'master' into s3
Tech-Circle-48 Oct 24, 2026
7c7b97c
Merge branch 'master' into s3
Tech-Circle-48 Oct 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions desktop/src-tauri/src/transport/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@ impl ServerApi {
text: text.to_owned(),
reason: None,
expected_revision: None,
description: None,
};
let body = serde_json::to_value(request)
.map_err(|_| ApiFailure::before(SafeError::InvalidInput))?;
Expand Down
2 changes: 1 addition & 1 deletion desktop/src-tauri/src/transport/operations.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"contractSha256": "5878999ce9e239d6642e4da858074b281ed465629432c578b3584eec47cbce6c",
"contractSha256": "d183fce040023694fe8e9c40e56f006b611f6ed906060ca3480de2e03833dbe3",
"operations": {
"get_liveness": {
"method": "GET",
Expand Down
11 changes: 11 additions & 0 deletions desktop/src-tauri/src/transport/wire.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1195,6 +1195,16 @@ pub struct RememberMemoryRequest {
skip_serializing_if = "Option::is_none"
)]
pub expected_revision: Option<Option<i64>>,
/// Optional supplied single-line summary. When omitted, the Server stores a whitespace-collapsed text prefix of at most 200 Unicode characters, including a trailing ellipsis when truncated. Explicit writes do not invoke a generation model.
#[ts(optional)]
#[ts(type = "string | null")]
#[serde(
rename = "description",
default,
with = "::serde_with::rust::double_option",
skip_serializing_if = "Option::is_none"
)]
pub description: Option<Option<String>>,
}

impl RememberMemoryRequest {
Expand All @@ -1206,6 +1216,7 @@ impl RememberMemoryRequest {
text,
reason: None,
expected_revision: None,
description: None,
}
}
}
Expand Down
89 changes: 88 additions & 1 deletion desktop/ui/src/generated/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,26 @@

// Generated from openapi/powercontext.yaml. Do not edit.
export interface paths {
"/v1/recall/manifest": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/**
* Read a bounded metadata-only recall manifest
* @description Lists active Atomic Memory metadata only. Empty inventory never proves the Scope has no context or authorizes skipping prepare_context. Fetch selected exact refs with get_artifact_revision. Pagination is live keyset order, not a cross-request snapshot.
*/
post: operations["get_recall_manifest"];
Comment thread
Tech-Circle-48 marked this conversation as resolved.
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/v1/atomic-memory/list": {
parameters: {
query?: never;
Expand Down Expand Up @@ -2313,14 +2333,47 @@ export interface components {
[key: string]: components["schemas"]["FeatureContract"];
};
};
RecallManifestRequest: {
scope_id: string;
kind?: string | null;
tags?: string[];
tag_match?: components["schemas"]["TagMatch"];
/** @default 50 */
limit: number;
cursor?: string | null;
};
RecallManifestEntry: {
ref: components["schemas"]["ArtifactReference"];
kind: string;
/** @description Stored summary, or null for historical records awaiting explicit summary completion. No body is read to synthesize it. */
description: string | null;
state_version: number;
};
RecallManifestResponse: {
items: components["schemas"]["RecallManifestEntry"][];
next_cursor: string | null;
/** @enum {string} */
code: "page" | "empty_inventory";
};
/** @enum {string} */
AtomicMemoryState: "active" | "forgotten" | "merged" | "retired";
AtomicMemoryReadContent: {
/** @default powercontext.atomic-memory.v1 */
schema: components["schemas"]["AtomicMemoryContentSchema"];
kind: string;
/** @description Memory text is NFC-normalized and trimmed before enforcing an 8192 UTF-8 byte limit. */
text: string;
/** @description A concise single-line summary of final text, in the same language. Required for new writes. */
description?: string | null;
};
AtomicMemoryWriteContent: {
/** @default powercontext.atomic-memory.v1 */
schema: components["schemas"]["AtomicMemoryContentSchema"];
kind: string;
/** @description Memory text is NFC-normalized and trimmed before enforcing an 8192 UTF-8 byte limit. */
text: string;
/** @description Optional supplied single-line summary. When omitted, the Server stores a whitespace-collapsed text prefix of at most 200 Unicode characters, including a trailing ellipsis when truncated. Explicit writes do not invoke a generation model. */
description?: string | null;
};
CreateAtomicMemoryArtifactRequest: {
/**
Expand Down Expand Up @@ -2914,7 +2967,7 @@ export interface components {
version: number;
family: components["schemas"]["CandidateFamily"];
status: components["schemas"]["CandidateStatus"];
proposal: components["schemas"]["ExperienceProposal"] | components["schemas"]["SkillProposal"] | components["schemas"]["ProfileCandidateProposal"] | components["schemas"]["AtomicMemoryWriteContent"] | components["schemas"]["TopicMemoryDreamProposal"] | components["schemas"]["HandoffContent"] | components["schemas"]["PromptContent"] | components["schemas"]["CatalogChangeProposal"];
proposal: components["schemas"]["ExperienceProposal"] | components["schemas"]["SkillProposal"] | components["schemas"]["ProfileCandidateProposal"] | components["schemas"]["AtomicMemoryReadContent"] | components["schemas"]["TopicMemoryDreamProposal"] | components["schemas"]["HandoffContent"] | components["schemas"]["PromptContent"] | components["schemas"]["CatalogChangeProposal"];
/** @description Exact Source evidence. Counted with artifact_refs toward a combined maximum of 32 references. */
source_refs: components["schemas"]["SourceReference"][];
/** @description Exact Artifact evidence. Counted with source_refs toward a combined maximum of 32 references. */
Expand Down Expand Up @@ -4416,6 +4469,8 @@ export interface components {
text: string;
reason?: string | null;
expected_revision?: number | null;
/** @description Optional supplied single-line summary. When omitted, the Server stores a whitespace-collapsed text prefix of at most 200 Unicode characters, including a trailing ellipsis when truncated. Explicit writes do not invoke a generation model. */
description?: string | null;
};
RetireMemoryEntryRequest: {
scope_id: string;
Expand Down Expand Up @@ -5358,6 +5413,38 @@ export interface components {
}
export type $defs = Record<string, never>;
export interface operations {
get_recall_manifest: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["RecallManifestRequest"];
};
};
responses: {
/** @description Operation completed. */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["RecallManifestResponse"];
};
};
400: components["responses"]["InvalidRequest"];
401: components["responses"]["Unauthorized"];
403: components["responses"]["Forbidden"];
404: components["responses"]["NotFound"];
409: components["responses"]["Conflict"];
422: components["responses"]["InvalidRequest"];
500: components["responses"]["InternalError"];
503: components["responses"]["Unavailable"];
};
};
list_atomic_memories: {
parameters: {
query?: never;
Expand Down
6 changes: 5 additions & 1 deletion desktop/ui/src/generated/ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,11 @@ export type RememberMemoryRequest = { scope_id: string, kind: string,
/**
* Must not exceed 8192 UTF-8 bytes after normalization.
*/
text: string, reason?: string | null, expected_revision?: number | null, };
text: string, reason?: string | null, expected_revision?: number | null,
/**
* Optional supplied single-line summary. When omitted, the Server stores a whitespace-collapsed text prefix of at most 200 Unicode characters, including a trailing ellipsis when truncated. Explicit writes do not invoke a generation model.
*/
description?: string | null, };
export type Role = "leader" | "standby";
export type ScopeDescriptor = { scope_id: string, title: string, summary: string, parent_scope_id?: string | null, context_references: Array<string>, external_references: Array<ScopeExternalReference>, version: number, };
export type ScopeExternalReference = { kind: string, value: string, };
Expand Down
2 changes: 1 addition & 1 deletion desktop/ui/src/generated/operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/

// Generated from openapi/powercontext.yaml. Do not edit.
export const contractSha256 = "5878999ce9e239d6642e4da858074b281ed465629432c578b3584eec47cbce6c";
export const contractSha256 = "d183fce040023694fe8e9c40e56f006b611f6ed906060ca3480de2e03833dbe3";
export const operations = {
"get_liveness": {
"method": "GET",
Expand Down
60 changes: 58 additions & 2 deletions docs/en/docs/workflows/atomic-memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ Submit to `POST /v1/scopes/S/artifacts`:
"content": {
"schema": "powercontext.atomic-memory.v1",
"kind": "decision",
"text": "Keep the public API asynchronous."
"text": "Keep the public API asynchronous.",
"description": "Asynchronous public API"
}
}
```
Expand All @@ -42,7 +43,8 @@ Submit complete content to `PUT /v1/scopes/S/artifacts/atomic-memory/M`, sending
{
"content": {
"kind": "decision",
"text": "Keep the public API asynchronous and provide a separate internal synchronous adapter."
"text": "Keep the public API asynchronous and provide a separate internal synchronous adapter.",
"description": "Async public API with a separate internal synchronous adapter"
}
}
```
Expand Down Expand Up @@ -271,3 +273,57 @@ authorization failures stop processing. The immutable Owner columns are only an

Scope checks using the same database with supported Builtin or Casbin providers share the business snapshot.
Independent decision stores and custom providers retain their configured read consistency contracts.


## Metadata-only recall manifest

`POST /v1/recall/manifest` (MCP/Python client: `get_recall_manifest`) lists metadata for active Atomic Memory only.
It does not cover Topic Memory, Experience, Skill, or Sources and does not replace `prepare_context`.

```json
{"scope_id": "S", "limit": 50, "kind": "decision"}
```

The response contains `items`, `next_cursor`, and `code` (`page` or `empty_inventory`). Each item has an exact `ref`,
`kind`, `description`, and `state_version`. Select useful refs, then use `get_artifact_revision` to read and cite their
full content. A description is a selection aid, not evidence. An empty filtered page never proves a Scope empty or
permits skipping `prepare_context`.

The cursor binds request identity and filters through a fixed-length fingerprint, so long Unicode tags do not
expand the returned token beyond the accepted 4,096-character limit. The fingerprint preserves exact Unicode filter
values, including distinctions between composed and decomposed strings. Cursors issued before a cursor-format upgrade
are rejected; restart pagination from the first page.

The limit defaults to 50 and accepts 1–100; oversized requests fail explicitly. Copy `next_cursor` into the next
request with the same Scope, principal, kind and tag filters. Cursors are signed and endpoint-bound. Ordering is by
Artifact ID using live keyset pagination, not a snapshot across requests: concurrent new IDs behind the cursor
require restarting traversal. A null cursor is the explicit end marker. Scope read permission is required. The primary key bounds traversal by Scope and ID; kind/tag filters may
scan more metadata rows than the returned page. This interface does not promise task-success or latency gains.

Every successful new write, including direct creation/replacement, merges and `remember_memory`, contains a nonblank,
single-line `description` of 1–200 Unicode characters in the same language as final text. Callers may supply it.
When omitted, the write path collapses whitespace in the final normalized text into single spaces and saves a
prefix of at most 200 Unicode characters (including a trailing `…` when truncated). This is a default preview,
not a model-generated summary. Explicit writes do not call a generation model, even when one is configured;
existing integrations can continue sending only text. Automatic extraction and
reconciliation generate descriptions alongside text in their existing call; they need no extra summary request.
Invalid generation output fails instead of silently publishing incomplete metadata. Custom Atomic Memory
prompt demonstrations must be updated for the v2 extraction/reconciliation definitions.

Descriptions are authoritative revision content and copied transactionally to `pc_atomic_memory_current.description`.
Manifest queries select narrow metadata columns, never `text`, `searchable_text`, vectors or `pc_artifacts.content`.
Neither reading the manifest nor opening an existing database generates summaries. Startup adds the nullable projection
column without scanning bodies. Existing projection rows remain null until explicit projection repair or a new write.
Topic recovery initialization accepts both the original pristine Atomic maintenance layout and its exact nullable-description
variant; unknown columns, changed FTS objects, and existing Atomic evidence remain rejected.
The existing offline `atomic-memory-rebuild-projection` command copies stored descriptions; it never generates new ones.
Its full verification/rebuild scans authoritative history and current rows and may re-embed active memories: this is
maintenance work, not part of incremental writes or manifest reads.

Historical revisions without a description remain readable and serialize without an extra null field. A null manifest
description explicitly means missing metadata, not a missing memory. To complete a historical summary, an authorized
writer reads the current revision and submits complete unchanged kind/text through ordinary replacement with the
current `If-Match` ETag. Supply a reviewed summary or omit it to store the bounded text preview. This creates a new revision; a stale ETag is
rejected. Never update historical JSON or its content hash in place. Restoring an old revision preserves its historical
absence of summary. Size, token estimates, timestamps, provenance summaries and other families are outside this first
manifest contract; no such field is computed by reading bodies.
48 changes: 46 additions & 2 deletions docs/zh/docs/workflows/atomic-memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ Atomic Memory 将每条事实、偏好或决策保存为独立的 `atomic-memory
"content": {
"schema": "powercontext.atomic-memory.v1",
"kind": "decision",
"text": "公开 API 保持异步。"
"text": "公开 API 保持异步。",
"description": "公开 API 保持异步"
}
}
```
Expand All @@ -40,7 +41,8 @@ Atomic Memory 将每条事实、偏好或决策保存为独立的 `atomic-memory
{
"content": {
"kind": "decision",
"text": "公开 API 保持异步,内部同步适配器另行提供。"
"text": "公开 API 保持异步,内部同步适配器另行提供。",
"description": "公开 API 异步,内部同步适配器独立提供"
}
}
```
Expand Down Expand Up @@ -251,3 +253,45 @@ unsupported 通过 `ServerResponseError.status_code`、`.code`、`.details` 检

使用同一数据库的受支持 Builtin 或 Casbin 服务进行 Scope 检查时,授权读取与业务数据共享快照。
独立的决策存储和自定义服务保留各自配置的读取一致性契约。


## 只读元数据的召回清单

`POST /v1/recall/manifest`(MCP/Python 客户端:`get_recall_manifest`)只列出当前活跃 Atomic Memory 的元数据,
不覆盖 Topic Memory、Experience、Skill 或 Source,也不替代 `prepare_context`。

```json
{"scope_id": "S", "limit": 50, "kind": "decision"}
```

响应包含 `items`、`next_cursor` 和 `code`(`page` 或 `empty_inventory`)。每项包含精确版本引用 `ref`、
`kind`、`description` 和 `state_version`。选择相关引用后,通过 `get_artifact_revision` 读取正文并引用证据。
摘要只帮助选择,不是证据。筛选后的空清单不能证明 Scope 为空,也不允许跳过 `prepare_context`。

游标通过固定长度指纹绑定请求身份和筛选条件,长 Unicode 标签不会使返回的游标超出请求允许的 4,096 字符上限。
指纹保留 Unicode 筛选值的原始形式,包括组合形式与分解形式之间的区别。
游标格式升级前签发的游标会被拒绝,需要从第一页重新分页。

每页默认 50 条,允许 1–100 条,超限明确报错。翻页时携带 `next_cursor`,保持 Scope、调用主体、类型和标签筛选一致。
游标带签名并绑定接口,按 Artifact ID 进行实时键集分页,不是跨请求快照;并发新增且 ID 排在游标之前的记录需要重新遍历。
`next_cursor: null` 明确表示末页。清单读取要求 Scope 读取权限。主键按 Scope 和 ID 限定遍历范围;类型或标签筛选可能扫描多于返回数量的元数据行,
此接口不承诺任务成功率或延迟收益。

新增、修订、合并和 `remember_memory` 成功写入时都包含 `description`:与最终正文同语言、非纯空白、单行、1–200 个 Unicode 字符。
调用者可以提供摘要;省略时,写入路径将最终规范化正文中的连续空白折叠为空格,保存最多 200 个 Unicode 字符的前缀,
超长时末尾用 `…` 标记截断,省略号计入长度上限。这是默认文本简介,不是模型生成的语义摘要。
显式写入不会调用生成模型,即使已经配置模型也一样;旧集成仍可只提交正文。
自动抽取和协调在原有模型调用中同时生成正文和摘要,不增加额外摘要请求;格式不合格时拒绝生成结果,不静默发布不完整数据。自定义 Atomic Memory 提示词示例需要适配 v2 抽取和协调定义。

权威摘要保存在对应 Artifact 内容版本中,并在同一事务中复制到 `pc_atomic_memory_current.description`。
清单查询只选择窄元数据列,不读取 `text`、`searchable_text`、向量或 `pc_artifacts.content`。
清单读取和数据库启动都不会生成摘要。启动只幂等添加可空的投影列,不扫描正文;已有投影行在显式修复或新写入前保持空值。
Topic 恢复初始化同时识别原始的空 Atomic 维护结构和仅新增可空简介列的结构;未知列、改动的 FTS 对象和已有 Atomic 证据仍被拒绝。
现有离线 `atomic-memory-rebuild-projection` 命令只复制已保存的摘要,不生成新摘要;完整校验和重建会扫描权威历史和当前行,
并可能重新生成活跃记忆的向量,属于维护工作,不在增量写入或清单查询中执行。

没有摘要的历史版本仍可读取,序列化不会多出空摘要字段。清单中的空摘要表示元数据尚未补齐,不表示记忆不存在。
补齐时,由有权限的写入者读取当前版本,携带当前 `If-Match` ETag,通过普通替换接口提交原有类型和正文。
可以提供已检查的摘要,或省略摘要以保存有界的正文简介。
这会创建新版本,过期 ETag 会被拒绝;不得原地修改历史 JSON 或内容哈希。恢复旧版本会保留当时缺少摘要的状态。
大小、Token 估算、时间戳、来源概要和其他记忆类型不在第一版契约中,不通过读取正文临时计算。
Loading
Loading