# 02 - 注入层设计（Injection Layer）

## 概述

Injection 层为不同运行主体提供"压缩后的稳定注入上下文"，在以下场景使用：

- 新会话启动时作为背景知识
- 长对话压缩后继续保留关键上下文
- 子 Agent 派生时传递必要背景
- 技能执行前快速注入项目约束

## 与现有代码的对接

**当前入口**：`prompt/sections.rs::build_user_context()` → 调用 `context/memory.rs::MemoryStore`

**改造方案**：替换 `MemoryStore::format_for_context()` 为 Injection 层的三级文件读取。

```rust
// prompt/sections.rs（改造后）
pub fn build_user_context(session_id: &str, workspace_id: Option<&str>) -> Vec<String> {
    crate::memory::injection::load_injection_context(session_id, workspace_id)
}
```

## 三级注入设计

### Level 1: Profile 注入

**职责**：用户长期不变的偏好和规则。

**文件路径**：`~/.tiangong/memory/profile/agent.md`

**内容规范**：
- 只保留高价值、长期稳定的信息
- 短句、结构化，可直接拼接进 Prompt
- 体积上限：500 tokens

**示例**：

```markdown
# Profile
- 默认使用简体中文回复
- 主要使用 Rust 开发，偶尔使用 Python
- 前端通常使用 Vue
- 优先给出工程可落地方案
- ID 使用 scru128，不使用 UUID
- 时间使用本地时间 chrono::Local::now().naive_local()
```

---

### Level 2: Workspace 注入

**职责**：当前工作区/项目的稳定背景。

**文件路径**：`~/.tiangong/memory/workspaces/<workspace_id>/agent.md`

**内容规范**：
- 项目目标、架构约束、阶段重点
- 体积上限：800 tokens

**示例**：

```markdown
# Workspace: 天工智能体
- 语言: Rust (edition 2024)，Cargo workspace 多 crate
- 框架: Tauri + React (GUI) / ratatui (TUI) / silent (Server)
- 当前阶段: Phase 17 多媒体结果语义收敛
- 存储位置: ~/.tiangong/
- 检查命令: cargo check --workspace
- 提交规范: conventional commits，不跳过 pre-commit hooks
```

---

### Level 3: Session 注入

**职责**：当前任务/近期会话的高时效信息。

**文件路径**：`~/.tiangong/memory/sessions/<session_id>/agent.md`

**内容规范**：
- 当前目标、最近决策、待办事项
- 体积上限：700 tokens
- 对话压缩后自动更新

**示例**：

```markdown
# Session
- 当前任务: 实现 Memory 系统 Phase A（Injection 层）
- 已决定: Memory 作为 tiangong-core 内部模块，不独立 crate
- 已完成: 文档拆分为 docs/memory-system/ 目录
- 下一步: 修改 build_user_context() 读取注入文件
```

---

## 组合注入顺序

```
Profile agent.md      ← Level 1
  + Workspace agent.md  ← Level 2
  + Session agent.md    ← Level 3
```

### 预算超限时的裁剪

当三层注入总量超过分配的 token 预算时，按以下优先级裁剪：

1. **优先保留** Session 级（最高时效价值）
2. **其次保留** Workspace 级
3. **最后保留** Profile 级

裁剪策略：按行逐条移除，从最低优先级层的末尾开始。

---

## 读取实现

```rust
// memory/injection.rs

use std::fs;
use std::path::PathBuf;

/// 加载三级注入上下文
pub fn load_injection_context(
    session_id: &str,
    workspace_id: Option<&str>,
) -> Vec<String> {
    let mut ctx = Vec::new();
    let base = memory_base_dir();

    // Level 1: Profile
    let profile_path = base.join("profile").join("agent.md");
    if let Some(text) = read_injection_file(&profile_path) {
        ctx.push(text);
    }

    // Level 2: Workspace
    if let Some(wid) = workspace_id {
        let ws_path = base.join("workspaces").join(wid).join("agent.md");
        if let Some(text) = read_injection_file(&ws_path) {
            ctx.push(text);
        }
    }

    // Level 3: Session
    let session_path = base.join("sessions").join(session_id).join("agent.md");
    if let Some(text) = read_injection_file(&session_path) {
        ctx.push(text);
    }

    ctx
}

fn read_injection_file(path: &PathBuf) -> Option<String> {
    let content = fs::read_to_string(path).ok()?;
    let trimmed = content.trim();
    if trimmed.is_empty() {
        None
    } else {
        Some(trimmed.to_string())
    }
}

fn memory_base_dir() -> PathBuf {
    dirs::home_dir()
        .unwrap_or_else(|| PathBuf::from("."))
        .join(".tiangong")
        .join("memory")
}
```

---

## 自动生成与更新

注入文件不建议完全手写，应由系统自动生成并允许人工修订。

### 更新触发时机

| 触发事件 | 更新层级 | 更新方式 |
|---------|---------|---------|
| 对话压缩后 | Session | 自动：从压缩摘要中提取 |
| 任务结束后 | Session + Workspace | 自动：更新已完成/下一步 |
| 阶段里程碑 | Workspace | 半自动：系统建议，用户确认 |
| 用户偏好变更 | Profile | 手动 / 系统检测到偏好变化时建议 |

### 版本保护

为防止自动更新覆盖用户手动修改：

```rust
/// 注入文件头部添加 hash 标记
/// 如果文件内容与 hash 不匹配（说明用户手动修改过），跳过自动更新
const INJECTION_HEADER: &str = "<!-- tiangong-auto:";

pub fn should_auto_update(path: &Path) -> bool {
    if let Ok(content) = fs::read_to_string(path) {
        if let Some(first_line) = content.lines().next() {
            if first_line.starts_with(INJECTION_HEADER) {
                // 提取 hash，与当前内容对比
                let stored_hash = extract_hash(first_line);
                let body = content.lines().skip(1).collect::<Vec<_>>().join("\n");
                let actual_hash = compute_hash(&body);
                return stored_hash == actual_hash; // 未被修改则允许更新
            }
        }
    }
    false // 文件不存在或无标记时，允许创建
}
```

---

## 内容风格要求

注入文件必须遵循：

- 只保留高价值信息
- 使用短句，避免冗余叙述
- 结构化（Markdown 列表）
- 可直接拼接进 Prompt
- 不包含原始长日志或代码块
