结论摘要
主程序是 Go/Wails,前端资源打包在二进制中,不是 Electron。
Markdown 是可读存储,SQLite 负责索引、FTS 和 embedding cache。
AX + ScreenCaptureKit + Vision OCR + 定向 CGEvent,强调后台操作不抢焦点。
-w -s 去符号构建,无法仅靠 strings 无损恢复。
本地文件与数据库
| 对象 | 路径 / 证据 | 作用 |
|---|---|---|
| App bundle | /Applications/Yansu.app | Wails 桌面 App,版本 0.1.276。 |
| 主程序 | /Applications/Yansu.app/Contents/MacOS/Yansu | Go 1.24.13,依赖 wailsapp/wails/v2、go-rod/rod、modernc.org/sqlite、sherpa-onnx。 |
| 用户数据 | ~/.yansu-agent | 数据库、截图、模型、记忆、bin、日志。 |
| Activity DB | ~/.yansu-agent/activity.db | 桌面活动、截图、OCR、pipeline runs、summary、prediction/handoff。 |
| Memory DB | ~/.yansu-agent/memory.db | memory/knowledge markdown 的索引、FTS、embedding cache。 |
| Sessions DB | ~/.yansu-agent/sessions.db | 聊天、stream items、cron、workspaces、crystal versions、git snapshots。 |
| 截图 | ~/.yansu-agent/activity/snapshots/YYYY-MM-DD/*.jpg | activity snapshot 原始图片。 |
| 长期记忆 | ~/.yansu-agent/memory/*.md | 人类可读的 memory/knowledge 文件。 |
Activity Pipeline 复原
activity_pipeline_runs 是后台活动加工流水线的审计表。它不是原始事件表,而是记录每轮扫描、过滤、模型判断和沉淀结果。
关键字段
| 字段 | 含义 |
|---|---|
kind | tick 是调度扫描,group 是实际处理一组活动。 |
prefilter_pass, prefilter_reason | 无需进模型的低价值活动会在这里被过滤。 |
ai_summary | 发给模型前的活动摘要,含时间线和应用列表。 |
ai_response_raw | 模型 triage JSON 原文。 |
worth_memory, worth_knowledge, worth_automation, worth_handoff | 不同沉淀分支的布尔判断。 |
memory_status, knowledge_status, handoff_status, crystal_status | 分支执行结果,例如 created、updated、dismissed。 |
实际样本
{
"id": 76,
"kind": "group",
"apps": ["飞书", "Warp", "loginwindow", "Codex", "Google Chrome", "微信", "QSpace Pro", "iShot"],
"event_count": 217,
"duration_ms": 650908,
"worth_memory": true,
"worth_knowledge": true,
"worth_handoff": true,
"worth_automation": false,
"crystal_status": "dismissed",
"memory_status": "created",
"knowledge_status": "created",
"handoff_status": "created"
}
这条记录是在本轮分析过程中 Yansu 自己生成的:它判断当前任务值得沉淀为 memory、knowledge,并且还生成了 handoff。
记忆机制复原
activity_ocr_frames,并建立 trigram FTS。~/.yansu-agent/memory/*.md。memory.db 分块、FTS、embedding。Activity DB schema 重点
| 表 | 作用 |
|---|---|
activity_sessions | 一次活动片段。包含 app_names、trigger_kind、ocr_status、snapshot_count、meeting/transcript/audio 字段。 |
activity_events | 原始事件时间线,data 为 JSON 文本。 |
activity_snapshots | 截图文件路径,并附带 ax_text、browser_url、content_type、app、parsed_json。 |
activity_ocr_frames | OCR 文本、confidence、embedding、embed_hash。 |
activity_ocr_fts | FTS5 trigram 全文检索。 |
activity_pipeline_runs | AI 加工流水线审计记录。 |
Memory DB 状态
| 项 | 值 |
|---|---|
| schema_version | 3 |
| embedding model | all-MiniLM-L6-v2 |
| chunk_tokens | 400 |
| chunk_overlap | 80 |
| memory chunks | 15 chunks, 10130 bytes |
| knowledge chunks | 9 chunks, 7310 bytes |
维度
user_insight topic_highlight deep_user_model agentic_memory custom
二进制和 CLI 文本显示,新会话可用 memory search、memory list --dimension agentic_memory 等能力检索这些记忆。
记忆如何被检索
现有证据显示 Yansu 有两条检索路径:宿主自动检索并注入上下文,以及智能体/用户显式调用记忆工具检索。两者使用同一套 memory.db 索引。
自动注入
| 证据 | 含义 |
|---|---|
Search memories using hybrid vector + FTS retrieval — the same index the app uses to auto-inject context into the agent. | Yansu CLI 帮助明确说 app 会用同一个索引自动注入 agent 上下文。 |
[memory] inject search failed | 二进制日志字符串显示存在 memory inject 阶段,失败时会记录。 |
DATA YOU HAVE (do NOT claim you lack it): | 宿主会把已检索到的数据作为 prompt 上下文交给模型。 |
AUTO-INJECT (already happening, no action needed): | 提示词会告诉 agent 某些上下文已经自动注入,不需要再主动查询。 |
<recent-activity-context> | 近期活动上下文也会以 prompt 片段形式注入。 |
因此,普通新对话里,Yansu 很可能会先用用户输入或会话上下文生成检索 query,再从 memory.db 做 hybrid search,把命中的记忆塞进系统/开发者上下文。
主动检索工具
| 工具/接口 | 作用 |
|---|---|
SearchMemory | Wails/App binding 中存在的搜索接口;二进制有 main.(*App).SearchMemory。 |
GetMemoryEntry, GetMemoryEntries | 读取单条或列表。 |
SaveMemoryEntry | 保存手动记忆或编辑后的记忆。 |
SyncMemoryIndex | 同步 markdown 文件到 SQLite 索引。 |
yansu memory search <query> --dimension agentic_memory | CLI 暴露的同一套 hybrid vector + FTS 搜索。 |
二进制 prompt 里还出现 memory search "<query>" --dimension user_insight、memory list --dimension agentic_memory 这样的用法提示,说明 agent 可被建议主动调用记忆检索。
Markdown metadata 与 memory.db 的分工
| 层 | 何时使用 | 说明 |
|---|---|---|
| Markdown 正文 | 长期存储、人类阅读、索引源文件 | 自动生成的 activity memory 通常不是 YAML frontmatter,而是普通 Markdown 字段,如 - **Dimension**: user_insight。 |
| Markdown metadata | 导入旧 Claude memory/feedback 时 | 导入文件可能保留 name、description、type、originSessionId frontmatter;这些内容会作为文本进入索引。 |
memory_files | 同步索引时 | 记录 path/source/hash/mtime/size,判断文件是否变更。 |
memory_chunks | 检索时 | 把 markdown 切成 chunk,保存 start_line/end_line/text/embedding/source。 |
memory_chunks_fts | 关键词检索时 | SQLite FTS5 虚拟表,用于 BM25/全文匹配。 |
memory_embedding_cache | 语义检索时 | 缓存 embedding,当前本机配置为 all-MiniLM-L6-v2。 |
Dimension 本身更像过滤/分组标签,而不是 skill 那种“description 触发器”。CLI 明确说 --dimension 只过滤 memory-source 命中,knowledge hits 会被 drop。
FTS 是什么
FTS 是 Full-Text Search,SQLite 里的全文搜索虚拟表。Yansu 用的是 FTS5:
CREATE VIRTUAL TABLE memory_chunks_fts USING fts5( text, id UNINDEXED, path UNINDEXED, source UNINDEXED, model UNINDEXED, start_line UNINDEXED, end_line UNINDEXED, tokenize='unicode61 remove_diacritics 2' );
简单说:FTS 负责关键词/短语匹配和 BM25 排序;vector search 负责语义相似。Yansu 的 memory search 是 hybrid,也就是两者合并。
CLI 与 Memory 同步机制
后续实测显示,yansu memory ... 不是直接打开 SQLite 或扫描 Markdown 的重型工具,而是一个很薄的本地 HTTP 客户端。真正读写 memory.db 和 ~/.yansu-agent/memory/*.md 的是正在运行的 Yansu 桌面 App 后端。
CLI 如何与主环境交互
~/.yansu-agent/bin/yansu memory search。YANSU_CRON_PORT 或 ~/.yansu-agent/cron-port。YANSU_CRON_SECRET 或 ~/.yansu-agent/cron-secret。127.0.0.1:<port>/memory/search 等本地 API。| 证据 | 结论 |
|---|---|
go version -m ~/.yansu-agent/bin/yansu 只显示 cobra、pflag、websocket 等轻量依赖,没有 SQLite 依赖。 | CLI 本身不像是直接读写 memory.db 的实现。 |
把 YANSU_CRON_PORT=1 后执行 memory search,报错中出现 Get "http://127.0.0.1:1/memory/search?...": connect: connection refused。 | memory search 走本地 HTTP API。 |
把 HOME 换成空目录后执行,报 cron API port not found。 | CLI 默认从 ~/.yansu-agent/cron-port / cron-secret 发现本地服务。 |
运行中的 Yansu.app 进程持有 memory.db、activity.db、sessions.db 及 WAL/SHM 文件句柄。 | 主 App 后端是数据库连接和持久化的所有者。 |
因此,智能体“调用 memory CLI”时,本质是调用宿主提供的本地受控 API。它不是绕过宿主去直接改数据库;如果要保存或同步记忆,也应是 CLI 发请求,Yansu 后端再执行 SaveMemoryEntry、SyncMemoryIndex 这类逻辑。
Markdown 如何与 memory.db 同步
同步关系更像“Markdown 是记忆实体,SQLite 是检索索引”。Markdown 文件保留人类可读内容;memory.db 保存路径登记、chunk、FTS 和向量缓存。
~/.yansu-agent/memory/*.md 是可读、可迁移的持久内容。path/source/hash/mtime/size,用于检测变化。chunk_tokens=400、overlap=80 切成 memory_chunks。memory_chunks_fts,用于关键词检索。all-MiniLM-L6-v2 embedding。| DB 表 | 同步职责 |
|---|---|
memory_files | 每个 Markdown 文件一行,记录 path、source、hash、mtime、size。 |
memory_chunks | Markdown 切块后的结构化副本,含 start_line、end_line、text、embedding。 |
memory_chunks_fts | SQLite FTS5 虚拟表,给关键词/短语检索使用。 |
memory_embedding_cache | 缓存 embedding,避免同一内容重复向量化。 |
如果 Markdown 被新增或修改,合理的同步过程是:后端发现 hash/mtime/size 变化,删除旧 path 对应 chunk,重新切块,更新 FTS,再生成或复用 embedding。CLI 文案里也明确说 memories “live in ~/.yansu-agent/memory/ and are indexed via FTS5 + vector search”,这说明文件是记忆来源,DB 是索引层。
本地抓包结果
为了验证 CLI 到主环境的交互方式,额外做了一次本地 HTTP 转发抓包:让 yansu memory 命令先请求临时代理 127.0.0.1:65284,代理记录 request/response 后再转发到真实 Yansu cron API。
| 项目 | 结果 |
|---|---|
| 抓包日志 | ~/Downloads/yansu-memory-http-capture.jsonl |
| 认证方式 | Authorization: Bearer <cron-secret>。报告和日志中已隐藏 secret。 |
| Search API | GET /memory/search?q=Codex+memory+retrieval&limit=2 |
| List API | GET /memory/list?limit=2 |
| Show API | GET /memory/show/activity-df6994e28cca7163 |
| 响应格式 | JSON。search 返回 path/source/startLine/endLine/score/snippet;show 返回完整 Markdown 内容和 metadata。 |
抓到的 search 请求
{
"method": "GET",
"path": "/memory/search?q=Codex+memory+retrieval&limit=2",
"request_headers": {
"Host": "127.0.0.1:65184",
"User-Agent": "Go-http-client/1.1",
"Authorization": "Bearer [REDACTED]",
"Accept-Encoding": "gzip"
},
"status": 200,
"response_headers": {
"Content-Type": "application/json"
}
}
云端模型请求
系统级网络层面,Yansu 当前通过本地 Clash 代理 127.0.0.1:7897 访问外网。未安装 MITM 证书时,只能看到代理连接和 TLS 加密流,拿不到 HTTPS 请求体明文。
不过 ~/.yansu-agent/yansu-gui.log 已经记录了模型请求元信息,包含:
[native] HTTP POST https://dashboard.yansu.ai/anthropic/v1/messages model=claude-haiku-4-5-20251001 bodyBytes=... hasXAPIKey=false hasAuth=true ct=text/event-stream
日志还会打印部分 suggestion prompt 的完整文本。因此后续要拿完整 prompt,有两条路线:优先挖 yansu-gui.log 中已经明文打印的 prompt;如果要抓所有云端请求体,则需要给 Yansu/系统代理安装受信任的 MITM 根证书,或对 Go HTTP client 做运行时插桩。
模拟聊天抓包观察
为了观察真实 agent 对话的 prompt 组织方式,进行了一个新建会话,两轮无害测试:
请用两句话介绍一下你自己,不要使用任何工具。刚才我让你做什么?请只回答用户上一轮的请求,不要扩展。
第一轮请求
09:19:10 [activity-context] INJECTED tag=1778894338864-0b3505c7d6415 summary_len=273 09:19:10 [compose-prompt] final-len=22958 (parallel, 10 chunks, skill-domain=chat) 09:19:10 [prompt-func] prompt-bytes=22958 history-bytes=0 09:19:10 [native-loop] model=claude-sonnet-4-6 messages=1 tools=14 max_tokens=64000 09:19:10 [native] HTTP POST https://dashboard.yansu.ai/anthropic/v1/messages bodyBytes=33359
含义:首轮没有历史消息,Yansu 先拼一个约 22.9KB 的基础 prompt,并把近期 activity context 注入进去;发送给 Anthropic-compatible endpoint 时,请求体约 33.3KB,带 14 个工具定义。
并行的 activity context 生成
09:19:11 [activity-context] generate: sessions=48 titles=8 ocr=10 user="so2liu" lookbackH=48 09:19:11 [native-loop] model=claude-haiku-4-5-20251001 messages=1 tools=0 09:19:11 [native] HTTP POST ... bodyBytes=4888 09:19:14 [activity-context] generate: haiku ok, summary_len=254
这说明 activity context 不是简单拼数据库字段,而是会把最近 48 小时的 sessions、titles、OCR 内容交给 Haiku 做一次摘要,然后把摘要注入主 agent prompt。
第二轮请求
09:19:33 [compose-prompt] final-len=21661 (parallel, 10 chunks, skill-domain=chat) 09:19:33 [prompt-func] prompt-bytes=21661 history-bytes=346 09:19:33 [native-loop] model=claude-sonnet-4-6 messages=3 tools=14 max_tokens=64000 09:19:33 [native] HTTP POST ... bodyBytes=32394
第二轮 messages=3,对应上一轮 user、上一轮 assistant、当前 user。history-bytes=346 说明多轮历史以普通 conversation messages 进入模型,而不是被揉进基础 prompt。基础 prompt 仍然重新组合,长度略有变化。
sessions.db 中保存的内容
sessions.db.stream_items 保存了用户消息、assistant 回复、thinking、run created/completed 等 stream items。本次测试会话 1778894338864-0b3505c7d6415 中可见:
0 user 请用两句话介绍一下你自己,不要使用任何工具。 2 thinking 根据 SOUL.md 中的指导...真诚有帮助...有个性... 3 assistant 我是Yansu,一个能帮你写代码、查资料、还能记住你工作习惯的AI助手... 5 user 刚才我让你做什么?请只回答用户上一轮的请求,不要扩展。 7 thinking 上一轮用户说的是:"请用两句话介绍一下你自己,不要使用任何工具。" 8 assistant 我是Yansu,一个能帮你写代码、查资料、还能记住你工作习惯的AI助手...
这里还暴露了一个 prompt 来源:模型 thinking 中提到 SOUL.md,说明主 prompt 内部包含一个类似人格/行为规范的文档,至少有“真诚有帮助、不是表演性、有个性、有观点、简洁”等规则。
会话结束后的记忆 triage
09:19:58 [memory] starting pipeline for conversation:1778894338864-0b3505c7d6415 09:19:58 [memory] triage ExecuteTask starting 09:19:58 [native-loop] model=claude-haiku-4-5-20251001 messages=1 tools=0 09:19:58 [native] HTTP POST ... bodyBytes=12903 09:20:01 [memory] triage: not worth for conversation:... — trivial test interaction... 09:20:01 [triage] memory decision=not_worth worth_memory=false worth_knowledge=false
这说明聊天结束或静置后,Yansu 会把 conversation 送入单独的 memory triage 模型调用,判断是否值得沉淀为 memory/knowledge。本次因为是测试对话,被判定为 not_worth,没有新增记忆。
当前能确定的 prompt 组织模型
MITM 抓到完整 Prompt
后续通过 mitmproxy 成功抓到了 Yansu 发往 https://dashboard.yansu.ai/anthropic/v1/messages 的 HTTPS 明文请求体。
方法
- 安装
mitmproxy,生成本地 CA。 - 把
~/.mitmproxy/mitmproxy-ca-cert.cer加入当前用户钥匙串并信任。 - 启动
mitmdump -p 8089 --mode upstream:http://127.0.0.1:7897,让 MITM 继续走原 Clash 上游。 - 临时把
~/.zprofile中代理端口从7897改为8089,启动 Yansu 后立刻恢复为7897。 - 确认 Yansu 进程连接
127.0.0.1:8089后发送测试消息:抓包测试:请只回复 OK,不要调用工具。 - 抓包结束后,重启 Yansu 回到
127.0.0.1:7897,并停掉 mitmproxy。
| 产物 | 路径 |
|---|---|
| 原始 MITM JSONL | ~/Downloads/yansu-anthropic-mitm-capture.jsonl |
| 拆分后的请求体 | ~/Downloads/yansu-mitm-extracted/ |
| 主 agent 请求 | 03-claude-sonnet-4-6.json / 03-claude-sonnet-4-6.messages.txt |
| 抓包脚本 | /Users/yangliu35/daily/yansu_mitm_capture.py |
抓到的请求分类
| 文件 | 模型 | 用途 | messages | tools | 大小 |
|---|---|---|---|---|---|
01-claude-haiku-4-5-20251001 | Haiku | 启动后的 activity-context warmup 摘要。 | 1 | 0 | 约 4.9KB |
02-claude-haiku-4-5-20251001 | Haiku | Crystal intent classifier,判断用户是否要创建交互式应用。 | 1 | 0 | 约 1.1KB |
03-claude-sonnet-4-6 | Sonnet | 主 agent 请求。 | 1 | 14 | 约 33.1KB |
04-claude-haiku-4-5-20251001 | Haiku | 对话后的 memory triage / 是否值得沉淀。 | 1 | 0 | 约 6.4KB |
主请求结构
主请求的 system 不是简单字符串,而是 Anthropic system content block,内容以 “You are Yansu, a helpful desktop coding assistant...” 开头。大量 Yansu 专属上下文被放在第一条 user message 的 text 中。
{
"model": "claude-sonnet-4-6",
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "<memory-cli>...</memory-cli>\\n\\n<activity-cli>...</activity-cli>\\n\\n<soul-context>...</soul-context>\\n..."
}
]
}
],
"system": [{"type": "text", "text": "You are Yansu, a helpful desktop coding assistant..."}],
"tools": [...14 tools...],
"thinking": {...},
"stream": true
}
主 message 内部顺序
| 区块 | 作用 |
|---|---|
<memory-cli> | 解释 memory 的定义、维度、自动注入机制、何时主动调用 yansu memory CLI,以及禁止声称“没有记忆”。 |
<activity-cli> | 解释 activity sessions、meeting transcripts、OCR、桌面活动 CLI,并要求在相关问题中主动使用。 |
<soul-context> | 人格和行为准则,来自 SOUL.md,包含“不要像 chatbot、要有观点、先自己查、尊重隐私”等规则。 |
<yansu-context> | 工具和浏览器使用规范,包括 cron/automation 约束、browser server API、browser state rules 等。 |
| 用户消息 | 最后追加真实用户输入:抓包测试:请只回复 OK,不要调用工具。 |
14 个主 agent 工具
cua_driver, TodoWrite, Bash, BashOutput, KillShell, Glob, WebSearch, Write, Grep, ExitPlanMode, Read, Edit, WebFetch, NotebookEdit。
关键结论
- Yansu 的“记忆能力”不是隐藏工具名,而是通过
<memory-cli>prompt 明确教 agent 何时调用yansu memory。 - 自动 memory 注入发生在宿主侧;如果命中,会在同一条 user message 里更早出现
<memory-context>/<knowledge-context>。 - Activity/会议/OCR 也是通过 prompt 暴露为 CLI 能力,要求 agent 主动用 Bash 调
yansu activity ...。 - 主 agent 的工具列表仍然是 Anthropic tools;Yansu 自己的 memory/activity CLI 不是 Anthropic tool,而是要求 agent 通过
Bash执行。 - 辅助链路很多:activity context warmup、crystal intent classifier、memory triage 都是独立 Haiku 请求。
Prompt 原文索引与构建洞见
原文文件入口
| 文件 | 用途 | 大小 | 链接 |
|---|---|---|---|
03-claude-sonnet-4-6.json |
主 agent 完整 Anthropic JSON 请求体,含 system、messages、tools、thinking、stream。 | 36KB / 374 行 | 打开 JSON |
03-claude-sonnet-4-6.messages.txt |
主请求中 message text 的可读拆分版,最适合直接看 prompt。 | 22KB / 268 行 | 打开文本 |
01-claude-haiku-4-5-20251001.messages.txt |
activity-context warmup prompt,负责生成当前用户工作状态摘要。 | 4.6KB / 85 行 | 打开文本 |
02-claude-haiku-4-5-20251001.json |
Crystal intent classifier,请求模型只回答 YES/NO。 | 1.2KB / 10 行 | 打开 JSON |
04-claude-haiku-4-5-20251001.messages.txt |
conversation memory triage prompt,判断对话是否值得沉淀为 memory/knowledge。 | 6.2KB / 100 行 | 打开文本 |
yansu-anthropic-mitm-capture.jsonl |
MITM 原始抓包。Authorization / x-api-key 已隐藏。 | 69KB / 8 行 | 打开 JSONL |
主 Prompt 行号索引
| 位置 | 区块 | 说明 |
|---|---|---|
JSON system | 基础系统提示 | 约 1142 字符,定义 Yansu 是桌面 coding assistant,约束文件路径、工具使用、编辑方式等。 |
messages.txt 第 2 行 | <memory-cli> | 解释 memory 的维度、自动注入、CLI 查询时机、禁止说“不记得”。 |
messages.txt 第 38 行 | <activity-cli> | 解释 activity sessions、meeting transcripts、OCR、桌面活动检索命令。 |
messages.txt 第 63 行 | <soul-context> | 人格层。规定 Yansu 的语气、价值观、主动性和隐私边界。 |
messages.txt 第 128 行 | <yansu-context> | 产品/工具层。包含 automation、browser、cron、工具使用规则等。 |
messages.txt 第 269 行附近 | 真实用户消息 | 最后追加用户输入:抓包测试,只回复 OK。 |
短摘录
| 来源 | 短摘录 | 洞见 |
|---|---|---|
| system | You are Yansu, a helpful desktop coding assistant | 身份定义放在 Anthropic system block,而不是普通 user text。 |
| memory-cli | WHAT MEMORY IS | 记忆模块是显式 prompt 教学,不是隐藏 Anthropic tool。 |
| memory-cli | AUTO-INJECT (already happening, no action needed) | 自动检索在宿主侧完成,agent 只消费已注入块。 |
| activity-cli | DATA YOU HAVE | Yansu 明确告诉 agent:桌面活动、OCR、会议 transcript 是“已经拥有的数据”。 |
| soul-context | You're not a chatbot. | 人格层很强,解释了 UI 回复里的非模板化风格。 |
| yansu-context | Available tools: | 浏览器、automation 等产品能力通过 prompt 说明和工具 schema 共同暴露。 |
构建链路复原
skill-domain=chat。构建洞见
- Prompt 分层很清楚:system 是通用 coding assistant 行为约束;message text 是 Yansu 产品能力、记忆能力、活动能力、人格和浏览器规则。
- Memory 是“宿主检索 + CLI 补查”双路径:自动注入靠宿主检索;主动查记忆靠 prompt 指示 agent 用 Bash 调
yansu memory。 - Activity 被提升成一等上下文:prompt 不把 OCR/会议当外部资料,而是告诉 agent 这些是它“已经有”的数据。
- 真实用户输入在最后:所有能力说明和上下文先进入同一条 user message,末尾才拼真实用户消息。
- 辅助模型很多:轻量 Haiku 负责分类、上下文摘要、记忆沉淀;Sonnet 负责主 agent 回答和工具执行。
- 工具边界不是全都 schema 化:memory/activity/cron 的一部分能力通过 CLI 文本协议暴露,真正 Anthropic tools 只有 14 个。
提示词与模型调用
完整 prompt 未能无损恢复,但可以确认 prompt 家族和输出 schema。
可见的 prompt / schema 片段
| 片段 | 含义 |
|---|---|
worthMemory, worthKnowledge, worthAutomation, worthHandoff | Activity triage 输出 schema。 |
## Memory Points, - **Dimension**, - **Activity Session** | memory/knowledge markdown 生成模板。 |
AI returned no memory points, AI returned no knowledge points | 抽取为空时的分支。 |
<recent-activity-context> | 新会话可注入近期活动上下文。 |
Return exactly 5 short, first-person prompts as strict JSON | composer suggestions 生成。 |
ADDITIONAL DATA SOURCES (OpenCLI + Computer Use API) | agent runtime context 注入。 |
-ldflags "-w -s",调试符号被剥离。prompt 还可能由多段字符串、模板和 runtime activity summary 组合,或由云端服务下发。因此不能仅靠静态 strings 拿到完整提示词。
下一步可做
- 用 Ghidra/IDA/Hopper 对
Yansu主程序做字符串 xref,围绕worthMemory、execute memory generation、activity-context找调用链。 - 动态抓本地 HTTP/Unix socket 和云端
/anthropic请求,通常比反编译更容易拿到完整 prompt。 - 注意 token 和个人隐私,抓包前要隔离输出。
Computer Use / cua-driver 逆向
Yansu 内置路径:
/Applications/Yansu.app/Contents/Resources/cua-driver-bundle/CuaDriver.app/Contents/MacOS/cua-driver
它是 Swift/原生 macOS 二进制,通用架构 x86_64 + arm64。help 输出显示:
AlmaComputerUse — macOS desktop automation helper (AX + ScreenCaptureKit)
Subcommands:
daemon [--socket PATH] [--idle-seconds N]
Start the long-running daemon. Opens a Unix domain socket and serves NDJSON requests.
上游关系
内置 README 指向 https://github.com/trycua/cua/tree/main/libs/cua-driver,并标注 MIT。二进制字符串保留 AlmaComputerUse,因此较可能是 trycua/cua-driver 系列的 Alma/Yansu 改造版本。
Swift 符号恢复出的模块
| 模块 | 恢复出的职责 |
|---|---|
Daemon | Unix socket daemon、NDJSON 请求、dispatch、idle timer、signal handlers。 |
MCPServer | MCP tools/resources/prompts、token、whitelist、PKCE、JSON sanitise。 |
AXBridge | Accessibility 读取、snapshot、click/type/press/scroll/setValue、launchApp、windowInfo。 |
ElementStore | snapshot element cache,resolve/refetch,AXObserver 监听元素销毁。 |
WindowList | CGWindow 枚举、top window、window layer、on screen、visible at point。 |
VisualIndex | 基于图片路径和 maxResults 生成 OCR region,字段含 ref/text/bounds/confidence。 |
EventDispatch | click/drag/scroll/pressKey/typeText,构造 CG/NSEvent,保焦点。 |
SCPermissionProbe | ScreenCaptureKit 权限检测。 |
SecCodePeer | 校验调用方签名,返回 teamId/bundleId。 |
PIPManager, LensOverlay | 窗口预览、overlay、点击/滚动/type badge。 |
近似 API 面
AXBridge.snapshot(bundle:pid:windowId:depth:interactiveOnly:store:wakeupSeconds) AXBridge.click(ref:store:strategy:button:clickCount:showCursor) AXBridge.type(ref:text:replace:store:showCursor) AXBridge.press(ref:key:store:showCursor) AXBridge.scroll(ref:direction:pages:store:showCursor) AXBridge.setValue(ref:value:store) AXBridge.launchApp(bundle:activates) EventDispatch.click(at:button:clickCount:pid:modifiers) EventDispatch.drag(from:to:button:pid:steps) EventDispatch.scroll(at:direction:pages:pid) EventDispatch.pressKey(_:pid:) EventDispatch.typeText(_:pid:) VisualIndex.index(imagePath:maxResults) -> [Region(ref,text,bounds,confidence)] WindowList.windows(for: pid) -> [Entry(id,title,bounds,pid,layer)]
分层操作策略
底层 API 证据
AXUIElementCopyAttributeValue AXUIElementPerformAction AXUIElementSetAttributeValue CGEventCreateKeyboardEvent CGEventSetIntegerValueField CGEventTapCreate CGWindowListCopyWindowInfo CGWindowListCreateImage SCStream Vision
Browser User 逆向
主程序 Go module 明确依赖 github.com/go-rod/rod v0.116.2。二进制里有大量 Chrome DevTools Protocol 方法名和 browser server 日志。
| 证据 | 解释 |
|---|---|
github.com/go-rod/rod | Go 侧浏览器自动化核心库。 |
Target.createBrowserContext, DOMSnapshot.captureSnapshot, Runtime.evaluate | Chrome DevTools Protocol。 |
YANSU_BROWSER_BRIDGE, [browser-server] bridge enabled | Yansu 自己封装了 browser bridge/server。 |
chromium-browser-snapshots, playwright.azureedge.net/builds/chromium | 可能按平台下载或复用 Chromium 构建。 |
结论:Browser User 主要是 Go + go-rod + CDP。Computer Use 负责原生 macOS app 和无法 CDP 接入的 Electron/CEF/国产桌面 app。
多轮上下文与自动检索抓包
2026-05-16 15:10-15:13 通过 MITM 做了三轮真实对话抓包。原始请求已保存为
/Users/yangliu35/Downloads/yansu-multiturn-mitm-capture.jsonl,
结构化拆解在 /Users/yangliu35/Downloads/yansu-multiturn-extracted-final/。
| 轮次 / 请求 | 模型 | messages | 自动上下文 | 结论 |
|---|---|---|---|---|
03-claude-sonnet-4-6 |
Sonnet 4.6 | 1: user | <recent-activity-context> |
第一轮只有当前 user message;包含桌面活动上下文,没有 memory/knowledge 命中块。 |
05-claude-sonnet-4-6 |
Sonnet 4.6 | 3: user, assistant, user | <knowledge-context> |
第二轮会基于本轮用户发言自动检索并注入 knowledge;不是智能体手动调用工具。 |
12-claude-sonnet-4-6 |
Sonnet 4.6 | 5: 两轮历史 + 当前 user | <knowledge-context> + <memory-context> |
第三轮再次自动检索,这次同时命中 knowledge 和 memory。 |
13-claude-sonnet-4-6 |
Sonnet 4.6 | 7: 加上 tool_use/tool_result | 沿用第三轮注入块 | Read 工具执行后,结果作为 Anthropic tool_result 回填给下一次模型请求。 |
System prompt
有 system prompt,并且主模型请求每次都带同一段约 1143 字符的 system content block。 原文已拆到以下文件,内容一致:
/Users/yangliu35/Downloads/yansu-multiturn-extracted-final/03-claude-sonnet-4-6.system.txt/Users/yangliu35/Downloads/yansu-multiturn-extracted-final/05-claude-sonnet-4-6.system.txt/Users/yangliu35/Downloads/yansu-multiturn-extracted-final/12-claude-sonnet-4-6.system.txt/Users/yangliu35/Downloads/yansu-multiturn-extracted-final/13-claude-sonnet-4-6.system.txt
它定义 Yansu 是桌面 coding assistant,强调只能通过工具接触用户系统、文件路径必须是绝对路径、Edit 要精确匹配、长运行命令要后台执行,并注入本轮临时工作目录。
第二次发言是否会自动检索
会。第二轮主请求 05-claude-sonnet-4-6.messages.txt 的结构是:
message 0: user 第一轮用户原话 message 1: assistant ALPHA message 2: user <knowledge-context> + <memory-cli> + <activity-cli> + ... + 第二轮用户原话
这说明检索发生在主模型请求之前,由宿主侧按当前 user utterance 自动完成。第二轮没有任何工具调用,智能体仍然看到了
<knowledge-context>,因此不是“智能体主动搜索后得到”的。
什么时候用自动块,什么时候用 memory CLI
抓包里的提示词规则很明确:每一轮 App 会先语义检索 memory/knowledge 并把命中结果放进
<memory-context> / <knowledge-context>。智能体应优先直接使用这些块。
只有当用户问题需要更深、更窄、或当前自动块不够时,才用 memory CLI 继续查;提示词还明确要求不要为同一件事重复调用 CLI。
读知识时如何展示
第三轮要求“必须使用 Read 工具”后,主模型先产生 tool_use:
Read {"path":"/Users/yangliu35/.yansu-agent/knowledge/conversation-deca122ee72fa6a6.md"}。
Yansu 执行后在本地 sessions.db.stream_items 写入:
kind=tool tool_name=Read tool_input={"path":"...conversation-deca122ee72fa6a6.md"}
kind=result tool_name=Read content=带行号的 Markdown 文件内容
随后的 13-claude-sonnet-4-6 请求把 assistant 的 tool_use 和 user 的
tool_result 一起作为消息历史传回模型,最后模型回答标题
LLM 多轮对话中知识/记忆上下文注入的验证方法。UI 侧表现为对话中出现可折叠的工具步骤/step chip,然后展示最终自然语言答案。
写 memory / knowledge 时如何展示
本次测试期间,后台记忆流水线自动生成了两个文件:
/Users/yangliu35/.yansu-agent/knowledge/conversation-deca122ee72fa6a6.md/Users/yangliu35/.yansu-agent/memory/conversation-deca122ee72fa6a6.md
用户可见展示不是插入一条 assistant message,而是 toast:New Knowledge 和 New Memory。
日志随后显示 memory sync 索引了这两个文件:2 indexed, 0 removed, 22 total files。
这说明写入可以由后台 triage 自动发生,不一定是主智能体工具调用。
多轮上下文构建
关键点:历史对话不是被揉进 system prompt,而是 Anthropic messages 数组里的交替 user/assistant。
每一轮新的自动检索块则被拼在“当前 user message”的最前面。
Embedding 模型与本地资源占用
当前这台机器上的 Yansu memory/knowledge 语义检索使用 all-MiniLM-L6-v2。
这是本地 ONNX 模型,不是每次检索都调用远端 embedding API。
| 项目 | 实测值 |
|---|---|
| memory 元数据模型 | memory_meta.model = all-MiniLM-L6-v2 |
| 模型文件 | /Users/yangliu35/.yansu-agent/models/all-MiniLM-L6-v2/model.onnx |
| 词表 | /Users/yangliu35/.yansu-agent/models/all-MiniLM-L6-v2/vocab.txt |
| 模型目录大小 | 86M,其中 model.onnx 约 86M,vocab.txt 约 226K |
| 向量维度 | 384 |
| 缓存向量 | 本机当前 memory_embedding_cache 有 52 条,平均每条 JSON 向量约 8064 字符 |
| memory.db 大小 | 约 1.0M |
| 当前索引规模 | 23 个文件,30 个 chunks |
| chunk 配置 | chunk_tokens=400,chunk_overlap=80 |
| Yansu 进程 RSS | 观察时约 1.25GB RSS;这是整 App 占用,不能全部归因于 embedding |
二进制字符串里能看到 ONNX Model (all-MiniLM-L6-v2)、ONNX Runtime、
set onnx intra-op threads、set onnx inter-op threads、
download onnx runtime 等证据;模型文件实际存在于用户数据目录。
注意:缓存表字段里的 provider 值显示为 openai,但结合本地 ONNX 模型文件、模型名、二进制字符串和设置页文案,
更像是内部 provider 命名或兼容层遗留字段;当前 active 模型实际是本地 all-MiniLM-L6-v2。
推理方式与跨平台判断
这个模型本质上是 Sentence Transformers 系列的 MiniLM encoder,ONNX 推理流程通常是:
用 vocab.txt 做 BERT WordPiece tokenization,构造 input_ids / attention_mask / 可能的
token_type_ids,送入 ONNX Runtime,取 token embeddings 后按 attention mask 做 mean pooling,再做 L2 normalize,
得到 384 维向量。Yansu 二进制里有 ONNX Runtime、set onnx intra-op threads、
set onnx inter-op threads 等字符串,说明它不是用 PyTorch,而是本地 ONNX Runtime。
跨平台方面,model.onnx 和 vocab.txt 本身是平台无关的;真正依赖平台的是 ONNX Runtime 动态库和 tokenizer/推理封装。
macOS arm64、Linux x86_64、Linux arm64/aarch64 理论上都能跑。若是 ARM 镜像,建议用 linux/arm64
基础镜像并安装/打包 arm64 版 onnxruntime;不要把 macOS 的 .dylib 或 x86_64 的 .so 复制进 ARM 容器。
速度方面,all-MiniLM-L6-v2 属于小模型,CPU 推理通常足够快,适合本地 memory chunk embedding。
对 400-token 左右 chunk,单条一般是毫秒到几十毫秒量级,批量时吞吐会更好。实际速度主要取决于 ONNX Runtime 线程数、
CPU 架构、是否启用 SIMD/NEON、batch size 和 tokenization 实现。Yansu 设置页里有 embedding test latency 文案,说明它自己也支持测一次端到端延迟。
无法继续确定的部分
- 主程序完整 Go 源码:二进制被 strip,当前没有源码包。
- 完整 prompt:可能动态拼接或由云端服务返回,静态 strings 只能拿片段和 schema。
- Yansu 对 trycua/Alma 的具体改动:需要对照上游源码或用 Ghidra/Hopper 做函数级 diff。
- 真实模型请求体:需要动态抓包或插桩,且要处理 HTTPS、token 和隐私。
~/.yansu-agent。
建议的下一步逆向路线
- 下载或 clone
trycua/cua,先看libs/cua-driver源码,再与 Yansu 符号表比对。 - 安装 Ghidra,对
Yansu做 string xref:从worthMemory、execute memory generation、activity-context、prompt-func反查调用链。 - 对
cua-driver做 Swift 符号级函数图,重点看Daemon.dispatch和EventDispatch.makeClickEvent。 - 如要 prompt,优先动态抓
/v1/messages或 Yansu gateway 请求体,静态反编译只是辅助。