docs/plan/2026-07-13-agents-md-injection-tech-plan.md
Rama main · réplica de lectura del repositorio original.
# AGENTS.md 动态上下文注入 - 技术方案
## 1. 需求背景
当前 Agent 的系统提示词由 `PromptEngine` 动态构建,包含角色定义、工作环境、工具指南、技能目录等模块。为了支持用户自定义工作区级别的规则(如编码规范、项目约定、提交规范等),需要增加对 `AGENTS.md` 文件的读取和注入能力。
用户可以在项目工作区根目录下放置 `AGENTS.md` 文件,系统会自动将其内容作为"工作区规则"注入到系统提示词末尾,使 Agent 在执行任务时遵循这些自定义规则。
## 2. 需求描述
### 2.1 功能需求
| 编号 | 需求项 | 说明 |
|------|--------|------|
| F-1 | 读取 AGENTS.md | 当工作区根目录存在 `AGENTS.md` 文件时,读取其内容 |
| F-2 | 注入系统提示词 | 将文件内容追加到系统提示词末尾,标题为 `## 工作区规则` |
| F-3 | 文件不存在时不注入 | 如果工作区不存在 `AGENTS.md`,系统提示词保持不变 |
| F-4 | 内容截断 | 文件内容超过 200 行时,仅保留前 200 行,末尾追加提示:`当前仅展示前200行规则,读取AGENTS.md文件以了解更多规则。` |
| F-5 | CLOUD 模式支持 | 服务端直接读取云端工作区的 AGENTS.md 文件 |
| F-6 | LOCAL 模式支持 | 桌面客户端上报 AGENTS.md 内容,服务端缓存并注入 |
### 2.2 不做的内容
| 编号 | 排除项 | 说明 |
|------|--------|------|
| E-1 | 不支持自定义标题 | 固定使用 `## 工作区规则`,不提供配置项 |
| E-2 | 不支持子目录 AGENTS.md | 仅读取工作区根目录,不递归扫描子目录 |
| E-3 | 不做内容缓存 | 每次请求都重新读取,保证实时性 |
| E-4 | 不做文件格式校验 | 不校验 AGENTS.md 的 Markdown 语法,原样注入 |
| E-5 | 不支持其他文件名 | 仅支持 `AGENTS.md`,不支持 `AGENTS.txt` 等 |
## 3. 技术选型
### 3.1 现有架构分析
**核心组件**:
- `PromptEngine` - 系统提示词构建引擎,`buildSystemPrompt()` 方法组装完整提示词
- `HarnessService` - 会话执行服务,`buildContext()` 构建执行上下文
- `AgentExecutionContext` - 执行上下文,携带会话、Agent、工作区等信息
- `LocalSkillRegistry` - 本地 Skill 上报登记表(内存态),LOCAL 模式复用此模式
**系统提示词构建顺序**(`PromptEngine.buildSystemPrompt()`):
1. Agent 角色定义(systemPrompt)
2. 最佳实践经验(experiences)
3. 工作环境信息(workspace、platform、shell 等)
4. 当前时间
5. 工具使用指南(TOOL_USAGE_GUIDANCE)
6. 技能目录(skill catalog)
7. 任务管理行为指令
8. 子代理委派行为指令
9. **→ [新增] 工作区规则(AGENTS.md)**
### 3.2 技术方案选型
| 方案 | 优点 | 缺点 | 结论 |
|------|------|------|------|
| A. 仅服务端读取 | 实现简单 | LOCAL 模式不支持 | ❌ 不采用 |
| B. 桌面端上报 + 服务端注入 | 两种模式统一支持 | 需要修改 WebSocket 协议 | ✅ 采用 |
| C. Agent 主动读取 | 无需修改后端 | 不满足"系统提示词一部分"的需求 | ❌ 不采用 |
**选择方案 B**:复用现有的 `LocalSkillRegistry` 上报模式,创建 `LocalAgentsMdRegistry` 存储桌面端上报的 AGENTS.md 内容。
## 4. 实现步骤
### 4.1 CLOUD 模式实现
#### 4.1.1 修改 PromptEngine
**文件**: `backend/src/main/java/cn/etarch/mao/harness/core/PromptEngine.java`
在 `buildSystemPrompt()` 方法末尾(`appendDelegateToolHints` 之后)添加 AGENTS.md 注入逻辑:
```java
// 工作区规则(AGENTS.md)
appendWorkspaceRules(sb, context, effectiveWorkspace);
```
新增方法:
```java
private static final int AGENTS_MD_MAX_LINES = 200;
private static final String AGENTS_MD_TRUNCATED_HINT = "\n> 当前仅展示前200行规则,读取AGENTS.md文件以了解更多规则。\n";
/**
* 向 system prompt 注入工作区规则(AGENTS.md)
* CLOUD 模式:服务端直接读取文件
* LOCAL 模式:使用桌面端上报的内容
*/
private void appendWorkspaceRules(StringBuilder sb, AgentExecutionContext context, String effectiveWorkspace) {
String content = null;
if ("LOCAL".equalsIgnoreCase(context.getExecutionMode())) {
// LOCAL 模式:使用桌面端上报的内容
content = context.getAgentsMdContent();
} else {
// CLOUD 模式:服务端读取文件
Path agentsMdPath = Path.of(effectiveWorkspace, "AGENTS.md");
if (Files.exists(agentsMdPath) && Files.isRegularFile(agentsMdPath)) {
try {
content = Files.readString(agentsMdPath);
} catch (IOException e) {
log.warn("Failed to read AGENTS.md from workspace: {}", agentsMdPath, e);
}
}
}
if (content == null || content.isBlank()) {
return;
}
// 截断处理
String[] lines = content.split("\\R", -1); // 保留尾部空行
boolean truncated = lines.length > AGENTS_MD_MAX_LINES;
StringBuilder ruleContent = new StringBuilder();
for (int i = 0; i < Math.min(lines.length, AGENTS_MD_MAX_LINES); i++) {
ruleContent.append(lines[i]).append("\n");
}
if (truncated) {
ruleContent.append(AGENTS_MD_TRUNCATED_HINT);
}
sb.append("## 工作区规则\n\n");
sb.append(ruleContent);
sb.append("\n");
}
```
### 4.2 LOCAL 模式实现
#### 4.2.1 创建 LocalAgentsMdRegistry
**新建文件**: `backend/src/main/java/cn/etarch/mao/harness/core/LocalAgentsMdRegistry.java`
```java
package cn.etarch.mao.harness.core;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 会话级 AGENTS.md 内容上报登记表(内存态,仅 LOCAL 模式使用)。
* <p>
* 桌面客户端在 LOCAL 模式下发送消息时,附带读取到的本地 AGENTS.md 内容。
* 服务端缓存后在构建系统提示词时注入。
*/
@Slf4j
@Component
public class LocalAgentsMdRegistry {
/** sessionId → 最近一次上报的 AGENTS.md 内容 */
private final Map<Long, String> reported = new ConcurrentHashMap<>();
/**
* 上报(覆盖式)某会话当前的 AGENTS.md 内容。
* @param sessionId 会话 ID
* @param content AGENTS.md 文件内容,null 或空白表示文件不存在
*/
public void report(Long sessionId, String content) {
if (sessionId == null) return;
if (content == null || content.isBlank()) {
reported.remove(sessionId);
} else {
reported.put(sessionId, content);
}
}
/**
* 获取某会话最近一次上报的 AGENTS.md 内容(可能为 null)。
*/
public String get(Long sessionId) {
if (sessionId == null) return null;
return reported.get(sessionId);
}
/**
* 会话结束时清理,避免内存无限增长。
*/
public void clear(Long sessionId) {
if (sessionId != null) {
reported.remove(sessionId);
}
}
}
```
#### 4.2.2 修改 AgentExecutionContext
**文件**: `backend/src/main/java/cn/etarch/mao/harness/core/AgentExecutionContext.java`
添加字段:
```java
/** 桌面端上报的 AGENTS.md 内容(仅 LOCAL 模式) */
private String agentsMdContent;
```
#### 4.2.3 修改 HarnessService
**文件**: `backend/src/main/java/cn/etarch/mao/harness/core/HarnessService.java`
1. 注入依赖:
```java
private final LocalAgentsMdRegistry localAgentsMdRegistry;
```
2. 在 `buildContext()` 方法中,LOCAL 模式分支下获取上报内容:
```java
// LOCAL 模式:获取桌面端上报的 AGENTS.md 内容
if ("LOCAL".equalsIgnoreCase(context.getExecutionMode())) {
String agentsMdContent = localAgentsMdRegistry.get(sessionId);
context.setAgentsMdContent(agentsMdContent);
}
```
#### 4.2.4 修改 StreamingWsHandler
**文件**: `backend/src/main/java/cn/etarch/mao/session/ws/StreamingWsHandler.java`
1. 注入依赖:
```java
private final LocalAgentsMdRegistry localAgentsMdRegistry;
```
2. 在处理 `send_message` 和 `edit_and_resend` 消息时,解析 `agentsMdContent` 字段并上报:
```java
// 解析桌面端上报的 AGENTS.md 内容
String agentsMdContent = data.path("agentsMdContent").asText(null);
localAgentsMdRegistry.report(sessionId, agentsMdContent);
```
3. 会话结束时清理:
```java
localAgentsMdRegistry.clear(sessionId);
```
### 4.3 WebSocket 协议扩展
桌面客户端在 `send_message` 和 `edit_and_resend` 消息中新增可选字段:
```json
{
"type": "send_message",
"sessionId": 11,
"data": {
"content": "用户消息内容",
"localSkills": [...],
"agentsMdContent": "# 项目规则\n\n- 使用 TypeScript\n- 遵循 ESLint 规范\n..."
}
}
```
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| agentsMdContent | string | 否 | AGENTS.md 文件内容,文件不存在时传 null 或不传 |
## 5. 落地清单
### 5.1 后端开发任务
| 序号 | 任务 | 文件 | 优先级 |
|------|------|------|--------|
| 1 | 创建 LocalAgentsMdRegistry 类 | `backend/src/main/java/cn/etarch/mao/harness/core/LocalAgentsMdRegistry.java` | P0 |
| 2 | AgentExecutionContext 增加 agentsMdContent 字段 | `backend/src/main/java/cn/etarch/mao/harness/core/AgentExecutionContext.java` | P0 |
| 3 | PromptEngine 实现 appendWorkspaceRules 方法 | `backend/src/main/java/cn/etarch/mao/harness/core/PromptEngine.java` | P0 |
| 4 | HarnessService 注入 LocalAgentsMdRegistry 并在 buildContext 中使用 | `backend/src/main/java/cn/etarch/mao/harness/core/HarnessService.java` | P0 |
| 5 | StreamingWsHandler 解析并上报 agentsMdContent | `backend/src/main/java/cn/etarch/mao/session/ws/StreamingWsHandler.java` | P0 |
| 6 | 编写 LocalAgentsMdRegistry 单元测试 | `backend/src/test/java/cn/etarch/mao/harness/core/LocalAgentsMdRegistryTest.java` | P1 |
| 7 | 编写 PromptEngine 关于 AGENTS.md 注入的单元测试 | `backend/src/test/java/cn/etarch/mao/harness/core/PromptEngineTest.java` | P1 |
| 8 | 会话结束时清理 registry(StreamingWsHandler close 事件) | `backend/src/main/java/cn/etarch/mao/session/ws/StreamingWsHandler.java` | P1 |
### 5.2 桌面端开发任务
| 序号 | 任务 | 说明 | 优先级 |
|------|------|------|--------|
| 9 | 扫描工作区 AGENTS.md 文件 | 在用户选择的工作区根目录下查找 AGENTS.md | P0 |
| 10 | 读取文件内容 | 读取 AGENTS.md 内容,处理编码和异常 | P0 |
| 11 | 随消息上报 | 在 send_message / edit_and_resend 消息中附带 agentsMdContent 字段 | P0 |
| 12 | 文件监听(可选) | 监听 AGENTS.md 文件变化,实时更新上报内容 | P2 |
### 5.3 测试验证
| 序号 | 测试场景 | 预期结果 |
|------|----------|----------|
| T-1 | CLOUD 模式,工作区有 AGENTS.md | 系统提示词末尾包含 "## 工作区规则" 和文件内容 |
| T-2 | CLOUD 模式,工作区无 AGENTS.md | 系统提示词无变化 |
| T-3 | CLOUD 模式,AGENTS.md 超过 200 行 | 仅展示前 200 行,末尾有截断提示 |
| T-4 | CLOUD 模式,AGENTS.md 为空文件 | 系统提示词无变化 |
| T-5 | LOCAL 模式,桌面端上报 AGENTS.md | 系统提示词包含上报的内容 |
| T-6 | LOCAL 模式,桌面端未上报 | 系统提示词无变化 |
| T-7 | LOCAL 模式,上报内容超过 200 行 | 服务端截断并添加提示 |
| T-8 | 同一会话多次请求 | 每次都重新读取(CLOUD)或使用最新上报值(LOCAL) |
### 5.4 文档更新
| 序号 | 文档 | 更新内容 |
|------|------|----------|
| D-1 | WebSocket 协议文档 | 新增 agentsMdContent 字段说明 |
| D-2 | 用户使用手册 | 说明 AGENTS.md 的用途和使用方式 |
| D-3 | API 文档(如有) | 更新会话相关接口说明 |
## 6. 风险与注意事项
### 6.1 安全风险
| 风险 | 缓解措施 |
|------|----------|
| AGENTS.md 包含恶意指令(Prompt 注入) | 与系统提示词同等信任级别,用户对自己的工作区负责 |
| LOCAL 模式上报超大内容 | 服务端在截断逻辑前增加总字符数限制(建议 100KB) |
### 6.2 性能影响
| 场景 | 影响 | 说明 |
|------|------|------|
| CLOUD 模式文件读取 | 每次请求增加一次文件 IO | AGENTS.md 通常很小(<10KB),影响可忽略 |
| 系统提示词变长 | 增加 token 消耗 | 200 行约 2000-4000 tokens,需关注上下文窗口 |
### 6.3 兼容性
- 向后兼容:不传 agentsMdContent 字段时行为不变
- 桌面端需同步更新,否则 LOCAL 模式下此功能不生效
## 7. 后续扩展(不在本次范围)
| 扩展项 | 说明 |
|--------|------|
| 支持多级 AGENTS.md | 项目根目录 + 子目录,按优先级合并 |
| 支持 .mao/agents.md | 隐藏目录下的配置文件 |
| AGENTS.md 语法校验 | 检查 Markdown 格式,给出警告 |
| Web UI 编辑器 | 在界面上直接编辑 AGENTS.md |