Skip to content
Open
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ client-tests.txt
desktop-test.txt
h-test.txt
test-fallback.txt
test2.txt
tsc-out.txt
arch-out.txt
vitest-result.json

# Compiled JS artifacts leaked into src directories
packages/infra/src/*.js
Expand All @@ -51,3 +55,4 @@ packages/desktop/pnpm-workspace.yaml
Thumbs.db
.idea/
.vscode/
.workbuddy/memory/
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ node_modules/
package-lock.json
pnpm-lock.yaml
*.md
vitest-result.json
test-output*.txt
3 changes: 2 additions & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100
"printWidth": 100,
"endOfLine": "auto"
}
24 changes: 1 addition & 23 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,7 @@ context:
memory:
enabled: false # 启用长期记忆
model: "" # 记忆提取模型,空字符串回退主模型
maxBytes: 16384 # 记忆文件最大字节数
promptMaxBytes: 8192 # 注入提示的最大字节数
extraTypes: [] # 自定义记忆类型
disabledTypes: [] # 禁用的记忆类型名
promptMaxBytes: 8192 # 注入提示的记忆内容最大字节数
```

### 字段详细说明
Expand All @@ -57,26 +54,7 @@ memory:
| `context.compactionModel` | `''` | 上下文压缩使用的模型,空字符串回退到主会话 LLM |
| `memory.enabled` | `false` | 是否启用长期记忆系统 |
| `memory.model` | `''` | 记忆提取使用的模型,空字符串回退到主模型 |
| `memory.maxBytes` | `16384` | 单个记忆文件的最大字节数 |
| `memory.promptMaxBytes` | `8192` | 注入 system prompt 的记忆内容最大字节数 |
| `memory.extraTypes` | `[]` | 自定义记忆类型列表 |
| `memory.disabledTypes` | `[]` | 禁用的内置记忆类型名列表 |

### 自定义记忆类型示例

```yaml
memory:
enabled: true
extraTypes:
- name: feedback
description: 工作流程中的教训和已验证的方法
enabled: true
- name: decision
description: 重要的架构和设计决策
enabled: true
disabledTypes:
- reference
```

---

Expand Down
120 changes: 39 additions & 81 deletions docs/memory.md
Original file line number Diff line number Diff line change
@@ -1,116 +1,74 @@
# 长期记忆系统

Coding Code 支持跨会话的长期记忆,自动从对话中提取和存储关键信息。本文档介绍记忆类型、内容分类、自动提取机制和手动编辑方法。
Coding Code 支持跨会话的长期记忆:自动从对话中提取关键信息,并在下一次会话开始时重新注入。本文档介绍记忆文件、自动提取机制和手动编辑方法。

---

## 内存类型
## 记忆文件

记忆文件存储在项目的 `.codingcode/memory.md` 中。
记忆存储为单个 Markdown 文件:

---

## 记忆内容
```
.codingcode/memory.md
```

内置三种记忆类型:
**整个文件就是长期记忆**,没有分区、没有标记块。文件的全部内容会作为记忆注入,也会作为"已有记忆"参与下一次提取。

| 类型 | 提取来源 | 内容 |
|------|---------|------|
| `user` | `[user]` 标签的消息 | 用户角色、技能栈、工作偏好及对 Agent 的纠正 |
| `project` | `[user]` + `[assistant]` 消息 | 架构决策、技术选型、部署信息 |
| `reference` | `[user]` + `[tool:*]` 消息 | 外部资源、文档、Dashboard 链接 |
```markdown
### 项目
- 采用 monorepo 架构,使用 pnpm workspaces
- 入口文件:packages/codingcode/src/cli.ts

可通过 `memory.extraTypes` 添加自定义记忆类型,通过 `memory.disabledTypes` 禁用内置类型。
### 用户偏好
- 偏好结构化 Markdown 输出
```

---

## 自动提取

Agent 在每次会话后自动执行记忆提取
记忆模式开启后,Agent 在会话结束时自动执行记忆更新

1. 构建 system prompt,包含各记忆类型的提取指引
2. 发送已有记忆 + 会话记录给 LLM
3. LLM 输出 `<memory>...</memory>` 块
4. 提取块内容,返回新记忆文本(null 表示无新内容)
5. 矛盾时新信息替换旧条目,同一会话以最新为准
1. 读取记忆文件全文作为"已有记忆"
2. 将会话记录(按 `[user]` / `[assistant]` / `[tool:名称]` 标注)与已有记忆一起发送给 LLM
3. LLM 输出整份**最新版记忆**,放在 `<memory>...</memory>` 块中
4. 直接用输出内容整体替换记忆文件(受字节上限约束)

提取使用的模型可通过 `memory.model` 配置,留空则回退到主会话模型。

---
模型自行决定更新哪些内容:可以新增条目、修改过时信息、删除不再相关的内容,代码不做"模型只改动哪部分"的任何假设。若模型没有输出有效内容、或输出与当前文件一致,则不写入。

## 记忆文件格式
### 提取提示词

记忆文件使用 Markdown 格式,自动提取内容包裹在标记块中
提取行为的规范全部写在提示词中,代码不感知记忆内容结构

```markdown
<!-- auto:begin -->
### user
- 偏好使用函数式编程风格
- 常用技术栈:React + TypeScript
- 只保留值得跨会话记住的信息:用户偏好与纠正、项目架构决策、技术选型、外部资源与链接等
- 忽略一次性任务、调试过程、报错堆栈、闲聊
- 输出必须是一份完整、自洽的最新记忆,而不是只输出变动部分
- 新旧信息矛盾时以最新为准
- 记忆用 `### 主题` 小节组织,小节下用 `- ` 列要点

### project
- 采用 monorepo 架构,使用 pnpm workspaces
- 入口文件:packages/codingcode/src/cli.ts
提取使用的模型可通过 `memory.model` 配置,留空则回退到主会话模型。

### reference
- [API 文档](https://example.com/api)
<!-- auto:end -->
---

手动添加的内容可以写在标记块之外,不会被自动提取覆盖。
```
## 手动编辑

### 标记块机制
记忆文件就是普通 Markdown,用户可以直接编辑:

- `replaceAutoBlock()`:原子替换 `<!-- auto:begin -->` 和 `<!-- auto:end -->` 之间的内容
- `stripMarkersForPrompt()`:去掉标记后注入系统提示
- `enforceMaxBytes()`:按 `### ` 小节逐个裁剪到字节上限(默认 16384 字节)
- `mergeAutoBlocks()`:以 `### ` 小节名为 key 合并,incoming 覆盖 base
- 手动写下的内容会在下次会话时作为记忆注入 Agent
- 手动编辑也会被下一次自动提取作为"已有记忆"读到;保留、修改还是删除由模型根据后续对话自行决定
- 自动提取在写入前会重新检查文件:若提取期间文件被手动改动,则放弃本次写入,避免覆盖用户编辑

---

## 配置

在 `codingcode.yaml` 中配置记忆系统:

```yaml
memory:
enabled: true # 启用长期记忆(默认 false)
model: "" # 记忆提取模型,空字符串回退到主模型
maxBytes: 16384 # 记忆文件最大字节数
promptMaxBytes: 8192 # 注入提示的最大字节数
extraTypes: [] # 自定义记忆类型
disabledTypes: [] # 禁用的记忆类型名
```

### 自定义记忆类型
在 `~/.codingcode/config.yaml` 中配置记忆系统:

```yaml
memory:
enabled: true
extraTypes:
- name: feedback
description: 工作流程中的教训和已验证的方法
enabled: true
- name: decision
description: 重要的架构和设计决策
enabled: true
disabledTypes:
- reference # 禁用内置的 reference 类型
enabled: true # 启用长期记忆(默认 false)
model: "" # 记忆提取模型,空字符串回退到主模型
promptMaxBytes: 8192 # 注入提示词的记忆内容最大字节数
```

---

## 手动编辑

记忆文件采用 Markdown 格式,支持手动编辑。手动内容可写在 `<!-- auto:end -->` 标记之后,不会被自动提取覆盖:

```markdown
<!-- auto:begin -->
### user
- 偏好使用函数式编程风格
<!-- auto:end -->

### 手动备注
- 项目部署流程:npm run build -> scp dist/ -> pm2 restart
- 数据库连接字符串在 Vault 中
```
记忆文件本身有 16KB 的硬上限,超限时按 `### ` 小节从后往前裁掉超出部分。
19 changes: 10 additions & 9 deletions docs/tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,16 +97,15 @@ interface ToolVisibilityPolicy {

### 审批流水线(始终生效)

六层决策链,按顺序执行,任一层返回 deny/allow 即终止:
五层决策链,按顺序执行,任一层返回 deny/allow 即终止:

| 层级 | 名称 | 逻辑 |
|------|------|------|
| 1 | **RuleEngine** | 规则引擎匹配,支持 glob 模式匹配工具名和参数,按优先级排序 |
| 2 | **ReadonlyWhitelist** | 只读工具自动放行(read_file, search_code, search_files, fetch_url, web_search, dispatch_agent, todo_write) |
| 3 | **PermissionMode** | 权限模式判断:`bypass`(全部放行)、`acceptEdits`(非破坏性工具放行)、`default`(继续下一层)。`plan` Profile 由独立的 `agent/profile.ts` 中的 `planProfileGateHook` 在 Layer 4 强制,不在此层处理 |
| 4 | **HookPreToolUse** | 钩子决策,可返回 allow/deny/ask/continue,支持 `modifiedInput` 修改参数 |
| 5 | **UserConfirmation** | 异步用户确认,支持 allow/deny/always/never 四种响应,always/never 会持久化为规则 |
| 6 | **AuditLog** | 每一层决策后记录审计日志,通过 `tool.approval.post` 钩子发出 |
| 2 | **PermissionMode** | 权限模式驱动的自动放行:`bypass`(全部放行)、`acceptEdits`(非破坏性工具放行,涵盖只读与编辑工具)、`default`(不自动放行,继续下一层)。只读工具不再有无条件的独立白名单层;`plan` Profile 由 `agent/profile.ts` 中的 `planProfileGateHook` 在下一层强制,不在此层处理 |
| 3 | **HookPreToolUse** | 钩子决策,可返回 allow/deny/ask/continue,支持 `modifiedInput` 修改参数 |
| 4 | **UserConfirmation** | 异步用户确认,支持 allow/deny/always/never 四种响应,always/never 会持久化为规则 |
| 5 | **AuditLog** | 每一层决策后记录审计日志,通过 `tool.approval.post` 钩子发出 |

### 预设安全规则

Expand All @@ -130,11 +129,13 @@ interface ToolVisibilityPolicy {
type PermissionMode = 'default' | 'acceptEdits' | 'bypass';
```

- `default`:逐层审批,危险操作需用户确认
- `acceptEdits`:非破坏性工具自动放行,减少确认弹窗
- `default`:不自动放行任何工具(含只读工具),全部逐层审批
- `acceptEdits`:非破坏性工具自动放行(涵盖只读工具与编辑类工具),破坏性工具仍需确认
- `bypass`:全部放行,跳过所有审批(慎用)

> `plan` 不再是 `PermissionMode` 的成员。plan Profile 通过 `AgentProfile.name === 'plan'` 结构化识别,由 `agent/profile.ts` 的 `planProfileGateHook` 和 `PLAN_PROFILE_ALLOWED_TOOLS` 共同限制工具。
> 原独立的 `ReadonlyWhitelist` 层(在 `default` 下也无条件放行只读工具)已废弃,其语义并入 `PermissionMode` 的自动放行判定:`acceptEdits` 视只读工具为非破坏性工具自动放行,`default` 不再自动放行。

> `plan` 不再是 `PermissionMode` 的成员。plan Profile 通过 `AgentProfile.name === 'plan'` 结构化识别,由 `agent/profile.ts` 的 `planProfileGateHook` 和 `PLAN_PROFILE_ALLOWED_TOOLS` 共同限制工具。因只读白名单层已删除,`dispatch_agent` 等在 plan 下不再被流水线上层提前放行,统一由 plan gate 拦截。

### OS 级沙箱(预留)

Expand Down
40 changes: 7 additions & 33 deletions packages/codingcode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,44 +8,18 @@
},
"exports": {
".": "./src/layer.ts",
"./agent/agent": "./src/agent/agent.ts",
"./agent/todo": "./src/agent/todo.ts",
"./agent/prompt": "./src/agent/prompt.ts",
"./session/store": "./src/session/store.ts",
"./session/io": "./src/session/io.ts",
"./session/types": "./src/session/types.ts",
"./session/messages": "./src/session/messages.ts",
"./core/path": "./src/core/path.ts",
"./core/workspace": "./src/core/workspace.ts",
"./core/error": "./src/core/error.ts",
"./core/result": "./src/core/result.ts",
"./core/types": "./src/core/types.ts",
"./context/context": "./src/context/context.ts",
"./hooks/registry": "./src/hooks/registry.ts",
"./tools/executor": "./src/tools/executor.ts",
"./mcp/client": "./src/mcp/client.ts",
"./mcp/types": "./src/mcp/types.ts",
"./skills/types": "./src/skills/types.ts",
"./approval/types": "./src/approval/types.ts",
"./approval/async-confirm": "./src/approval/async-confirm.ts",
"./server/create": "./src/server/index.ts",
"./server/adapter": "./src/server/adapter.ts",
"./server/port-discovery": "./src/server/port-discovery.ts",
"./client": "./src/client/http/index.ts",
"./client/types": "./src/client/types.ts",
"./client/http": "./src/client/http.ts",
"./client/http-clients": "./src/client/http/index.ts",
"./server": "./src/server/index.ts",
"./direct/agent-runtime": "./src/direct/agent-runtime.ts",
"./direct/sessions": "./src/direct/sessions.ts",
"./direct/settings": "./src/direct/settings.ts",
"./direct/models": "./src/direct/models.ts",
"./agent/stream-adapter": "./src/agent/stream-adapter.ts",
"./checkpoint/checkpoint-service": "./src/checkpoint/checkpoint-service.ts",
"./checkpoint/shadow-git": "./src/checkpoint/shadow-git.ts",
"./checkpoint/bootstrap": "./src/checkpoint/bootstrap.ts",
"./llm/factory": "./src/llm/factory.ts",
"./llm/client": "./src/llm/client.ts",
"./layer": "./src/layer.ts",
"./subagent/types": "./src/subagent/types.ts"
"./approval/types": "./src/approval/types.ts",
"./agent/profile": "./src/agent/profile.ts",
"./core/error": "./src/core/error.ts",
"./core/types": "./src/core/types.ts",
"./llm/client": "./src/llm/client.ts"
},
"dependencies": {
"@ai-sdk/deepseek": "^2.0.35",
Expand Down
Loading
Loading