# 06 - 模块划分与代码归属

> 更新说明（2026-04-17）：
> 本文档中历史上提到的 `UDS IPC` 方案，现已调整为跨平台的 `TCP loopback + 动态端口 + endpoint 文件发现 + token 鉴权`。
> Windows 不再依赖 Unix-only 能力，`tiangong-memory` 的 IPC 统一以 TCP 抽象实现。

## 原则

Memory 系统从一开始就作为独立 crate `tiangong-memory` 实现，不嵌入 `tiangong-core`。

理由：
1. Memory 是跨模式共享的基础设施（GUI、TUI、Server、CLI 都依赖），与 core 的智能体逻辑正交
2. Memory 拥有独立的 Actor 运行时、IPC 服务、Leader 选举等复杂机制，值得独立编译和测试
3. 独立 crate 让各入口（tiangong-cli、tiangong-server、src-tauri）直接依赖 `tiangong-memory`，无需经过 core 中转
4. 避免 core 包含过多基础设施代码，保持 core 专注于智能体编排逻辑

---

## Crate 结构

```
crates/
  tiangong-memory/           ← 【新增 crate】Memory 独立库
    Cargo.toml
    src/
      lib.rs                 ← crate 入口，导出公共 API
      types.rs               ← MemoryNode, MemoryKind, MemoryScope 等
      command.rs             ← MemoryCommand 枚举（消息协议定义）
      handle.rs              ← MemoryHandle（调用方持有的客户端句柄）
      actor.rs               ← MemoryActor（独立 tokio task 运行时）
      store.rs               ← 内存索引 + 磁盘读写（仅 Actor 内部访问）
      injection.rs           ← Injection 文件读写
      writer.rs              ← Episode/Decision 写入器
      recall.rs              ← Progressive Recall（Query 分析 + 双引擎召回 + 融合重排）
      rumination.rs          ← 反刍逻辑（Micro/Meso/Meta）
      search/                ← 【新增】双引擎检索
        mod.rs               ← 检索统一入口
        tantivy.rs           ← Tantivy 全文检索（BM25）
        qdrant.rs            ← Qdrant 向量语义检索
        reranker.rs          ← 融合重排器
        embedding.rs         ← Embedding 接口抽象
      db/                    ← 【新增】元数据库
        mod.rs               ← 数据库入口
        sqlite.rs            ← SQLite 加密连接、CRUD
        schema.rs            ← 建表、迁移
      ipc/                   ← 跨进程 IPC 服务端/客户端
        mod.rs
        server.rs            ← UDS 服务端
        client.rs            ← UDS 客户端
        protocol.rs          ← 帧协议、序列化
      election/              ← Leader 选举与迁移
        mod.rs
        registry.rs          ← ProcessRegistry、进程注册表
        heartbeat.rs         ← 心跳与健康检测
        watcher.rs           ← Follower 监控、自动重连

  tiangong-core/src/         ← 智能体核心逻辑（依赖 tiangong-memory）
    context/
      memory.rs              ← 【废弃】迁移到 tiangong-memory
      organizer.rs           ← 【修改】压缩完成后通过 Handle 发送反刍命令
    prompt/
      sections.rs            ← 【修改】build_user_context() 通过 Handle 查询
      assembler.rs           ← 【修改】通过 Handle 查询 Recall 结果
    workspace_index/         ← 【新增】工作区索引（Phase C+）
      mod.rs
      file_tree.rs
      symbol_index.rs        ← Phase D
```

### Cargo.toml 依赖关系

```toml
# crates/tiangong-memory/Cargo.toml
[package]
name = "tiangong-memory"
edition = "2024"

[dependencies]
tiangong-llm = { path = "../tiangong-llm" }   # Memory LLM + EmbeddingProvider + RerankProvider trait
tokio = { version = "1", features = ["sync", "time", "fs", "net", "rt"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
chrono = { version = "0.4", features = ["serde"] }
scru128 = "3"
anyhow = "1"
tracing = "0.1"
libc = "0.2"              # 进程存活检测
rusqlite = { version = "0.32", features = ["bundled-sqlcipher"] }  # SQLite 加密存储
sha2 = "0.10"              # 密码派生
hex = "0.4"                # hash 编码
tantivy = "0.22"           # 全文检索引擎
qdrant-client = "1"        # 向量检索 SDK
async-trait = "0.1"        # async trait 支持
```

```toml
# crates/tiangong-core/Cargo.toml
[dependencies]
tiangong-memory = { path = "../tiangong-memory" }
```

```toml
# crates/tiangong-cli/Cargo.toml
[dependencies]
tiangong-memory = { path = "../tiangong-memory" }
tiangong-core = { path = "../tiangong-core" }
```

```toml
# crates/tiangong-server/Cargo.toml
[dependencies]
tiangong-memory = { path = "../tiangong-memory" }
tiangong-core = { path = "../tiangong-core" }
```

```toml
# src-tauri/Cargo.toml (GUI)
[dependencies]
tiangong-memory = { path = "../crates/tiangong-memory" }
tiangong-core = { path = "../crates/tiangong-core" }
```

### 依赖方向

```
tiangong-cli ──┐
tiangong-server──┤──→ tiangong-core ──→ tiangong-memory
src-tauri ────┘         ↑                    ↑
                        │                    │
                        └────────────────────┘
                    （各入口也可直接依赖 memory）
```

> 各入口 crate 直接依赖 `tiangong-memory`，用于启动时初始化 `MemoryHandle`，然后将 Handle 传入 core。
> core 也依赖 `tiangong-memory`，用于类型引用和 Handle 调用。

---

## 各模块职责详述

### `lib.rs`

crate 入口，导出公共 API：

```rust
pub mod types;
pub mod command;
pub mod handle;
pub mod actor;
pub mod election;
pub mod ipc;
mod store;
mod db;
mod search;
mod injection;
mod writer;
mod recall;
mod rumination;

pub use handle::MemoryHandle;
pub use command::MemoryCommand;
pub use election::ProcessType;
pub use types::*;

/// 启动 Memory 系统，返回 Handle
/// 所有运行模式（GUI、TUI、Server、CLI）统一调用此函数
pub async fn start(
    workspace_id: Option<String>,
    process_type: ProcessType,
) -> anyhow::Result<MemoryHandle> {
    MemoryHandle::connect_or_spawn(workspace_id, process_type).await
}
```

> 外部 crate 只接触 `MemoryHandle`，不直接访问 `MemoryStore`。
> Store、Writer、Recall 等均为 Actor 内部私有模块。

### `command.rs`

定义 Actor 接收的消息协议，详见 [08-并发安全策略.md](08-并发安全策略.md)。

### `handle.rs`

调用方持有的客户端句柄，支持自动重连，可任意 `Clone` 跨线程传递：

```rust
#[derive(Clone)]
pub struct MemoryHandle {
    inner: Arc<RwLock<HandleInner>>,
}

enum HandleInner {
    Local { tx: mpsc::Sender<MemoryCommand> },
    Ipc { client: IpcClient },
}

impl MemoryHandle {
    /// 查询（async，等待响应）
    pub async fn load_injection(&self, session_id: &str, workspace_id: Option<&str>) -> Vec<String>;
    pub async fn recall(&self, anchors: RecallAnchors, limit: usize) -> Vec<RecallHit>;

    /// 写入（fire-and-forget，不阻塞）
    pub fn write_episode(&self, episode: Episode);
    pub fn run_micro_rumination(&self, turn_result: TurnResult);

    /// 启动与连接
    pub async fn connect_or_spawn(workspace_id: Option<String>, process_type: ProcessType) -> Result<Self>;

    /// 生命周期
    pub async fn shutdown(&self);
}
```

### `actor.rs`

Memory 独立运行时，串行处理所有命令：

```rust
pub struct MemoryActor {
    store: MemoryStore,    // 独占持有，无需锁
    cmd_rx: mpsc::Receiver<MemoryCommand>,
}

impl MemoryActor {
    pub fn spawn(workspace_id: Option<String>) -> Result<MemoryHandle>;
    async fn run(mut self);  // 事件循环
}
```

### `election/`

Leader 选举与迁移机制，详见 [08-并发安全策略.md](08-并发安全策略.md)。

### `ipc/`

跨进程通信层，UDS 服务端/客户端、帧协议、序列化。

### `types.rs`

所有 Memory 相关数据结构定义，详见 [01-记忆分层设计.md](01-记忆分层设计.md)。

### `store.rs`

替代现有 `tiangong-core/context/memory.rs`，协调三层存储：

```rust
use crate::db::SqliteStore;
use crate::search::{TantivyEngine, QdrantEngine, Reranker};

/// 当 embedding 未配置时，`qdrant` 为 `None`，向量相关操作自动跳过，
/// 召回降级为 Tantivy 单引擎模式。
pub(crate) struct MemoryStore {
    db: SqliteStore,                    // SQLite 元数据库
    tantivy: TantivyEngine,             // 全文检索引擎
    qdrant: Option<QdrantEngine>,       // 向量检索引擎（embedding 未配置时为 None）
    reranker: Reranker,                 // 融合重排器
    base_dir: PathBuf,
}

impl MemoryStore {
    pub fn open(base_dir: PathBuf) -> Result<Self> { ... }
    pub async fn write_memory(&mut self, node: &MemoryNode, embedding: Option<Vec<f32>>) -> Result<()> { ... }
    pub async fn dual_recall(&self, anchors: &RecallAnchors, intent: QueryIntent, limit: usize) -> Result<Vec<RecallHit>> { ... }
    pub fn flush(&mut self) -> Result<()> { ... }
}
```

> Store 不直接暴露底层引擎，由 Store 统一编排三层存储的读写和检索。

### `db/`

SQLite 加密元数据库，职责：MemoryNode CRUD、详细数据存储、生命周期管理。
详见 [07-持久化格式与技术选型.md](07-持久化格式与技术选型.md)。

### `search/`

双引擎检索子系统，包含四个子模块：

| 子模块 | 职责 |
|-------|------|
| `tantivy.rs` | Tantivy 索引创建、文档添加、BM25 查询 |
| `qdrant.rs` | Qdrant collection 管理、point upsert、语义查询 |
| `reranker.rs` | 分数归一化、时间衰减、重要度加权、双命中奖励、结果融合；Phase D 可接入 `tiangong-llm` 的 `RerankProvider` 进行模型精排 |
| `embedding.rs` | 封装对 `tiangong_llm::embedding::EmbeddingProvider` 的调用，提供 Memory 特化便捷方法（如 `embed_text` 单条封装） |

详见 [03-渐进式回忆.md](03-渐进式回忆.md) 和 [07-持久化格式与技术选型.md](07-持久化格式与技术选型.md)。
```

### `injection.rs`

三级注入读取，详见 [02-注入层设计.md](02-注入层设计.md)。

### `writer.rs`

Episode 写入器，接收 turn 执行结果，调用独立 Memory LLM 提取摘要。未配置 Memory LLM 时只能使用规则提取，不得静默复用主 `chat` 模型。

### `recall.rs`

锚点提取和粗召回，详见 [03-渐进式回忆.md](03-渐进式回忆.md)。

### `rumination.rs`

三级反刍实现，详见 [05-反刍与反思层.md](05-反刍与反思层.md)。

---

## 与其他 crate 的集成点

### 各入口 crate 初始化

```rust
// tiangong-cli/src/main.rs
use tiangong_memory::{MemoryHandle, ProcessType};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let memory = tiangong_memory::start(workspace_id, ProcessType::Cli).await?;
    // 将 handle 传入 core
    let core = TiangongCore::new(memory.clone(), ...);
    core.run().await?;
    memory.shutdown().await;
    Ok(())
}
```

```rust
// tiangong-server/src/main.rs
use tiangong_memory::{MemoryHandle, ProcessType};

async fn main() -> anyhow::Result<()> {
    let memory = tiangong_memory::start(workspace_id, ProcessType::Server).await?;
    // Server 作为长期运行进程，最适合持有 Leader
    let core = TiangongCore::new(memory.clone(), ...);
    // ...
}
```

### tiangong-core 集成

#### 1. `prompt/sections.rs`

```diff
- pub fn build_user_context(session_id: &str) -> Vec<String> {
-     let store = MemoryStore::load_from_disk();
-     store.format_for_context(session_id)
- }
+ pub async fn build_user_context(
+     session_id: &str,
+     memory: &tiangong_memory::MemoryHandle,
+ ) -> Vec<String> {
+     memory.load_injection(session_id, None).await
+ }
```

#### 2. `prompt/assembler.rs`

```diff
  // PromptAssembler 持有 MemoryHandle（Clone 成本极低）
+ memory_handle: tiangong_memory::MemoryHandle,

  async fn assemble(&self, ...) -> AssembledPrompt {
      ...
      let user_context = build_user_context(session_id, &self.memory_handle).await;
+     let recall_context = self.memory_handle.recall(anchors, 10).await; // Phase C
      ...
  }
```

#### 3. `context/organizer.rs`

```diff
+ memory_handle: tiangong_memory::MemoryHandle,

  async fn compress_if_needed(&mut self, ...) {
      ...
+     self.memory_handle.run_meso_rumination(session_id, workspace_id);
  }
```

#### 4. `runtime.rs`（EventLoopRunner）

```diff
+ memory_handle: tiangong_memory::MemoryHandle,

  async fn on_turn_completed(&mut self, turn_result: &TurnResult) {
      ...
+     self.memory_handle.run_micro_rumination(turn_result.clone());
  }
```

> 所有集成点通过 `MemoryHandle` 通讯，不直接访问 `MemoryStore`。
> 写入类操作使用 fire-and-forget，不 `.await`，不阻塞主流程。

---

## 迁移策略

### `context/memory.rs` 的废弃

1. Phase A 完成后，`tiangong-memory/injection.rs` 完全替代 `MemoryStore::format_for_context()`
2. 将 `context/memory.rs` 中仍在使用的 utility 函数迁移到 `tiangong-memory/store.rs`
3. 标记 `context/memory.rs` 为 `#[deprecated]` 并在 Phase B 完成后删除

### 无需 feature flag

由于 Memory 是独立 crate，各入口直接在 Cargo.toml 中控制是否依赖即可。不需要 feature flag——Memory 是所有模式的必要基础设施，不允许关闭。
