Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 3 additions & 3 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,9 @@ Scheduler 的独立单工作线程执行 Maintenance,不占学习、模型探

`consolidation.py` 保留 broad 候选筛选与判断,合并前检查说话人、立场、涉及人、时间、数字和否定,排除置顶和最近人工正文修订;正文取较完整的现有输入(同长取较早),保留全部来源及修订/操作记录,相信程度至多为输入最大值。被并入行以 `lifecycle=deleted, merged_into=<结果 ID>` 占位并由触发器禁止复活。模型 IO 在事务外;写回重新核对修订及语义快照,同轮矛盾/未决关系阻止涉及它们的合并。

按 DECISIONS 2026-10-10,矛盾与派生依据复核只有报告和标注路径,不写正文/相信程度,不重复 M2 扣减。`consolidation_annotations` 持久记录建议类型、来源、相关记忆/替代目标、写入时修订与报告 ID;同结论唯一键去重,管理员确认/清除是元数据。建议只进入管理详情与报告,即使已确认也不进入宿主 prepare/search;详情按当前修订与标注修订比较显示“标注后已修改”。报告明确标为模型建议并附源消息摘录。
`report_only_v1` 下,按 DECISIONS 2026-10-10,矛盾与派生依据复核只有报告和标注路径,不写正文/相信程度,不重复 M2 扣减。`consolidation_annotations` 持久记录建议类型、来源、相关记忆/替代目标、写入时修订与报告 ID;同结论唯一键去重,管理员确认/清除是元数据。建议只进入管理详情与报告,即使已确认也不进入宿主 prepare/search;详情按当前修订与标注修订比较显示“标注后已修改”。报告明确标为模型建议并附源消息摘录。

`resolution=auto_v1` 是第二种处理方式(设置项,默认仍是 `report_only_v1`,未知或旧值一律归一成 `report_only_v1`,运行快照固定)。模型只给判断和互有细节的合并草稿,分数、生命周期和标注由代码按规则写:推断档 `belief=min(belief,49)`,降一档到设计 10.1 下一档上沿,进入遗忘 `lifecycle=forgotten, retention=min(retention,F-1)`。`evidence_trace.py` 沿 `sources` 追溯原始消息(防循环,不经过失效依据),只追溯到 self_output 的依据不算独立。工作开始时人工编辑或置顶的记忆记为 `protected`,不调用模型。矛盾按规格第 4 节核对后执行 supersedes/reported_vs_self/coexist/undecided,核对失败一律 undecided;依赖按追溯到的不同原始消息计独立依据(目标记忆自己直接引用、不是 self_output 的消息也算),0 条进入遗忘并标 `insufficient_support`。每项自动处理写自己的标注(去重键含运行和工作),已清除的旧标注不会吞掉新标注。互有细节的对先由确定性检查核对草稿(双方独有片段、数字、否定、限定、主体引用、时间、实体,以及全部词项——含“已经、想、过”等功能词,去掉标点和连接词——必须正好等于双方词项的并集),失败记 `mutual_merge_rejected` 和固定原因,保持两条、不写建议;通过后存续记忆写新正文(修订号+1,清向量),另一条按既有合并路径标记。`memory_revisions.reason` 用固定枚举(`auto_superseded`、`auto_reported_downgrade`、`auto_insufficient_support`、`auto_merge`、`auto_merge_mutual`、撤销 `auto undo`)。跨对互不连带:`blocked` 只含共用记忆的矛盾——判为 undecided 的,以及已判为矛盾、但因预算耗尽或调用失败还没处理完的对。同一运行内依赖链上较早的自动处理使祖先记忆变化时,不把下游工作判为过期,规则重新读取当前状态。
`resolution=auto_v1` 是第二种处理方式,也是默认(设置项;`DEFAULTS`、设置请求模型和缺字段时的回退都是 `auto_v1`,未知或旧值一律归一成 `report_only_v1`,运行快照固定)。迁移 038 只把没有任何 `consolidation_settings_saved` 操作记录、值仍为迁移写入的 `report_only_v1` 的库改为 `auto_v1`,不改运行快照。模型只给判断和互有细节的合并草稿,分数、生命周期和标注由代码按规则写:推断档 `belief=min(belief,49)`,降一档到设计 10.1 下一档上沿,进入遗忘 `lifecycle=forgotten, retention=min(retention,F-1)`。`evidence_trace.py` 沿 `sources` 追溯原始消息(防循环,不经过失效依据),只追溯到 self_output 的依据不算独立。工作开始时人工编辑或置顶的记忆记为 `protected`,不调用模型。矛盾按规格第 4 节核对后执行 supersedes/reported_vs_self/coexist/undecided,核对失败一律 undecided;依赖按追溯到的不同原始消息计独立依据(目标记忆自己直接引用、不是 self_output 的消息也算),0 条进入遗忘并标 `insufficient_support`。每项自动处理写自己的标注(去重键含运行和工作),已清除的旧标注不会吞掉新标注。互有细节的对先由确定性检查核对草稿(双方独有片段、数字、否定、限定、主体引用、时间、实体,以及全部词项——含“已经、想、过”等功能词,去掉标点和连接词——必须正好等于双方词项的并集),失败记 `mutual_merge_rejected` 和固定原因,保持两条、不写建议;通过后存续记忆写新正文(修订号+1,清向量),另一条按既有合并路径标记。`memory_revisions.reason` 用固定枚举(`auto_superseded`、`auto_reported_downgrade`、`auto_insufficient_support`、`auto_merge`、`auto_merge_mutual`、撤销 `auto undo`)。跨对互不连带:`blocked` 只含共用记忆的矛盾——判为 undecided 的,以及已判为矛盾、但因预算耗尽或调用失败还没处理完的对。同一运行内依赖链上较早的自动处理使祖先记忆变化时,不把下游工作判为过期,规则重新读取当前状态。

auto_v1 的初判使用 `consolidation_pair_auto_v1.md`(report_only 仍用 pair_v3)。模型的 reason 只作记录:通过现有引用核对就展示,否则存档为 `archived_reason`,报告改用由已核验字段生成的确定性文本,结构结论不因 reason 失败。evidence、keep_id、old_id/new_id、依据 ID 写成文字时,只有能唯一对应材料中可见的整数 ID 才转换,绝不猜测;类型错误、缺字段、多余字段和不合法 ID 走一次机械修复,修复后重新做全部授权核对。初判为 merge 但独有片段不是正文子串时,把不合格片段和两条正文发回纠正一次,仍不合格才降级;初判为 conflict 的对不受片段检查影响,照常进入关系判定。规划时因可见范围不同而分开的相关对,与被替代记忆的跳过一样,按运行汇总记录(`skip:visibility_differs`)。

Expand All @@ -213,7 +213,7 @@ auto_v1 的初判使用 `consolidation_pair_auto_v1.md`(report_only 仍用 pai

目标阶段每次读取最多 100 个未合并的开放目标及修订;逐目标在自己的短事务调用 `review_goal_basis(conn, current, goal_id=..., expected_revision=...)`,将依据观察、目标历史、报告项和维护游标原子提交。采用目标线的单项事务接口,是为了使报告/游标与目标变化一同回滚;独立的 review_goal_basis_items 仍供其他分页调用者使用。冲突跳过并汇总,下一次从 0 扫描;新目标不越过本次 goal_through 上界。依据恢复/再变化由目标模块结束旧标注,不自动删除或关闭目标,过期标识沿用实时投影。

`PATCH /admin/api/settings/consolidation` 暴露 max_calls 及 merge_enabled、conflict_enabled、dependency_enabled、persona_enabled、goal_review_enabled(全开),时间复用 lifecycle.maintenance_time;更新与审计同事务。模型项目开关进入候选指纹和待办筛选,关闭期间不消耗该类调用,重新打开可发现延期项目;合并和矛盾共用一轮分类,关闭矛盾建议时仍防止矛盾记忆合并。报告通过原维护接口汇总变化、失败、模型建议、persona 结果、目标复核和全部模型用量;无变化/未到期只汇总,记忆建议始终仅管理员可见。
`PATCH /admin/api/settings/consolidation` 暴露 max_calls、resolution(默认 auto_v1,可退回 report_only_v1)及 merge_enabled、conflict_enabled、dependency_enabled、persona_enabled、goal_review_enabled(全开),时间复用 lifecycle.maintenance_time;更新与审计同事务。模型项目开关进入候选指纹和待办筛选,关闭期间不消耗该类调用,重新打开可发现延期项目;合并和矛盾共用一轮分类,关闭矛盾建议时仍防止矛盾记忆合并。报告通过原维护接口汇总变化、失败、模型建议、persona 结果、目标复核和全部模型用量;无变化/未到期只汇总,记忆建议始终仅管理员可见。

## M3 persona

Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -752,11 +752,11 @@ prepare 和 search 为每条最终返回的记忆追加 `subject_annotations`,

合并沿用已选的 broad:确定性找候选并排除不兼容的说话人、立场、涉及人、时间、数字、否定,模型确认同一事实后才合并;置顶和最近正文修订为人工编辑的记忆不参与。合并判断逐条引用 A/B 各自独有的细节,片段必须是对应正文规整空白后的子串;缺失或无效时按不确定处理,不合并、不重试改写。只有一边有独有细节时强制保留该条,即使它更短或更晚;两边都没有独有细节时按原规则选已有的一条原文。正文原样保留,不生成第三种说法,并校验正文及来源;结果为有效状态、继承全部来源,相信程度不超过输入的最大值。被并入方用 `deleted + merged_into` 保留永久占位,原 ID 不复活、不召回、不接受反馈。同轮矛盾对不合并。

双方互有对方没有的细节时不自动合并,给管理员留下“合并待核对”建议,列出双方 ID、独有细节及来源摘录。底层沿用现有建议类别,管理页按 `suggestion_type=merge_details` 单独显示“合并待核对(双方互有细节,不代表事实矛盾)”;整理报告和记忆详情均列双方独有细节与记忆链接,可核对、确认或清除。采纳只确认建议,不执行合并;手动合并专用入口留待界面后续实现。建议不进入回复准备或查询返回,正文与相信程度保持不变。
处理方式为“只报告”时,双方互有对方没有的细节不自动合并,给管理员留下“合并待核对”建议,列出双方 ID、独有细节及来源摘录。底层沿用现有建议类别,管理页按 `suggestion_type=merge_details` 单独显示“合并待核对(双方互有细节,不代表事实矛盾)”;整理报告和记忆详情均列双方独有细节与记忆链接,可核对、确认或清除。采纳只确认建议,不执行合并;手动合并专用入口留待界面后续实现。建议不进入回复准备或查询返回,正文与相信程度保持不变。

依据 DECISIONS 2026-10-10,矛盾和依赖复核只生成**模型建议**及可撤销标注(争议、依据不足、被某条记忆替代),不改正文、相信程度或删除对象,不重复 M2 的保留强度扣减。材料附双方来源摘录,每条最多取最早一条与最近三条,各最多 300 token。建议只在管理员整理报告和记忆详情中显示,附来源供核对;可采纳为“已确认”或清除,重复结论不重加,之后正文修订显示“标注后已修改”。未确认和已确认的建议都不进入回复准备或查询返回。置顶及人工编辑的记忆也可收到建议。
处理方式为“只报告”时,依据 DECISIONS 2026-10-10,矛盾和依赖复核只生成**模型建议**及可撤销标注(争议、依据不足、被某条记忆替代),不改正文、相信程度或删除对象,不重复 M2 的保留强度扣减。材料附双方来源摘录,每条最多取最早一条与最近三条,各最多 300 token。建议只在管理员整理报告和记忆详情中显示,附来源供核对;可采纳为“已确认”或清除,重复结论不重加,之后正文修订显示“标注后已修改”。未确认和已确认的建议都不进入回复准备或查询返回。置顶及人工编辑的记忆也可收到建议。

设置页“梦境整理”可选择处理方式。默认仍是“只报告”(`report_only_v1`);选择“自主处理”(`auto_v1`)后,互有细节的合并、矛盾和依赖由模型判断、程序按规则直接处理,不等管理员采纳:
设置页“梦境整理”可选择处理方式。默认是“自主处理”(`auto_v1`),可随时退回“只报告”(`report_only_v1`);未知或旧值一律按“只报告”处理,已接受的运行按自己的设置快照完成。升级迁移 038 只调整未被管理员选择过的旧默认:值为迁移写入的 `report_only_v1`,且 `admin_operations` 没有任何 `consolidation_settings_saved` 记录时,改为 `auto_v1`。任意一次整理设置保存都算,包括只改调用上限、开关或运行时间,这些数据库保持原值。自主处理时,互有细节的合并、矛盾和依赖由模型判断、程序按规则直接处理,不等管理员采纳:

- 互有细节:模型写合并正文,程序逐项核对(双方独有细节、数字、否定、限定、主体、时间、实体,正文用词必须正好是两条原文用词的并集,“已经、想、过”这类词也不能增删),不通过就保持两条并记录原因。
- 矛盾:本人明确更正时旧记忆标“已被替代”、相信程度降到推断档并进入遗忘;本人说法与他人转述冲突时转述降一档;历史与当前并存时不动;无法判定时两条都保留并标争议。依据只有角色自己发言的按无法判定。
Expand Down
37 changes: 36 additions & 1 deletion frontend/src/ConsolidationSettings.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,14 +133,49 @@ test("整理设置保存时间、调用预算与所有开关,并显示操作
expect(within(section).getByText(/只影响之后接受的新运行/)).toBeVisible();
});

test("处理方式显示服务端的默认值,可退回只报告", async () => {
settings.consolidation.resolution = "auto_v1";
render(<SettingsPage />);
const section = await screen.findByRole("region", { name: "梦境整理设置" });
const select = within(section).getByLabelText(
"矛盾、依赖与互有细节合并的处理方式",
);
expect(select).toHaveValue("auto_v1");
expect(
within(section).getByRole("option", { name: /^自主处理(默认)/ }),
).toBeInTheDocument();
expect(within(section).getByText(/每项都有操作记录和撤销入口/)).toBeVisible();
fireEvent.change(select, { target: { value: "report_only_v1" } });
expect(within(section).getByText(/只报告:矛盾和依赖复核/)).toBeVisible();
await userEvent.click(
within(section).getByRole("button", { name: "保存梦境整理设置" }),
);
await waitFor(() =>
expect(
requests.find(
(r) => r.url.endsWith("/consolidation") && r.init?.method === "PATCH",
)?.body,
).toEqual({ resolution: "report_only_v1" }),
);
await waitFor(() =>
expect(settings.consolidation.resolution).toBe("report_only_v1"),
);
expect(
await within(section).findByLabelText(
"矛盾、依赖与互有细节合并的处理方式",
),
).toHaveValue("report_only_v1");
});

test("处理方式可切换为自主处理,也可退回只报告", async () => {
settings.consolidation.resolution = "report_only_v1";
render(<SettingsPage />);
const section = await screen.findByRole("region", { name: "梦境整理设置" });
const select = within(section).getByLabelText(
"矛盾、依赖与互有细节合并的处理方式",
);
expect(select).toHaveValue("report_only_v1");
expect(within(section).getByText(/只报告:/)).toBeVisible();
expect(within(section).getByText(/只报告:矛盾和依赖复核/)).toBeVisible();
fireEvent.change(select, { target: { value: "auto_v1" } });
expect(within(section).getByText(/每项都有操作记录和撤销入口/)).toBeVisible();
await userEvent.click(
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/consolidation-labels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ export const consolidationBudgets = [
["persona_max_calls", "persona 每轮调用上限", 30],
] as const;
export const consolidationResolutions = [
["report_only_v1", "只报告(默认):不改动记忆,由管理员决定"],
["auto_v1", "自主处理:模型判断,程序按规则执行,事后可撤销"],
["report_only_v1", "只报告:不改动记忆,由管理员决定"],
["auto_v1", "自主处理(默认):模型判断,程序按规则执行,事后可撤销"],
] as const;
export const autoRelations: Record<string, string> = {
supersedes: "本人已更正,旧记忆进入遗忘",
Expand Down
2 changes: 1 addition & 1 deletion src/iris/consolidation.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
RESOLUTIONS = ('report_only_v1','auto_v1')
DEFAULTS = {'enabled': True, 'max_calls': 300,
'entry_profile_max_calls': 60, 'person_summary_max_calls': 40, 'persona_max_calls': 30,
'method': 'broad', 'resolution': 'report_only_v1',
'method': 'broad', 'resolution': 'auto_v1',
'merge_enabled': True, 'conflict_enabled': True, 'dependency_enabled': True,
'persona_enabled': True, 'goal_review_enabled': True, 'entry_profile_enabled': True, 'person_summary_enabled': True}
PROMPT_VERSION = 'consolidation_report_only_v2'
Expand Down
12 changes: 12 additions & 0 deletions src/iris/migrations/038_consolidation_default_auto.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-- consolidation_hidden_v3 passed: auto_v1 becomes the default resolution.
-- Upgrade only the untouched migration-written report_only_v1. Any administrator
-- save of consolidation settings, whatever it saved (including a save made only
-- to change a budget, a switch or the time), preserves the stored value. Run
-- snapshots stay fixed: an unfinished run keeps the resolution it accepted.
UPDATE runtime_settings
SET value_json=json_set(value_json,'$.resolution','auto_v1')
WHERE key='consolidation'
AND CASE WHEN json_valid(value_json) THEN json_extract(value_json,'$.resolution') END='report_only_v1'
AND NOT EXISTS (
SELECT 1 FROM admin_operations WHERE action='consolidation_settings_saved'
);
2 changes: 1 addition & 1 deletion src/iris/settings_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ class ConsolidationSettings(Input):
entry_profile_max_calls: int = Field(default=60, ge=0, le=1000, strict=True)
person_summary_max_calls: int = Field(default=40, ge=0, le=1000, strict=True)
persona_max_calls: int = Field(default=30, ge=0, le=1000, strict=True)
resolution: Literal['report_only_v1', 'auto_v1'] = 'report_only_v1'
resolution: Literal['report_only_v1', 'auto_v1'] = 'auto_v1'
merge_enabled: bool = Field(default=True, strict=True)
conflict_enabled: bool = Field(default=True, strict=True)
dependency_enabled: bool = Field(default=True, strict=True)
Expand Down

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/iris/web/index.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!doctype html>
<html lang="zh-CN">
<head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="theme-color" content="#6855a6" /><title>Iris · 相处与记忆</title> <script type="module" crossorigin src="/assets/index-B4TaaPaG.js"></script>
<head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="theme-color" content="#6855a6" /><title>Iris · 相处与记忆</title> <script type="module" crossorigin src="/assets/index-VjoATs1a.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-dGc5fORt.css">
</head>
<body><div id="root"></div></body>
Expand Down
Loading