Skip to main content
Glama
README.md
# 共享项目库

> **多智能体共用的项目状态控制平面。**
> 不同 AI 会话之间不共享聊天记录,只共享**结构化结论**——谁在做什么、做到哪、踩过什么坑、代码长什么样。

![面板](docs/02-panel-open.png)
<p align="center"><sub>配套的悬浮球面板 —— 人只需要看:这个项目拆成几个任务、完成几个</sub></p>

## 它解决什么问题

一个 AI 会话的上下文是临时的,一关就没了。多个会话要接力做一个项目时,传统做法是把聊天记录复制来复制去——
既费 token 又不准确,而且"踩过的坑"永远传不下去。

**这个库把项目状态放在会话之外**:新会话一进来读一次库,就知道前面发生了什么、现在该干什么、哪里是雷区。

## 两个消费者,一份事实来源

```mermaid
flowchart LR
    PG[(PostgreSQL<br/>唯一事实来源)]
    AI[AI 会话们<br/>读结构化数据]
    YOU[你<br/>看极简进度]
    PANEL[悬浮球面板]

    PG -->|MCP 工具| AI
    AI -->|写结论| PG
    PG -->|SSE 毫秒推送| PANEL
    PANEL --> YOU
    YOU -.->|复制接续块| AI
```

| 消费者 | 看到什么 | 怎么用 |
|---|---|---|
| **AI 会话** | 任务合同、检查点、踩过的坑、代码地图、决策、交接 | MCP 工具读写(`project_context_pack` / `project_checkpoint` / ...) |
| **人** | 项目名 · 状态 · `完成/总数` | [悬浮球面板](https://github.com/Paeonia-wh/shared-lib-panel):点任务复制一句自带指令的文本 |

## 核心概念

| 概念 | 是什么 |
|---|---|
| **Project** | 一个项目(有 root_path、隔离的数据域) |
| **Task** | 可领取的工作单元(有合同、依赖、租约、负责人) |
| **Contract** | 任务的验收标准(干成什么样算完) |
| **Checkpoint** | 我干完了什么 / 没干什么 / **踩过什么坑** / 下一步 |
| **Code Map** | 代码的结构化地图(模块、职责、调用关系、新不新鲜) |
| **Artifact** | 真实产出的文件(只声明路径不算,对账会判 rejected) |
| **Handoff / Reconciliation** | 交接说明 / 独立对账结论 |


---

**English** — A **project state control plane** shared by multiple AI agent sessions. Sessions don't share chat
logs; they share *structured conclusions*: what's being worked on, what's done, which pitfalls were hit, and what
the code looks like. One source of truth (PostgreSQL), two consumers: agents read structured data via MCP tools,
humans watch a minimal progress panel ([shared-lib-panel](https://github.com/Paeonia-wh/shared-lib-panel)).

See [docs/SETUP.md](docs/SETUP.md) to get it running.

## 配套面板(另一个仓库)

**[shared-lib-panel](https://github.com/Paeonia-wh/shared-lib-panel)** —— 悬浮球形态的进度面板:

| 开机 | 点开 | 任务级 |
|---|---|---|
| ![球](docs/01-ball-only.png) | ![面板](docs/02-panel-open.png) | ![任务](docs/03-tasks.png) |

> ℹ️ 上面三张是**用面板自己的渲染函数 + 自己的样式表**画出来的(含真引擎出的球),
> **项目数据是编的**(不是真项目)—— 结构、配色、字号就是面板真实的样子。

面板**只读**:它不写库,只负责「复制一段指令给你粘给 AI」;AI 干完写库,面板通过 SSE 自己刷新。
角落那颗球本身也有反馈:库里发生事它动一下、整体状况(有活在跑/有卡住/全闲)变化时它的呼吸和视线跟着变。

## 快速开始

```bash
git clone https://github.com/Paeonia-wh/codex-memory.git
cd codex-memory/repo

./scripts/start-memoryd-silent.ps1     # 起 PostgreSQL + memoryd(手工排障用)
#    ⚠ 不要用 scripts/start-services.ps1 —— 它**已废弃**:
#      它走 pg_ctl(会拉起一个终端窗口),而且传参不对会**静默失败**
#      (实测在 pg-silent.err.log 里留下过 "pg_ctl: 命令行参数太多")。
#    开机自启走 scripts/autostart.pyw(计划任务 CodexMemoryAutostart 调用它)。
```

环境搭建见 **[docs/SETUP.md](docs/SETUP.md)**;给 AI 会话看的用法见下方。

---

# 平台内部:多智能体项目知识与状态平台

> 上面那半是**给使用者的**(它是什么、怎么跑起来)。
> 从这里往下是**开发者文档** —— 实现细节、工具清单、写入边界、体检机制。

供 Codex 与 DeepSeek 多会话共享的本地项目知识、任务状态和证据服务。运行目录固定在 D:\codex-memory;原始文件只读,凭据不会进入 Git。

> **用户手册在 `D:\codex-memory\README.md`** —— 讲这是什么、别的会话怎么用、以及怎么把一个已有项目迁进来。

第一版核心模型:

- 一个项目可以注册多个 Codex/DeepSeek 会话;每个会话独立领取任务,但共享 PostgreSQL 项目状态。
- Project、Task、Session、Run、Checkpoint、Event、Artifact、Decision、Handoff 都带项目边界和来源。
- Obsidian 只是 D 盘上的人类可读派生投影;PostgreSQL 是协调状态源,MCP 是 Agent 入口。
- 会话上下文是临时的;任务状态、检查点、决策和交付物是可恢复的长期状态。

## 运行

项目使用 D 盘独立 Python 3.12 环境。`scripts/bootstrap.ps1` 设置进程缓存后安装环境;`scripts/run.ps1` 启动本地服务。依赖版本由安装完成后的锁定清单记录。

## 对话使用(0.2)

本产品没有网页。通过Codex说“列出待确认记忆”“解释这条记忆”“确认/拒绝这条”“纠正它”“设置两天后过期”或“预览知识库清理”即可使用相应MCP工具。确认以你批准的具体内容为准;算法反馈不能替你确认。

同作用域、同类型的相同内容会去重;候选默认30天过期,每作用域最多200候选、2000活动记忆。运行数据采用50GiB写入预算,另保留D盘100GiB余量。知识不足或预算不足会显式报告。

清理默认仅预览,应用前自动创建加密备份;删除失效向量及30天以上未被引用的非活动文本块。原文件、记忆和检查点不在清理目标内。此版本不宣称自动学习模型参数或完整语义纠错。

## 共享项目库使用

这里的“共享项目库”指项目协作状态本身:项目、任务、合同、会话、检查点、产物、交接、决策和代码地图。
它和资料检索/证据 catalog 是两套边界:资料不会因为被检索到就变成项目事实,项目会话也不能把聊天原文或私有推理直接写进项目状态。

项目状态通过 `project_bootstrap`、`project_create`、`project_for_path`、`project_baseline_capture`、`project_session_register`、`project_sessions`、`project_task_create`、`project_task_contract`、`project_task_claim`、`project_ready_tasks`、`project_task_dispatch`、`project_task_assign`、`project_lease_sweep`、`project_workspace_register`、`project_workspace_create`、`project_workspace_release`、`project_artifact_publish`、`project_context_pack`、`project_checkpoint`、`project_session_heartbeat`、`project_handoff`、`project_reconcile`、`project_plan_propose`、`project_plan_review`、`project_control_set`、`project_run_step_start`、`project_run_step_finish`、`project_overview`、`memory_primary_status`、`memory_primary_set` 和 `project_export_obsidian` 管理。第一版只允许 `codex` 和 `deepseek` 两类会话;同一类模型可以注册多个会话。

项目登记后可调用 `project_baseline_capture` 保存低基数基线:代码根目录、Git 分支、HEAD、工作树是否干净、变更文件数量,以及共享状态对象数量。它只记录数量,不写入文件名、凭据或聊天内容。`project_code_map_write` 可附带 Git commit SHA;读取代码地图时会返回最近地图版本和更新时间。

迁入带 `root_path` 的代码项目时,`name` 是给用户看的显示名称,必须包含中文(例如“农业局项目”);`project_key`、任务键和会话标签不受此限制,供不同 AI 或不同会话内部命名。任务进入 `done`、`failed` 或 `cancelled` 后,服务会自动捕获基线、刷新 D 盘投影并记录 `task_auto_synced`,不需要用户额外提醒。**终态检查点只在「该任务一条检查点都没有」时才会自动补一条空壳** —— 已经有检查点就不再补(所以「标 done 一定会有检查点」是错的;真正记录过程的是会话自己写的检查点,见下面「会话级写入边界」)。

索引维护先调用 `memory_source_scope_audit` 查看作用域污染,再用 `memory_source_scope_reconcile(dry_run=true)` 预览。确认后才用 `dry_run=false`:它会停用越界来源、修正历史会话作用域、重标记向量并清理陈旧派生向量,但不会删除原始文件。

正式来源 allowlist 保存在 `D:\\codex-memory\\data\\sources.json`;每个项目必须使用独立 source root/scope,禁止把整个 `D:\\codex` 作为某一个业务项目的来源。

`project_export_obsidian` 会在项目投影中生成 `CODE_MAP.md` 和 `BASELINE.md`,并从项目 `README.md` 链接到它们。PostgreSQL 仍是事实源,投影可以删除后重建。

`project_task_dispatch` 有两种语义:传 `task_key` 时精确领取那一个任务(多个 worker 各领各的模块,不会抢同一个队列);不传时先领指派给自己的任务,再领无人认领的,但绝不抢占**租约仍然有效**的任务。⚠ 三种情况下「不抢」不成立:① 对方的租约已过期(那是设计如此,租约过期即回收);② 任务处于 `blocked`/`review` 且无人持有(那时谁都能接手,包括你知道 `task_key` 直接指定);③ `assignee_label` 指向一个**不存在的会话名**时,预留视为失效、任务回到自由队列。`project_task_assign` 负责建立这种预留,`project_sessions` 让总控看见已经注册的执行会话及其 session ID。

`memory_search` 的 `hybrid` 默认走全文和向量召回;需要图片/截图相似度时显式传 `visual=true`,避免普通文本问题加载图像模型。
带标注问题集时使用 `memory_evaluate` 或 `scripts/evaluate.py` 生成 Recall、引用定位和声明支持率报告;没有标注时系统返回 `not_configured`。

DeepSeek Harness 的自定义 profile 还注册了 `codex_memory_tool`。因此只使用 DeepSeek 时,它可以直接调用共享平台的项目、任务、Checkpoint、Artifact、Context Pack 和交接工具;该桥现在**已开放创建任务、计划提案与复核、任务指派、补记已完成任务(`project_task_reopen`)**——不开放这些会让会话「平台内无路可走」而只能改数据库(实测发生过)。仍不开放:验收对账的最终裁定、架构图导出、投影写入、强制释放工作区等高风险动作。已领取任务的 DeepSeek 会话可以补充自己任务的合同、记录绑定任务的决策、提交待验收对账,以及合并自己的代码地图片段;这些写入受服务端会话/任务归属校验保护。唯一例外是用户明确从该入口新建项目时使用一次 `project_bootstrap`;bootstrap 会一次性写入有界初始计划,随后锁定结构化计划。

结构化计划变更不能直接覆盖:会话先用 `project_plan_propose` 写待审提案,审核会话用 `project_plan_review` 以 `plan_version` compare-and-swap 提交或拒绝。`project_control_set` 可以暂停或归档项目而不删除状态;暂停时不能领取任务、启动运行或创建工作区。运行中的副作用步骤使用 `project_run_step_start/finish`,成功步骤重试会复用结果。

Codex MCP 工具会从 FastMCP 连接 Context 自动生成项目级 session principal,并在第一次写入前登记会话,减少模型手填 session ID 的误用。计划审批的本地 loopback 面板由 `scripts/project_review_panel.py` 提供,浏览器不接触 memoryd service token。

### 会话级写入边界

每个 Codex 或 DeepSeek **会话实例**都是独立主体;不能只按模型类型判断权限。同一个模型的不同会话也必须各自领取任务、续租和写自己的检查点。

- **检查点只允许任务的 owner 写**,且任务必须处于 `running`/`review`。(这条现在是**服务端硬门禁**:非 owner 写入会收到 `PermissionError`。审计实测过 24 条越权写入 —— 一个任务被别的会话写了 9 条,而检查点是项目摘要喂给每个新会话的唯一状态来源,所以这道门是必需的。)
- 代码地图写入要求**在这个项目里干过**(拥有任务,或近 7 天内有过检查点/事件);整张 `replace=true` 重写不是普通 worker 操作。
- 合同和决策必须记录创建会话;`verified` 对账必须由不是任务负责人的独立会话完成。
- DeepSeek 桥默认不开放架构图导出、Obsidian 投影和项目 `AGENTS.md` 写入;架构图只在用户明确要求时由受控会话生成。
- 租约默认 15 分钟(`project_session_heartbeat` 可续,最长 24 小时)。过期后 `project_lease_sweep` 会把 `running` 任务退回 `pending` 并复位会话。**但它对 `blocked`/`review` 不适用** —— 那两类由「孤儿回收」处理:无主且卡在非终态超过 24 小时才回队列;另外声称 `working` 但心跳超过 2 小时没动的会话会被回落到 `idle` 并释放任务。
- 终态自动同步会记录代码地图新鲜度:`fresh`、`stale`、`unversioned` 或 `missing`。地图不新鲜时,交接投影和 Context Pack 会明确警告,不能把它当作当前架构事实。
- 说明书是行为提示,不是安全边界;真正的拒绝在共享服务端执行。

任务合同、工作区、产物、Checkpoint 和对账记录进入共享状态。**产物登记的是路径,而文件可能被归档/移动/改写** —— 所以后台每 60 秒复检一遍,把每个产出的状态推进到终态:`produced`(在)/ `missing`(文件没了)/ `drifted`(文件还在但内容变了 —— 靠发布时记的 SHA-256 判)。Context Pack 里不可信的产出会带 `[MISSING]` / `[DRIFTED]` 标记,**别拿它当证据**(实测 54 条产出里 21 条指向的不是文件)。此外:完整聊天记录、私有推理和未发布文件不会自动进入共享层。每次 `project_context_pack` 都会同时返回项目摘要和结构化代码地图(节点、边、版本、新鲜度),因此接力会话会先读代码地图再进入任务;地图为 `stale`、`unversioned` 或 `missing` 时必须先核对,不能当成当前架构事实。`remote-approved` 是唯一允许进入远程模型 Context Pack 的产物可见性。`project_deepseek_run` 只连接本地 DeepSeek Harness `/chat`,不会回退到直接 API。

项目状态和生产证据 catalog 都使用 D 盘本机 PostgreSQL 的独立 `codex_memory` 数据库;连接由 `CODEX_MEMORY_DATABASE_URL` 指定,catalog 可用 `CODEX_MEMORY_CATALOG_DATABASE_URL` 覆盖。首次切换会保留并迁移 `D:\codex-memory\data\catalog.sqlite`,不会删除原文件。Obsidian 投影写入 `D:\codex-memory\vault\projects`,可随时删除后从 PostgreSQL 重建。

## 平台自己的体检机制(不变量)

这一整轮修的问题几乎都是**同一个病:守卫写在了错误的一层**。三个真实例子:

- `_why_not_claimable()` 是个只读的排障提示,可它在"说"任务不可领 ——
  而真正决定能不能领的是 `claim_task()`。两者一度互相矛盾。
- daemon 的写入门禁里有"任务 owner 不能自批对账"的守卫,而 store 层的
  `reconcile()` **没有** —— 直接调 store 就绕过了。
- `reap_expired_leases` 的 WHERE 只匹配 `status='running'`,
  于是 `blocked` + 无租约的任务**永远清不掉**(实测挂过 23 小时)。

共同点:**规则写在了「提示」里、「门」里、或某一条 SQL 里,而不是被守卫的操作本身。**

`codex_memory/invariants.py` 给每条不变量标注**由哪一层强制**:

| `enforced_layer` | 含义 | 可信度 |
|---|---|---|
| `db_constraint` | 数据库约束 | 最强,绕不过 |
| `db_trigger` | 数据库触发器 | 强 |
| `app_write_path` | 应用层写入路径(store 方法里的校验) | 中,绕过 store 就没 |
| `prompt_only` | **只写在提示词/文档里** | 最弱,实际没人守 |

每条除了声明层,还带一条**可执行的只读 SQL 检查**(查出违规行数,0 = 通过)。
于是它有两个用途:

- **运行时哨兵**:daemon 后台每 60 秒跑一遍,有违规记 `invariant_violated` 事件
- **CI 断言**:`python scripts/check_invariants.py`(退出码非 0 即拦下)

**基线机制**:规则是新加的,而库里已经有规则之前写的行。
每条不变量可以带 `baseline`(已知历史欠账条数)——
违规数 ≤ baseline 报"历史欠账"(不拦 CI),超出才是"新违规"(失败)。
这样既诚实(欠账一直看得见)又可用(新问题会红)。
**baseline 只许调小,不许调大**,和 `prompt_only` 的纪律一样。

随手查:`project_invariants` 工具返回登记册摘要 + 当前所有违规。

## 交付与验收

详见 `docs/archive/PLAN.md` 和 `docs/archive/STATUS.md`(这两份已归档;当前状态以 `project_overview` 与 `project_invariants` 的实时输出为准)。未通过真实验收的能力不能宣称完成;搜索相似度不是事实正确概率。审计页检查索引状态与未完成能力。

Maintenance

ActivityMaintained
ResponsivenessNo issues