内容
English | [简体中文](README.zh-CN.md)
# CashCode
CashCode 是一个本地托管的个人 AI 代理框架,其灵感来自 Codex、Hermes 和 OpenClaw。它支持跨会话的持久内存、本地技能、MCP 工具生态系统和流式聊天。
## 功能概述
| 模块 | 功能描述 |
|---|---|
| WebSocket 通信 | 流式聊天和工具事件通知 |
| 内存系统 | 跨会话持久化、上下文压缩和长期记忆蒸馏 |
| 工具执行 | ReAct 环路、内置工具和动态工具 |
| 代理角色 | 由 SOUL.md 驱动的可配置身份 |
| MCP 系统 | 内置和用户目录、市场管理、显式连接、延迟激活、stdio/SSE 传输 |
| 技能系统 | 自然语言搜索、两阶段延迟加载、会话摘要、管理 API 和自我进化提案 |
## 架构概述
```
用户(前端 WebSocket 客户端)
│
WebSocketChannel
(ws://127.0.0.1:8765)
│
MessageBus
(asyncio 队列解耦)
│
SimpleAgentLoop
┌───────────────┴───────────────┐
│ │
ToolRegistry(内置) DeferredAwareRegistry
save_memory, web_fetch (MCP 工具默认隐藏)
skill_search/load/read ├── tool_search -> 发现 MCP 工具
tool_search, mcp_prepare ├── mcp_prepare -> 延迟连接
│ └── skill_search/load -> 延迟加载技能
│
▼
SimpleAgentRunner(非流式 ReAct 环路)
│
┌─────────┴──────────┐
│ │
内置工具执行 MCP 工具执行
│ MCPToolWrapper
│ session.call_tool()
│ │
│ MCP 服务器(stdio/SSE)
│
▼
伪流式传输 -> 前端
│
MemoryStore
┌─────────────────────┐
│ history.jsonl │ <- 会话历史
│ metadata.json │ <- 跨回合状态,如激活集(V2)
│ MEMORY.md │ <- 全局长期记忆
│ SOUL.md │ <- 代理角色配置
└─────────────────────┘
│
┌─────────┴──────────┐
│ │
Consolidator Dream
(上下文压缩) (长期记忆蒸馏)
```
## 目录布局
```
CashCode/
├── server/
│ ├── main.py # FastAPI + WebSocket 服务入口
│ ├── .env # 可选的非 LLM 环境配置
│ ├── requirements.txt
│ ├── data/ # 技能、用户 MCP 和自我进化运行时数据(不提交)
│ │ ├── mcp/servers.json # 用户创建的 SSE MCP 配置
│ │ └── skills/
│ │ ├── user/ # 用户上传和用户管理的技能
│ │ └── agent/ # 聊天或代理创建的技能
│ ├── memory/ # 运行时内存数据(不提交)
│ │ ├── SOUL.md # 代理角色文件(提交)
│ │ ├── MEMORY.md # 全局长期记忆(运行时生成)
│ │ ├── .dream_cursor # Dream 游标(运行时生成)
│ │ └── <chat_id>/ # 按会话历史目录
│ │ ├── history.jsonl
│ │ ├── metadata.json # 激活集和其他跨回合状态(V2)
│ │ └── .cursor
│ └── app/
│ ├── agent/
│ │ ├── loop.py # SimpleAgentLoop 主环,包括 MCP 初始化
│ │ ├── runner.py # SimpleAgentRunner ReAct 环路
│ │ └── tools/
│ │ ├── base.py # 工具抽象基类
│ │ ├── registry.py # ToolRegistry
│ │ ├── mcp.py # MCP 连接层(stdio/SSE + MCPToolWrapper)
│ │ ├── mcp_cache.py # 工具模式磁盘缓存
│ │ ├── tool_search.py # 延迟激活系统(DeferredAwareRegistry、
│ │ │ # ActivatedToolSet、ToolSearchTool、
│ │ │ # MCPPrepareTool、BM25 索引)
│ │ ├── memory.py # SaveMemoryTool
│ │ ├── web.py # WebFetchTool、WebSearchTool
│ │ ├── filesystem.py # ReadFileTool、WriteFileTool...
│ │ ├── search.py # GlobTool、GrepTool
│ │ ├── shell.py # ExecTool
│ │ └── result.py # 工具结果投影,用于模型、前端和持久性
│ ├── skills/ # 技能存储、加载、管理 和 自我进化
│ │ ├── archive.py # ZIP 安全验证和解包
│ │ └── builtin/ # 只读内置技能(提交)
│ ├── api/
│ │ ├── skills.py # 技能管理 API
│ │ ├── mcp.py # MCP 市场、CRUD 和连接 API
│ │ └── skill_evolution.py # 技能自我进化提案 API
│ ├── memory/
│ │ ├── store.py # MemoryStore,包括会话元数据
│ │ ├── consolidator.py # 上下文压缩
│ │ └── dream.py # 长期记忆蒸馏
│ ├── ws/
│ │ └── channel.py # WebSocketChannel
│ └── bus/
│ ├── queue.py # MessageBus
│ └── events.py # InboundMessage、OutboundMessage
├── mcp_servers/ # 内置 MCP 配置和测试服务器(允许提交)
│ ├── mcp_config.json # MCP 服务器配置(stdio/SSE)
│ ├── test_stdio_mcp/
│ │ └── server.py # stdio 测试服务器(say_hello)
│ └── test_sse_mcp/
│ └── server.py # SSE 测试服务器(say_hello)
├── mcp_cache/ # 工具模式磁盘缓存(不提交)
└── openspec/ # 变更管理
├── specs/ # 主要规格文档
└── changes/archive/ # 存档的变更记录
```
## 1. WebSocket 协议
服务监听 `ws://127.0.0.1:8765`,HTTP API 在 `http://127.0.0.1:8000` 上可用。
### 客户端 -> 服务器
```json
{"type": "message", "chat_id": "uuid", "content": "用户消息"}
{"type": "message", "chat_id": "uuid", "content": "检查仓库", "metadata": {"mentioned_skills": [{"name": "code-review", "label": "代码审查"}], "selected_mcp_connectors": [{"server": "github", "label": "GitHub"}]}}
{"type": "ping"}
{"type": "new_chat"}
{"type": "attach", "chat_id": "uuid"}
{"type": "cancel", "chat_id": "uuid"}
```
### 服务器 -> 客户端
```json
{"event": "ready", "chat_id": "...", "client_id": "..."}
{"event": "tool_calls", "chat_id": "...", "tool_name": "web_fetch", "stream_id": 123}
{"event": "tool_result", "chat_id": "...", "tool_name": "web_fetch", "result": "...", "stream_id": 123}
{"event": "delta", "chat_id": "...", "text": "...", "stream_id": 123}
{"event": "stream_end", "chat_id": "...", "stream_id": 123}
{"event": "done", "chat_id": "...", "duration_sec": 2.3}
{"event": "error", "detail": "..."}
```
### 架构说明
`MessageBus` 是一个 asyncio 队列,它将 WebSocket 通道与代理环路解耦。`WebSocketChannel` 将客户端帧转换为 `InboundMessage` 实例,并将它们推送到队列中。`SimpleAgentLoop` 消费队列并通过 `WebSocketChannel` 将 `OutboundMessage` 实例路由回给订阅者。
## 2. 内存系统
内存系统有三个层次,从短期到长期:
```
InboundMessage
│
▼
history.jsonl <- 层 1:原始对话流(仅追加)
│
▼
Consolidator <- 层 2:上下文压缩(每回合,触发字符阈值)
│
▼
Dream <- 层 3:长期记忆蒸馏(定时,每 5 分钟)
│
▼
MEMORY.md <- 全局长期记忆注入到系统中
```
### 层 1:MemoryStore(`store.py`)
每个 `chat_id` 映射到一个子目录。`history.jsonl` 是一个仅追加的对话账本:
```jsonl
{"cursor": 1, "role": "user", "content": "hello"}
{"cursor": 2, "role": "assistant", "content": "hello!"}
{"cursor": 3, "role": "tool_calls", "content": "[TOOL_CALLS: web_fetch]", "tool_calls": [...]}
{"cursor": 4, "role": "tool", "content": "fetch result...", "tool_call_id": "call_xxx"}
{"cursor": 5, "role": "assistant", "content": "based on the page content..."}
{"cursor": 6, "role": "summary", "content": "summary...", "keep_from_cursor": 3}
```
**重启恢复(智能加载)**:`load_history_smart()` 查找最后一个 `summary` 记录,读取其 `keep_from_cursor`,并重新加载摘要以及 `cursor >= keep_from_cursor` 的近期消息。这可以防止以前压缩的旧消息在重启后被喂回压缩器。
**全局文件**:
- `SOUL.md` - 代理角色(提交)
- `MEMORY.md` - 跨会话长期记忆(运行时生成)
- `.dream_cursor` - JSON 文件,记录每个 `chat_id` 的 Dream 处理进度
### 层 2:SimpleConsolidator(`consolidator.py`)
在每次对话回合后触发。当字符阈值超过(默认为 `40,000`)时,它会压缩较旧的消息:
```
策略:累积压缩,非增量
to_compress = history[:keep_from] 包括现有的摘要前缀
to_keep = history[keep_from:] 保留最近 50% 的字符
压缩的内存历史:
[累积摘要] + [保留消息]
追加到 history.jsonl:
{"role": "summary", "keep_from_cursor": N, ...}
```
**关键设计点**:
- **累积压缩**:每个摘要都包含前一个摘要,因此重启后只需要最新的摘要即可恢复完整上下文。
- **`keep_from_cursor` 元数据**:记录保留的首个消息的游标,以便 Smart Load 可以精确恢复保留的尾部。
- **原子持久性**:首先写入 `history.jsonl`,然后在成功后更新内存历史。失败会回滚以保持一致性。
- **每个 chat_id 的 asyncio.Lock**:防止对同一会话的并发请求进行重复压缩。
### 层 3:SimpleDream(`dream.py`)
通过 `asyncio.create_task` 每 5 分钟在后台运行,使用两阶段 LLM 流:
```
阶段 1:分析
输入:来自所有 chat_id 的未处理历史记录 + 当前的 MEMORY.md
输出:分析报告,描述要添加、更新或删除的长期记忆
阶段 2:生成
输入:阶段 1 报告 + 当前的 MEMORY.md
输出:完整的新的 MEMORY.md
```
每次运行后,它都会在 `memory/.dream_cursor` 中推进 Dream 游标,因此下一次运行只会处理新添加的条目。
### MEMORY.md 注入
每次都会像这样构建系统提示:
```python
soul = store.read_soul() or _DEFAULT_SOUL
memory = store.read_memory()
system_prompt = soul
if memory:
system_prompt += f"\n\n## 您已经记住的信息\n{memory}"
```
## 3. 工具执行系统
### 架构:两阶段执行
```
用户消息
│
▼
阶段 1:SimpleAgentRunner(非流式 ReAct 环路)
│
├── LLM 调用,工具 = [...] 且 stream=False
│ │
│ ├── 如果存在 tool_calls -> 发送 `tool_calls` WebSocket 事件
│ │ -> 执行工具
│ │ -> 发送 `tool_result` WebSocket 事件
│ │ -> 追加结果 -> 调用 LLM 再次
│ │
│ └── 如果没有 tool_calls -> 返回 final_text
│
▼
阶段 2:伪流式传输
将 final_text 分成 15 个字符块并发送 `_stream_delta` 事件
前端体验与实时流式传输匹配
```
完整的工具链被持久化到 `history.jsonl`(`user -> tool_calls -> tool_result -> assistant`),因此可以在重启后重建工具上下文。
### 工具基类(`tools/base.py`)
```python
class Tool(ABC):
@property
def name(self) -> str: ...
@property
def description(self) -> str: ...
def parameters(self) -> dict: ...
def to_openai_schema(self) -> dict: ...
async def execute(self, **kwargs) -> str: ...
```
### 内置工具
| 工具名称 | 类 | 描述 |
|---|---|---|
| `save_memory` | SaveMemoryTool | 立即将重要事实写入 MEMORY.md |
| `web_fetch` | WebFetchTool | 使用 `httpx` 获取 URL 内容,最高 3000 个字符 |
| `web_search` | WebSearchTool | 使用 DuckDuckGo Instant Answer API |
| `read_file` | ReadFileTool | 读取 `WORKSPACE_DIR` 下的文件,最高 500 行 |
| `write_file` | WriteFileTool | 创建或覆盖 `WORKSPACE_DIR` 下的文件 |
| `edit_file` | EditFileTool | 精确的字符串替换(`old -> new`) |
| `list_dir` | ListDirTool | 列出目录内容并显示类型标记 |
| `glob` | GlobTool | 文件模式搜索,如 `**/*.py`,最多 100 个结果 |
| `grep` | GrepTool | 跨文件正则表达式搜索,最多 50 个结果 |
| `exec` | ExecTool | 在 `WORKSPACE_DIR` 下执行 shell 命令,30 秒超时 |
## 4. MCP 系统
CashCode 实现了一个完整的 MCP(Model Context Protocol)集成层,以便外部 MCP 服务器工具可以直接暴露给代理。
### MCP 市场和配置源
`MCP 市场` 侧边栏合并了两个配置源:
- `mcp_servers/mcp_config.json` 是项目自带的只读目录,支持 stdio 和 SSE。这些条目在市场中被标记为内置,可以连接或断开,但不能编辑或删除。
- `CASHCODE_DATA_DIR/mcp/servers.json` 是用户目录,默认位于 `server/data/mcp/servers.json`。用户可以创建、编辑和删除条目。目前,仅 SSE 支持用户创建的连接器。
用户 MCP 条目包括内部名称、显示标题、描述、HTTP(S) SSE URL 和可选的头部。头部值存储在本地用户数据文件中,不会静态加密。API 和编辑表单仅返回 `********`; 提交该占位符会保留现有秘密。连接错误、工具元数据和聊天历史永远不会回显头部值。
创建或编辑配置不会自动连接。在用户点击连接后,后端完成传输握手和 `list_tools`; 只有这样,条目才会显示为已连接。断开连接会关闭传输并删除该服务器拥有的实时工具。连接失败在市场中以红色的可重试错误显示。
| API | 描述 |
|---|---|
| `GET/POST /api/mcp/servers` | 查询合并的目录或创建用户 SSE MCP 条目 |
| `PUT/DELETE /api/mcp/servers/{name}` | 编辑或删除用户 MCP 条目;内置返回 `403` |
| `POST /api/mcp/servers/{name}/connect` | 显式连接并发现工具 |
| `POST /api/mcp/servers/{name}/disconnect` | 显式断开连接并清除实时工具 |
| `GET /api/mcp/servers/{name}/tools` | 查询实时工具或缓存信息,当配置指纹仍然有效时 |
### 在聊天中选择 MCP 或技能
在撰写器中输入 `@`,在令牌边界处,选择 `MCP` 或 `Skill`,然后搜索当前可用的列表。MCP 列表仅包括已连接的服务器,已发现的实时工具。技能列表仅包括已启用的技能,其依赖项已满足。所选项目显示为可移除的芯片。单个消息最多可包含 10 个选择,总消息体不能为空。
选择的结构化 WebSocket 元数据不会追加到任务文本中。显式选择的 MCP 权限仅在当前回合有效,但市场传输保持连接,直到用户断开编辑、删除或服务退出。技能和 MCP 提及与用户消息一起持久化,并在会话重新打开时恢复。旧的 `<skill>` 文本语法仍然支持兼容性。
### 核心设计:延迟激活
暴露每个工具给 LLM 会增加上下文窗口大小。CashCode 使用 **延迟激活**:
```
LLM 默认可见: MCP 工具默认隐藏:
tool_search <- 发现工具 mcp_test_stdio_mcp_say_hello X
mcp_prepare <- 按需连接 mcp_test_sse_mcp_say_hello X
save_memory
web_fetch ...
用户询问调用 stdio 测试 MCP
↓
LLM 调用 tool_search("stdio Hello Cash")
↓ BM25 命中stdio 测试服务器存根
LLM 调用 mcp_prepare("test_stdio_mcp")
↓ 建立 stdio 连接 -> list_tools -> 激活
LLM 调用 mcp_test_stdio_mcp_say_hello()
↓
返回 "Hello, Cash" 且可以直接调用下一次
```
### 架构层
```
┌──────────────────────────────────────────────────────────┐
│ DeferredAwareRegistry(面向LLM的视图) │
│ - 内置工具始终可见 │
│ - MCP工具在激活前保持隐藏 │
│ - 缓存键包括激活修订版 │
├──────────────────────────────────────────────────────────┤
│ ActivatedToolSet(会话状态层) │
│ - LRU字典,容量为50 │
│ - 上下文变量绑定到当前异步任务 │
│ - 持久化到内存/<chat_id>/metadata.json │
├──────────────────────────────────────────────────────────┤
│ ToolSearchTool + ToolSearchIndex(发现层) │
│ - BM25(k1=1.5,b=0.75)+ CJK双字节 + 驼峰式 │
│ - 合并磁盘缓存和实时注册表 │
│ - 当不存在实时工具时,发出服务级存根 │
├──────────────────────────────────────────────────────────┤
│ mcp_cache.py(离线缓存层) │
│ - 每个服务器一个JSON文件 │
│ - 在传输指纹发生变化时失效 │
│ - 在成功的mcp_prepare后写入 │
├──────────────────────────────────────────────────────────┤
│ MCPPrepareTool + lazy_connect(连接层) │
│ - 调用lazy_connect -> 建立mcp_sessions │
│ - stdio启动子进程;SSE连接到HTTP │
│ - 握手 -> list_tools -> 注册包装器 │
├──────────────────────────────────────────────────────────┤
│ MCPToolWrapper(适配层) │
│ - 对外暴露Tool接口 │
│ - 对内委托给session.call_tool() │
│ - 命名格式:mcp_{server_name}_{tool_name} │
└──────────────────────────────────────────────────────────┘
```
### 两种传输协议
| 传输方式 | 配置 | 服务器启动方式 | 典型用例 |
|---|---|---|---|
| **stdio** | `命令 + 参数` | 不需要单独的进程;CashCode在`mcp_prepare`期间启动 | 本地命令行工具 |
| **SSE** | `URL` | 需要单独运行的HTTP服务 | 公共MCP服务和第三方API |
```json
// mcp_servers/mcp_config.json
{
"test_stdio_mcp": {
"type": "stdio",
"command": "python",
"args": ["mcp_servers/test_stdio_mcp/server.py"],
"display_name": "stdio测试MCP",
"description": "提供一个say_hello工具,返回Hello, Cash"
},
"test_sse_mcp": {
"type": "sse",
"url": "http://127.0.0.1:8090/sse",
"display_name": "SSE测试MCP",
"description": "提供一个say_hello工具,返回Hello, Cash,需要单独启动服务"
}
}
```
### 本地测试MCP服务器
| 服务器 | 传输方式 | 工具 | 启动方式 |
|---|---|---|---|
| `test_stdio_mcp` | stdio | `say_hello()` -> `Hello, Cash` | 由`mcp_prepare`自动启动 |
| `test_sse_mcp` | SSE | `say_hello()` -> `Hello, Cash` | 需要手动启动 |
```bash
# 在使用SSE服务器之前启动
python mcp_servers/test_sse_mcp/server.py
```
### 完整会话流程
```
1. 代理以延迟模式启动
-> 读取mcp_config.json而不连接
-> 磁盘缓存仍然允许tool_search发现工具
2. 用户请求使用stdio测试MCP
-> tool_search("stdio Hello Cash")找到test_stdio_mcp存根
-> 它指示需要mcp_prepare("test_stdio_mcp")
3. mcp_prepare("test_stdio_mcp")
-> 启动stdio子进程
-> list_tools -> 注册MCPToolWrapper -> 写入mcp_cache
-> 激活mcp_test_stdio_mcp_say_hello
4. mcp_test_stdio_mcp_say_hello()
-> 返回"Hello, Cash"
5. 后续回合可以直接调用它
-> DeferredAwareRegistry在ActivatedToolSet中看到该工具
-> tool_search跳过,因为激活状态在回合之间持续
```
---
## 5. 技能系统
CashCode在服务器端提供本地技能运行时。模型始终可见`skill_search`、`skill_load`、`skill_read_resource`和管理`agent_skill_manage`,但系统不会预先将每个已安装技能的描述或主体注入上下文。
### 存储目录
`CASHCODE_DATA_DIR`默认为`server/data`,具有以下结构:
```text
skills/user/ 用户管理的技能包
skills/agent/ 聊天创建或批准后,代理拥有的技能包
skill-snapshots/ 用于替换、回滚和无效删除的恢复快照
skill-evolution/ 自我进化证据和提案
```
内置技能位于`server/app/skills/builtin`,运行时只读。有效的命名空间优先级是`builtin < user < agent`,因此优先级较高的包会覆盖优先级较低的同名包。目录元数据会公开遮蔽源。
### 技能包格式
每个技能都是一个包含`SKILL.md`的目录,可选地包括`references/`、`templates/`、`scripts/`和`assets/`。目录名称必须与前面的`name`匹配。`name`仅允许1-64个小写字母、数字、点、空格和连字符。它用于目录、API路由、精确搜索标识、`@`选择、冲突检查、哈希和快照。`display_name`是可选的,最多80个字符,仅用于UI标签。
```yaml
---
name: example-workflow
display_name: 示例工作流
description: 描述功能和使用场景
version: 1
tags: [示例]
triggers: [示例请求]
requires:
tools: [read_file]
mcp_servers: []
bins: []
env: []
optional:
tools: []
mcp_servers: []
---
```
加载器验证YAML字段类型、名称、UTF-8编码、文件大小、支持的目录和路径边界。它拒绝绝对路径、`..`遍历和指向包根目录之外的符号链接。加载技能不会自动运行脚本、安装二进制文件、修改环境变量、身份验证或启动未声明的MCP服务器。
### 技能市场、导入和编辑
`技能市场`侧边栏显示内置、用户上传和聊天创建的代理技能,分页列出。列表使用`display_name`作为主标签,同时保留规范`name`作为稳定标识符。禁用的技能和缺少依赖的技能仍然可见,以便于故障排除和重新启用。内置技能标记为内置,只能查看。用户和代理技能可以查看、编辑、启用、禁用和删除,确认后生效。
导入入口接受单个ZIP技能包。ZIP可以单独放置`SKILL.md`在根目录或包装在一个顶级目录中。最终目录名称源自前面的`name`。后端强制执行这些限制:10 MiB存档大小、256个成员、20 MiB总提取大小、`SKILL.md`最多80 KB,每个支持文件最多200 KB。路径遍历、驱动器字母路径、重复路径、加密成员、符号链接和特殊文件被拒绝。
上传的软件包始终进入`skills/user/`。存档中的`_meta.json`不能声称内置或代理所有权。如果同名技能已存在于`builtin`、`user`或`agent`中,API返回`409`而不是覆盖或遮蔽它。解压和完整验证发生在隐藏的临时目录中。只有有效的软件包才会原子化发布并刷新目录。
当用户在聊天中明确要求创建技能时,代理首先加载内置的`skill-creator`,然后调用`agent_skill_manage(action=create)`。该工具始终写入`skills/agent/`并启用该包。它使用与目录加载器相同的验证规则,然后使用共享的`SkillStore`强制跨根唯一性、原子发布和目录刷新。成功需要`success=true`和具有相同名称、哈希和`source=agent`的可见记录。验证失败时,代理可能只能修复内容并重试该托管工具,或报告失败。通常的`write_file`和`edit_file`拒绝写入用户和代理技能根目录,创建流程不能绕过管理器,使用`exec`、`curl`或临时HTTP请求。
Python工具注册和内置`skill-creator`合约仅在服务启动时加载。更改服务器端代码后,CashCode必须重启。旧进程不会自动拾取`agent_skill_manage`或写入保护,历史版本仍可能尝试`write_file`或`exec`,因此它们无法验证新创建流程。
无效目录被排除在搜索、`@`选择和正常技能列表之外。市场在单独的诊断区域显示它们,删减、长度限制的源代码、目录名称和错误消息。诊断行无法查看、编辑、启用、禁用或选择。用户和代理无效条目公开显式删除操作。确认后,后端重新验证目标仍然无效,将整个目录移动到`skill-snapshots/invalid/<source>/...`并刷新目录。内置无效条目无法删除。
### 技能自我进化预览
自我进化默认关闭,可以通过在`server/.env`中添加环境变量启用:
```ini
SKILL_EVOLUTION_ENABLED=true
# 默认值
SKILL_EVOLUTION_MIN_TOOL_CALLS=2
SKILL_EVOLUTION_RECURRENCE=2
```
只有满足以下所有条件时,某个回合才会贡献自我进化证据:
- 进化已启用。
- 回合成功完成并持久化。
- 回合中至少调用了`SKILL_EVOLUTION_MIN_TOOL_CALLS`次工具,默认为2。
- 回合中没有工具调用失败。
- 运行程序未达到其最大迭代限制。
证据仅在达到重复阈值后才有资格生成提案:相同的流程指纹必须至少出现`SKILL_EVOLUTION_RECURRENCE`次,默认为2。工作流程指纹是从规范的用户输入和该回合中使用的工具集生成的。
只有截断和删减的证据被保留。一旦达到重复阈值,限制的Evolver可以读取有界技能摘要和`skill-creator`合约来生成提案。它无法访问通用文件系统、Shell、Web、MCP或直接技能突变路径。
提案通过`/api/skill-evolution/proposals`列出、批准或拒绝,并且从不自动应用。批准会重新检查所有权、基准哈希和完整的包验证。只有`agent`技能可以通过此路径创建或修改;内置和用户技能不可以。版本号快照在应用之前创建,之后仍可通过版本API回滚。
---
## 6. SOUL.md:代理角色配置
`server/memory/SOUL.md`定义了代理的身份和行为规则。直接编辑此文件以调整代理的风格。不需要更改代码,重启后即可生效。
如果文件不存在,系统会自动回退到代码中的内置`_DEFAULT_SOUL`字符串。
---
## 7. 快速入门
### 环境设置
```bash
cd server
pip install -r requirements.txt
```
### LLM配置
后端不需要预先创建`.env`文件,启动前无需配置模型服务。启动后端和前端后,在左侧边栏底部打开`Settings` -> `LLM Settings`并保存连接信息:
- `Generic API`:OpenAI兼容接口的API基础URL和API密钥。
- `Ollama`:Ollama服务地址,例如`http://127.0.0.1:11434`。
模型不会在设置面板中激活或保存。返回聊天视图,从左侧发送箭头旁的模型选择器中为当前回合选择模型。列表分别发现配置的Generic API模型和Ollama模型。每个消息携带其自己的提供者和模型选择,因此切换模型不会重写存储的凭证。
首次有效保存时,CashCode会创建一个用户私有的`settings/llm.json`。默认情况下,该文件存储在Git工作空间之外:
- Windows:`%LOCALAPPDATA%\CashCode\settings\llm.json`
- macOS:`~/Library/Application Support/CashCode/settings/llm.json`
- Linux:`$XDG_CONFIG_HOME/cashcode/settings/llm.json`或`~/.config/cashcode/settings/llm.json`(当XDG未设置时)
API读取时不会返回存储的机密。测试或托管部署可以覆盖配置根目录,使用`CASHCODE_CONFIG_DIR`。如果指向项目本地的`server/data`,则目录和LLM机密回退已由Git忽略。
对于旧用户,当新配置文件尚不存在时,现有`DEEPSEEK_API_KEY`和`DEEPSEEK_API_BASE`值会一次性迁移到新文件中。`DEEPSEEK_MODEL`不会迁移,因为模型现在从聊天编辑器中选择。迁移不会重写`.env`;一旦存在新文件,LLM配置将不再读取这些遗留变量。
### 可选运行时配置
这些非LLM设置可以通过进程环境变量或可选的`server/.env`覆盖。省略时使用默认值:
```ini
WS_HOST=127.0.0.1
WS_PORT=8765
CASHCODE_ALLOWED_ORIGINS=http://127.0.0.1:5173,http://localhost:5173
# 文件系统工具和exec工具的工作目录
# WORKSPACE_DIR=/your/workspace/path
# CASHCODE_DATA_DIR=/your/runtime/data/path
# CASHCODE_CONFIG_DIR=/your/private/config/path
```
### 启动服务
```bash
cd server
python main.py
```
启动后:
- HTTP API:`http://127.0.0.1:8000`
- WebSocket:`ws://127.0.0.1:8765`
- MCP服务器延迟连接,首次使用时激活
- 前端`MCP Market`也可以显式连接或断开
- 梦背景任务每5分钟运行
### 使用 SSE MCP 服务器(可选)
SSE 服务器必须单独启动。stdio 服务器不:
```bash
# 在另一个终端中启动
python mcp_servers/test_sse_mcp/server.py
# 启动后:SSE 测试 MCP 可在 http://127.0.0.1:8090/sse 上访问
```
### 测试连接
```python
import asyncio, json, websockets
async def test():
async with websockets.connect("ws://127.0.0.1:8765") as ws:
ready = json.loads(await ws.recv())
chat_id = ready["chat_id"]
await ws.send(json.dumps({
"type": "message",
"chat_id": chat_id,
"content": "你好,请记住我的名字是 Cash"
}))
async for msg in ws:
data = json.loads(msg)
print(data)
if data.get("event") == "done":
break
asyncio.run(test())
```
---
## 8. 前端
前端位于 `client/` 目录下,使用 Vite、React 19、TypeScript 和 Tailwind v4 构建。
### 在后端启动时启动前端
```bash
# 终端 1:启动后端
cd server && python main.py
# 终端 2:启动前端
cd client && npm install && npm run dev
# 打开:http://localhost:5173
```
### 前端功能
- 新聊天、MCP 市场、技能市场和可折叠历史的侧边栏导航
- 具有内置徽章、用户 SSE 配置、头部、连接、断开、编辑和删除的 MCP 市场
- 具有源和状态、分页搜索、ZIP 上传、全文编辑、启用、禁用和删除的技能市场
- 作家中带有两级 `@` 选择器和技能或 MCP 选择芯片
- 带有 Markdown 渲染、代码高亮和表格的流式聊天消息
- 带有旋转器和结果预览的工具调用进度显示
- 自动 WebSocket 重新连接
- 暗色主题
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器