螺壳系统文档

螺壳(Luoke)是螺壳实验室(Luoke Labs)开发的一组本机 AI 工具:所有模型推理和数据都在用户自己的 Mac 上。本文档描述系统的结构、各组件的职责与接口、数据如何流动,以及运行和发布方式。

组件

组件仓库职责
螺壳(主控)luoke-core用户装的应用「螺壳」;本机模型推理的唯一入口;安装、升级、卸载;调度空闲任务
记忆luoke-mem共用的本机记忆;唯一形成关于用户结论的地方
邮件助手luoke-mail分拣、清理、精选、起草;可撤回的处理自动执行并每日复查
输入法luoke-imemacOS 输入法;沙盒引擎出候选,本地模型重排和联想
agentluoke-agent唯一主动联系用户的组件;合并提问、建议和工具的事项
螺壳体检luoke-audit在空闲时查这台 Mac 的空间和安全;只经 agent 说话(未发布)
深度搜索luoke-search给本机文件和照片图库建索引,按字词和意思找回;没有界面,入口在螺壳(经 agent)(规划中)

架构

flowchart LR
  subgraph Mac["用户的 Mac"]
    direction LR
    mail["邮件助手"]
    ime["输入法"]
    agent["agent(螺壳的后台组件)"]
    search["深度搜索(未发布)"]
    audit["螺壳体检(未发布)"]
    subgraph core["主控"]
      engine["推理引擎(底座)"]
      workers["打分 / 联想 / 嵌入进程"]
      mem[("记忆 mem.db")]
      ep["高级用户端点"]
    end
  end
  mail -- "生成任务" --> engine
  ime -- "打分、续写" --> workers
  mem -- "归纳" --> engine
  mail -- "记录" --> mem
  ime -- "记录" --> mem
  mem -- "理解" --> mail
  mem -- "理解" --> ime
  mem -- "提问、建议" --> agent
  agent -.->|"调用能力(接口默认关闭)"| mail
  agent -.->|MCP 工具| search
  agent -.->|MCP 工具| audit
  search -.->|嵌入| workers
  ext["外部工具(如 pi)"] -.-> ep --> engine

核心原则

  • 本机就是边界:数据不离开用户自己的设备;在本机内部,各组件可以读取完成工作所需的任何数据。
  • 模型只经主控:所有推理通过主控的任务接口进行,组件不自行加载模型。
  • 只有记忆形成结论:工具记录发生了什么,记忆据此形成带依据、分三档把握程度的理解。
  • 先做、事后复查:能撤回的处理在用户确认一次习惯后自动执行并给出提示;不可逆或对外的操作始终由用户点按。
  • 凭证不进记忆:密码、验证码、密钥、令牌、完整卡号和证件号在写入时遮去。

决策记录见 决策。

阅读顺序

核心概念

配件

由主控安装和管理的一个签名 .app,例如邮件助手、输入法,以及未发布的深度搜索、体检。agent 现在是开发版,主控不管它,以后随螺壳装好。每个配件在 luoke-accessory.json 中声明自己的身份、需要的任务、空闲任务、数据目录和给 agent 的能力。Python 配件自带固定版本的 Python。记忆没有独立的 .app,它打包在主控里。

任务

配件向主控请求推理的单位,名字形如 luoke-mail.triage。配件只说要哪个任务,不指定模型;主控按配方和机器的硬件档决定用哪个模型、参数是多少。任务分四种:

kind做什么例子
generate生成文字(流式,约束为 JSON)邮件分拣、记忆归纳
score给候选打分输入法重排
predict续写输入法联想
embed向量深度搜索

提示词、输出格式和业务判断写在调用任务的配件里;主控只提供通用的推理能力。

硬件档

按内存把机器分为 16、32、128 三档,每档有自己的底座模型、内存上限和底座是否常驻。每个任务在哪一档开启,由评测决定:关着的任务连续 3 遍全部通过才开启(当天评一遍,另两遍在发布后补跑,下一版生效),开着的一遍不过即关闭。

请求等级

主控按三个等级调度:打字(输入法打分和联想,最优先)、用户在等(用户正在等结果的请求)、后台(可以等待的工作)。后台工作只在机器空闲、接通电源、未过热、内存充足时运行,用户一回来就让路。

空闲任务

配件登记的"空闲时运行"的命令,例如记忆的归纳、邮件助手判断旧邮件。主控只决定何时启动(安静时刻、满足最短间隔),不关心它做什么;用户回来时让它收尾。

记录

工具写入记忆的"发生了什么":谁(用户、第三方、规则自动执行)、做了什么、什么时候真实发生,可附原文。记录是理解的依据。

理解与把握程度

记忆从记录归纳出的关于用户的认识:事实、关注的话题、不感兴趣、处理习惯、常用说法、精选口味。每条理解引用它的依据,并有三档把握程度:

档含义谁能用
猜测推出来但证据不足不使用,只在记忆界面中显示
比较确定依据足够各工具可以使用
确定用户说过或确认过各工具可以使用,可据此自动执行

一条推断能到哪一档,取决于依据的类型和数量(见记忆 · 定档规则)。会变的理解太久没有新依据会变成"以前的事"。

提问与建议

记忆在空闲时整理理解,产出两类请求:提问(只问用户才能定、答了有用的猜测)和建议(如"以后这类邮件都自动归档?")。它们由 agent 或相关工具的页面展示,用户的回答交回记忆定档。

处理习惯与自动执行

用户对某个发件人某类邮件反复做同样的处理,记忆形成一条处理习惯并建议自动执行;用户同意后,邮件助手在同步时照做。只有能撤回的动作(归档、标已读、加标签)会自动执行,每次都可在"今日处理"中撤销,撤销一次即视为反证。

能力

工具在清单中向 agent 声明的可报事项和可执行动作,经工具协议(MCP 子集)调用。会改动数据的调用只在用户点按卡片时发出。

数据流

一封邮件

sequenceDiagram
  participant M as 邮箱(IMAP)
  participant L as 邮件助手
  participant C as 主控
  participant R as 记忆
  participant U as 用户
  L->>M: 同步收件箱
  L->>C: triage(用户在等或后台)
  C-->>L: 建议、摘要、截止日期、可疑
  L->>M: 标已读;按确定的习惯归档或加标签
  L->>R: 记录 mail-read、mail-auto
  U->>L: 在"今日处理"中撤销或改建议
  L->>R: 记录 mail-override、mail-undo-auto
  R->>C: 空闲时归纳话题和习惯
  R-->>L: 建议"以后都这样?"
  1. 后台循环按间隔同步,新邮件逐封交给主控的 luoke-mail.triage,"关于用户"取自记忆中比较确定以上的事实和习惯。
  2. 分拣后自动标已读(一条可整批撤销的操作记录),并执行记忆中确定、可自动执行的处理习惯;结果进入"今日处理"。
  3. 用户的操作(改建议、亲手归档、撤销自动处理)写成记录。
  4. 记忆的归纳任务从这些记录形成处理习惯和话题,向邮件助手提出建议;用户同意后习惯成为确定,下一次同步开始自动执行。

打的字

sequenceDiagram
  participant E as 打字引擎(沙盒)
  participant P as 个人化组件
  participant C as 主控
  participant R as 记忆
  E->>P: score(候选)
  P->>C: luoke-ime.rerank
  C-->>P: logprobs
  P-->>E: 分数(引擎据此重排)
  P->>R: ime-commit(上屏 + 前文,凭证已遮去)
  R->>C: 空闲时归纳常用说法和话题

提问和建议

  1. 记忆的反刍在每次归纳任务的开头和结尾运行,生成提问(每天 ≤ 2)和建议(每天 ≤ 3),写入 asks。
  2. agent 每 300 秒读取一次,在窗口、通知或简报中展示;与邮件有关的建议也在邮件助手的记忆页中展示。
  3. 用户的回答经 Memory.reply 交回,记忆据此定档。

外部工具

高级用户可开启主控的本机端点,让 pi 等工具通过 OpenAI 兼容接口使用本机模型,包括用户自己登记的模型。这些请求与配件请求一起由主控调度,按"用户在等"处理。

螺壳(主控)

用户装的应用「螺壳」(螺壳.app);"主控"是它在内部分工里的叫法。本机所有模型推理的唯一入口,也是各应用的安装器。它按配方为每个硬件档选择模型,调度各配件的请求和空闲任务,监管推理引擎和专用模型进程,并负责下载、升级和卸载。

仓库 luoke-core · bundle id ai.luoke.core(开发版 ai.luoke.core.dev,名为「螺壳(开发)」,数据在 luoke-core-dev,不认领 luoke://)· 接口细节见「参考 · 主控任务接口」「参考 · 配件清单与配方」

组成

进程谁启动说明
菜单栏本体 螺壳.app(装在 /Applications 或 ~/Applications;改名前的 螺壳主控.app 升级时换成新名字)用户、登录项或 luoke://Swift:菜单栏海螺图标和菜单、设置、「应用」窗口(逐个应用安装、升级、回到上一版本、卸载)、主控小窗口、首次运行、安装器、检查更新和升级、卸载、打开记忆界面
主控进程本体自带 Python:python -m luoke_core serve,在 Unix socket run/hub.sock 上提供任务层;父进程退出时随之退出
推理引擎主控oMLX(引擎包内的 omlx-cli serve),只监听 127.0.0.1 随机端口并带随机密钥;密钥写在引擎运行目录里只有本账户能读的 settings.json(auth.api_key),不进命令行和环境变量;以 HF_HUB_OFFLINE=1 运行,不去网上找模型;运行底座模型
打分 / 联想工作进程主控,首次请求或预热时;空闲 10 分钟停,下次用到再起引擎包 Python 运行 score_worker.py(HF_HUB_OFFLINE=1),经管道通信,不监听端口
嵌入工作进程主控,首次请求或预热时;空闲 10 分钟停,下次用到再起embed_worker.py,同上
空闲任务主控配件 .app 自带的 Python(或清单 exec 指定的 Contents/MacOS 下的可执行文件)运行清单中 jobs 的命令,环境带 LUOKE_HUB_SOCKET
常驻配件本体以 --resident 启动;10 分钟内最多自动重启 3 次
记忆界面本体用主控自带的 Python 运行记忆的入口
高级用户端点主控内的线程OpenAI 兼容,只听 127.0.0.1(17860 起,被占往后找);Host 只认 127.0.0.1 / localhost,带 Origin 头的请求答 403;连续请求按能耗模式歇息

生命周期

启动

  1. 本体判断是否以安装器身份运行:位于认得的安装位置(/Applications 或 ~/Applications 下的 螺壳.app、螺壳主控.app)时不是;否则文件名不是 螺壳.app、位于 /Volumes/ 或被系统隔离时是,进入安装流程。
  2. 不是装好的那份(按 /Applications/螺壳.app、/Applications/螺壳主控.app、~/Applications/螺壳.app、~/Applications/螺壳主控.app 的顺序取第一个 bundle id 相同的)时弹框说明后退出。先装上菜单栏图标;上次换包没成时弹框说明原因(「App 管理」挡住时指明去系统设置里打开),升级标记作废;把 luoke 命令行链接改指到装好的那份;在后台清理上次经「检查更新」升级时留下的安装包(首次运行没走完也清)。首次运行未完成时显示首次运行向导(升级标记作废,有预选时预选上那个应用,主控进程到下载那一步才启动);否则启动主控进程,有升级标记(run/upgrade.json)时重新登记登录项(用户关掉过的不动)并接着显示升级卡片;没有则直接拉起常驻配件,有预选(run/preselect.json)时打开「应用」窗口在那一行里装。主控进程启动时注销 previous.noindex 里旧版的 LaunchServices 登记。
  3. 主控先绑定 socket 并应答(启动期间除 /tasks、/status、/downloads 外答 busy),再启动引擎:清理上次留下的孤儿进程、等待底座出现、做冒烟检查。
  4. 启动采样线程,进入运行状态;设置中开启了端点则开启端点。

运行

采样线程每 5 秒检查引擎是否崩溃,刷新电源、温度和内存压力,按需打断后台工作、卸载闲置底座、跑探针、每 60 秒重扫配件清单、调度空闲任务,并写入 state.json。本体每 3 秒轮询一次 /status。空闲超过 10 分钟的专用模型工作进程停掉;内存压力 critical 时全部停掉,30 秒内不起。

退出

本体发 SIGTERM,等待 40 秒。主控依次停止端点、空闲任务(SIGTERM,15 秒后 SIGKILL)、工作进程、引擎,最后删除自己绑定的 socket。

调度

请求等级

等级来源待遇
打字打分、联想(输入法)立即处理;记为交互;16 GB 档会立即卸载底座让出内存
用户在等生成请求带 X-Luoke-Priority: user-waiting;端点请求优先;正在运行的后台工作若预计剩余超过 11 秒则被打断(返回 interrupted);端点请求先等完能耗模式的歇息再开始
后台X-Luoke-Priority: background;空闲任务需通过后台接纳

交互中指最近 60 秒内有过用户在等的请求或新的预热;安静时刻指距最近一次此类事件满 5 分钟。

后台接纳

按顺序检查,第一条不满足即挡回:

条件返回原因
全机后台已暂停(电池、低电量模式、过热、内存压力)paused各原因
底座放不下pausedmemory
交互中busyinteractive
最近 180 秒打过分busyscored_recently
16 GB 档且不在安静时刻busynot_quiet
别的配件的空闲任务在运行busyidle_job
引擎里已有一项后台工作busyengine_busy
能耗模式的歇息中(空闲任务所属配件的请求不挡回,由主控挂着等歇息过去,最多 4 分钟)busypower_cooldown

能耗模式(全局):全速、均衡、静音。每项后台工作、每条本机端点请求结束后,按占用时长的 0、1、3 倍歇息,端点一次最多歇 600 秒;均衡、静音下端点一次只放一条请求;打字和配件里用户在等的请求不受影响。静音模式下温度 fair 即按过热暂停后台。专用模型通道(嵌入)每个任务有独立通道和间歇,不占用引擎的后台名额;用户在等的嵌入查询插在后台批(每 8 条)之间。

内存

  • 模型能否加载要过两道检查:大小不超过引擎上限 (guard_gb − 2) × 0.95;且不超过 Metal 可用上限减去当前 wired 内存(按物理内存的 74%–84% 估算)。端点的用户模型独占引擎时,上限改为 内存 − 8%(3–8 GB)− 常驻专用模型 − 1 GB,换回底座时还原。
  • 起专用模型工作进程前先算剩余内存(Metal 上限 − wired + 已加载的底座),装不下它的 wired_gb 就不起,答 not_ready(retry_after 30)。
  • 引擎上限随正在运行的工作进程动态调整。
  • 引擎同时只有一个"当前模型";端点登记的小模型放得下时与底座同时加载,放不下则独占(先卸掉其他模型)。
  • 底座闲置超过本档 idle_unload_secs 后卸载;有后台工作或有效的预热租约时不卸。

空闲任务

同一时间只运行一个。启动条件:主控空闲、处于安静时刻、后台未暂停(清单可声明允许电池)、距上次启动满 min_interval_minutes(上次被收尾时缩短为 10 分钟)。用户回来、别的配件的后台工作占用引擎、超时、后台暂停、卸载配件或主控退出时收尾;任务自己发的请求和它引起的底座加载不算占用。

配方和档位

  • 配方(recipe.json)定义硬件档(内存范围、引擎上限、底座是否常驻)、模型(目录、类型、常驻内存)、每档的模型分配、引擎版本、每个任务的参数、允许接入的配件版本和评测证据。
  • 选档:取第一个满足 min_gb ≤ 内存 < max_gb 的档;用户可在设置中选择更低的档(如 128 档的"省空间")。
  • 任务开关:每个任务在哪一档开启由出版流程的支持台账决定:未开启的档须 3 遍评测全部通过才开启(复测在发布后补跑,补满后下一版开启);开启的档一遍不过即关闭;没评到的档沿用上一版;"上一版"指最近一个真正发布出去的版本;beta 版全部重新评测、重新打包,不沿用别的版本的证据。
  • 冒烟检查:引擎启动后测一次实际的预填和生成速度,后台估时使用实测值。
  • 探针:打分、联想工作进程就绪后测 p90 延迟,超过配方 latency_ms 时把任务标为较慢。

下载、安装、升级、卸载

  • 下载:依据发布清单(每项有文件列表、大小、sha256、多个镜像地址)。多地址时先测速排序;每个地址重试 3 次,403、404、410 直接换地址;支持续传;sha256 对上才落位。本机已有同名模型(~/.omlx/models、Hugging Face 缓存、LM Studio)时逐文件核对后 APFS 克隆,不重复下载。首次运行以后发现还缺模型时自动补下(失败后从 30 秒起加倍间隔,最长半小时);网络按流量计费或开了低数据模式时不自动补下,只在设置「下载」的「还缺」一行说明,点「重试」照下;首次运行、「应用」窗口、换档是用户点的,照下。下了模型或引擎包才重启引擎、重做冒烟检查,只装应用不动引擎;发布版的新引擎冒烟检查通过后,删除 engines/ 里的其他版本。
  • 配件安装校验:codesign --verify --deep --strict、Team ID、bundle id 与清单一致、公证票据有效。
  • 安装位置:主控 DMG 是拖放窗口,左边「螺壳」、右边「应用程序」快捷方式,拖过去装在 /Applications/螺壳.app。在 DMG 里直接双击「螺壳」时提示先拖进「应用程序」,另给「帮我装进去」。发布页只放螺壳的 DMG(Luoke.dmg),应用在「应用」窗口里装;应用的安装器(安装<应用>.app)和「帮我装进去」把主控拷进 /Applications/螺壳.app(写不进去时拷进 ~/Applications/螺壳.app),去掉下载标记并登记;目标位置已有 bundle id 不同的同名应用时不覆盖,改放另一处,两处都被占才弹框请用户改名或挪走。已装的主控以实际位置为准:/Applications、~/Applications 两处,螺壳.app、螺壳主控.app 两个名字都认,只认 bundle id 相同的,两处都有时用 /Applications 那份:另一份被打开时(例如登录项还指着它)直接交给装好的那份后退出,装好的那份启动时把没在跑的另几份移进 previous.noindex、注销登记并重新登记登录项(用户关掉过的不动);已装着主控而安装器不比它新时,把预选交给它后退出。应用(邮件助手等)照旧装在 ~/Applications。
  • 首次运行:步骤条五步:这台 Mac(预检:档位、磁盘、系统版本、照实说明体验;内存不到 16 GB 或系统版本低于引擎要求时停在这一步)→ 选应用 → 确认下载 → 下载安装(逐项进度,主控排第一行;总进度的剩余时间在头 10 秒、下到 2% 以前不估;应用装好即打开,不等模型;含本机试跑)→ 完成;后来加装应用在「应用」窗口那一行里装,不走向导。离开下载页时注册登录项;下载页结束时分几种结局给出下一步:有项没下下来(先继续 / 重试)、试跑没过(重新检查 / 继续)、缺底座、主控没起来(重试会重启主控)。确认页上废纸篓里有旧输入法时提示。完成页:输入法的 --status 答 not_registered 时调一次它的 --register,每秒检查是否已添加;添加后输入法没在跑时在后台拉起它,可试打并一键切换(--select),切过去后试打框重新取得焦点,切不过去、或输入法报告还登记着别的副本时给「重新登记输入法」,重新登记后仍不行则提示注销重新登录;装了邮件助手时有一行写「已经在浏览器里打开了,第一次要连接邮箱。」,给「打开」;列出每个应用的通知权限(经启动器的 --notify-status):已允许 / 还没问过(按钮发一条测试通知,让系统询问)/ 已关闭(按钮打开该应用的通知设置),未全部允许时每 2 秒刷新。
  • 升级:同一张卡片走完五步:下载安装包 → 核对签名和公证 → 换成新版螺壳 → 补齐推理引擎和模型 → 本机试跑,大小、位置相同。有两条路:菜单「检查更新」(前三步在「检查更新」窗口里),或打开比已装的新的安装器(应用的安装器,或在主控 DMG 里直接双击「螺壳」;第三步在安装器的卡片里)。两条路都先把新版拷到环境目录的 staging.noindex 并核对签名,写 run/upgrade.json,起一个独立的换包脚本,再结束旧主控和记忆进程、自己退出。脚本等它退出(最多 2 分钟),把旧版移入 previous.noindex,把新版换到目标位置并登记,再用 /usr/bin/open 以用户身份打开新版,结果写入 run/swap-result.json。目标位置:已装在 /Applications 的还在那里;装在 ~/Applications 的,/Applications 写得进去就搬过去,否则留在原处;名字统一为 螺壳.app。目标目录写不进去(标准账户的 /Applications)时,脚本经系统的管理员授权框以 root 运行;授权框在旧主控退出之前弹出,取消则什么都不动、原来的主控照常用;只有换包两步用 root,打开新版时切回用户身份。换包没成时脚本把旧版放回并重新打开,旧版启动时弹框说明原因;脚本起不来或授权被取消时卡片写原因,给「先用着」和「重试」。新主控读到标记(读后删除)接着显示卡片:常驻配件在卡片一开始就拉起,补齐未装的引擎包和本档缺的模型,等本机试跑通过或推迟后写「已升级到 X」和这一版的更新说明;已装的应用有新版本时列出名字并给「去升级应用」。应用不随主控升级。补齐或试跑没成时卡片写哪一步、为什么,给「先用着」(标记写回,下次主控启动接着补)和「重试」。经应用的安装器顺带升级的,完成后到「应用」窗口那一行里接着装那个应用。
  • 应用:菜单「应用」打开,列出这一版发布清单里的应用(组件配件不列)和清单外已装的应用,每个应用的安装、升级、回到上一版本、卸载都在它那一行里做。没装的标「未安装」,点「安装」由主控按清单下载、核对、放好(读写记忆的应用连同螺壳记忆一起装),装好后经 /usr/bin/open 打开,常驻的以 --resident 在后台拉起,输入法装好后登记一次并提示到系统设置里添加;系统版本不够的写原因、不能装。首次运行没走完时,点「安装」或应用安装器交来预选,都回到首次运行并预选上这个应用。已装且清单版本更新的标「有新版本」、写「旧版 → 新版」,点「升级」只换这一个应用:普通应用先退出、换好后拉起(原来在跑的重新打开);输入法先换包,再结束旧进程并立即重新拉起。结果写在窗口顶部,没成时旧版照常能用。有可升级的应用时顶部提示「升级螺壳不会顺带升级应用」。「…」菜单里的「卸载」把这个窗口换成卸载确认,完成或取消后回到这里。菜单「应用」一行、「应用」窗口、检查更新窗口和升级完成卡片按同一判断提示应用有新版本:已装版本比发布清单旧、这台 Mac 的系统版本够、不是用户回到上一版本时换下的那一版。
  • 检查更新:菜单「检查更新」打开窗口,按本机主控的渠道查:内部版查 luokelabs/luoke-internal-builds 里的 internal-*,beta 和正式版查 luokelabs/luoke-releases 里的 beta*;每页 100 条往后翻,翻到出现这个渠道的版本为止(最多 10 页),取发布时间最新的一版,built_at 更新才算;有新版本时列出发布说明的「更新说明」一节和大小,说明这里只升级主控、应用在「应用」里单独升,给「以后再说」和「升级到 …」。升级时在这个窗口里走前三步:下载主控 DMG(显示进度),核大小、sha256、公证票据,只读挂载后核 .app(螺壳.app 或 螺壳主控.app)的签名和 Team ID,拷到 staging.noindex 再核一次签名,卸下并删掉 DMG,起换包脚本(要管理员授权时在这里弹),然后主控退出,由新版接着显示卡片;连不上、核对不过、某一步失败时给「重试」(升级中失败还给「先用着」),原来的主控不动。螺壳已是最新、但已装的应用有新版本时,窗口里提示并给「去升级应用」。本机版、开发版不查,窗口里说明。默认自动检查(内部版每 3 小时、beta 每天一次;启动 60 秒后,之后每小时看是否到时间,查成了才算;开关在窗口底栏,升级中不显示),发现新版只在菜单「检查更新」一行显示「有新版本」,点它直接显示那一版,不自动安装。
  • 回到上一版本:「应用」窗口里某个应用在 previous.noindex 里有经过验证的上一版时,它那一行的「…」菜单有「回到上一版本(版本号)」,就在这一行里做:主控先核签名、Team ID、bundle id、公证,两处对调并注销备份目录里的登记;普通应用对调前先结束,输入法对调后再结束旧进程并立即重新拉起(不动输入源);结果写在窗口顶部。换下的那一版不再算作新版本,再选一次可换回。主控把每个被本版配方认可的配件版本,连同本版当时放行的任务(评测通过的,加上配方里没有评测的,如草稿、每日摘要),记入 verified-history.json(每配件 3 个版本)。换回的版本、以及主控升级后还没升级的应用,只能用记录中的任务;记录外的任务答 task_unavailable,提示到「应用」里升级。无记录的版本不提供回到上一版本。
  • 卸载:入口在「设置 › 关于 › 卸载螺壳」(全部)和「应用」窗口某一行的「…」›「卸载」(该应用),直接到确认页,取消或完成后回到原来的窗口。输入法的输入源还启用时先停用(macOS 26 起不能代停,拦下并请用户在系统设置里移除),删完再核一次,并删除它的临时目录和锁;结束进程;删除 .app 时连同 previous.noindex 里的旧版,并注销同 bundle id 的其他副本(含废纸篓里的);可选"一起从记忆里忘掉"和"删除数据"(删数据时先跑配件自己的 uninstall 命令)。只删除配件清单 data 中声明且符合配件自身标识的路径;不删除 mem.db。完成页列出还留着的数据和位置。"卸载全部"删除环境目录、登录项、设置和命令行链接,把主控移到废纸篓后退出。
  • LaunchServices 登记:装配件前先注销同 bundle id 的其他副本(含废纸篓里的);previous.noindex 里的旧版在换装、回滚后和主控启动时都注销登记;「检查更新」只挂载安装包、不打开里面的主控,拷到暂存处时注销两边;换包脚本注销移入 previous.noindex 的旧版、登记换上的新版。主控和安装器拉起别的应用一律经 /usr/bin/open,不把自己记成被拉起者的父进程(否则 macOS 27 上已退出的父进程会一直留在程序坞)。
  • 重新登记输入法(切不过去时):结束它、注销其他副本,用新复制的一份换回原位并重新登记,不改内容、不重新下载。
  • 通知:首次运行、加装、升级都不发通知,结果只在窗口里。

故障和降级

情况表现
引擎崩溃以新端口和密钥重启;600 秒内崩溃 3 次熔断,生成任务保持 paused 直到主控重启
请求中途引擎出错连接断开且尚无输出时自动重试一次;引擎在流里报错时不把错误当答复转给配件,内存类按内存不够处理,其余以 engine_failed 结束
模型放不下用户在等的请求返回 needs_user 并附占用内存的进程列表;后台请求返回 paused
引擎因内存拒绝(开始前答 400,或在流里报内存中止,尚无输出)卸载并重新加载底座以释放 MLX 缓冲池,重试一次;仍不行答内存不够
冒烟检查放不下推迟,每 300 秒在空闲时重试
专用模型进程空闲或内存紧张停掉;按需重起时答 not_ready(retry_after 2),内存不够时 retry_after 30
专用模型进程失败 ≥ 3 次该任务暂停,不再重启
下载失败分三类说明:服务器上找不到、连不上、核对不过
配件版本未通过验证调用返回 unknown_accessory

菜单和窗口

  • 菜单:第一行是状态(启动中 / 工作中 · 底座模型 / 空闲 · 底座模型),后台暂停时下一行写原因(在用电池、低电量模式、过热、等内存);然后是 记忆(随主控带的时候才有)、应用、设置、检查更新,「应用」「检查更新」有新版本时写成「名称:有新版本」、值加粗;最后「退出螺壳」(⌘Q)。主控没连上或引擎出错时,第一行是「出错了」,下一行写原因(进程退出码、连不上的原因或熔断原因),给「重新启动螺壳」和「导出诊断信息」。菜单栏图标是单色海螺,五种状态,都不动:虚线(启动中)、实心(准备好了)、半满(在处理请求或后台任务)、空心(后台暂停)、实心带圆点(出错:连不上主控、引擎没在运行或熔断)。
  • 窗口期间:主控的任何窗口(首次运行、升级卡片、应用、卸载、检查更新、设置、主控小窗口)开着时,主控出现在程序坞和 ⌘Tab 里,全关后回到只在菜单栏。同一流程只开一个窗口:卸载接替「应用」或设置窗口,安装器的升级卡片出现后「检查更新」窗口关掉。
  • 再次打开主控:有安装器交来的预选时,首次运行没走完就回到首次运行并预选那个应用,否则在「应用」窗口那一行里装;没有预选时有开着的窗口就叫到前面;都没有且首次运行已走完时弹出主控小窗口,显示运行状态(在运行 / 启动中 / 没连上),说明主控在菜单栏的海螺图标里、找不到时可能的原因,给「打开菜单栏设置」,并列出 记忆(随主控带的时候才有)、应用、检查更新、设置 四个入口。⌘, 打开设置窗口。
  • 设置:通用(能耗模式 全速 / 均衡 / 静音、使用电池时仍运行后台、登录时启动螺壳)、下载(有下载或缺模型时出现:逐项进度或排队中,缺的可重试)、模型(占多少空间——可选档多于一个时出现,磁盘不够换档时在下面写明;底座模型;换档等待时的说明;已下载的模型及大小;本机放行)、高级(本机端点)、关于(螺壳版本即发布号、导出诊断信息、卸载螺壳)。Finder「显示简介」里的版本也是发布号(如 beta1、internal-1027;CFBundleShortVersionString),主控版本号在 CFBundleVersion。档位、引擎、本机试跑在诊断信息里。
  • 只给本体用的接口(下载、设置、端点、冒烟、卸载、回到上一版本、重新登记)按 socket 对端进程核对是不是本体,/settings、/endpoint 也认主控自带的 luoke 命令行;不是的答 forbidden(403)。

文件

环境目录 ~/Library/Application Support/luoke-core/,详见「参考 · 文件位置」。日志只记元数据(任务、优先级、token 数、耗时、结果),请求文字不落盘、不进日志。

已知限制

  • 内存压力会暂停和打断后台、停掉专用模型工作进程,但不主动卸载底座。
  • 熔断不会自动恢复,需要重启主控。
  • /status、/jobs/run 不核身份(/jobs/run 只在内部版、本机版开放)。
  • 配方不支持撤回已发布的配件版本,也不声明最低主控版本。
  • 端点只有 /v1/models 和 /v1/chat/completions;luoke launch 只支持 pi。

记忆

螺壳各组件共用的本机记忆:一个 SQLite 文件。工具写入"发生了什么"(记录);记忆是唯一形成结论的地方,从记录归纳出带依据、分三档把握程度的理解,并在空闲时整理,产出想问用户的问题和建议。

仓库 luoke-mem(包 luoke_mem,仅标准库,Python 3.9+)· 版本 0.23.4 · schema 7 · 接口和表结构见「参考 · 记忆」

职责

  • 保存三类数据:记录(records)、理解(notes,每条引用它所依据的记录)、想问的和建议(asks)。
  • 归纳关于用户的理解:事实、关注的话题、不感兴趣、处理习惯、常用说法,以及只供精选使用的口味。
  • 写入时遮去凭证;提供整份记忆的查看和管理界面。

运行形态

形态如何启动说明
库工具进程内 Memory(path, tool)WAL、secure_delete、10 秒锁等待;工具使用主控内置的那份 luoke_mem
归纳任务主控空闲任务:python -m luoke_mem --path {memory} learn,最短间隔 120 分钟、超时 30 分钟、不在电池上跑文件锁 mem.db-learn.lock 保证同一份记忆只跑一个;收到 SIGTERM 时已完成的批次保留;记忆文件不存在时什么都不做也不创建;主控不可达或任务报错时退出码 1,标准错误只写计数和原因码
界面luoke://memory → 主控执行 -m luoke_mem app独立进程,监听 127.0.0.1 随机端口,带令牌;600 秒无请求退出
命令行python -m luoke_mem …sources、forget-tool、ui、app、learn

记录与理解

记录

每条记录有写入的工具、说话人(user、third_party、system)、性质(statement、activity、receipt)、话题(如 mail-read、ime-commit)、正文、结构化数据 data(可含原文)和真实发生时间 happened_at。

理解的类别

类别含义谁用
fact关于用户的事实邮件分拣、起草
interest关注的话题输入法、agent
dislike不感兴趣精选
preference处理习惯(发件人 × 邮件类别 → 动作)邮件助手自动执行
term常用说法输入法候选加分
taste精选口味,不算对用户的了解,不提问精选

三档把握程度

由状态推出,不单独存储:

档条件
猜测状态 proposed 或 observed
比较确定状态 active,用户未表态,来源是推断
确定状态 active,且用户确认过或来源是用户本人

被否定(rejected)、被取代(superseded)、以前的事(past)不属于任何一档。工具读取时默认只得到比较确定以上的理解。

首要原则

记忆的每条规则都从宽开始,再由每个用户自己的反馈收紧(0.27 起,代码在 fit.py)。没有「这类不该提」的固定名单;同一条规则对不同的用户可以收到不同的松紧。不归反馈调的有三类:硬线(凭证遮掉、数据不离开本机、健康法律宗教政治只认用户的话、用户说过的不被推翻);算错了的 bug;用户没表态前,规则不替用户收回已经说过的话。

定档规则

一条推断能否成为"比较确定",取决于它所依据的记录话题和数量。门槛写作(条数, 不同日子数, 首尾相隔天数):

理解能撑到"比较确定"的记录
factsent-mail (2, 2, 7)
interestsent-mail、own-site、digest-feedback (2, 2, 7);mail-read (3, 3, 60,另需合计打开 ≥ 5 封、发件人 ≥ 2 个);digest-open (3, 2, 28);ime-commit (3, 3, 0)
dislikedigest-feedback (2, 2, 7)
preferencemail-override (2, 2, 7)
termime-commit (3, 3, 0)
taste用户的任何记录
  • 表中没有的组合(如 own-label、mail-cleared、mail-acted)最多是猜测。规则自动执行的记录(data.by 不是 user)和事务邮件的打开不算依据。
  • 名字只有品牌或来源名(如"Zillow""X 相关动态""领域:来源名")的话题不会成为兴趣,也不会成为精选口味。
  • 第三方来信推出的事实:同一来源 ≥ 6 封、跨 ≥ 3 个月、打开 ≥ 2 封、最近 6 个月内仍有来信时直接生效。
  • 健康、法律、宗教政治相关的推断必须由用户确认。
  • 门槛按用户调整:每类提议从已有的表里现算(fit.tally、fit.standing),不存第二份。「否定」分量 ≥ 2 且多于确认,升一档;≥ 4 且至少是确认的两倍,升两档;确认 ≥ 3 次且从未否定,降一档。分量:否定、在记忆页删掉、建议点「不用」各 1,接受后又撤回、建议点「别再提」各 2,问了一直不答到过期 0.5。删掉的只留每类的条数,不留内容;敏感的连条数也不留。
  • 一类不牵连别的类:处理习惯按「动作 × 邮件类别」分,事实按话题的前缀(如 服务:、账户:),兴趣和口味按来源(打字或邮件),常用说法一类,退订建议一类。
  • 门槛动了什么:处理习惯要的次数(升档后已有的也先不提);常用说法和打字话题生效要的天数、次数;打字话题升档后不提前问;退订建议要的清理次数;事实升档后,待确认的挪到「观察中」(observed)。默认门槛不变。
  • 以前的事:会变的理解(包括猜测和用户确认过的)最新依据超过 730 天,标为 past,不再使用也不再提问,相关的未答问题撤回;姓名、学校、家人等不会变的不受影响。用户说"现在还是"即恢复为确定;同一类被这样唤回 ≥ 2 次后,这一类的期限加倍为 1460 天。
  • 以前的事再次出现(工具又提出同一条):只因太久而变成以前的事的,按原样回来——用户确认过的回到确定,否则为猜测、按新依据定档;用户自己说过"以前是,现在不是"的回到猜测,保留表态时间,新依据不能让它自行生效,只能再问用户。
  • 邮件推出的兴趣:关于用户的、由邮件记录撑起的兴趣最多 8 条生效;重新定档时按依据条数保留前 8 条,其余回到猜测。
  • 取代与合并:同类同话题的新理解生效时,取代用户未表态的旧推断("其他"话题除外);说法相近的话题合并为一条。同一机构的事实只留一条:机构类话题(「机构:X」,以及旧的「账户:」「服务:」「会员:」「学校:」「住房:」「出行:」「其他:」)的名字相同即合并,「同事:」「客户:」「工作:」这类不算机构、不合并;被否定的那条只挡住同样的说法,新说法并进已有那条;未表态的只在说法相同而更完整时换字,用户确认过的保留原话,新说法另记、反刍时问"现在还是这样吗"。已有的重复在反刍时并进先有的(确认过的优先);界面上可把并进来的说法拆出来,拆出的不再合并。

归纳

归纳任务每次依次执行:反刍、打字线、话题线、再反刍一次。反刍不用模型、很快,放在最前面,任务被主控提前收掉时也照样产出提问和建议。两条线都通过主控的后台生成任务调用模型,结果的数量和依据由代码判定,模型只负责提名。

打字线(luoke-mem.typing)话题线(luoke-mem.topics)
输入输入法的 ime-commit(排除输入法自动上屏的),每段 ≤ 300 字邮件助手的用户记录(排除规则执行的、事务邮件),最近 365 天,按来源聚合,每来源最新 3 封原文各 ≤ 280 字、合计 ≤ 1200 字
触发新增 ≥ 80 段,或有新增且距上次 ≥ 1 天上次没读完的来源立即继续;否则新增 ≥ 20 条,或有新增且距上次 ≥ 1 天;提示词版本变化时从头重新归纳
开跑前先查 GET /tasks:这一档没有该任务、或任务状态不是 available 时不发请求,记下原因
产出常用说法(2–12 字)、关注的话题(≤ 20 字)关注的话题("领域:具体对象",≤ 24 字,每批 ≤ 5 个)、精选口味、不感兴趣(只来自精选里的"没兴趣",≥ 2 条记录)
定档说法:最近 90 天出现 ≥ 2 次为猜测,3 天 5 次生效;话题:依据跨 3 天生效,不足为猜测。说法只出现 1 次的留作候选,下次再数依据 ≥ 2 条记录的话题成为精选口味(直接生效);同时按定档规则判断能否成为关于用户的兴趣(邮件推出的最多 8 条);只有品牌或来源名的丢弃
进度每批完成后推进游标,被叫停时从未读的段继续一开始就把要读的来源记入 learning 表 topics.pending,每批完成划掉一批,被叫停时下次从没读完的来源继续
提示词luoke_mem/learn.py PROMPTluoke_mem/topics.py PROMPT(版本 5)

主控答"忙""暂停""被打断"等表示稍后再来时,当前这条线停下,已完成的批次保留;主控因其他后台任务或能耗模式的歇息而忙时,打字线最多原地等 300 秒。

反刍

不使用模型,每次归纳任务开头和结尾各执行一次;结果计数写入 learning 表 rumination.last。

  • 降档:打字归纳出的兴趣和常用说法,最新依据超过 90 天且用户未表态,回到猜测。
  • 整理:用户未表态的推断(猜测和已生效的)每次反刍按现行规则重查:日常词的常用说法、靠长词里的字母才够门槛的常用说法(拉丁字母按整词数,「hub」不算进「github」;已生效的退回猜测,用户表过态的不动)、只有一段打字依据的话题、只有品牌或来源名或以"动态""通知"等结尾的兴趣和口味(口味在生成时就跳过这类名字)、只靠自建标签或整批清理撑起的兴趣和不感兴趣,收起不删;重复的事实并进先有的。
  • 处理习惯:同一(邮箱, 发件人, 邮件类别, 动作)出现 ≥ 2 次形成一条习惯;来源是 mail-override 的新建议和 mail-acted 的动作,只认 ACTION_NAMES 里的动作(archive、read、trash、spam、flag、keep、label、filter)。来自改建议的按定档规则判定,仅来自亲手处理的为猜测。用户撤销一次自动执行即回到猜测。改建议后当场处理只算一次(mail-acted 的 after_override 覆盖整批,或 10 分钟内同一发件人、同一动作)。
  • 想问的(都不问敏感的):兴趣和处理习惯里依据无法自行撑到比较确定、180 天内有依据的猜测;打字线再多一天就会生效的话题(这一类门槛升档后不提前问);同一来源 ≥ 6 封但最近 90 天没有来信、用户未表态的机构事实,和用户确认过、邮件里说法变了的机构事实(问"现在还是这样吗")。前两类在前,机构事实在后,各按最新依据和依据条数排序;每天最多 2 个,14 天过期;不再值得问的撤回。
  • 常用说法不问用户:只影响候选排序,错了几乎没代价,够门槛自己记下,不要的在记忆页删。
  • 建议:每天最多 3 条。"以后都这样?"(有发件人和类别、动作可自动执行的处理习惯,用户点"好"即成为确定并可自动执行);"退订?"(30 天内用户亲手整批清掉同一发件人 ≥ 2 次)。
  • 去重:同一件事已有未答的问题时不再生成;"以后再说"或过期未答 7 天后、已答或"不用"60 天后才可能再问;"别再提"永不再问。

读写关系

组件写入的记录读取
邮件助手mail-read、mail-acted、mail-override、mail-auto、mail-undo-auto、mail-cleared、sent-mail、received-mail、digest-open、digest-feedback、own-label、own-site事实和习惯(分拣、起草)、可自动执行的习惯、口味、给它的 asks
输入法ime-commit、ime-correction、ime-forget常用说法;可读全部记录
agent只经 reply 交回回答asks 和全文依据

任何组件都能读取全部记录和依据;删除其他工具写入的内容只能在记忆自己的界面中进行。

已知限制

  • 界面没有"以后再说"和单独的"以前是"按钮(只能通过修改框的文字识别)。
  • 归纳中某条输入(打字或话题的一批)连续出错两次就跳过,游标前移;其他错误不打断整次归纳。
  • 重新定档、以前的事只在整份记忆被打开时运行(界面、命令行、归纳任务)。
  • 没有向工具索取更多原文的通道;自由回答不参与学习。
  • 命令行 forget-tool luoke-mem 被拒绝(说明原因,退出码 1):记忆自己的内容只能逐条删除或清空整份记忆。
  • 敏感词和凭证识别基于正则,存在漏判和误判。

邮件助手

用本机模型整理整个邮箱:逐封分拣近期邮件,清理旧邮件,体检订阅并挑选精选,起草回复放入草稿箱。可撤回的处理会自动执行,并在"今日处理"页集中复查。

仓库 luoke-mail(包 luoke_mail)· 版本 0.18.14 · 支持 Gmail、QQ、网易 163/126、自定义 IMAP,可连接多个邮箱

职责

  • 为每封邮件给出建议(回复、加入日程、待跟进、归档、移到垃圾箱),附类别、优先级、摘要、截止日期、日程字段、回复要点和可疑标记。
  • 执行用户的操作并保留撤销步骤:归档、移到垃圾箱、移到垃圾邮件、加星标、标已读、加标签、写草稿、加入日历(Gmail 打开 Google 日历的新建日程页面,其他邮箱下载 .ics 文件)、退订。
  • 不发信,不永久删除:删除即移到垃圾箱,回复只存入草稿箱。
  • 把用户的操作写成记忆记录;关于用户的结论由记忆形成。

运行形态

入口行为
--app --resident主控拉起的常驻实例:本机网页服务 + 后台循环。后台整理开启时经 PUT /accessory/resident 声明为常驻(清单里 resident: false),关闭或清空数据时先 POST /accessory/exiting 再退出
--app打开正在运行实例的页面,没有则在后台启动一个
--job clean|digest|learn主控调度的空闲任务(分拣以前的未读邮件、判断旧邮件 / 读订阅和挑精选 / 学习),每次最多 25 分钟,被叫停时做完当前一步退出
--forget-secrets卸载前删除钥匙串中的本工具条目
  • 网页服务:只绑定 127.0.0.1,端口默认 8765(被占用时依次试后面 19 个;指定了就只用那一个);令牌保存在 token 文件(0600),请求需带 X-Luoke-Token;页面从地址里的 ?t= 取得令牌后立即从地址栏去掉,按这个地址存进 localStorage,同一地址的各个标签页、服务重启或升级后都能用,接口返回 401 时清掉,没有令牌时显示「无法验证访问权限。请从「螺壳邮件助手」App 重新打开页面。」;校验 Host;HTML 带 CSP;请求体上限 1 MB;以 server.lock 保证单实例。
  • 日志:常驻实例的标准错误写入 service.log,开头记版本;每条通知同时记入 run/notifications.json。
  • 后台循环:每 30 秒一次(有空闲任务时 5 秒),启动 60 秒后才开始。后台整理默认关闭,开启需确认说明并已连接邮箱、有模型;间隔默认 30 分钟(可选 15、30、60、120),对每个邮箱执行同步流程;一个邮箱失败不影响其他。
  • 主控:经 Unix socket 调用任务(X-Luoke-Accessory: luoke-mail/<版本>,优先级 user-waiting 或 background);任务状态和参数(max_chars、batch、sender_verdict)从 GET /tasks 读取并缓存 60 秒;页面可见时预热 luoke-mail.triage。打包版只经主控。
  • 记忆:优先使用主控内置的 luoke_mem(GET /tasks 的 luoke_mem.path,要求 ≥ 0.23.0),以 tool="luoke-mail" 打开;记忆比库新时常驻实例以退出码 3 退出并提示更新。
  • agent 接口:outbox 和 MCP 工具(run/agent.sock)已实现但默认关闭,环境变量 LUOKE_MAIL_AGENT=1 才开启;清单不声明 capabilities(备用声明在 packaging/agent-capabilities.json)。开启时 agent 只能执行邮件助手自己做出的方案、只用卡片给出的选项,批量移到垃圾箱或垃圾邮件仍要处理模式;退订和开启处理模式不经 agent。

同步与分拣

  1. 读取收件箱最近 N 天(默认 30)的邮件,新邮件最多取 100 封,每 10 封读一批、读到就入库;连接断了重开一次接着读,再断就保留已读的,下次同步从断处继续。只取前 1 MiB、不改变已读状态;纯文本部分其实是 HTML 源码的按 HTML 读;长于 60 字符的链接写成 https://域名/…;正文去引用后截到 6000 字。
  2. 对照收件箱现状:已不在的标记为完成;推断用户在别处做了什么(归档、删除、读过、加标签,写入 mail-acted,where=elsewhere;每次最多查 30 封的去向);邮件助手 3 天内操作记录里动过的邮件不算用户的处理,分析后标已读不移动邮件、不在此列;记为已移走却仍在收件箱的放回待处理。
  3. 邮箱第一次同步时,把收件箱里当时未读、在分拣范围以前的邮件记成一个队列(新的在前,只记这一次)。
  4. 补充发件域名验证结论(Gmail 的 DMARC),以及用户最近 7 天自己归档但可能仍需处理的邮件(Gmail,最多 50 封)。
  5. 逐封分拣(luoke-mail.triage)。命中的规则作为上下文提供给模型;模型可提出不同建议。寄出日期早于今天的邮件,提示词多一行寄出日期,没写年份的日期和「明天」这类说法按寄出日期换算,代码兜底 triage.settle_dates。用户只在抄送、不在收件人里时,提示词多一行「一般 archive,priority 不超过 medium」,代码再把 high 降为 medium(triage.settle_cc)。任务参数 sender_verdict 为 false 时(16、32 GB 档),提示词里不给发件域名验证结论。
  6. 依次执行:最近归档的邮件只留需要用户处理的、精选来源排队、自动标已读、按习惯处理、处理螺壳自己的邮箱、更新往来邮件状态(每次最多问 10 串,只问有新邮件的)。

以前的未读邮件

  • 第 3 步记下的队列作为空闲任务分拣(空闲任务中排最前,属于主控的 clean 任务):每次取 10 封(取时不标已读),入库标 items.older,以后台优先级交给 luoke-mail.triage,每段最多 4 分钟;主控说暂停或模型出错时停下,下次再分。分好的当场标已读;照确认过的习惯归档、加标签(以及螺壳自己的邮箱的自动处理)在下一次同步时做,和其他待处理邮件一样。
  • 同步不等它们,收件箱的"未分析"数也不计它们;"清理 → 收件箱中的旧邮件"不再列出它们。
  • 以后用户自己标回未读的旧邮件不再排进来;分拣范围放大后,新范围里的邮件由正常同步分拣。

安全规则

  • 邮件内容视为数据。试图指挥 AI、索要密码或验证码、冒充、催促转账的邮件标为可疑;只要模型判为可疑,动作一律改为忽略、类别改为钓鱼。
  • 页面渲染不使用 innerHTML。

自动处理

动作条件撤销
标已读收件箱中已分析、没出错的邮件(设置 flow.mark_read,默认开);每次同步一条操作记录(动作 seen);邮件助手标的已读另行记录(read_by_us),不当作用户读过今日处理中逐封"标为未读",或在操作记录中整批撤销("分析后标为已读 N 封");撤销过的不再自动标。不写入记忆(不是用户的动作)
按习惯归档、标已读、加标签记忆中确定的处理习惯(发件人 × 邮件类别);跳过可疑、需用户处理、高优先级、登录和安全类通知、撤销过的邮件今日处理中逐封撤销,记一次反证(mail-undo-auto)
螺壳自己的邮箱设置中标为 own 的邮箱:照建议归档或移到垃圾箱今日处理中逐封撤销

用户自己的邮箱里,移到垃圾箱、垃圾邮件、退订不会自动执行。处理模式(默认关,开启需确认)只控制不可逆的批量操作:批量移到垃圾箱或垃圾邮件、含删除的清理、清理某发件人的历史邮件。需要处理模式的批量按钮照样能点,旁边写明需要处理模式,点了弹出开启说明,开启后接着执行。

通知

系统通知由后台整理在每个邮箱同步后发出。agent 正在读 outbox 时(10 分钟内读过),新邮件、快到期、精选、清理分析完、今日处理这五类不再发通知,交给 agent。收件箱中的旧邮件全部分析完时通知一次建议归档、移到垃圾箱、待确认各几封,点开进入清理。

  • 需要回复、加入日程、待跟进的新邮件(3 天内到达、非可疑):封数和最急的两封。
  • 按习惯自动处理后:处理了几封,点开进入"今日处理"。
  • 截止日期在明天及以前的:每件只提醒一次。
  • 精选:每周一 08:00 之后的第一次后台整理;今日处理:每晚 20:00 之后一次。
  • 通知权限:经应用的启动器(--notify-status)查询 macOS 是否允许本应用通知,缓存 1 分钟。设置的"后台整理"一行:被拒绝或提醒关闭时显示警告和"打开通知设置"(打开系统设置中本应用的通知页,回到页面时重新检查);从未询问过时提示点"发送测试通知"并选"允许"。"发送测试通知"发一条测试通知并说明结果。
  • 邮箱连接失败连续 2 次才报,同一问题只报一次;模型不可用时报一次"N 封新邮件尚未分析";钥匙串因锁屏读不到时不报,下一轮再试。

页面

页面内容
今日处理某天每封邮件的结果,需要用户处理的排在前面;可撤销、标未读、改建议;可查看前 7 天。补分的以前未读邮件单列"以前的未读邮件"一节,仍等用户处理的标"已过期"(截止日期已过)、"快到期"(3 天内)或收到日期(一个多月前收到)
收件箱同步、分组显示建议、快到期(3 天内到期或过期不到 7 天)、等对方回复的往来、逐封或批量处理、草稿、日历。卡片时间显示「今天 HH:MM」「昨天 HH:MM」或日期;类别和优先级相同的,新的在前。日程已过的不进快到期,显示「日程已过」;同一预约已从本工具加过日历的显示「已在日程中」
清理收件箱中的旧邮件和已归档邮件:只读邮件头,模型在后台逐封判断,按类别和来源分组执行;星标和保留类标签的邮件不参与;切换邮箱或标签后迟到的答复不再绘制,分析开启但在等其他任务时显示"已分析 x / y"
精选最近 7 天约 20 条、更早每月约 10 条;收藏和"没兴趣"写入记忆
订阅体检按发件人统计,建议退订、无法退订、可疑勿退订或常读;可退订、批量归档、清理历史、生成规则
规则按发件人、域名、邮件列表的规则;导出 Gmail 过滤器前用过去一年邮件预演命中数和误伤;操作记录可撤销
记忆想问你的、全部类别的理解及依据、用户网站(每 30 天重读一次)、学习的暂停与继续、打开整份记忆
设置邮箱(含"螺壳自己的邮箱")、本机模型(打包版显示"螺壳 · 模型名"和就绪标记,模型名取 GET /tasks 中分拣任务的 model_name,不解析 combo;换档在主控设置里)、处理模式、后台整理(含通知权限提示和"发送测试通知")、自动标已读、往来邮件、功耗、分析范围

页面在浏览器的 localStorage 中记住上次查看的邮箱,刷新或重新打开后仍停在该邮箱(仍连接时);示例邮件只在还没连接邮箱时出现;存不了时显示第一个邮箱。

主控任务

任务提示词输入 → 输出批量与失败
triagetriage.py一封邮件(含寄出日期、抄送、发件域名验证结论)、规则、关于用户(≤ 600 字)→ 类别、优先级、动作、摘要、截止日期、可疑等一次一封;两次失败后记错误,下次同步重试。以前的未读邮件以后台优先级、每批 10 封
drafttriage.py回复要点 + 邮件 → 正文一次
senderstriage.py发件人最近 8 个主题 → 类型每批 ≤ 10;失败则不改判
cleanuptriage.py邮件头 → 归档、删除、保留类别每批 ≤ 25;失败一律保留
digest-readdigest.py一封订阅正文(≤ 12000 字)+ 口味 → 条目(≤ 8)、评分、摘要一次一封,每次取 3 封排队;模型内存不足时缩到 4000 字重试;格式不对重问一次
digest-pickeditor.py候选条目 → 入选及理由每批 ≤ 30;失败按阅读分数排
learn-sentlearn.py用户发出的邮件(只留本人写的部分)→ 事实每批 ≤ 8
learn-orgslearn.py往来机构的封数和主题 → 事实(机构名 ≤ 40 字,事实 ≤ 60 字;写入记忆的话题为「机构:名称」,类别只留在邮件助手)每批 ≤ 10,最多 30 个机构
threadthreads.py往来串最后 ≤ 6 封(每封 ≤ 600 字,末尾注明最后一封是谁写的)→ 轮到谁(user_turn / other_turn / done,代码映射为等你 / 在等对方 / 已了结)、从何时、要做什么每次同步最多 10 串;两次答案不可用判为已了结;主控忙或出错时停下,下次再问

写入记忆的记录

所有记录的 data 带 sender、by(user / rule / suggestion)和 account(邮箱的不透明键);正文经凭证遮蔽。示例邮箱不写真实记忆。

topic说话人 / 性质何时写主要字段
mail-readuser / activity每个订阅每个整月一条(群发或有 List-Id)封数、打开数、类别、主题、最多 3 封原文(事务类不带)
mail-acteduser / activity用户在页面或在别处归档、删除、标垃圾、读过、加标签动作、封数、类别、where(here / elsewhere)、标签、after_override(同一步里先改了建议的封数,有才写)
mail-overrideuser / activity用户改建议原建议、新建议(用记忆的动作名:trash、keep、archive 等;分拣原始代码在 suggestion)、类别
mail-autouser / activity,by=rule习惯自动执行后习惯编号、动作、封数
mail-undo-autouser / activity撤销习惯的自动处理习惯编号
mail-cleareduser / activity 或 receipt清理执行后发件人、类别、封数
sent-mailuser / statement从发出的邮件中抽出事实(至少两封一致);用户发起的邮件主题本人写的部分(≤ 800 字)
received-mailthird_party / statement往来机构的推测依据封数、首末月份、月数、打开数
own-labeluser / statement用户的邮箱标签封数、首末日期(happened_at 为标签下最新一封的日期)
own-siteuser / statement用户网站的文章发布日期
digest-feedback、digest-openuser精选中收藏、没兴趣、打开标题、来源、摘要

从记忆读取

内容用途
context(["fact","preference"], 600)分拣和起草的"关于用户"
确定且可自动执行的 preference(本邮箱、动作 archive / read / label)按习惯自动处理
context(["taste","interest","dislike"], 500) + 最近的收藏和没兴趣精选阅读和挑选
asks(tool="luoke-mail")记忆页"想问你的"

数据和文件

位置内容
~/.luoke-mail/config.json邮箱、模型、处理模式、后台整理、功耗、学习、往来、自动标已读等设置
~/.luoke-mail/luoke.db邮件和建议(items,older 标记补分的以前未读邮件)、规则、发件人决定、缓存、清理(old_mail)、操作记录和撤销步骤(log)、精选、学习候选
钥匙串 luoke-mail:imap、luoke-mail:engine邮箱授权码、模型服务密钥;Mac 锁定时读不到会跳过本轮,不视为未保存
service.log、app.log常驻实例的标准错误(启动时超过 2 MB 先另存为 service.log.1);从应用后台启动的实例的标准错误(启动时超过 2 MB 另存为 app.log.1);两份日志每行带时间,一分钟内的重复行只计数
run/notifications.json最近 100 条通知:时间、标题、发送方式、启动器的结果(posted / not allowed / failed / no answer)
token、server.json、server.lock页面令牌(0600);运行中实例的 pid、端口、是否常驻;单实例锁
idle.lock、resident空闲工作锁(应用和主控的空闲任务不同时做);后台整理已开启的标记
account-salt生成邮箱不透明键(写入记忆的 account)的随机值;删除数据目录后邮箱得到新键
run/agent.sockagent 接口,仅在 LUOKE_MAIL_AGENT=1 时存在

可靠性

  • 写入核实:移动后检查是否真的移走,没移走的不标已处理;不支持 MOVE 的服务器先确认副本存在才标删除,只对自己标记的邮件做 UID EXPUNGE。
  • 撤销:先用上次见到的 UID 找并核对 Message-ID,再按 Message-ID 搜索,不是 Gmail 的再查文件夹里最新 500 封;改动前先备好撤销步骤,断线时记"结果未确认"并保留撤销。
  • 主控不可用:邮件照常入库、暂不分析,主控就绪后自动开始;模型忙时用户等待的请求原地等,后台任务延后。
  • 邮箱差异:网易登录后需发 IMAP ID;QQ 先查 CAPABILITY 再用扩展命令;Gmail 归档即移到"所有邮件",标签用 X-GM-LABELS。

已知限制

  • 发件域名验证、最近归档的分拣、加标签、过滤器导出只支持 Gmail。
  • 单封移到垃圾箱和退订不需要处理模式。
  • 往来邮件只看最近 30 天、已发送中最新 60 封,按主题和对方一人归串,等待阈值固定为 3 天。
  • 撤销过自动标已读的邮件仍记为"邮件助手标的已读",用户之后自己打开它也不计入订阅的打开数。

输入法

macOS 输入法。壳负责按键、组字、候选窗和上屏;候选全部由沙盒里的引擎子进程给出。个人化组件把上屏写入记忆,并把打分和联想请求转给主控。

仓库 luoke-ime · 版本 0.1.32(build 35)· bundle id ai.luoke.inputmethod.Luoke

结构

部分实现职责
壳Swift:LuokeShell(平台无关的状态机和引擎宿主)+ LuokeIME(InputMethodKit 外壳);另有共用状态机的命令行版按键、组字、候选窗、上屏、沙盒、转问
打字引擎自带小鹤双拼 xiaohe、自然码双拼 ziranma、全拼 quanpin(苹果转换引擎和 RIME 出候选)。自然码和小鹤是同一个程序,以 --scheme ziranma 运行,只换韵母键位表,声母、零声母、词库和 RIME 的数据都和小鹤共用(RIME 用雾凇自带的自然码方案);评测时加 --from-xiaohe 把小鹤的按键换成自然码的,600 句语料上和小鹤一样(先给的 78.8%,重排后 89.8%)出候选,经个人化组件请求打分后重排。三个打字引擎都有评测用的选项 pause(毫秒,默认 0)和 pause_when(slow / always):键后停顿够久才重排;默认关。
个人化组件engines/personal(Python,仅标准库),运行在主控自带的 Python 上写入记忆;把打分和联想转给主控

进程模型

  • 壳由系统拉起,不进程序坞;用户自己打开 LuokeIME.app 时也作为输入法服务运行。每个输入框一个 Session;全进程一个 EngineManager,主线程每秒轮询配置和引擎状态。带不认识的参数时打印用法退出。只运行一份:父进程不是 launchd 时直接退出;用 $TMPDIR/ai.luoke.inputmethod.Luoke.lock 文件锁占位(最多等 3 秒),已有一份时请在跑的那份按需弹添加引导,自己退出。
  • 换版:每 5 秒读一次磁盘上包里的 build 号,和启动时不同且新包的可执行文件在时,等所有输入框空闲(不在组字、没挂着逗号、不在快捷键会话、联想没打开)且 3 秒没有按键、设置窗口和添加引导都关着,收尾退出,1 秒后用 open -g 从原路径拉起新版。
  • 引擎以 posix_spawn 在独立进程组中启动,命令为 /usr/bin/sandbox-exec -p <模板> …,工作目录是引擎文件夹。run[0] 为 luoke-python 时使用螺壳(主控)包内的 Python:依次找 /Applications、~/Applications 下的 螺壳.app、螺壳主控.app,核对 bundle id ai.luoke.core,不问 LaunchServices;都没有时用 PATH 里的 python3;参数前加 -B -E -s -u。自带引擎用固定 PATH;第三方引擎第一次启动时才解析登录 shell 的 PATH(最多 3 秒)。
  • 通信:标准输入输出上每行一个 JSON,第一条必定是 hello;写入走每个引擎自己的串行队列。
  • 停止:关闭 stdin,1 秒后对整个进程组 SIGKILL。
  • 崩溃与卡死:一次询问 1 秒没有答复视为卡死并杀掉;60 秒内崩溃 3 次则停用该引擎。
  • 退出:收到 SIGTERM、SIGINT、SIGHUP、正常结束或换版让位时,结束系统日志订阅,并向主控发 POST /accessory/exiting(1 秒超时)。
  • 重启条件:设置文件变化、用户引擎目录变化、非自带引擎的文件变化、options.json 变化、主控 Python 被替换(每 10 秒检查)。

引擎协议

版本 1。完整规格见 luoke-ime/docs/引擎协议.md。

方向消息
壳 → 引擎hello、query(输入、前文、应用)、committed、deleted、forget、forget_all、unforget、start / stop / cancel(快捷键引擎)、ask、reply
引擎 → 壳answer(候选、more、autocommit)、show、commit、end、ask、reply

引擎说明 engine.json

  • 必填 protocol(=1)、name、kind、run;打字引擎需 input_keys,快捷键引擎需 hotkey。
  • 可选 context(0–2000 字前文)、app、feedback、forget_all、asks(⊆ personal、hub)、options、punctuation。
  • permissions:network(none / local / any)、write(≤ 16 个路径)、local_ports、microphone、camera、screen、gpu、memory(只有个人化组件能声明)。
  • 声明 asks: personal 的引擎只能 network: none,或 local 并用 local_ports 限定端口;个人化组件不能联网,不能声明麦克风、摄像头、屏幕,asks 里只能有 hub。

第三方引擎

  • 声明联网、GPU 或写权限的第三方引擎不能启用。
  • 启用需要用户确认,确认记录绑定引擎说明的指纹(关键字段的 SHA-256),说明变化后需重新确认。
  • 不会被自动选中;与自带引擎同名时使用独立的数据目录 <id>.third-party。
  • 引擎只能向个人化组件请求 score 和 ping;predict 只有壳能发起。

候选流程

  1. 按键经 MacKeys.translate 翻译,进入会话;空闲时按下输入键会读取前文(安全输入时不读)。
  2. 壳向打字引擎发 query;只采用最新一次询问的答复,50 ms 内收到的最后一份直接显示。
  3. 打字引擎先给快答复(改选过的词、苹果候选、RIME 补充),能重排时标 more;再经壳把 score 转给个人化组件,个人化组件转主控 luoke-ime.rerank,最多等 0.7 秒。
  4. 逐词重排:模型得分须比苹果整句首选高出阈值(自然对数 3)才替换;换掉首选时补发答复,界面让新首选淡入。
  5. 空格、数字、标点等收尾键在还没有任何答复时先等第一份答复,最多 1 秒(判定卡死);卡死则按原始字母上屏,数字原样交给应用。之后最多再为补发等 150 ms。回车、Esc 不等。上屏后,空格或数字选中的会接着请求联想。
  • 拼音显示位置:默认显示在输入框里(显示串),可按全局或应用改成显示在候选窗里。
  • 「-」:第一页按「-」(翻页键为 - 和 = 时),已选部分和拼音串照原样字母上屏,再交给应用一个「-」(可打 luoke-ime、gpt-4);不在第一页照旧翻页。
  • 数字后的标点:数字后按逗号先挂在光标处,接数字上屏半角「,」,接别的键上屏「,」,退格就不要;数字后的「.」「:」保持半角。
  • { }:中文状态下默认打成「」(设置里能关),不看引擎的标点表。
  • 忘掉一个词:候选上按 Shift+⌫,告诉打字引擎和个人化组件。

联想

  • 在空格或数字选词、标点、快捷键引擎上屏、选中联想后触发;开不开按应用算:应用设置优先,没设时终端(Terminal、iTerm2、Ghostty、Warp 等十个)默认关,其余跟随全局;个人化组件须在运行。上屏后 0.25 秒内没有按键才发出请求,期间按键即取消。
  • 个人化组件先从最近 5000 段上屏中找用户写过的接续(最多 3 个),再请主控续写(/v1/continue,k=8、6 个 token),首 token 是汉字且概率 ≥ 0.2 才采用,合计最多 5 个。
  • 发出后 1 秒内无答复则放弃;未激活时淡显前 3 个、不放显示串,按键照常交给应用,4 秒后收起;按 Tab 激活后列出全部,数字、空格选,方向键和 Tab 移动,Esc、回车、退格收起;激活后高亮的那个放在光标处当显示串(拼音显示在候选窗里时也一样),免得 Chromium 把 Tab 交给网页。
  • 有的应用(Grok 这类 Electron 应用)在输入法接下 Tab 后自己又插一个制表符:选词时连它一起换掉;收起联想时不去掉。

个人化组件

决策表(PersonalPolicy)

情形写入记忆引擎学改选打分和联想
安全输入否否否
用户暂停或该应用禁止记录 · 自带组件否否是
用户暂停或该应用禁止记录 · 第三方组件否否否
记录未开启 · 自带组件否是是
记录未开启 · 第三方组件否是否
其余是是是

"记录开启"对自带组件默认为真(存储用户关闭的列表 memory_off),对第三方组件需用户同意(memory_consent)。

写入记忆的记录

topic内容data
ime-commit同一应用中连续上屏合成的一段(间隔 > 60 秒、换应用、换了出字的引擎或前文对不上时断段;满 2000 字或停手 20 秒写入;切走时的自动上屏单独一条)app、source、kind、auto、context(前文末 200 字,标为第三方)
ime-correction选了非首选、删掉刚上屏的字选择或删除的明细
ime-forget用户在候选中删除某词input、text
  • 凭证经 mask_secrets 只遮那一串;敏感类别(健康、法律纠纷、宗教、政治)按整段文字计算。
  • 保留期(个人化组件选项 keep,只能改 options.json)可选永久、365、90、30 天,启动时和每 6 小时清理一次,只清 ime-commit、ime-correction,被理解引用的保留。
  • 壳每 15 秒 ping 个人化组件取记忆状态,菜单和设置窗口显示「记录中 / 已暂停 / 当前应用禁止 / 已关闭 / 不可用」;第一次用时在候选窗提示一次在哪里关。
  • 读取:最近 5000 段 ime-commit,用作联想素材。

主控任务

任务端点用途
luoke-ime.rerankPOST /tasks/<task>/v1/score候选打分(按任务参数分批,默认 16 个、前文 2000 字)
luoke-ime.predictPOST /tasks/<task>/v1/continue联想续写

请求超时 2 秒。收到 hello 时对两个任务各预热一次。发布版只经主控打分(选项 scorer 写什么都当 hub);主控不可用时没有重排,联想只给用户自己写过的接续。

设置和文件

位置内容
~/.luoke/ime/settings.json当前引擎、全局默认(进入应用时中英文、记住各应用模式、拼音显示位置、联想)、候选窗(排列、每页数、字号)、键位(翻页键、单按 Shift、{ } 打「」)、中英文间自动空格、按应用覆盖、记住的各应用模式、开发模式、记录设置、确认过的第三方引擎、主控 socket;第 1 版读入时迁移并另存 settings.v1.json;本版不认识的键(顶层和 recording 内)原样写回,配件回滚后再升级时不丢
~/.luoke/ime/engines/<id>/用户安装的引擎
~/.luoke/ime/data/<id>/引擎数据:options.json;小鹤、全拼另有 memory.json(改选、忘掉的词)和 forgotten.json(供设置窗口「记忆」页列出、撤销)
~/.luoke/ime/records/开发模式记录,留 7 天

设置窗口四页:通用(输入方案、中英文、排版、候选窗口、联想、「记录输入内容到记忆」和「暂停所有记录」)、应用(每个应用的默认输入模式、拼音显示位置、联想、禁止记录,可恢复跟随全局)、记忆(记了什么、打开螺壳的记忆界面、当前方案学到的词和忘掉的词;忘掉的可逐个撤销,学到的可确认后全部清掉)、高级(开发模式记录、记录文件夹、引擎文件夹、设置文件)。每行带一句说明;窗口里不放文本框。

输入法菜单只放常用操作:输入方案、当前中英文模式、清掉学到的词、当前应用(此应用设置、在此应用关闭联想(按实际是否联想打勾,终端里取消勾选即打开联想)、在此应用禁止记录)、记忆记录状态(开启 / 暂停 / 恢复、管理记忆)、设置、高级(开发模式、打开引擎文件夹、打开设置文件);引擎停用、沙盒拦截、等确认的第三方引擎也列在这里。

安全

  • 安全输入(IsSecureEventInputEnabled):不读前文、不写记忆、不打分;一段组字中碰到过安全输入,整段都按安全输入处理。
  • 沙盒:默认拒绝;禁止读取钥匙、凭据、浏览器、邮件等 40 多个路径和整个 ~/.luoke(只放行自己的文件夹);不许创建符号链接;禁止借助 open、osascript、剪贴板等外发。
  • 拦截上报:每条拒绝带标签,壳读取系统日志,严重的列入菜单。系统日志订阅(log stream)套在一个 sh 里,输入法退出(含被强制结束)时随之结束。

安装与添加

LuokeIME --register 只注册(macOS 26 起不再请求启用,须用户在系统设置里添加);--status 输出一行 JSON(state:not_registered / not_added / added_not_selectable / ready,另有 registered、added、consent_list、tis_enabled、tis_selectable、current、other_copies(含废纸篓里同一 bundle id 的包)、version),供螺壳的首次运行向导和诊断用;--select 切换后另起进程核对,调用失败退出码 1,调用成功却没切过去退出码 3;另有 --disable、--current。配件清单的 capabilities.listing 给出「添加应用」时显示的一句话介绍(tagline:「小鹤、自然码双拼和全拼,本机模型帮你挑词、往下联想,从你打的字里学」)和类别「输入」(category),不写 permissions。用户自己打开 LuokeIME.app 而还没添加时弹添加引导,每秒检查,加好后提示去任意输入框试打。引导和安装、卸载脚本的提示都用系统设置的原词「键盘 → 文字输入 → 输入法 → 编辑…」;引导卡片右上角能点开「图文教程」:三步,每步一张系统设置的截图(简体中文、浅色,缩到 900 宽)配一句话,可以上一步、下一步、返回,加好了自动退出(--render-guide tutorial1|2|3 能单独画出这几页)。螺壳首次设置的完成页用同一套教程。

自带的 librime 在编译时把源码路径换成相对路径(-ffile-prefix-map),包里的库不带编译机的目录。

系统要求

螺壳只在 macOS 26 起安装输入法(清单 min_macos 26);LuokeIME.app 本身的最低系统是 14.0,供手动安装验证旧系统。

已知限制

  • 个人化组件不读记忆中的理解(常用说法等),打分没有个人化。
  • 候选窗不能用鼠标点选;引擎选项没有 A2UI 界面。
  • 快捷键引擎不检查冲突、停用不进菜单。
  • 个人化组件卡死不检测;转问没有限流。
  • 没有打错纠正。
  • 快捷键引擎上屏后的退格不转给个人化组件。
  • 引擎停用、被沙盒拦下只在候选窗提示和菜单里显示,不发系统通知。
  • 收起联想时,应用自己插的制表符留着。

agent

螺壳的后台组件,螺壳里唯一主动找用户的部分。以后并进螺壳:不在「应用」列表里单独勾选,随螺壳装好,设置里有「主动提醒」开关;现在的独立开发版「螺壳 agent(开发)」是过渡形态。它把记忆的提问和建议、各工具要报的事合并成卡片和简报,挑时机告诉用户,并代表用户调用工具声明的能力。当前版本不使用模型。

仓库 luoke-agent · 核心 0.0.1 · 自带 luoke_mem 0.23.4 · 按代码 536f134 核对 · 不在 v1 首发中,和体检、螺壳窗口进同一个 v1.x

职责

  • 从记忆读取待问的问题和建议(asks),从各工具读取待报事项(outbox),统一排序后在窗口、通知和简报中呈现。
  • 把用户的回答交回来源:交给记忆的经 Memory.reply,交给工具的经工具协议调用对应能力。
  • 只在用户点按卡片时发出会改动数据的调用。不自行推断用户信息,也不替工具执行动作。

进程模型

部分实现职责
壳Swift 应用(macOS 14+),菜单栏常驻,依赖 A2UI 渲染包菜单栏、窗口、系统通知、卡片渲染、上报用户是否在场
核心Python 包 luoke_agent(仅标准库),运行在主控自带的 Python 上读记忆、连接工具、事项库、决定何时说
  • 启动:设计上主控按配件清单(resident: true,登录时带 --resident,不开窗口)拉起壳;现在配方和发布流水线里都没有 agent,主控不装也不拉起它。壳起核心的命令是 <python> -B -E -s -m luoke_agent serve --home H --memory M --parent-pid P,可再带 --hub S、--tool 配件=socket(可多个)、--memory-app;环境只给 PATH、HOME、PYTHONDONTWRITEBYTECODE、LUOKE_AGENT_PYTHON,标准错误追加到 <home>/logs/core.log。
  • 借主控的 Python:壳按 bundle id ai.luoke.core 找到主控,用它的 Contents/Resources/python/bin/python3;每次起核心都重新找,主控换版本、改名、挪位置都跟得上。
  • 存活:核心意外退出时壳会重启它,间隔从 2 秒翻倍到 60 秒;核心每 2 秒检查父进程,壳退出时随之退出。核心同时盯着 LUOKE_AGENT_PYTHON 指向的文件:它被换掉(设备号或 inode 变了),或路径消失超过 30 秒,核心以退出码 75 退出,壳立刻重起,不等间隔,并把间隔复位为 2 秒;核心稳定跑过 60 秒后才退出时,间隔也复位。
  • 壳与核心之间:<home>/run/agent.sock 上的 HTTP/1.1,见「界面协议」。
  • 轮询:核心每 5 秒一个周期;工具 outbox 每 60 秒读一次,记忆和主控每 300 秒各读一次,简报到点前 5 秒起每个周期都读记忆,直到简报发出;窗口打开时立即把全部来源读一遍。一个来源出错不影响其他,暂时不在运行的来源卡片先收起,回来后照旧。
  • 使用的主控接口(只在给了 --hub 时):GET /accessories 发现已安装的工具(只认 capabilities.protocol 为 1 且有 socket 的,排除自己);主控没答时保持现有工具,答了而清单里没有的工具算已卸载,卡片撤下、事项记为撤回;--tool 指定的不受影响。核心每次启动时经 GET /tasks 取主控内置的 luoke_mem 路径,取不到或导入失败时用自带的副本。壳正常退出时调 POST /accessory/exiting,告诉主控别再拉起。

打包形态

形态身份怎么运行
开发版ai.luoke.agent.dev,「螺壳 agent(开发)」必须显式给 --home、--memory、--python,缺了就在窗口里报错、不起核心
试用版开发版加 trial.json,由 app/install_trial.sh 装成 ~/Applications/螺壳 agent(开发).app读 ~/.luoke/mem.db,数据在 ~/.luoke/agent,借主控的 Python。不带配件清单、不带 --hub:主控不管它,它也不问主控,所以用自带的 luoke_mem,不接工具
--releaseai.luoke.agent,「螺壳 agent」固定位置,默认连主控的 socket。正式签名和公证还没做,没有发布过

界面协议(核心 ↔ 壳)

方法请求答复
GET /feedServer-Sent Events,先推全部卡片,再推变化,每 15 秒心跳事件 a2ui、feed、notify、busy、result
POST /action{surfaceId, sourceComponentId, context}{text, error}
POST /presence{idle, locked, asleep},壳每 30 秒和状态变化时上报{}
POST /window{open, front},front 表示窗口在最前面,不报就按 open{}
POST /notifications{status, alerts},壳每 30 秒上报系统通知的授权{};status 缺失或超过 40 字符答 400
GET /status—只有元数据:items、surfaces、notices、sources(记忆为 missing / ok / too_new,工具为 running / down)、brief_due、window_open、window_front、notifications、present
POST /say—501(未实现对话)
  • 事件内容:feed 为 {order, open, notices};notify 为 {id, title, body, urgent, silent, surfaceId},同一 id 的通知换掉上一条;busy 为 {surfaceId};result 为 {surfaceId, text, error, replaced},replaced 表示卡片已换成结果,界面不再重复写那句话。
  • 窗口里的状态提示(notices):工具没在运行、螺壳记忆打不开、螺壳记忆比螺壳新请更新、螺壳没在运行暂时找不到工具、这一版不在螺壳验证过的版本里、系统通知被关掉、系统正在问能不能发通知。
  • 系统通知被关:通知到不了时,窗口里说一句怎么在系统设置里打开,不替用户改系统设置;系统还在问用户(not_determined)时,窗口里说明答了以后才到得了;状态变化在 core.log 记一行。通知到不了时的处理见「何时说」。
  • 壳的命令行:--notify-status 打印一行 JSON {authorization, alert},不发通知、不请求授权、不起核心,10 秒没答复退出 5;--notify <标题> <副标题> <正文> [--open <参数>] 发一条通知,退出码 0 交给系统了、3 没允许、4 系统拒收、5 十秒没答复、64 用法不对。两者和主控启动器的同名参数一致,给主控首次运行的完成页用。
  • 调试子命令:python -m luoke_agent feed|tap|presence|window --home H,用文字看卡片、点按钮、报在场、开关窗口。

工具协议

工具在配件清单的 capabilities 中声明协议版本(1)、socket 路径、可报事项(reports[]:kind、title、默认 urgency)和动作(actions[]),并在自己的 Unix socket 上提供 MCP 子集。完整规格见 luoke-agent/docs/工具协议.md。目前没有已发布的配件声明 capabilities.protocol:输入法的清单只有 listing,agent 跳过它;邮件助手的实现默认关闭,声明单放在 packaging/agent-capabilities.json,不进发布清单;体检和深度搜索已实现协议并在清单中声明,但都未发布。agent 实际只有记忆一个来源。

传输

  • 每个请求一条连接:POST /mcp,请求体为一条 JSON-RPC 2.0 消息,上限 1 MB;MCP 协议版本 2026-07-28,在 params._meta 中携带。
  • 超时:连接 10 秒;读 outbox、列工具 10 秒;只读调用 30 秒;写调用 300 秒。socket 不存在视为工具未运行,不算错误。

方法

方法用途
server/discover协议版本和服务能力;工具要实现,agent 现在不调
resources/read读 outbox:luoke://<配件>/outbox,内容 {items:[…]}
tools/list能力列表,带 readOnlyHint
tools/call调用能力;写调用必须在 _meta 中带 ai.luoke/user-action({surfaceId, componentId, at}),否则工具返回 needs_user_action
  • outbox 事项字段:id、kind、urgency、deadline、expires、created、why_now、title、summary、card。缺 id 或 title 的丢弃;kind 缺省 other;urgency 不合法时用清单里这一类的默认,再没有就是 normal。outbox 是一份完整状态,上一轮有、这一轮没有的事项视为撤回。完成的编号在记录保留的 30 天内不再显示,工具要重新报同一件事须换新编号;撤回的编号再列出来就恢复,说过的不重说。
  • 回执:工具实现了 outbox_ack(看 tools/list)时,agent 在一件事第一次说给用户时回 state: "shown",每件一次(进了简报、发了急事通知,或用户正看着窗口时进来);用户答完回 answered,放下回 dismissed。用户不在、简报没发出去、系统通知到不了时不回 shown。
  • 调用结果的 structuredContent 可带 card(更新原卡片)、done(事项完成,卡片保留 30 分钟以便撤销)。失败码:needs_user_action、invalid、not_ready、needs_setup、stale、failed。
  • 撤销是结果卡上的一个写按钮,走同一条点按路径。
  • 打开东西的动作(打开文件、在访达中显示、打开工具自己的页面)一律带 ai.luoke/where: mac,只在用户点了按钮时才调,不管是否只读;readOnlyHint 由工具照实写。

卡片(A2UI)

  • A2UI v0.9.1(消息的 version 字段写 "v0.9"),目录 ai.luoke/a2ui/agent/1,组件限于 Text、Row、Column、List、Card、Divider、Button、CheckBox、ChoicePicker、TextField、DateTimeInput、Icon。
  • 校验:根组件存在、可达组件 ≤ 200、累计 ≤ 1000、无环、单父、绑定路径存在、文字 ≤ 2000 字;未通过校验的卡片改为纯文本卡。
  • agent 自己处理的动作:agent.open_url(仅 https,按钮文字后附域名)、agent.dismiss;agent.remind 只用于 agent 自己的「少说」卡片,工具卡片用了点按时答「不认识这个按钮」。
  • 渲染器:Swift 包 LuokeA2UI(macOS 14、iOS 17),LuokeA2UICore 只依赖 Foundation,LuokeA2UI 是 SwiftUI 画法;校验规则和 luoke_agent/a2ui.py 一条条对应。另有输入法用的目录 ai.luoke/a2ui/ime/1。DateTimeInput 目前画成文本框。

协议里有、agent 还没做的

  • 基础页和权限(§1a,已定):工具可在 capabilities 里加 service(summary、pages、ui)、permissions、listing;对应资源 summary、page/<id>、permissions 用相对 id。权限分四种:system、account、delegate、each-time,状态由工具照实报。窗口归螺壳.app 画,agent 供内容;主控和 agent 现在都不读这些字段,体检和深度搜索已实现工具一侧(基础页只有体检有)。
  • 问螺壳(§1b,草案):service.ask 的 suggest、search 两个只读动作,只在用户点开或打字问时才调。search 回 hits:固定形状的数据,不是 A2UI,由 LuokeA2UI 里只给窗口用的 HitsList 画。草案还给目录加 Image,现在不在目录里。主控、agent 都没有实现。
  • 不从卡片发起的调用(§5,草案):用户在对话里问时,只读工具的结果带一张新卡片,不进 outbox 和简报。agent 没有对话,未实现;深度搜索已按此回。
  • ai.luoke/where: mac(§5):标明只在 Mac 上有意义的工具,手机上不调。agent 没有手机端,不读它。
  • 工具自己做了的事(§8a,已定):auto-done(normal,能撤销给撤销,撤不回给「以后别自动做」)和 daily-review(low,每天一份)。agent 不需要专门处理,按普通事项走。体检已报 auto-done;邮件助手未实现。

与记忆的交互

  • 以 Memory(path, tool="luoke-agent") 打开记忆;文件不存在时不创建,遇到版本过新的库时提示更新。
  • 读取 asks(status=["open"]),该不该问、每天几条由记忆决定;带 about_note 的问题和建议附上该理解最新的 3 条依据原文(来源、日期、每条 120 字以内),没有时显示 ask 的 evidence。记忆的提问和建议 urgency 默认为 normal,走简报。
  • 回答经 reply(ask_id, choice, text) 交回:
卡片按钮 → choice
是非类建议(answer.type 为 yes_no,或 action.capability 为 habit)好 → useful(处理习惯同时确认这条理解)· 不用 → useless · 别再提 → never
其余建议(包括退订建议)知道了 → useful · 别再提 → never
关于某条理解的问题对 → yes · 不对 → no · 以前是,现在不是 → past · 按我改的 → revise · 以后再说 → later(记忆收起这条理解下开着的问题,至少 7 天内不再问;之后由反刍视情况作为新的一条再问)
自由问题(单选、是非、文字)交回或 是 / 不是 → answer · 以后再说 → later(只收起这一条)

带 about_note 的卡片多一个「在螺壳记忆里看」,打开 luoke://memory?note=N,不写入。记忆现在只产生关于理解的问题和建议,自由问题这一行暂时用不到。

何时说

  • 排序:urgency(urgent、normal、low)→ deadline → 首次出现时间。
  • 每类的方式(modes):now、brief、window、off;未设置时 urgent 为 now,其余为 brief。
  • 急事:用户在 Mac 前(120 秒内有上报、未锁屏、屏幕未睡、空闲 < 180 秒)时立即通知,不受免打扰时段限制。一轮只有一件时,标题是事项标题、正文是 why_now(没有则用 summary);同一轮有多件时合成一条,标题「N 件急事」,正文列前 3 个标题,点开定位第一件。
  • 简报:第一件待简报的事项(normal;只有 low 时距上一份 low 简报 ≥ 24 小时)出现后计时 60 分钟,到点后等用户在场且不在免打扰时段才发;每份含全部 normal,最多附 2 件 low;通知正文列前 3 个标题;上一份未打开窗口时新的一份静音。
  • 窗口:窗口在最前面、用户也在场时算正看着,现有事项和新来的事项都算看过,不再发通知。窗口开着但最小化、在别的应用后面,或人已离开,都不算。
  • 通知到不了:系统通知被拒、横幅关着或用户还没答时不发、也不算说过,急事和简报都排着,打开后补发;壳还没上报过授权时照常发。
  • 少说:同一类连续 3 次没有回应,从 brief 降为 window,并给出一张"以后只放在窗口里"的卡片,用户点了才收起,核心重启后画回来。同一次说的同类事项只算一次。算没回应的:通知过、到下一份简报时仍没看过的;通知后过期的;用户按「放下」。用户答复或点「还是提醒我」把连续计数清零。
  • 清理:过期事项撤下;窗口关着时,完成的卡片 30 分钟后撤下,完成或撤回超过 30 天的记录连同 contents 一起删除。

数据和文件

位置内容
~/.luoke/agent/数据目录(700)
state.db(600)表 items(事项状态)、contents(卡片内容,用于重启后恢复和撤销,撤下后留作历史)、settings、meta、reactions(每类展示、回应、忽略的统计);put_off 已不再使用
run/agent.sock界面 socket(600)
logs/core.log只记元数据:版本、socket、异常类型、错误码

设置默认值:brief_minutes 60、away_seconds 180、ignore_limit 3、quiet 无、modes 空。brief_minutes 可由 serve --brief-minutes 写入,modes 只由「少说」改,其余没有修改途径。

安全

  • 写调用只在 POST /action(用户点按)时发出,并附 user-action;方案编号、链接、问题编号等写死的值只认核心保存的卡片声明,不采用界面送来的同名值。
  • 工具和记忆送来的文字一律按纯文本渲染;每张卡片带来源前缀,按钮只转给出这张卡片的来源。
  • socket 只靠文件权限(目录 700、文件 600)限定在当前账户内,不核对调用方身份。

已知限制

  • 没有对话,不使用模型;outbox 靠轮询,未实现订阅。
  • 写调用只看 tools/list 的 readOnlyHint,不对照清单里声明的 actions。
  • deadline 只影响排序,过期不会标记;off 与 window 效果相同。
  • 设置没有界面;卡片上用户改过的选项只存在壳里,壳重启、核心重启或重新连上时丢失;core.log 不轮转。
  • 简报通知的正文包含事项标题。
  • 核心只在启动时取一次主控的 luoke_mem 路径,主控换版本后靠退出码 75 重起再取;连不上主控时隔 2 秒重试,共 3 次,都不行就用自带的副本,直到下次重起。
  • 连主控时要过版本校验:配方的 verified 没有 luoke-agent,主控不答它的请求,agent 只能说明这一版用不了工具;主控一侧还没接。
  • 下一版修:本页按仓库主线写,试用版换装后才生效的有:窗口要在最前面才算看过、通知到不了时不算说过、少说按次计数且卡片不丢、来源读不到时事项不丢、改写含密码类内容时提示正确、主控不认版本时单独提示、重启间隔复位。这些修复有单元测试,窗口状态的计算在 AppKit 测试程序上跑过,开发版 app 本身还没在真窗口上点过。

螺壳体检

在空闲时查一遍这台 Mac:空间去向、会重建的缓存、卸载残留、久未动的项目、重复和久未打开的大文件,以及常驻项、进程、键盘监听、权限、浏览器扩展、MCP 配置、基础安全设置和病毒。是不是垃圾、能不能删由确定的规则决定。结果经 agent 告诉用户;四类按规则挑出的东西不用点就清,其余的删除只执行用户在卡片上点过的方案。除缓存外都移到废纸篓,能放回。

仓库 luoke-audit(Swift 包,三个程序 luoke-audit、luoke-audit-serve、luoke-audit-admin)· 版本 0.1.0,规则包 0.1.0,按代码 3a6c9cb 核对 · 和 agent、螺壳窗口进同一个 v1.x;接进螺壳(luoke-core#27)在 beta1 之后 · 只支持 macOS 26.2 及以上

职责

  • 查的时候不改任何东西;定时的一轮查完以后才清(见「清理」)。能用元信息(大小、时间、inode、签名、链接数)判断的不读内容;要内容的(重复文件的哈希、明文凭据、散落的密钥)只在本机读,内容不进报告、日志和卡片。
  • 每条发现带结论(放心删 safe / 要确认 confirm / 仅供参考 info)、类别、严重度和依据。只有会重建的缓存能得到"放心删"。
  • 检查没跑成记成"没扫描"并写原因,不算没有发现。
  • 不主动找用户:没有通知、没有菜单栏图标,要说的事放进 outbox,由 agent 挑时机。
  • 不带模型。病毒扫描用随包的 ClamAV 和 YARA(特征扫描,不是模型)。

运行形态

程序行为
luoke-audit「螺壳体检.app」的主程序,用户给「完全磁盘访问」的就是它。扫描、出方案、执行方案、不用点就清都在它里面;从不开 socket。空闲任务 scan --this-mac --autoclean、update --this-mac 起它;agent 的每个请求起一次 luoke-audit mcp(标准输入一条 JSON-RPC,标准输出答复,答完就退出)
luoke-audit-serve只管给 agent 的 socket ~/.luoke-audit/run/agent.sock,不读不动用户的文件,不要完全磁盘访问;只链接 AuditSocket,不含检查和清理的代码。每个 POST /mcp 以"自己负责"(responsibility_spawnattrs_setdisclaim)起同一文件夹里的 luoke-audit mcp,只传 0、1、2 三个描述符,等 290 秒。每 300 秒(--quick-every)起一次 luoke-audit scan --quick 看新下载的
luoke-audit-admin单独签名(ai.luoke.audit.admin)。平时不运行,只在用户输入 Mac 的密码后以管理员身份做一次「要密码的删除」,见「清理」。不是常驻的 root 辅助程序
  • 谁起:空闲任务由主控按配件清单的 jobs 起(每天一次,接着电源)。luoke-audit-serve 要常驻:配件清单写了 resident_exec、resident_command,resident 还是 false,主控还不认这两个字段。本工具还没进配方,目前主控不会起它,都要手动起。
  • 第一轮:进配方以后,主控发现新配件没有运行记录,第一个满足条件的周期就跑(用户安静、后台条件允许、没有别的任务在跑),不用等一天。配件清单里 update 排在 scan 前,第一轮先下病毒库再扫描。第一轮就带 --autoclean,persistence.new 只记基线,space.growth 要攒几天历史。luoke-audit-serve 自己不起第一轮;想马上查用 audit_scan_now(不清东西)。
  • 装好的包只认 --this-mac:在 .app 里运行时,--home、--data、--tmp、--projects、--tool-dir、--fixture、--clock-start 和 fixture、eval-cleanup 子命令都拒绝;findings、status、update 要带 --this-mac。扫描工具只用包里的,不找 Homebrew 的(那些文件夹用户可写,会借到完全磁盘访问)。
  • --this-mac 查哪里:家目录、本用户的临时目录(DARWIN_USER_TEMP_DIR),/Library 和 /var/db/receipts(找系统目录里的卸载残留),项目目录取 ~/Developer、~/Projects、~/Documents/GitHub、~/code、~/src、~/repos、~/workspace 里存在的;数据放 ~/.luoke-audit。
  • 没有完全磁盘访问时:不碰 macOS 会弹框问的文件夹(桌面、文稿、下载、iCloud 云盘、~/Library/CloudStorage、Containers、Daemon Containers、Group Containers)。能不能读用打开 TCC 库试探,不弹框,试不出算没有。看不到的部分跳过,写成"没有完全磁盘访问,…没看",已看到的照报;受保护文件夹里的程序签名记成"不知道",不当成不存在;方案不碰这些路径,快速扫描不跑;记过的大小和哈希不忘。outbox 出 needs-access 卡片。受保护文件夹里的缓存不看,也记成"没看"(发现里不出现,不用点就清也不碰);其余缓存和临时文件夹照常清,安装包和重复文件没有发现就不动。
  • 一次一轮:每个数据目录用 round.lock 保证空闲任务和 agent 叫的那一轮不重叠;快速扫描另有 quick.lock;一个数据目录只能起一个 luoke-audit-serve(serve.lock);一份方案同时只在一个进程里执行(plans/<编号>.lock)。
  • 跑得轻:scan、update 以 utility 优先级运行(磁盘 I/O 同档,起的 clamscan、yara、git 也是);只在 iCloud 的文件不读(读会触发下载)。外部命令都带超时,超时连进程组一起杀。
  • 被打断:收到 SIGTERM 时停在做完的那一步、保存报告、退出码 0;没来得及查的规则沿用上一轮的发现并在 skipped 里注明,量过的留给下一轮。扫描被打断的一轮不做不用点就清;清的时候被打断见「不用点就清」。
  • 快速扫描(scan --quick):只在接着电源、不在低电量模式、内存压力不高(kern.memorystatus_vm_pressure_level ≤ 1)时跑;FSEvents 日志显示下载或桌面有改动才扫,只跑病毒扫描,用精简库,跳过 120 秒内写过的文件(修改时间在未来的不算还在写);有跳过的文件时不记这次的事件号,下一次不管日志有没有改动都再看,没变的沿用记录不重扫;结果并进上一轮的报告;整轮在跑就不看。还没有完整的一轮或没有完全磁盘访问时不做。

检查什么

一轮按顺序跑 23 条规则,每条记耗时(ruleSeconds)。下表按类别合并;"结论"是规则给的。只有会重建的缓存是"放心删";仅供参考的不删东西,写清怎么处理。

类别发现(rule)结论要点
会重建的缓存cache.rebuildable.*放心删20 项:pip、SwiftPM、Go、Xcode DerivedData、Cargo(registry 的 cache 和 src)、Bun、uv、npm(只算 ~/.npm/_cacache)、Homebrew、Yarn、CocoaPods、Gradle(caches 和 wrapper)、Poetry(cache 和 artifacts)、node-gyp、Composer、Xcode 文档、模拟器缓存;≥ 50 MB 才报
临时目录leak.temp-prefix要确认同一前缀(mkdtemp 式后缀)≥ 20 个、合计 ≥ 100 MB、1 天没动;给出数量、日期范围和每天多少
久未动的项目stale.project、stale.artifacts、stale.folders要确认 / 仅供参考项目目录下 6 个月(182 天)没动的;查 git(未推送提交、未提交改动、stash、没有远程、被忽略的非构建产物、仓库旁不在 git 里的东西),全干净才"要确认",否则"仅供参考"并写会丢什么。stale.artifacts 是老项目里 25 种会重建的构建产物,往下 4 层、合计 ≥ 100 MB,查是否被 git 跟踪和内嵌仓库。git 带 --no-optional-locks,关掉仓库配置里会运行程序的项
空间space.growth、space.low、space.partial仅供参考家目录下一层(~/Library 的 Caches、Application Support、Containers、Developer 和 .cache、.local/share 再细分一层)和临时目录每天记大小,比 7 天内最早的记录每天多 ≥ 1 GB 报;可用空间 < max(20 GB, 10%) 报;一轮量大小最多 10 分钟;~/Library/CloudStorage 从不量
系统层空间space.tm-snapshots、space.vm-disk、space.ai-model(s)、space.xcode-*、space.simulators仅供参考;单个模型和旧设备支持文件要确认Time Machine 本地快照留了 2 天以上;容器和虚拟机磁盘 ≥ 5 GB;AI 工具下载的模型单个 ≥ 2 GB 单列、合计 ≥ 5 GB 报一条;Xcode 旧的设备支持文件(≥ 100 MB、30 天没写)、归档 ≥ 1 GB、模拟器 ≥ 5 GB
重复文件space.duplicates要确认下载、桌面、文稿、影片、音乐、图片里 ≥ 10 MB、内容相同的几份(大小、头尾 64 KB、整份 SHA-256);多占按 APFS 独占大小算,克隆和硬链接不算;一组多占 ≥ 50 MB 才报,最多 20 组
久未打开的大文件stale.large-file要确认Spotlight 记的上次打开时间在一年前、≥ 500 MB,最大的 20 个
残留leftover.app-data、leftover.installer、leftover.partial-download、leftover.relocated-items、leftover.intel-homebrew(-links)要确认 / 仅供参考~/Library 和 /Library 里以 bundle id 命名、没有已装应用对得上、30 天没写、≥ 1 MB 的(名字像钱包、密码库、密钥、VPN、验证码、ssh 的,或同一开发商还有应用装着的,只"仅供参考");下载文件夹里 14 天前来的安装包(≥ 5 MB、没挂着);7 天没写、≥ 1 MB 的没下完的下载;升级挪出的旧配置;Apple 芯片上的旧 Intel 版 Homebrew 和它留下的失效链接
常驻项persistence.*要确认 / 仅供参考本用户 LaunchAgent 指向不存在的程序;系统级 LaunchAgents / LaunchDaemons、应用登记的后台程序和登录项(launchctl print gui/<uid>)、crontab、shell 配置和 /etc/paths 里的 PATH:读签名(Security,不运行程序)、本用户能否换掉。记下见过的(第一轮只记),之后新出现、没人担保的报 persistence.new(高)
启动钩子startup.*仅供参考动态库注入环境变量(dyld-insert)、授权插件、登录钩子、终端启动命令、iTerm 自动运行、periodic 脚本
新装的应用apps.bad-signature、apps.unsigned、apps.not-notarized要确认 / 仅供参考最近 30 天放进「应用程序」的:签名对不上、没签名或临时签名、没公证;附隔离标记里下载它的程序。浏览器装成应用的网页不算
进程process.*要确认 / 仅供参考程序文件已删除的进程;同一父进程下同一程序 ≥ 20 个;跑了 ≥ 6 小时的孤儿
键盘监听keyboard.unvouched、keyboard.watchers仅供参考用 CGGetEventTapList 列出在监听键盘事件的程序;没人担保的算中或高
对外露出exposure.*仅供参考本用户程序在回环以外监听 TCP 端口(libproc);正在运行的命令行里带密码、令牌(只记种类);SSH:~/.ssh 和钥匙的权限、没有口令的钥匙、弱算法、authorized_keys、~/.ssh/rc
凭据secrets.plaintext、secrets.stray-key仅供参考工具明文存的凭据(.git-credentials、.netrc、.npmrc、.pypirc、Docker、gh);下载、桌面、文稿往下两层里的私钥、证书包、云服务密钥。只读小文件的开头,只记种类和个数
浏览器browser.*仅供参考7 个 Chromium 系浏览器和 Firefox 的扩展、被策略强装的(高)、侧载的、原生消息程序;不看 Safari
MCP 配置mcp.servers、mcp.secrets仅供参考7 处 AI 工具的 MCP 配置:装了哪些服务器,配置里有没有明文密钥
权限授予permission.stale-grant要确认TCC 里允许的授权,应用或路径已经不在;读不了 TCC 记成没扫描("需要完全磁盘访问")
基础设置posture.*仅供参考FileVault、SIP、Gatekeeper、自动登录(高);防火墙、隐身模式、锁屏和锁屏延迟、自动检查更新、待装更新且 30 天没重启;远程登录、屏幕共享、文件共享、互联网共享、AirDrop 对所有人、访客账户;用户加的根证书(只算不限主机、不是拒绝的);XProtect 60 天没更新。只报不好的状态
病毒malware.*要确认 / 仅供参考见下

病毒扫描

  • 扫什么:下载文件夹里 30 天内来的文件(按修改时间或加入时间)、Spotlight 记着在家目录别处下载的(kMDItemWhereFroms)、下载来的应用里的程序(Contents/MacOS 全部,另加至多 50 个可执行文件)、本用户 LaunchAgent 运行的程序;一轮最多 2000 个。
  • 只扫新的:扫过的记在 malware_scanned(路径、inode、大小、修改时间、当时认出的);没变的 7 天后再扫,病毒库更新后 3 天内来的文件用新库再扫;没有要扫的就不起 clamscan。
  • ClamAV:--max-filesize=100M --max-scansize=400M --alert-exceeds-max=yes,太大的报 malware.too-large;读不了的文件不交给扫描器,下一轮再看;文件名带控制字符的单独报 malware.odd-name。整轮 clamscan 限 30 分钟、YARA 15 分钟。
  • YARA:自带两条规则(反弹 shell、伪造的系统密码对话框),加上 macOS 自带 XProtect 的规则(取版本新的那份;编不过时只用自带的),发现记成 malware.yara.xprotect.<规则>。
  • 精简库:快速扫描用的库由 sigtool --unpack 拆开后去掉 Win.、PUA.Win. 开头的特征和 PE 的节、导入表哈希,每个库文件一个子目录、只重做变了的;快速扫描扫过的记成 +quick,下一次整轮用全库再扫。
  • 病毒库更新(update):每天最多一次;freshclam 在克隆出的临时目录里下载,sigtool --info 逐个校验通过才换库,任何一步失败保留旧库;被上游限流时保留 freshclam 的冷却状态。库超过 14 天没更新报 malware.stale-db,照样扫。

增量

  • 量大小:每个文件夹和它下面两层 ≥ 1 MB 的子目录记下 FSEvents 事件号、inode、大小;下一轮回放日志,没改动、inode 没变、真正量过不到 7 天的沿用,被碰过的只重量改过的子目录。日志换了或丢了细节就全量。
  • 临时目录、老项目、卸载残留:同样按 FSEvents 沿用上次的结论(temp_entries、project_state、leftover_state),最长 7 天。
  • 遍历用 getattrlistbulk,不跟符号链接,不进挂在里面的别的卷;APFS、HFS+ 上空文件夹不打开,不够 20 个的临时目录前缀组不细看。新装应用和常驻项的签名结果按文件记下,最多沿用 7 天。

清理

  • 方案:audit_plan 或 outbox 卡片从上一轮的发现出方案(最多 30 项),每项可选移到废纸篓、先不动、以后别提,重复文件另有换成克隆,失效的链接是删除。放心删的默认移到废纸篓,要确认的默认先不动。"仅供参考"的不进方案。要密码的项单独一份方案、单独一个按钮。方案存在 plans/,30 天后删掉。
  • 能进方案的:缓存、残留(含没下完的下载、旧的 Intel 版 Homebrew)、临时目录的成批前缀、干净的久未动项目和其中的构建产物、久未打开的大文件、重复文件、单个 AI 模型、Xcode 旧设备支持文件、本用户指向不存在程序的 LaunchAgent 和新出现的本用户 LaunchAgent、病毒库或 YARA 认出的文件。目标只能在家目录或临时目录里面(不能是家目录、临时目录、项目目录本身);要密码的那份例外。
  • 执行(audit_apply):没有 ai.luoke/user-action 就拒绝;只执行本工具出过的方案、只接受方案里列的选项,没送的项当"先不动"。每个目标再核对:还在、不是链接、设备号和 inode 没变、在家目录或临时目录里、父目录本用户可写、里面没挂别的盘、查过以后没改过(会重建的缓存 cache.rebuildable.* 除外,归在缓存类别里的 AI 模型和 Xcode 设备支持文件要重下,同样要查;未来时间不算;核对总共最多 2 分钟);久未动的项目再问一次 git。核对不上的跳过并写原因。
  • 怎么删:移到废纸篓(trashItem)。LaunchAgent 先 launchctl bootout 再移配置。重复文件移走前确认要留的那份还在且内容相同;换成克隆前逐字节再比一遍,几份都留着;比的时候读失败算不一样。系统级常驻项、登录项、crontab 不进方案:登录项打开系统设置,crontab 说用 crontab -e 删哪一行,系统级常驻项说在访达里移走(会要密码)。

不用点就清

四类默认开,只在主控定时起的、带 --autoclean 的一轮里、查完且没被打断时做;agent 叫的一轮、快速扫描都不清。用户说过「以后别提」的不挑。

类判定怎么清
会重建的缓存位置正好是缓存清单里的那一处、是真文件夹不是链接、里面没挂盘、用它的程序都没在运行。清单里没写谁在用的不自动清(测试要求每项都写)Go、uv、npm 用工具自己的清理命令,其余直接删文件夹。不进废纸篓,放不回;还留 ≥ 1 MiB 算没清完
安装包下载文件夹里的普通文件,名字去掉版本、芯片、系统词后正好是已装的应用(≥ 4 个字母);有隔离标记、有签名、来了 ≥ 14 天;名字带版本时不高于已装版本;没被打开、没挂着移到废纸篓
重复文件副本在下载文件夹里,来和改都 ≥ 7 天、不在项目里、独占 ≥ 1 MiB;留的那份在下载之外、在本地、不在构建类文件夹里,内容相同;副本没被打开移到废纸篓
测试工具的临时文件夹只认 6 个前缀(Puppeteer、Playwright 的配置文件夹和产物、Lighthouse);先要有 leak.temp-prefix 发现;每个文件夹 3 天没写、属于本用户、没挂盘、浏览器没开着、没被任何命令行点名;读不到进程列表就都不清移到废纸篓
  • 移进废纸篓的三类,动手前和点过的方案做同样的核对,每项前重读开关,ctime 不晚于这一轮开始。
  • 开关:audit_autoclean 开关全部四类。从某类放回过东西,那一类以后不再自动清(autoOff),关了再开时清空。状态文件读不出就不清,之后从"关"开始。
  • 被打断:缓存和移进废纸篓的每项之间都看一次,用户回来就停在两项之间,剩下的等下一轮。主控发 SIGTERM 15 秒后强杀,工具自己的清理命令可能跑两分钟,所以每清一项就写一次记录;这一轮的报告没存下时,由下一轮那一件告诉用户。
  • 清了的记成 auto-done:卡片给 [放回](整批,缓存除外)和 [以后别自动清]。

要密码的删除

  • 范围:/Library 的 Application Support、Caches、Logs 里 root 拥有的卸载残留,和旧的 Intel 版 Homebrew(Cellar、Caskroom、Homebrew 三个文件夹全动或全不动)。按钮是「移到废纸篓(要输入密码)」,选项绑在 /admin_choices。
  • 流程:用户进程先再判一遍;点了以后弹 macOS 的授权框(NSAppleScript),在终端里是 sudo -k,每次都问。root 那一侧把 luoke-audit-admin 拷进只有 root 能写的临时文件夹,核对是小于 100 MB 的普通文件,按发布签名验这份拷贝,用空环境运行。它按和扫描同一个判定重判每一项,再看整棵树,30 天内写过或有别的账户东西的不动,逐层不跟链接地打开,renameatx_np 移进废纸篓。结果只经标准输出交回,由用户进程写记录。取消密码框时方案保留。
  • 在终端里:luoke-audit system --this-mac 列出,--trash a0,a1 移走;root 直接跑会被拒绝。

放回

  • 清理记录写在只追加的 cleaned.jsonl,audit_restore 按一条(c-…)或一批(p-… / auto-…)放回,能放回的都带 ai.luoke.audit.cleaned 标记。
  • 用 RENAME_EXCL 放,原位置有同名的放成「名字(放回)」。整批不带报毒的文件和常驻项;受保护的位置不放回;要密码删的要在访达里拖。缓存放不回。

接口

给 agent 的 MCP 工具

按 agent 工具协议(版本 1,MCP 2026-07-28 的无状态子集:server/discover、resources/list、resources/read、tools/list、tools/call),在 ~/.luoke-audit/run/agent.sock 上 POST /mcp,请求体至多 1 MB。卡片是 A2UI v0.9(目录 ai.luoke/a2ui/agent/1)。不支持订阅。

工具只读参数 → 答复
audit_status是— → 上一轮的时间和耗时、三种结论各多少、放心删能腾出多少、病毒库版本和日期、没查成的规则、缓存是否自动清、是否正在查;带卡片
audit_findings是可选 verdict(safe / confirm / info)、category、limit(1–50,默认 20)→ 发现列表和卡片;标成"以后别提"的不列
audit_scan_now是— → 另起 luoke-audit scan --for-agent,马上答复;这一轮不清东西;已经在查就说一声
audit_plan是可选 scope(all / cache)→ 方案和确认卡片
audit_apply否(destructive)plan_id、choices → 移了、克隆了、部分处理、已不在、没动的各几项;什么都没剩时 done: true
audit_ignore否finding_id → 以后不再报这一条
audit_restore否id(c-…、p-…、auto-…)→ 放回一条或一批
audit_autoclean否on(旧的 enable 照旧认)→ 开或关不用点就清的四类
audit_reveal是finding_id → 在访达里选中;东西不在了答 stale。标 ai.luoke/where: mac
audit_open_settings是grant(隐私与安全性里的某项权限,或「登录项」)→ 打开系统设置对应页。标 Mac
outbox_ack是id、state(shown / answered / dismissed);answered、dismissed 的不再出

非只读的工具没有完整的 ai.luoke/user-action(surfaceId、componentId、at)答 needs_user_action。其他错误码:not_ready、stale、invalid、failed。发现的编号跨轮稳定:规则名加规范化路径(或规则给的键)的哈希。卡片里家目录下的路径写成 ~/…。

资源

资源(相对 id)内容
outbox(luoke://luoke-audit/outbox)见下
summary{count, text},count 算放心删、要确认和风险高的仅供参考,其余仅供参考不算
permissionsfda 当场探测(granted / denied / ask)、auto(on / off)、admin(恒为 ask,每次问)
page/overviewA2UI 基础页:一句摘要、上一轮的计数、现在查一下
page/cleanedA2UI 基础页:最近 20 批清理,能放回的带按钮

outbox

kindurgency什么时候出
securityurgent病毒库或 YARA 认出的文件、新出现的没人担保的常驻项,每件一条、跨轮同一 id;放下(dismissed)后不再出。卡片给移到废纸篓 / 停掉并移到废纸篓(本用户的 LaunchAgent)/ 打开「登录项」设置、在访达里显示、以后别提
spacenormal只在可用空间低(space.low)时,每天一条;有会重建的缓存时同一张卡片带清理方案
needs-accesslow这一轮有检查因为没有完全磁盘访问没查成;带 "permission": "fda" 和打开系统设置的按钮
auto-donenormal一轮的那一件,有自动清了的时候。上半部是清了的,[放回]、[以后别自动清];下半部是要用户决定的和风险高的提醒。过期时间取清理后 7 天和这一轮开始后 3 天里晚的
findingslow一轮的那一件,没有自动清了的、但有放心删、要确认或风险高的仅供参考的时候:摘要和清理方案,3 天后过期。用户关了自动清时附「以后自动清」;权限残留附打开系统设置的按钮

一轮一件:编号 round/<这一轮开始的时间>,下一轮的那件顶替它;只剩风险不高的仅供参考时不放。agent 还没说过(没回 outbox_ack shown)的自动清带进下一轮那件,最多一周,卡片画最近 2 次;什么都没清掉只是失败的只写一句。space.growth 不进 outbox,只在发现列表里。

卡片上列什么:标题数到的都在卡片上,在方案里、在要收回的授权里,或单独列出。单独列的是进不了方案的要确认项(例如 apps.unsigned、process.deleted-binary)和风险高的仅供参考(例如 posture.filevault、browser.forced-extensions,keyboard.unvouched、mcp.secrets 判为高的),每种至多 10 条,高的在前,每条写怎么办,有的话带打开系统设置和在访达里显示的按钮,这些按钮都不删东西。放不下的写"还有 N 条"。

命令行

子命令做什么
scan --this-mac [--json]查一轮;--autoclean 查完做不用点就清(空闲任务这样起,不和 --for-agent、--quick、--home、--data 一起用);--for-agent agent 叫的一轮;--quick 快速扫描;--if-none 已有报告就不查
update --this-mac [--force]更新病毒库,每天最多一次
report终端里看上一轮,按放心删、要确认、仅供参考分组
findings、status [--json]上一轮的全部发现 / 概况
cleaned --this-mac [--json]清理记录:移进废纸篓的、直接删的,哪些还能放回
restore --this-mac <编号>…放回一条(c-…)或一批(p-… / auto-…)
system --this-mac [--trash a0,a1]列出要密码才能删的;--trash 把这几项移到废纸篓,sudo 每次在终端里问密码
eval-cleanup [--cases <文件>]清理规则评测,挑错一个就退出码 1(只在开发版)
mcp答一个 MCP 请求(luoke-audit-serve 用)
fixture <目录>生成假用户目录(只在开发版);luoke-audit-serve --fixture <目录> 起只查它的服务,时钟从固定日期往前走,移到废纸篓只挪进 <目录>/Trash,不调 launchctl
version程序和规则包版本

开发版用 --home、--data、--tmp、--projects、--tool-dir 指向假目录和自己的数据目录。

生成任务

两个任务的提示词在 prompts/(照顶层 ADR 0019 的格式),评测程序读这一份;建议参数 temperature 0、top_p 1、不思考。体检的程序里还没有调用主控的代码,提示词也不打进包:现在报告、卡片和 outbox 的文字都由规则生成,不经模型,有没有模型产出一样。

任务提示词输入 → 输出
luoke-audit.explain第 4 版,至多 120 字、300 token一条发现(结论、标题、位置、大小、依据)→ 一两句大白话:是什么、为什么报、处理会怎样。不改结论,不编数字,不说已经处理,would_lose 每项都说,不写命令
luoke-audit.identify第 5 版,200 token认不出的目录的元信息(路径、大小、最后改动、文件数、常见扩展名、前几项的名字,不含内容)→ JSON {name, what, kind, sure},kind 为 cache / app-data / user-data / logs / tool / build / unknown;能不能删不由模型说

依赖

依赖用来做什么现状
主控 luoke-core空闲任务 scan(每天一次,memory_mb 1500,60 分钟)、update(每天一次,800,20 分钟),都不用电池;生成任务 luoke-audit.explain、luoke-audit.identify;常驻起 luoke-audit-serve;安装、升级、卸载;GET /tasks 给出写记忆用的 Python配件清单 packaging/luoke-accessory.json 写了这些;主控的配方里还没有本工具,也不认 resident_exec
agent luoke-agent唯一的提醒出口:调 MCP 工具、画卡片、转达 outbox体检这边已做好;agent 正式接入和螺壳窗口在同一个 v1.x
记忆 luoke-mem写"发生了什么",五种记录:audit-cleaned、audit-ignored、audit-malware、audit-auto-cleaned、audit-autoclean。不写路径,报毒的只写文件名。经 <螺壳的 python> -m luoke_mem record --tool luoke-audit,只认螺壳 Team 签名的 Python,让它自己负责,不沾完全磁盘访问代码已有;进配方之前主控答 403,写不进去。问不到主控就静默跳过。不读记忆
macOSFSEvents、getattrlistbulk、Security(验签名)、Spotlight(mdfind)、libproc、CGGetEventTapList、launchctl、tmutil、fdesetup、csrutil、spctl、sysadminctl、hdiutil、pkgutil、codesign、security、defaults、crontab、TCC 数据库、XProtect 规则系统自带;不用会弹管理员密码框的 sfltool,不用会卡住的 lsof
ClamAV、YARA特征扫描;病毒库由 freshclam 从 database.clamav.net 下载随包;往外只有这一项下载,不上传

数据

位置内容
~/.luoke-audit/数据目录(700),排除出 Time Machine
audit.dbSQLite(WAL):sizes(每天各文件夹的大小,留 90 天)、measured / temp_entries / project_state / leftover_state(增量用的事件号、inode、大小或结论)、malware_scanned、file_hashes(重复文件的 SHA-256,文件不在了就删)、seen(见过的常驻项)、meta(病毒库版本、更新时间、日志游标、签名缓存、常驻项基线、快速扫描的事件号)。都是关于这台 Mac 的事实,删了下一轮重建
last-report.json上一轮的报告(600,luoke-audit.report/v1):发现、没扫描的项、规则包和病毒库版本、各规则耗时;每轮覆盖
agent-state.json用户经 agent 说的(600,加锁读改写):以后别提的发现、outbox 的回执(60 天;报毒和权限请求的放下回执一直留,至多 500 条)、自动清的开关和放回过的类别(autoOff)、最近 20 次自动清、办完的方案。读不出时挪到一边,不覆盖
cleaned.jsonl清理记录,只追加,放回靠它
plans/出过的方案(每项的路径、设备号、inode)、执行时的锁和要密码那一步的待办标记,30 天后删
clamav/、clamav-quick/病毒库和精简库;更新时用 clamav.staging、clamav.previous;freshclam.conf 每次更新前重写
run/agent.sockMCP socket(目录 700、socket 600),旁边的锁文件在服务退出时删掉
round.lock、quick.lock、serve.lock见「运行形态」
  • 报告和卡片里不放文件内容和凭证的值;凭据类发现只有位置、种类和个数。
  • 标准错误只写退出码和出错的类型,不写发现的内容。

打包

  • scripts/package_app.py 拼「螺壳体检.app」:bundle id ai.luoke.audit,LSUIElement,只出 arm64,最低 26.2。Contents/MacOS 里有 luoke-audit、luoke-audit-serve(ai.luoke.audit.serve)、luoke-audit-admin(ai.luoke.audit.admin)和 clamscan、freshclam、sigtool、yara;它们要的库在 Frameworks,路径改成 @rpath;ClamAV 的 RAR 支持运行时由 LD_LIBRARY_PATH 指到 Frameworks。
  • 由内向外签名,hardened runtime,没有 entitlements(临时签名时扫描工具带"可以载入别人签的库",正式签名不许有);可选公证。Developer ID 证书和公证条目由出版机器给,脚本不带凭据。
  • Resources 里有去掉草案标记的配件清单、bundled-tools.json(每个随包程序和库的版本、来源、SHA-256)、许可证(ClamAV 为 GPLv2)和第三方说明。
  • 打完必核对:签名完整、都是 hardened runtime、没有 get-task-allow、没有包外的库路径、luoke-audit-serve 里没有 AuditCore / AuditService 的代码、第三方库只有允许清单里的、第三方说明覆盖每个文件、RAR 支持真能载入、Info.plist 和配件清单一致、清单没有草案标记、exec 和 resident_exec 是包里的程序、包里的 luoke-audit 不收开发参数,再用包里的 clamscan 和 YARA 真扫一个 EICAR 测试文件。Swift 包不加第三方依赖(测试里也查)。

评测

  • 生成任务:评测集合成(bench/evalsets/ 第 3 版:explain 32 条、硬性 13;identify 30 条、硬性 8),每条有确定的检查,三遍都过才算过。bench/run_eval.py 可经主控的任务层(--hub,给发布脚本用)或本机端点跑;退出码 0 硬性全过、1 没过、3 没评成。
底座(各 3 遍,温度 0)explain 第 4 版identify 第 5 版
qwen3.5-9b-4bit(32 GB 档)31 / 32,硬性 13 / 1329 / 30,硬性 8 / 8
gemma-4-26b-8bit(128 GB 档)29 / 32,硬性 13 / 1329 / 30,硬性 8 / 8
  • 清理规则:evals/cleanup/cases.json 72 例(安装包 27、缓存 18、重复文件 17、临时文件夹 10),luoke-audit eval-cleanup 跑,挑错算硬门槛。
  • 性能:bench/perf.py 在 12 万文件的假家目录上跑 release 版,CPU 时间或内存比基线差 25% 以上、且超过下限(CPU 1 秒、内存 20 MB)算不过;墙钟只报不比。一台真实机器上一整轮 348 秒降到 140 秒(空文件夹不打开以后)。基线量于 21 条规则时(64 GB 测试机):不带病毒库,第一轮 CPU 3.6 秒、没变的一轮 0.65 秒、改了 1% 的一轮 1.25 秒,进程树峰值约 30 MB;带病毒库第一轮峰值约 1 GB(clamscan 载入全库),没变的一轮约 37 MB。
  • 测试:AuditCoreTests 224 个 @Test、AuditServiceTests 173 个,全部用生成的假用户目录;scripts/agent_selftest.py 用 luoke-agent 的参考实现对 luoke-audit-serve --fixture 走一遍。

已知限制

  • 没接进主控:配方里没有本工具,主控不认 resident_exec。所以定时的一轮、不用点就清、写记忆现在都不会在装好的螺壳上发生。
  • 没有模型参与:explain、identify 只有提示词和评测,程序里还没调用;认不出的目录不会被起名。
  • 没有窗口:主程序是命令行,双击 .app 只报用法后退出。两页基础页要等螺壳的窗口来画,agent 现在不读;卡片在 agent 接入后显示在它的窗口里。现在能看的是终端的 report、findings、status、cleaned、system。
  • 规则写在代码里:规则、门槛、缓存清单都在 Swift 里,没有规则文件;规则包版本只是一个常量。加一条规则要改代码、发新版。
  • 要密码的删除还没在真机上弹过密码框。
  • 没有按类别手动关自动清的开关:只能全关,或放回一次让那一类停下。

主控任务接口

主控在 Unix socket 上提供的 HTTP/1.1 接口,供配件调用。每次请求一条连接。

连接

  • socket:~/Library/Application Support/luoke-core/run/hub.sock(目录 0700,文件 0600)。配件依次查找:显式路径 → 环境变量 LUOKE_HUB_SOCKET → 默认位置。
  • 身份:请求头 X-Luoke-Accessory: <配件>/<版本>。版本须在配方 verified 中,或本机放行,或是本机 verified-history.json 中有记录的已安装版本(换回的上一版,只能用记录中的任务);带任务名的请求还要求任务存在且属于该配件。
  • 响应头:X-Luoke-Contract: 1、X-Luoke-Tasks-Version(配方、档、模型分配、引擎版本、任务参数的摘要,变化即应重读 /tasks)。
  • 请求体上限 32 MB。

错误

错误体为 {"code", "message", "retry_after", …};有 retry_after 时同时给 Retry-After 头。

HTTPcode含义
400invalid_request请求格式或参数不合法
403unknown_accessory配件或版本未通过验证,或任务不属于该配件
403forbidden主控本体专用的路由,对端不是本体(或允许的命令行)
404task_unavailable、unsupported任务不存在;本档不支持该任务;换回的上一版调用其记录外的任务
413input_too_long超过任务的 max_chars
503busy暂时不能接纳,附 reason(见主控 · 后台接纳)
paused全机后台暂停或本档放不下
not_ready模型正在加载或工作进程按需重起(retry_after 2);内存不够或内存压力 critical 后暂不启动工作进程(retry_after 30)
needs_user用户在等但内存放不下,附 occupants(占用内存的进程)
engine_failed引擎或工作进程出错

流式生成过程中的错误以 event: luoke.error 事件给出,另有 interrupted(后台请求被用户请求打断)。

任务

GET /tasks

返回 {contract, hub_version, tier, tasks_version, background: {state, reasons}, tasks: {<名>: {kind, status, reason, progress, slow, combo, model_name, params, vector_space?}}, luoke_mem?: {path, version}}。只列出请求方自己的任务;model_name 是只用来显示的模型名(配方的 label,没有则取目录名,本档没有模型时为 null);luoke_mem 给出主控内置的记忆库路径。

生成 POST /tasks/<task>/v1/chat/completions

  • 任务 kind 为 generate。请求为 OpenAI 格式的 messages,必须 stream: true,带 luoke_items(本批条目数,≤ batch);response_format 可用 json_schema 或 json_object。
  • 请求头 X-Luoke-Priority 必填(user-waiting 或 background);X-Luoke-Allow-Battery: 1 可选。
  • 主控覆盖:max_tokens = item_tokens × luoke_items + fixed_tokens;采样参数取配方;中和消息中的模型特殊 token;答复中的 model 改写为组合名。
  • SSE 流中另有 event: luoke.progress(queued、loading、running)。

打分 POST /tasks/<task>/v1/score

kind score。请求 {context, candidates}(候选 ≤ batch,默认 16;前文 ≤ max_chars,默认 2000);答复 {logprobs, model, cache_hit, ms}。超时 10 秒。

续写 POST /tasks/<task>/v1/continue

kind predict。请求 {context, k?, max_tokens?, stop?};答复 {branches: [{tokens: [{text, logprob}], finish}], model, cache_hit, ms}。旧接口 /v1/predict 仍保留。

嵌入 POST /tasks/<task>/v1/embed

kind embed。请求 {texts: [...]}(任务参数允许时可用多模态 inputs);答复 {vectors, dims, model, vector_space, ms}。默认每批 32 条、单条 32000 字、合计 320000 字。后台请求走专用模型通道,被挡回时返回 lane_busy 并附 retry_after_ms。

预热 POST /warmup

请求 {task, lease_id?};答复 {accepted, lease_id, expires_in: 600},或 {accepted: false, code}(task_unavailable、unsupported、deferred)。新的预热算作交互,续期不算。打分、联想、嵌入任务的预热会启动工作进程;内存不够时照样答应,真正请求时答 not_ready。

配件管理

路由说明
GET /accessories已安装配件的公开字段:accessory、bundle_id、name、version、type、resident、memory、tasks、capabilities、jobs
PUT /accessory/resident{resident},声明是否常驻
POST /accessory/exiting配件主动退出前通知,主控停止只由它使用的工作进程

主控本体专用

以下路由要求身份为 luoke-core,并按内核报告的对端 PID 核对对端是主控进程的父进程(即本体)。/settings 和 /endpoint* 另外接受主控自带的命令行 luoke(与主控同一个 Python 可执行文件、参数为 -m luoke_core.cli)。不符时答 forbidden。

路由说明
GET /status运行状态(不要求身份):档、底座、引擎、交互、后台、冒烟、设置、下载、空闲任务、端点(不含密钥)等
GET/POST /downloads缺少的下载项和进度;POST 开始下载
GET/PUT /settingspower_mode、background_on_battery、tier、endpoint_enabled、endpoint_port、endpoint_key
GET /endpoint、POST /endpoint/key端点状态和密钥;生成新密钥
POST /endpoint/models、/endpoint/models/remove登记、撤销用户自己的模型
POST /smoke重新做冒烟检查
POST /uninstall{accessory, data?, forget?, dry_run?};也删除 previous.noindex 里该配件的旧版(答复 previous)
GET /accessories/previous留有旧版的配件:{accessories: [{accessory, name, type, bundle_id, current, previous, verified, release}]};verified 为假的不能换回
POST /accessories/rollback{accessory} → {accessory, from, to, type, bundle_id};无旧版或无验证记录 unsupported,签名不过 invalid_request,对调失败 engine_failed
POST /jobs/run让某个空闲任务在下一个安静时刻立即运行(只在内部版、本机版和实验室版可用)

高级用户端点

  • 默认关;开启后监听 127.0.0.1,默认端口 17860(被占则向后找)。
  • 检查:Host 必须是 127.0.0.1 或 localhost;带 Origin 头一律拒绝;密钥非空时校验 Authorization: Bearer(用户可自定义或留空)。
  • GET /v1/models:底座名和 user/<名字>(用户登记的模型)。POST /v1/chat/completions:按"用户在等"调度。
  • 错误:model_not_found(404)、too_large(507,超过独占上限:内存 − 8%(3–8 GB)− 常驻专用模型 − 1 GB)、not_ready、busy、engine_failed(503)。
  • 命令行 luoke launch pi [--model M] 自动读取端口和密钥,在 pi 的配置中写入 luoke provider 并启动 pi。

配件清单与配方

配件清单 luoke-accessory.json

位于配件 .app 的 Contents/Resources/。主控只加载 contract == 1 且有 accessory、bundle_id 的清单。

字段说明
contract、accessory、bundle_id必填
name、version显示名和版本;升级只换比清单旧的版本(按数字比较,不降级)
typeapp(装在 ~/Applications)、input-method(装在 ~/Library/Input Methods)、component;只有 app 可常驻
entry交给自带 Python 的入口参数
resident是否常驻(配件也可经 PUT /accessory/resident 声明)
min_macos可选;这台 Mac 的系统版本低于它时不能装、说明原因(发布清单照抄)
memory是否读写记忆;决定卸载时能否"一起忘掉"、首次运行是否需要记忆
tasks需要主控提供的任务(name、kind、main)
jobs空闲任务:name、command(可用占位符 {memory}、{tool})、min_interval_minutes(默认 120)、allow_battery(默认否)、timeout_minutes(默认 30)、exec(可选:不带 Python 的配件用 Contents/MacOS 下的可执行文件名,不能含路径)
capabilities给 agent 的能力声明,主控原样转交、不解释(规格见 agent 的工具协议),可含 service、permissions、listing;出版时把 listing 的 tagline、category 和各权限的说明(id、kind、title、facts、without)抄进发布清单的 listing,不抄 size_mb、min_tier、权限的默认值和动作名
data卸载时可删除的数据路径,只接受与配件自身标识绑定的形式
commandsuninstall(删除数据前运行)、forget-tool(从记忆中忘掉本工具写入的内容)
icon、ui_element打包用

打包布局

<名字>.app/Contents/MacOS/<配件标识>             启动器
<名字>.app/Contents/Resources/luoke-accessory.json
<名字>.app/Contents/Resources/python/bin/python3   钉住版本的 python-build-standalone
<名字>.app/Contents/Resources/python/lib/python3.X/site-packages/<包>

由 luoke-core/tools/package_python_app.py 生成。

校验

  • 调用时:版本在配方 verified 中,或在内部版、本机版主控上被本机放行(tools/local_allow.py,只放行配方中已有配件的新版本);或是本机 verified-history.json 中记录过的已安装版本(只限记录中的任务)。
  • 安装时:代码签名、Team ID NJQ5W3T9K5、bundle id 与清单一致、公证票据有效。
  • 回滚时:旧版同样核签名、Team ID、bundle id、公证票据,不运行它,版本从其清单读取。

配方 recipe.json

字段说明
tiers.<档>min_gb、max_gb、guard_gb(引擎内存上限)、base_resident(底座是否常驻)、background_input_chars、idle_unload_secs
models.<名>dir、kind(base / special)、wired_gb、size_gb、速度、vector_space(嵌入)等
assignment.<档>{base, special: {<任务>: <模型>}}
engine引擎版本和位置
tasks.<名>accessory、kind(generate / score / predict / embed)、params("*" 加按档覆盖)、base_params(采样)、latency_ms、tiers(开启的档)
verified.<配件>versions(允许接入的版本)和各任务的评测证据
evidence评测记录:配件、版本、任务、模型、档、结论、评测集和证据的 sha256
expectations首次运行预检页照实说明的体验:tiers 按档(如 16 GB 档:邮件后台整理平均每封约 50 秒,点「同步新邮件」每封约 22 秒),ime 按芯片类别(m1、base、pro),typing_unsupported 用在不支持打字归纳的档

任务参数

kind参数(默认)
generateitem_tokens(200)、fixed_tokens、batch、max_chars、think;配件自定义的参数原样转交(如 sender_verdict)
scorebatch(16)、max_chars(2000)、prefix_snapshots(16)、probe
predictk(8)、n(6)、max_tokens、max_chars、probe
embedbatch(32)、max_tokens(2048)、max_chars(32000)、max_total_chars(320000);多模态另有图片、音频的批量和大小限制

硬件档

档内存引擎上限底座底座常驻
16< 24 GB10 GBqwen3.5-9b-4bit否(用完即卸,打字时让路)
3224–64 GB20 GBqwen3.5-9b-4bit是
128≥ 64 GB100 GBgemma-4-26b-8bit(可选省空间用 9B)是

所有档的打分和联想模型为 qwen3.5-0.8b-base-4bit(独立进程,约 2.7 GB)。

发布清单 release.json

{contract, release, hub_version, built_at, local_only?, items: {<名>: {kind: model | engine | accessory | hub, version, dir, files: [{path, size, sha256}], urls, license?, accessory?, bundle_id?, type?, name?, memory?, installer?, min_macos?, listing?}}, installers?}(installers 只在另出应用安装包时有;发布页只放螺壳的 Luoke.dmg)。模型有 ModelScope 和 Hugging Face 两个镜像,安装包放在 GitHub 上:beta 和正式版在 luokelabs/luoke-releases,内部版在 luokelabs/luoke-internal-builds。主控的"检查更新"从 items.hub 取 DMG。

记忆接口与表结构

Python 包 luoke_mem(仅标准库,3.9+)。工具只需 __init__、memory、sensitive 三个模块;运行在主控里的配件应使用 GET /tasks 返回的 luoke_mem.path。

打开

  • Memory(path=None, tool="luoke"):工具使用。默认路径 $LUOKE_HOME/mem.db,未设置时为 ~/.luoke/mem.db。文件 0600,目录 0700;记忆文件或其 -wal、-shm、-journal 是符号链接时拒绝打开。文件不存在时创建(0600);只看不改的入口(命令行 sources、forget-tool、learn)不创建。
  • WholeMemory(path=None):整份记忆,供记忆自己的界面、命令行和归纳任务使用;打开时执行重新定档(只降不升)、以前的事、相近话题合并。第一次定档前把整份记忆复制为 mem.db.before-0.18,14 天后或有内容被忘掉时删除。
  • 文件 schema 比库新得多(千位不同)时抛 TooNew。

工具接口

记录

方法说明
record(speaker, kind, text, *, topic, data, ref, happened_at, sensitive)写一条记录;正文和 data 中的字符串先遮去凭证
redate(rid, happened_at)修正本工具一条记录的发生时间
records(*, tool="", topic, kind, speaker, ref, since, limit)tool="" 为本工具,传工具名读该工具,None 读全部
forget_records(*, topics, since, until, match, keep_cited)删除本工具的部分记录,只依赖这些记录的理解一并删除
forget_tool(tool=None, *, dry_run)删除本工具写入的全部内容;用户说过的话和表过态的理解转到记忆名下保留

理解

方法说明
state(kind, text, …)用户亲口说的,直接为"确定"
propose(kind, text, cites, …)推测;按定档规则可能直接生效
conclude(kind, text, cites, …)由用户自己的记录得出;依据须全部来自用户,按定档规则为比较确定或猜测
confirm(nid) / reject(nid) / was(nid) / later(nid) / revise(nid, text)用户的决定:确认、否定、以前是、以后再说、改写
reject_family(nid)「这一类都不要」:把这条所在一类里用户还没表态的(待确认和观察中)都标为否定,返回条数
notes(kinds, status, tool, confidence)默认只返回生效的(比较确定和确定);每条带 confidence、data、auto、fresh
note(nid)一条理解及其全部依据(含原文)
recent(since, limit)未经用户表态而直接生效的推断
context(kinds, limit=600)按类别拼成供模型使用的一段文字
forget(nid)删除理解、其历史版本及只被它引用的记录

想问的和建议

方法说明
asks(status=["open"], *, tool)未过期的问题和建议;tool 筛出与某工具相关的
reply(aid, choice, text=None)交回用户的回答:关于某条理解的问题 yes / no / past / revise / later(later 把问题记为 later,7 天后由反刍重新提出);自由问题 answer / later(7 天后再问);建议 useful(关于习惯的建议同时确认该习惯)/ useless / never;其他组合抛 ValueError

工具函数

mask_secrets(text)(遮去密码、验证码、密钥、通过 Luhn 校验的卡号、证件号)、find_secrets、sensitive_kinds(text)(健康、法律、宗教政治)、confidence(note)、default_path()。

表结构(schema 7)

表列
recordsid、tool、speaker、kind、topic、text、data_json、ref、happened_at、recorded_at、sensitive
notesid、tool、kind、text、origin(user / inferred)、status、superseded_by、valid_from、valid_to、formed_at、decided_at、why、topic、sensitive、data_json
cites(note_id, record_id)
asksid、tool、kind(question / suggestion)、text、why、about_note、about_kind、urgency、answer(JSON)、status、created_at、expires_at、decided_at、answer_record、sensitive、evidence、rank、action(JSON)、key
candidatesid、kind、text、record_ids、first_seen、last_seen(归纳中积累的候选)
learningname、value:fit.deleted(用户删掉的理解,按类只记条数);归纳进度(typing.last_id / last_at / last_run、topics.last_id / last_at / last_run / pending / version)、每次结果(typing.last_outcome、topics.last_outcome、rumination.last)、以前的事唤回次数(past.revived.<kind>)、一次性修复标记
grants遗留,不再使用

升级只加不改:新表、可空的新列、新索引。版本存于 PRAGMA user_version。

取值

字段取值
records.speakeruser、third_party、system
records.kindstatement、activity、receipt
notes.kindfact、interest、dislike、preference、term、taste
notes.statusproposed、active、rejected、superseded、observed(观察中:这一类门槛升档后,待确认的事实挪到这里)、past
asks.statusopen、answered、dismissed、withdrawn、later、never
preference 的 data{account, sender, action, name, category, label | filter};可自动执行的动作 archive、read、label、filter

记录话题一览

工具topic
邮件助手mail-read、mail-acted、mail-override、mail-auto、mail-undo-auto、mail-cleared、sent-mail、received-mail、own-label、own-site、digest-open、digest-feedback
输入法ime-commit、ime-correction、ime-forget
记忆note:<kind>(用户亲口说的)、note-decision、note-later、ask-answer、ask-dismissed

命令行

命令说明
python -m luoke_mem [--path P] sources各工具写入的条数
forget-tool <tool> [--dry-run]忘掉某工具写入的内容;luoke-mem 被拒绝(说明原因,退出码 1)
ui [--open] [--note N] [--view recent]打印界面地址
app [--note N] [--view recent]在浏览器中打开界面(luoke://memory 的入口)
learn [--force] [--again] [--hub SOCK]运行一次归纳;--force 有新记录就跑;--again 从头重新归纳(打字记下的不重复,话题线用户没表态的话题重来);已有一个在跑时直接返回

界面

独立进程,监听 127.0.0.1 随机端口;页面令牌在 ?t=,接口令牌在 X-Luoke-Token;校验 Host 和 Origin;600 秒无请求退出。路由:/api/ping、/api/summary、/api/overview、/api/records、/api/notes、/api/note、/api/recent;修改用 POST /api/change(confirm、reject、reject-family、forget、revise;修改文字 ≤ 200 字,拒绝凭证)、/api/forget-tool(拒绝 luoke-mem)、/api/forget-all(需输入「清空」)。/api/grants、/api/grant 为遗留接口,不再影响读取。界面状态(pid、端口、令牌)存于 mem.db-ui.json(0600)。

任务与提示词

所有经主控调用模型的任务:所属配件、种类、各硬件档是否开启、提示词位置。档位以发布时的支持台账为准,下表为最近一次内部版的台账。每个提示词的输入、输出、校验和评测见「提示词」页。

任务kind1632128提示词 / 实现
luoke-mail.triagegenerate开开开luoke_mail/triage.py
luoke-mail.draftgenerate开开开luoke_mail/triage.py;无评测,沿用
luoke-mail.cleanupgenerate开开开luoke_mail/triage.py
luoke-mail.sendersgenerate开开开luoke_mail/triage.py
luoke-mail.digest-readgenerate开开开luoke_mail/digest.py;无评测,沿用
luoke-mail.digest-pickgenerate开开开luoke_mail/editor.py;无评测,沿用
luoke-mail.learn-sentgenerate开开开luoke_mail/learn.py
luoke-mail.learn-orgsgenerate开开开luoke_mail/learn.py
luoke-mail.threadgenerate开开开luoke_mail/threads.py
luoke-mem.typinggenerate关开开luoke_mem/learn.py;16 档过 1 遍,连过 3 遍才开
luoke-mem.topicsgenerate开开开luoke_mem/topics.py
luoke-ime.rerankscore开开开无提示词(打分)
luoke-ime.predictpredict开开开无提示词(续写)
digup.embedembed关关关无提示词(嵌入);预留给深度搜索,归配件 digup。深度搜索请求的是 luoke-search.embed,配方尚未改名,verified 也没有收录

提示词约定

  • 提示词写在调用方配件里,随请求发给主控;主控不保存、不记录请求文字。
  • 输出一律用 JSON schema 约束;配件负责解析和校验,失败时有明确的退路(重问一次、判为保留、跳过)。
  • 输出上限由主控按 item_tokens × 条目数 + fixed_tokens 定,调用方给的 max_tokens 不用。
  • 邮件、网页、打的字等输入一律声明为数据,其中给 AI 的指令不执行。
  • 结果的数量、依据和把握程度由代码判定,模型只负责提名;例外是订阅阅读的分数由模型给,≥ 2 才进精选候选。

评测

  • 评测集和硬性条目写在主控的 tools/release_evals.json,评测结果写入配方的 evidence,并决定各档开关。起草回复、订阅阅读、精选挑选没有评测,各档一直沿用上一版的"开"。
  • 分拣的硬性条目:正常邮件零误报,可疑邮件最多漏 1 封。
  • 往来邮件:已了结的不能报为"等你","等你"的不能漏,都一次不能错。模型答"轮到谁"(user_turn / other_turn / done),由代码映射为等你 / 在等对方 / 已了结。
  • 话题归纳:只写品牌名或来源名即为失败;另记是否有具体对象。

提示词

每个生成任务的提示词要点:送进去什么、要求输出什么、代码怎么校验、失败时怎么退。只做摘要,原文在各配件仓库。任务名、种类和各档开关见「任务与提示词」。

共同做法

  • 提示词、输出 schema 和校验都在调用方配件里;主控只做通用的生成:不思考(enable_thinking 关),把 response_format 的 JSON schema 交给引擎约束,消息里的特殊 token 先中和。
  • 输出上限由主控定:item_tokens × 条目数 + fixed_tokens;调用方给的 max_tokens 不用。输入超过任务的 max_chars 答 input_too_long,条目数超过 batch 拒绝;调用方事先按上限二分截短或减少条目。
  • 输入里的邮件、订阅、打的字一律声明为数据,其中给 AI 的话不执行。
  • 结果多少、能不能生效、把握几档由代码和记忆的表决定;模型只负责提名和分类。
  • 采样参数在配方里按任务定:起草回复温度 0.3、top_p 0.9,其余生成任务温度 0、top_p 1;top_k 一律写 0。
  • 回答不能用时:邮件助手多数任务把原回答(截到放得下,最多 2000 字)和一句纠正接在消息后面再问一次;订阅体检和精选挑选不重问,往来邮件原样再问一次。记忆不在当次重问,见各任务。
  • 两个客户端都不看 finish_reason:输出被截断时,邮件助手按"无法解析"处理,记忆按"这批没读完"处理,下次减半重来。

邮件助手

luoke-mail.triage 分拣

项要点
位置luoke_mail/triage.py:SYSTEM_PROMPT、build_messages、parse_output
输入今天的日期和星期;邮件寄出日期早于今天时,再给寄出日期和星期,并要求没写年份的日期和"明天""下周三"这类说法按寄出日期换算;用户邮箱;用户只在抄送里、不在收件人里时加一句:除非邮件点名要用户做事或回答问题,一般 archive,priority 不超过 medium,回复要点不替用户答应派给别人的事;记忆的"关于用户 / 处理习惯"块(≤ 600 字);用户给这个发件人定的规则(有就给);是否精选来源;发件域名验证结论(配方参数 sender_verdict:16、32 档不给,128 档给);邮件头(发件人、收件人、抄送、时间、主题、附件名 ≤ 10 个)和正文。正文先去掉引用的往来、以 > 开头的行和不可见字符,超过 60 个字符的链接写成 https://域名/…,最多 6000 字;整条消息再按档的字符上限截短(16 / 32 / 128 GB:6000 / 12000 / 32000 字)
输出13 个字段都必填:category(8 选 1)、priority(high / medium / low)、action(reply / schedule / follow_up / archive / ignore)、summary(≤ 40 字)、reason、due_date(只填邮件明写的期限)、5 个 event_*(只在 schedule 时填)、reply_points(≤ 3 条)、suspicious;文字字段用设置的语言
判断规则reply:对方在等用户回信(回答问题、确认时间、发回要的资料都算);对方还在问能不能来、或请用户从几个时间里挑一个,也是 reply;schedule 只给时间已定的会议、预约和活动;follow_up:要用户做、但不用回信的事(付账单、填表、取件、续费);只被抄送、且邮件明确让别人处理的,通常 archive;due_date 只填邮件明写的期限,"下周三"这类按今天换算;回复要点不编邮件里没有的人名、名单和数字。用户的规则一般照办,只有明显要用户亲自处理或看起来可疑时才给不同的 action,并在 reason 里说明;"移到垃圾邮件"的规则让模型给 ignore;精选来源的邮件一般 archive
校验与退路解析不出 JSON 或 action 不在枚举:带一句纠正重问一次,再不行报错;category / priority 不在枚举改成 other / medium;日期须 YYYY-MM-DD、时间须 HH:MM,否则置空;只有 schedule 且日期有效才生成日程;suspicious 为真时强制 ignore + phishing;没写年份、又比寄出日期晚 300 天以上的期限和日程日期,按整年往回拉(邮件里写了那一年或"明年"的不拉);用户只在抄送里的邮件,模型给了 high 也降成 medium
预算输出 320 + 80 token;温度 0
评测48 封(20 封难题,含钓鱼、注入、发件域名验证、长跟踪链接后的期限);硬性:正常邮件 0 误报,可疑邮件最多漏 1 封。不覆盖规则、记忆块、精选来源、非中文输出,也不评摘要和回复要点

luoke-mail.draft 起草回复

项要点
位置luoke_mail/triage.py:DRAFT_PROMPT、draft_reply
输入记忆块(≤ 600 字);分拣给的回复要点(没有时让模型自己写得体的回复);原邮件(邮件头和正文同分拣,截短;有发件域名验证结论时各档都带);提示词里有今天的日期。不含往来历史
输出{"body"}:用原邮件的语言,不编造事实、日期、数字和承诺,要用户补的地方用【】标出;设了签名就用它署名,没设就不署名、也不编名字;不写用户没给出的电话、住址、账号、证件号;不含主题行
校验与退路只查正文非空;不行带一句纠正再问一次,再不行报错。草稿只存进草稿箱,由用户自己发
预算输出 800 + 100 token;温度 0.3、top_p 0.9
评测无

luoke-mail.cleanup 清理判断

项要点
位置luoke_mail/triage.py:old_prompt、build_old_messages、parse_old_output
输入今天的日期;每封旧邮件一行:发件人、日期、主题(≤ 100 字)、Gmail 分类、用户贴的标签(≤ 4 个);一批 ≤ 25 封,按字符上限再减。星标和"保留"类标签的邮件不送
输出items[{n, kind}]:kind 11 类(归档范围没有 todo);拿不准选保留类,不选 promotion / newsletter / notification
校验与退路编号越界或 kind 不在枚举的丢掉;一条可用的都没有就重问一次;两次都不行,整批记为 other(不建议删)
预算每封 30 + 60 token
评测40 封(归档 30、收件箱 10);硬性:该留的不能判成会建议删的三类

luoke-mail.senders 订阅体检

项要点
位置luoke_mail/triage.py:SENDER_PROMPT、classify_senders
输入很少打开的发件人:名称、地址、最近 ≤ 8 个主题(各 ≤ 60 字);一批 ≤ 10 个
输出items[{n, kind}]:kind 7 类;拿不准选 transactional / security / other
校验与退路越界、不在枚举的丢掉;解析失败返回空(什么都不建议),不重问
预算每个 25 + 60 token
评测25 个发件人;硬性:账单、登录提醒、个人往来不能判成会建议退订的类别

luoke-mail.digest-read 订阅阅读

项要点
位置luoke_mail/digest.py:PROMPT、build_messages、parse
输入今天的日期;记忆的"不感兴趣 + 精选口味"块(还没有精选口味时用"关注的话题";不感兴趣排在前面;≤ 500 字);收藏和点过"没兴趣"的标题各 ≤ 8 条;订阅正文(HTML 转文字,≤ 12000 字,链接编成〔n〕,≤ 40 个,去掉退订、偏好设置、隐私条款、在浏览器查看、分享和 mailto 链接;正文后附链接表:编号、链接文字(≤ 60 字)、域名),再按档的字符上限截短
输出items ≤ 8 条:title(≤ 30 字)、score(0–3)、lasting、link(编号或 null)、topics(1–3 个)、summary(≤ 80 字,只依据原文)和 why(score ≥ 2 时填);提示词要求 2 分以上要克制,和不感兴趣相关的不超过 1 分
校验与退路分数夹到 0–3;链接编号不在列表里就不带链接;文字截长;解析失败重问一次,再失败跳过这封。score ≥ 2 才进精选候选
预算输出 1300 + 100 token
评测无

luoke-mail.digest-pick 精选挑选

项要点
位置luoke_mail/editor.py:PROMPT、choose、edit
输入同阅读的口味块和标题例子;候选每批 ≤ 30 条,最后一轮最多两倍配额(近 7 天 40 条),再按字符上限减:标题、摘要、来源、日期、另有几家报道
输出picks[{n, why}],最多 k 条(不感兴趣的宁可少选,不凑数),why ≤ 30 字
校验与退路候选不多于 k 条时不问模型,全部入选;编号去重、去越界、why 截到 60 字;一条可用的都没有时,改按用户点过有用、阅读分数、报道家数、日期排序。选几条由代码定(近 7 天 20 条,更早每月 10 条),多轮淘汰
预算每条 60 + 60 token
评测无

luoke-mail.learn-sent 了解你:发出的邮件

项要点
位置luoke_mail/learn.py:SENT_PROMPT、parse_facts;生效规则在 learner.py
输入用户邮箱;每封:日期、收件人和抄送(各 ≤ 4 个)、主题、用户自己写的部分(去掉引用和转发,≤ 800 字,取头尾);一批 ≤ 8 封
输出facts[{topic, who, text, n}]:topic 15 类之一,关系类填 who,text ≤ 30 字,n 为依据的邮件编号
校验与退路topic 不在列表、没有有效编号的丢掉;文字遮凭证;两封邮件一致才交给记忆,单值类(职位、单位等)以最新依据为准;解析失败重问一次,再失败这批没有事实
预算每封 100 + 100 token
评测4 组;硬性:第三方的说法、秘密、一次性安排的词不能出现

luoke-mail.learn-orgs 了解你:机构来信

项要点
位置luoke_mail/learn.py:RECEIVED_PROMPT、parse_facts
输入三封以上、跨两个月以上、至少打开过一封的机构(问过的不再送):名称、地址、封数、首末月份、最近 ≤ 6 个主题;一批 ≤ 10 个
输出同上,topic 8 类之一,who 为机构名,text 写明机构和用户的关系(≤ 30 字,不用人称)。提示词里的例子用虚构的机构名(青禾银行、远帆证券、集贝网),不用「某某…」这种句式:模型会照着例子的句式写,曾把评测题里叫「某某商城」的广告商写成"在某某商城进行购物"
校验与退路同上;只写机构名的丢掉,缺机构名的补上;结果只作猜测,等用户确认
预算每个 60 + 100 token
评测2 组;硬性:只发广告的不能当客户,不能记某一笔金额

luoke-mail.thread 往来邮件

项要点
位置luoke_mail/threads.py:PROMPT、parse
输入一串往来的最后 ≤ 6 封(按字符上限再减):日期、用户或对方(名字)、主题、本人写的正文(≤ 600 字);末尾注明最后一封是谁写的
输出state(user_turn / other_turn / done)、since(YYYY-MM-DD)、what(≤ 25 字);拿不准选 done
校验与退路state 映射为等你 / 在等对方 / 已了结,解析不了或不在枚举就原样再问一次(不带纠正),再失败按已了结;since 格式不对置空;done 时清空另两项
预算60 + 100 token
评测16 串(10 串硬性);硬性:已了结的不能报成等你,等你的不能漏,都一次不能错

记忆

luoke-mem.typing 常用说法和话题

项要点
位置luoke_mem/learn.py:PROMPT、messages、parse、_term、_interest
输入已记下的话题、还在观察的话题、用户否定过的、用户在输入法里删掉的候选;打的字每段 ≤ 300 字,一批 ≤ 40 段、≤ 6000 字
输出terms[{text, evidence}](2–12 字,照用户的写法);interests[{text, evidence}](≤ 20 字,至少两段依据,最多 3 个)
校验与退路编号越界丢掉,兴趣不足两段丢掉;常用说法由代码在近 90 天的打字记录里数:见到 2 次起作猜测问用户,满 3 天、5 次才生效,词长、常用词表、凭证、否定过的拦掉;兴趣依据跨 3 天才生效,之前是猜测。回答不完整(被截断,或不是含两个列表的 JSON)时这批不算读过:下次每批减半重来,同一批两次不行才跳过
预算每段 8 + 300 token
评测24 例;硬性:一次性安排、别人的事、删掉的候选、秘密不能出现;底线:说法召回 70、话题召回 60、每例普通词误收 ≤ 0.5;32 GB 档有一条已知问题:一例的话题名里带上一次性安排的"答辩"

luoke-mem.topics 邮件里的话题

项要点
位置luoke_mem/topics.py:PROMPT、sources、parse、take
输入已记下的话题(≤ 30)和否定过的(≤ 20);按来源汇总的邮件助手记录(一年内、用户自己的、非事务邮件),每个来源 ≤ 1200 字:名称、往来次数(来信、打开、精选里读过 / 有用 / 没兴趣)、邮件助手说过的"讲的是"、最近 ≤ 3 封原文开头(各 ≤ 280 字);一批 ≤ 40 个来源,16 档 ≤ 20 个、≤ 3000 字
输出interests ≤ 5 个,写成"领域:具体对象"(≤ 30 字);dislikes ≤ 3 个,只认精选里说的"没兴趣"
校验与退路超过 60 字、没有有效来源编号、含凭证的丢掉;只是品牌或来源名的丢掉:整条只有拉丁字母;没有"领域:对象"、又以"动态 / 通知 / 资讯 / 更新 / 消息"结尾;或冒号后只剩来源名、只剩拉丁字母加"动态"之类。写出领域和具体对象的(哪怕以"动态"结尾)保留;跨轮累计 ≥ 2 条记录才算;每个话题立即作为精选口味,作为"关注的话题"要过记忆的表且最多 8 个;不感兴趣要有两条以上精选里点"没兴趣"的记录(收藏、标签、清理、没打开都不算)。回答不完整时这批来源留到下次(每批减半),同一批两次不行才跳过;提示词换版本时全部重读
预算每个来源 8 + 300 token
评测3 个虚构的人共 12 例;硬性:话题名和不感兴趣里不能有一次性的事、别人的事、健康、凭证,话题只是来源名或纯拉丁字母算失败;另用 5 个该留、6 个该丢的固定话题名检查品牌筛选,错一个也算硬性失败;另记话题召回、是否具体、不感兴趣的召回,和没有"没兴趣"依据的不感兴趣

没有提示词的任务

  • luoke-ime.rerank:只送前文和候选,主控返回每个候选的对数概率。
  • luoke-ime.predict:只送前文,主控续写若干分支,停在标点。
  • luoke-search.embed(配方里仍叫 digup.embed,还没分到任何档):只送加好检索前缀的文字,返回向量。
  • agent 和系统检查目前不调用生成任务。

文件位置

应用

位置内容
/Applications/螺壳.app主控(也可能在 ~/Applications,以实际位置为准;改名前为 螺壳主控.app,升级时换成新名字)(内含自带 Python、记忆代码、luoke 命令行)
~/Applications/螺壳邮件助手.app邮件助手
~/Applications/螺壳 agent(开发).appagent 的试用版(app/install_trial.sh 装,主控不管它);以后并进螺壳,见决策 0014 的 10-24 修订
~/Library/Input Methods/LuokeIME.app输入法
$TMPDIR/ai.luoke.inputmethod.Luoke.lock输入法单实例锁
~/.local/bin/luoke命令行链接(可选安装)

主控 ~/Library/Application Support/luoke-core/

开发版(bundle id ai.luoke.core.dev)用 luoke-core-dev/,结构相同。

路径内容
run/hub.sock任务接口 socket
logs/hub.log、logs/app.log、logs/jobs/日志(只记元数据)
engines/<版本>/、engine/<版本>/引擎包(发布版只留当前版本);引擎运行目录(含引擎密钥 settings.json(0600)、engine.log、engine.pid.json)
models/模型;用户登记的模型在 user-<名字>
downloads/下载暂存(应用的包在 downloads/apps/)
state.json运行状态:常驻、冒烟、探针、空闲任务、端点用量
settings.json能耗模式、电池、档、端点
endpoint-key、user-models.json端点密钥(0600);登记的模型
local-verified.json本机放行
verified-history.json各配件版本由哪一版发布认可、当时放行哪些任务(每配件 3 个版本),供回到上一版本和主控升级后还没升级的应用使用
downloads/update/、run/update-mount.json检查更新下载的主控安装包,拷到暂存处后立即删除;run/update-mount.json 只为清理旧版留下的挂载而读
previous.noindex/换装前的旧版主控(由换包脚本移入,不论原来在 /Applications 还是 ~/Applications)和配件;应用可在「应用」窗口里回到上一版本,卸载配件时一并删除
staging.noindex/升级主控时先放新版、核对签名,再由换包脚本移到装好的位置
run/upgrade.json、run/preselect.json升级标记(新主控读后删除);安装器交来的预选应用
run/swap.sh、run/swap-result.json、logs/swap.log换包脚本、上次换包的结果(下次启动取走)、换包日志

记忆 ~/.luoke/

路径内容
mem.db(及 -wal、-shm)记忆,0600;可用 LUOKE_HOME 换位置
mem.db-ui.json、mem.db-learn.lock界面进程状态(0600);归纳任务锁
mem.db.before-0.18第一次重新定档前的整份副本,14 天后或有内容被忘掉时删除
agent/agent 的 state.db、socket、日志
ime/输入法的设置、用户引擎、引擎数据、开发记录

邮件助手 ~/.luoke-mail/

路径内容
config.json设置
luoke.db邮件、建议、规则、清理、操作记录、精选
token、server.json、server.lock页面令牌;运行中实例的端口;单实例锁
idle.lock、resident、account-salt空闲工作锁;后台整理已开启的标记;邮箱不透明键的随机值
run/agent.sockagent 接口(默认关闭,LUOKE_MAIL_AGENT=1 时才有)
service.log、app.log日志

深度搜索 ~/.luoke-search/(未发布)

路径内容
目录本身700,整个排除出 Time Machine
.lock设置和状态文件的跨进程锁,0600
settings.json、state.json设置;未执行的方案和 outbox 的状态,都是 0600
index.noindex/index.sqlite(及 -wal、-shm)索引,0600
run/agent.sockMCP socket,0600,目录 700
run/stopped-file.json看门狗停下时正在读的文件,下次记入索引后删除

字段见深度搜索页。

钥匙串

luoke-mail:imap(邮箱授权码)、luoke-mail:engine(直连模型服务的密钥)。

环境变量

变量作用
LUOKE_HUB_SOCKET指定主控 socket(指定后不回退默认位置)
LUOKE_HOME记忆所在目录
LUOKE_MAIL_HOME邮件助手数据目录
LUOKE_MAIL_PORT邮件助手网页端口(未用 --port 时)
LUOKE_MAIL_AGENT设为 1 时开启邮件助手的 agent 接口
LUOKE_SEARCH_HOME深度搜索数据目录(发布版拒绝启动)
LUOKE_SEARCH_EMBED_TASK、LUOKE_SEARCH_OPENER、LUOKE_SEARCH_PHOTOS深度搜索开发用的开关(发布版删掉,LUOKE_SEARCH_DEV_APP 只在构建时用),见深度搜索页
PI_CODING_AGENT_DIRluoke launch pi 写入配置的位置

运行与发布

版本

类型标签说明
内部版internal-MMDD每天固定时间把各组件主线的提交钉在顶层仓库并打标签;出版流程在三个硬件档上评测,签名公证后发布为预发布版
本机版local-MMDD-HHMM只在一台机器的档上评测,签名公证、不上传,用于开发机上的快速验证
beta / 正式beta*封闭测试和公开发布

beta 和正式版的安装包发布在 luokelabs/luoke-releases,团队用的内部版发布在 luokelabs/luoke-internal-builds(10-28 起分开;过渡的那一版两边都有);模型在 ModelScope 和 Hugging Face 各有一份。

出版流程

  1. 读取钉住的各组件提交,打包配件 .app。
  2. 按配方在各档评测每个任务,每一格(任务 × 档)当天只评一遍;已有且输入未变的证据复用。
  3. 评测看门狗:某档某任务 10 分钟没有进展(生成任务看实验室主控日志里有没有成功的请求,其他任务看评测程序的输出是否增长),即停掉评测程序,这一格记为没过(engine_refused_memory 或 stalled),接着评其余各格。
  4. 按支持台账决定各档开关:上一版开着的,失败一遍即关;关着的(上一版关、新任务、评测表 hold 的)当天过了也照"关"发布;这一版没评的档沿用上一版。生成配方和差异报告;仍开着的组合总分比上次低出容差,或主要任务一档都没开时停止。
  5. 签名、公证、生成 DMG 和发布清单,上传。目标是当天 24:00(JST)前发出,不等复测。
  6. 发布后复测:关着、当天过了的格子再补跑到 3 遍(hold 的不复测,失败一遍即停),结果只写回这一版的证据和台账,不改已发布版本的开关。下一版中,上一版补满 3 遍都过、这一版也过(或没评)的格子开启。

每一步记录在 steps.json 中,失败后可以从中断处继续;复测中途停下,下次接着补。

换装与回滚

  • 用户:主控菜单「检查更新」下载并核对新版,由换包脚本换上、新版主控接着补齐引擎和模型(同一张卡片;标准账户把螺壳装在 /Applications 时要一次管理员授权;应用在「应用」窗口里逐个升级),或打开新版的安装器(应用的安装器,或在 DMG 里直接双击「螺壳」);数据和模型保留;旧版主控保存在 previous.noindex。单个应用可在「应用」窗口那一行「…」里回到上一版本,主控按那一版当时的验证接受它。
  • 开发:本机版用同样的安装器升级;配件的新版本可经"本机放行"在内部版或本机版主控上先行使用。

测试机器

机器配置用途
开发机Apple M5 Max · 128 GB128 档评测、日常试用
16 GB 测试机Mac mini M1 · 16 GB16 档评测、干净机首次运行
32 GB 测试机32 GB32 档评测、实验

诊断

  • 主控设置中可导出诊断:机型、芯片、内存、首次运行各步时刻、运行状态和日志尾部,只含元数据。
  • 主控日志 logs/hub.log;空闲任务的标准错误在 logs/jobs/,最后 4 KB 也记在 state.json。
  • 输入法的沙盒拦截在输入法菜单中列出。

决策

影响多个组件的设计决策以 ADR(Architecture Decision Record)记录,原文保存在顶层仓库 docs/adr/。0001–0012 已归档,下列为当前有效的决策。

编号决策要点
0013本机就是边界数据不离开用户自己的设备(设备之间端到端加密);本机组件之间不为隐私设限;凭证不进记忆,走钥匙串;高级用户端点的密钥可自定义或留空。修订(2026-10-21):写进用户自己选定并在用的账户(他的邮箱、日历等)不算外传,内容仍不能发到螺壳自己的服务器和用户没选过的第三方;深度搜索的本机索引可以原样存文件里认出的完整证件号、卡号,也原样送主控的嵌入模型,交给 agent 或画成卡片时再遮(只遮那一串、保留后四位),名字像密码、密钥的文件照旧不读,记忆仍不存凭证的值
0014分工模型在主控,理解在记忆,事情在工具,沟通在 agent;工具之间不互相调用,只有 agent 调用工具的能力;高级用户端点经主控调度。修订(2026-10-21):日历由主控提供(EventKit 薄接口:按时间段读、写一个事件并返回编号,日历权限挂在主控名下,配方登记哪些配件能用,第三方配件要先得到用户同意,beta1 之后做);浏览器桥(native messaging host)也放在主控里,插件经它把页面信息交给主控的任务、调用日历接口、写记忆记录,网页内容只接受固定格式。修订(2026-10-24):用户看到的应用「螺壳」就是主控(螺壳.app,bundle id 仍是 ai.luoke.core);agent 并进它,对话、卡片、提示从螺壳的窗口和通知出去,agent 作为后台组件保留,分工不变
0015记忆如何形成理解工具只记录、记忆读原文下结论;三档把握程度;合理默认、按反馈调整;看依据的时间;空闲时反刍产出提问和建议;不按上下文隔离
0016替用户做事能撤回的在确认习惯后自动执行并提示,事后每日复查;不可逆或对外的始终由用户点按;agent 自己的邮箱可按类型放开外发。修订(2026-10-20):系统检查查出的"会重建的缓存"(npm、uv、pip、Xcode DerivedData、Homebrew 旧版本等)用户确认一次后可对该类自动清理,每次提示,对应应用在用时跳过;其余类别每次由用户点按,删除优先移到废纸篓;只往本机下载的签名规则包、特征库不算数据外传
0017v1 首发首发邮件助手和输入法,主控和记忆随装;Apple 芯片、16 GB 起、macOS 26.2+;先封闭测试再公开。修订(2026-10-20):所有面向用户的界面按同一套设计规范,由品牌负责人在 luoke-brand 牵头制定,各项目照着改;beta1 只修明显的不一致,整体改版在 beta1 之后;顶层仓库公开,各组件仓库仍私有。修订(2026-10-22):主控的所有窗口和邮件助手的网页在 beta1 前按设计规范整体改,记忆界面、输入法设置、agent 卡片仍在 beta1 之后;封闭测试顺延到 11-02,只做一批,招满 30 位为止,开测时对应的项目在 GitHub 上公开。修订(2026-10-23):记忆页和输入法设置也在 beta1 前改(agent 卡片仍在之后);主控里加「应用」窗口,各应用单独安装、升级、换回、卸载,升级主控不再顺带替换应用;「螺壳主控」改名「螺壳」,bundle id、数据目录和命令名不变;版本截止 10-28 20:00,beta1 10-29。修订(2026-10-28):内部版换到单独的公开仓库 luokelabs/luoke-internal-builds,luoke-releases 只放 beta 和正式版,螺壳按渠道查对应仓库,过渡的那一版两边都发;检查更新往后翻页,随 beta1.1。螺壳、邮件助手、记忆、输入法四个组件公开:原名是公开仓库,从新的初始提交开始,团队开发和 issue 在 <名>-private,往公开仓库同步每次先问主人。修订(2026-10-29):beta 和正式版的发布包在 GitLab 上放一份镜像,GitHub 仍是主渠道;私有仓库留一个和公开仓库 main 完全一样的 public 分支,同步时把导出的树作为一个新提交加在上面,扫过泄漏再推成公开 main,不改写历史。修订(2026-10-31):公开仓库只在发 beta 或正式版时同步,四个组件一起,是发版流程里的一步;从下一个公开版起,beta 和正式版只钉 public 上的提交,内部版照旧从私有 main 出。公开仓库发现泄漏时立刻修。修订(2026-11-01):要发给大家用的组件默认开源,体检、agent、深度搜索第一次随 beta 或正式版发布时建公开仓库;agent 和体检一起上,和螺壳窗口进同一个 v1.x,用户看得到清了什么、点得到「放回」之前,体检不开放
0018推理和权重归主控体系内没有脱离主控的模型运行实例;垂类模型也做成主控的任务,对延迟敏感的走优先通道。修订(2026-10-20):嵌入任务由深度搜索(luoke-search)接手,任务名由 digup.embed 改为 luoke-search.embed,接口、评测和多模态安排不变
0019提示词放成文件每个用到模型的配件在仓库根目录 prompts/ 里按任务放 system、user、retry 的 .txt 和 schema.json,代码只读文件、填占位符;枚举只写在 schema 里;本站「任务与提示词」页改为从各仓库生成;出版证据记 prompt_sha256;没有评测集的任务补齐。beta1 之后推进
0020引擎换成自己做的薄引擎只服务配方模型,底层用官方 mlx-lm,不换 mlx-serve。先进内部版,beta 线和 11-02 的测试不换;推理和权重仍归主控(ADR 0018)。现在的引擎还是 oMLX。

设计原则

  • 合理默认,按反馈逼近:不要求用户事先设定规则或逐条审阅;用户的确认、否定和撤销就是调整的依据。
  • 测试版不过度保守:能撤回的能力默认开启,配上提示和撤销,通过真实使用检验。
  • 本地优势不打折:在本机内部充分使用数据,体验才能弥补本地模型和算力的差距。
  • 产出要像用心的助理:理解要说清领域和具体对象,不照搬标题或品牌名。

已知问题

跨组件的未决事项和已发现但未修复的问题。各组件自身的限制见对应章节的"已知限制"。

功能缺口

  • 记忆没有向工具索取更多原文的通道;话题归纳只读记录中已有的原文。
  • 输入法的个人化组件不读记忆中的理解,打分没有个人化。
  • agent 没有对话能力,不使用模型;不在 v1 首发中。
  • 配方不支持撤回已发布版本。
  • Gmail 转发过滤器的一键生成未完成;规则导出的过滤器未在真实 Gmail 上验证导入。

质量

  • 16 GB 档(9B)的邮件分拣会把正常邮件里的催促语(例如正规商城写"请在某日前上传凭证,逾期退款申请将自动关闭")判成可疑;32 GB 档判对。还没有专门盯这种情况的评测题。
  • 16 GB 档的打字归纳:beta1.1 没开(评测时过时不过,候选版 beta1.1-rc2 开过);内部版 internal-1031-2 起开了。
  • 32 GB 档的打字归纳偶尔把一次性安排(例如"答辩")放进话题名,两个候选分数打平;配方里记为已知问题。

缺陷

  • 螺壳:先升螺壳、后升应用时,旧版邮件助手写不了草稿和每日摘要:这三个任务没有评测记录,进不了本机的验证历史。beta1 有这个问题;internal-1026-5 起已修,已经写下的旧记录不重写,升一下邮件助手就好。
  • 螺壳:换引擎后旧引擎留在磁盘上(每份约 1.6 GB)。beta1 有这个问题;beta1.1 起新引擎检查通过后删掉别的版本。
  • 邮件助手:往返和多段行程只加第一段:分拣只输出一个日程,往返机票、多段行程、系列活动只能挑一个或揉成一个,结束时间可能取成最后一段的(luoke-mail#34)。
  • 记忆:同一机构换了名字仍各成一条:按「机构:」后的名字原样合并,模型给的名称不同(如英文名、带域名、中文名)就不合并;别人来信推出的事实仍是一机构一句。
  • 记忆:「其他:」话题下的人和机构可能并成一条:旧版邮件助手把机构来信放在「其他:机构名」下,所以「其他:」仍按机构合并;发出邮件里写的「其他:某人」若和机构同名会被并进去。
  • 记忆:改建议并当场处理被算成两次时形成的处理习惯不重算,仍按原依据保留。

待讨论

  • 社区和第三方配件的接入流程、审核与安全边界。
  • agent 何时使用模型、第三方工具规则、手机端。

术语表

术语含义
主控(luoke-core)本机推理入口和安装器
配件由主控安装和管理的应用:邮件助手、输入法,以及未发布的深度搜索、体检
深度搜索(luoke-search)本机文件和照片图库的索引与检索工具,没有自己的窗口,经 agent 的 MCP 工具和命令行使用
任务配件向主控请求推理的单位,形如 luoke-mail.triage
底座每档用于生成任务的主模型
专用模型只服务某类任务的小模型,如打分、嵌入
硬件档按内存划分的 16 / 32 / 128 三档
配方描述各档模型、任务参数和允许接入版本的 recipe.json
支持台账出版时记录每个任务在每档开或关的依据,发布后的复测结果也写在这里
冒烟检查引擎启动后的实测,得出本机速度
空闲任务配件登记、由主控在安静时刻启动的命令
安静时刻距最近一次用户在等的请求满 5 分钟
用户在等用户正在等待结果的请求等级
记录写入记忆的"发生了什么"
理解记忆从记录形成的关于用户的认识
把握程度理解的三档:猜测、比较确定、确定
以前的事会变、且长期没有新依据的理解
反刍记忆在空闲时整理理解、产出提问和建议的过程
处理习惯"某发件人的某类邮件通常这样处理"的理解
今日处理邮件助手中复查当天全部处理结果的页面
螺壳自己的邮箱用户为 agent 开设、由邮件助手全自动处理的邮箱
能力工具声明给 agent 的可报事项和可执行动作
个人化组件输入法中负责写记忆、转发打分和联想的组件
引擎(输入法)沙盒中给出候选的子进程,如小鹤双拼
本机放行在内部版或本机版主控上允许尚未进入配方的配件新版本接入
高级用户端点主控提供给外部工具的 OpenAI 兼容接口

文档修订

2026-11-02

  • 体检页按代码 3a6c9cb 复核:第一轮先下病毒库;快速扫描不再漏刚写完的文件;风险高的仅供参考进一轮那一件和 summary;不用点就清在两项之间停、边清边记;缓存没写谁在用的不自动清,受保护文件夹里的缓存没访问时不看;模型和设备支持文件执行前也查改动;needs-access 改 low;一轮卡片列出标题数到的每一条。已知限制去掉五条;页头写明和 agent、螺壳窗口进同一个 v1.x。

2026-11-01

  • 决策页记 ADR 0017 的 11-01 修订:组件默认开源,agent 和体检一起上。agent 页头写明发布安排。
  • agent 页跟到 536f134(最初按 49f1083):/status 加 window_front,通知授权未答时窗口里说明;窗口在最前面且用户在场才算看过;系统通知到不了时不算说过;少说按次计数、卡片重启后画回来;撤回的编号再列出来就恢复;/window 加 front;打开东西的动作(§5)。已知限制里这些修复标为下一版修。
  • agent 页按代码 f04a2e0 复核:自带 luoke_mem 0.23.4;主控换 Python 时核心以 75 退出、壳立刻重起;启动命令和打包形态(现在主控不装它,试用版不连主控);界面协议补 POST /notifications、/status 字段、事件内容、壳的 --notify;outbox_ack 已发送,去掉过时的已知限制;drop 不是工具字段;A2UI v0.9.1;"以后再说"的准确含义和自由问题的按钮;急事合并、少说按件计、清理只在窗口关着时;新增一节写协议里有、agent 还没做的(§1a、§1b、§5、§8a);已知限制加读代码核到的缺陷。
  • 跨页:总览加体检并把 agent 调邮件助手画成虚线(接口默认关闭);概念、词汇表不再把 agent 算作主控管的配件;文件页改成试用版的实际路径;体检页、深度搜索页写明基础页、对话、螺壳窗口都还没做。
  • 深度搜索页按代码 2f06213 复核:outbox 三类(加 needs-setup,needs-access 改 low);工具 8 个(加 search_request_access,search_open 要点按);资源、照片权限点「继续」才问、发布版判定和搜索范围、遮凭证七类、schema 5、上限表、同主题和规模成绩;主控不认识深度搜索的原因改成配方 verified 没收录(403);补向量重做媒体在看门狗下。
  • 文件页加深度搜索的数据目录和环境变量;任务页、总览、概念、词汇表把深度搜索的状态改成未发布。已知问题:16 GB 档打字归纳按 internal-1031-2 的配方更新。

2026-10-31

  • 体检页按代码 b17d7ed 复核:三个程序(加 luoke-audit-admin);规则 23 条,加启动钩子、键盘监听、浏览器、MCP 配置、SSH 和共享等检查,缓存 20 项;四类不用点就清(默认开)的确切判定;要密码的删除、放回、没有完全磁盘访问时的行为;11 个 MCP 工具和新增的资源;一轮一件的 outbox;写记忆的五种记录;清理规则评测;已知限制按现状重写。
  • 决策页补 ADR 0017 的 10-29、10-31 修订。已知问题:32 GB 档银行通知一条去掉(核过 beta1.1 配方),旧引擎一条改成 beta1.1 起已修。
  • 记忆页按代码复核到 0.27.0:加「首要原则」;门槛按每类提议、用户的各种反应现算,分量和门槛动了什么写清;常用说法不再问用户,拉丁字母按整词数。参考页加 reject_family、reject-family、fit.deleted。
  • 已知问题按 beta1.1 和 internal-1031 安装包里的配方改:话题归纳、机构归纳各档都已开,两条去掉;16 GB 档的打字归纳没开,改正原来写的「已打开」。
  • 决策页加 0020(引擎换成自己做的薄引擎)。

2026-10-30

  • 输入法页按代码复核到 0.1.32:加自然码双拼(和小鹤共用程序和词库,只换韵母键位,评测成绩相同);添加引导用系统设置的原词并加图文教程,螺壳完成页同用;清单介绍语;自带 librime 不再带编译机路径。「安装与输入源」改名「安装与添加」。

2026-10-29

  • 提示词页按邮件助手 0.18.19 复核:分拣在用户只在抄送里时多一句提示,模型给 high 也降成 medium;发件域名验证结论 16、32 档都不给;机构归纳的例子换成虚构机构名,说明为什么不用「某某…」。已知问题页加 128 GB 档机构归纳暂时关着。
  • 按 luoke-core main 复核检查更新:内部版查 luoke-internal-builds、beta 和正式版查 luoke-releases,每页 100 条往后翻(最多 10 页);运维页、发布清单页同步写明两个发布仓库。决定页补 ADR 0017 的 10-28 修订(内部版换仓库、四个组件开源)。已知问题页去掉指向私有 issue 的链接,32 GB 档银行通知一条写明 beta1.1 起撤掉。

2026-10-28

  • 邮件助手页按代码复核到 0.18.14(beta1.1 用的 0.18.11.3 功能相同):页面令牌存进 localStorage、同步分批入库和断点续读、长链接写法、分拣的寄出日期 / 只在抄送 / sender_verdict、五类通知和清理分析完、收件箱卡片时间和已过日程、处理模式按钮、加入日历、撤销的查找顺序、日志轮转。输入法页复核到 0.1.23:换版等设置窗口和添加引导关着、--status 的全部字段和退出码、--disable / --current、清单的 listing、菜单里联想的勾选。
  • 螺壳页、配件清单页、文件页按 luoke-core eb1ad95 复核:引擎密钥写进 0600 的 settings.json、不联网找模型;计费 / 低数据网络不自动补下;只装应用不动引擎、换引擎后删旧版本;首次设置的进度估算、试打框重新聚焦、邮件助手完成行;验证历史记"本版放行的任务";Finder 版本是发布号;"上一版"只认发布出去的、beta 全部重评;发布页只放 Luoke.dmg;配件清单的 main、min_macos、capabilities 里的 service / permissions / listing、配方的 background_input_chars 和 expectations、发布清单字段。

2026-10-27

  • 新增「螺壳体检」组件页(luoke-audit,按代码 6fa5b14 核对):两个程序、19 条规则、病毒扫描和增量、清理与撤销、给 agent 的 10 个 MCP 工具和 outbox、两个生成任务、数据和打包、评测、已知限制。
  • 提示词页按代码复核(luoke-mail 0.18.11、luoke-mem 0.26.0、luoke-core 570eef2):共同做法补采样参数、重问方式和截断后的处理;分拣补寄出日期、正文的预处理(长链接写成 https://域名/…、6000 字)、日期回拉、判断规则,评测改为 48 封;起草回复的输入、署名规则和重问;精选阅读、挑选的口味块、链接表、候选数;learn-orgs 的入选条件;往来邮件的重问;打字和话题的失败处理、话题名上限 60 字、品牌筛选和"没兴趣"的依据、话题评测的硬性条目;luoke-search.embed 在配方里仍叫 digup.embed。
  • 已知问题:32 GB 档银行审批通知误报、打字归纳"答辩"两条记为配方里的已知问题;16 GB 档打字归纳 internal-1027 起打开;补两条缺陷(先升螺壳后升应用时草稿和摘要被拒、换引擎后旧引擎不删),都已在 main 上修;去掉 128 档话题分数偏低。

2026-10-26

  • 已知问题:邮件助手往返和多段行程只加第一段(luoke-mail#34)。
  • 已知问题(质量):16 GB 档分拣把正常邮件的催促语判成可疑。
  • 输入法按代码复核(luoke-ime 0.1.19,307aa9e):换版时空闲自己让位并重新拉起;找螺壳的 Python 先 /Applications 后 ~/Applications、两个名字都认;第一页按「-」打连字符、数字后的逗号、{ } 打「」、Shift+⌫ 忘词;联想按应用算、终端默认不联想、激活后高亮项放在光标处;设置窗口四页和精简后的菜单;新增「安装与输入源」一节;ime-commit 断段和保留期的细节;已知限制补四条。

2026-10-25

  • 主控按代码复核(luoke-core 0590c6e):新装放 /Applications,安装盘改成拖进「应用程序」的窗口,在安装盘里双击给「帮我装进去」;已装的以实际位置为准、两处两名都认;升级改为先暂存核签名、再由独立的换包脚本换上并用 open 打开,管理员升级时从 ~/Applications 搬进 /Applications,标准账户写不进去时弹管理员授权框;内部版每 3 小时自动检查;主控和安装器拉起别的应用一律经 /usr/bin/open。文件位置页补换包脚本、结果和日志,agent 一行改为「螺壳 agent」;运维页跟着改。
  • 已知问题:去掉升级后程序坞留死图标(luoke-core 6bd94be 修好,10-25 升级实测无死图标)。主控 c30aad3:两处各有一份时,打开旧那份会交给装好的那份,装好的那份启动时收走另几份;目标位置被别的应用占着时改放另一处。
  • 照顶层 CONTEXT.md「对外怎么叫」统一名称:主控页标题改为「螺壳(主控)」,agent 页去掉「螺壳」这个名字、写明是螺壳的后台组件和以后的安装方式;概览、邮件助手页跟着改。
  • 主控 5fef093:空闲任务的请求也照能耗模式歇息,主控挂着等,不再豁免(静音模式下风扇不再猛转)。

2026-10-24

  • 主控按代码复核(luoke-core 7d97ce3):应用名改为 螺壳.app(开发版「螺壳(开发)」),旧名字升级时换掉;新增「安装位置」「应用」两条:每个应用的安装、升级、回到上一版本、卸载都在「应用」窗口那一行里做,升级主控不再顺带升级应用;升级改为同一张卡片走完五步,主控自己的回到上一版去掉,换包或补齐没成时给「先用着」和「重试」;检查更新只升级主控、可「去升级应用」;首次运行、加装、升级不再发通知;菜单只剩状态、记忆、应用、设置、检查更新、退出,图标五种状态不动;主控小窗口给四个入口;设置分通用、下载、模型、高级、关于。文件位置和运维页跟着改。
  • 决策页补 0014 的 10-24 修订(主控就是用户看到的「螺壳」,agent 并进它)、0017 的 10-23 修订,新增 0019(提示词放成文件)。
  • 已知问题新增主控三条:升级后程序坞的死图标、升级卡片运行中常驻应用不在、首次运行没走完时「应用」里的安装不回到向导。
  • 主控 db4ff62:升级卡片一开始就拉起常驻应用,「应用」行上的安装也先回首次运行,升级后重新登记登录项;已知问题去掉对应两条。

2026-10-23

  • 主控按代码复核(luoke-core e49cf49):能耗模式改为全局并管本机端点,名称为全速 / 均衡 / 静音;新增"菜单和窗口"一节(左对齐的「名称:值」菜单、窗口期间出现在程序坞、主控小窗口、设置、只给本体用的接口核对对端进程);升级写成两步(安装器换主控、新主控升级页确认其余);检查更新窗口的步骤、状态和自动检查时机;首次运行的 16 GB 预检拦截、下载结局、输入法一键切换和重新登记;恢复旧版本应用、卸载、LaunchServices 登记清理、通知;引擎在流里报错的处理;任务开关的复测和"没评到沿用上一版"。已知限制去掉"只核对自报身份"一条。
  • 主控任务接口:/tasks 补 model_name。任务与提示词:精选挑选改为"最多 k 条",退路改为一条可用的都没有时才按阅读分数排序(luoke-mail 0.17.11)。
  • 主控 d185b1e 修好当天发现的缺陷:下载和试跑有结果就发通知;自动检查更新查成了才记时间,核对不过的安装包删除;均衡、静音下端点一次只放一条;升级页没走完就关,常驻应用照常拉起、下次再提示。已知问题中对应四条去掉。
  • 主控开发版身份(ai.luoke.core.dev「螺壳主控(开发)」、数据在 luoke-core-dev);升级时新版先放进 staging.noindex 核签名再一步换上,「App 管理」挡住时照实说明(luoke-core 3c44c63)。
  • 记忆、邮件助手按代码复核已知缺陷(luoke-mem 0.25.3、luoke-mail 0.17.16):同一机构的事实合并、确认过的保留原话并问"现在还是这样吗"、反刍时整理已生效的推断、猜测也会变成以前的事、想问的加上打字和机构事实、改建议后当场处理只算一次;邮件助手机构名 40 字、话题「机构:名称」、after_override、按习惯归档跳过高优先级和安全通知。已知问题去掉机构名截断和整理范围两条,另三条改写为余下的缺口,新增两条(带冒号的人际话题被当成机构、否定一条后新说法不再提出)。
  • 记忆 0.25.4:只有机构类话题头才按名字合并(同事、客户、工作等不并,此前误并的打开时拆回);被否定的那条只挡同样的说法;精选口味生成时跳过"动态""通知"等结尾的名字。已知问题去掉对应三条,新增「其他:」话题下人和机构可能并成一条。

2026-10-22

  • 新增"提示词"页:逐个生成任务写明输入、输出约定、校验与退路、预算和评测;任务表按最近一次内部版的台账更正档位(往来邮件三档开、话题归纳 16 档开、打字归纳 16 档仍关),注明起草回复、订阅阅读、精选挑选没有评测,往来邮件的硬性条目改为都一次不能错,补主控定输出上限的算法;决策页补 0013、0014 的 2026-10-21 修订(写进用户自己的账户不算外传、深度搜索索引可存完整号码并在输出时遮,日历和浏览器桥由主控提供)和 0017 的 2026-10-22 修订(主控窗口和邮件网页 beta1 前统一改版,封闭测试 11-02 起一批)。
  • 在主人的真实数据上核查了一遍记忆的产出(只看计数和理解那一句,记录在团队内部);已知问题新增五条缺陷。

2026-10-21

  • 新增深度搜索章节(按登记的设计,尚无代码),总览、核心概念、术语表、任务表补上深度搜索;出版流程改为每格当天评一遍、10 分钟无进展的看门狗、复测挪到发布后并在下一版生效;主控首次运行补五步步骤条和完成页的通知权限,空闲任务补 exec;决策页补 0016–0018 的 2026-10-20 修订(luoke-core cd6a6c7)。
  • 深度搜索章节按代码重写(luoke-search 0.1.0,2a26c1b):运行形态(常驻 .app、FSEvents 更新、能停能续的第一遍和常驻兜底、单个文件的看门狗、长文件先读开头、跳过像数据集的文件夹)、各类文件的读法、字词与意思的混合打分(默认做法 A,其他做法只在 eval --scoring 里)、拒答门槛未开启(confident 无结果为 false、有结果为 null)、MCP 工具和命令行、数据文件、对主控的依赖(客户端用 luoke-search.embed,主控配方仍为 digup.embed;清单未登记 jobs)、各题库与拒答的评测结果。邮件助手更新到 0.16.3:范围以前的未读邮件作为空闲任务补分、标已读并在今日处理单列,清理不再列出;设置里的通知权限提示、"打开通知设置"和"发送测试通知";service.log 和 run/notifications.json;清理页丢弃迟到的答复;刷新后停在上次查看的邮箱。
  • 邮件助手 0.16.4:旧未读邮件照习惯处理在下一次同步时做;日志只按 2 MB 另存,不再清空。

2026-10-20

  • 按项目文档的方式重写:分为概览、组件、参考、运维四部分;组件章节按职责、运行形态、接口、数据、故障、限制组织;新增核心概念、数据流、主控任务接口、配件清单与配方、记忆接口与表结构、任务与提示词、文件位置、术语表。
  • 各组件内容按代码逐项核对(luoke-core 756d33a、luoke-mem 0.23.1、luoke-mail 0.15.0、luoke-ime 0.1.6、luoke-agent 0.0.1)。
  • 正文不再记录讨论过程;决策见"决策"页。
  • 主控补检查更新、配件回滚、专用模型进程的空闲停止与内存检查、引擎内存拒绝后的重试、端点独占上限;输入法补单实例、退出通知、收尾键等待、联想停顿、引擎评测选项(luoke-core fad536a、luoke-ime 0.1.9)。
  • 已知问题:去掉记忆的三条缺陷(luoke-mem 0.23.2 已修)和输入法的两条(隐私说明已改;最低系统版本不同是有意的,见输入法章节)。
  • 主控本体专用路由改为按对端进程核对,forbidden 为 403(luoke-core f5850c1);已知问题中主控的两条去掉。
  • 邮件助手 0.15.2:自动标已读进入操作记录、可整批撤销;agent 应用方案只在批量移到垃圾箱或垃圾邮件时要处理模式;已知问题中邮件助手的两条去掉。
  • 记忆、邮件助手、agent 按代码逐项复核(luoke-mem 0.23.3、luoke-mail 0.15.4、luoke-agent 7b158a9):记忆补归纳顺序(反刍在前后各一次)、话题线断点续读、开跑前查任务、以前的事再次出现时的规则、邮件推出的兴趣上限、各线的定档和进度;邮件助手补后台整理默认关闭与常驻声明、端口顺延、通知、agent 接口默认关闭、往来邮件"轮到谁"、设置的模型名、数据目录文件,更正订阅阅读和日志说明;agent 补自带 luoke_mem 版本、窗口打开即读、简报计时、"以后再说"由记忆 7 天后重提;任务表更正话题归纳档位。已知问题新增三条缺陷。
  • agent 自由问题不再提供"以后再说","以后再说"一律由记忆决定何时再问;已知问题中对应一条去掉(luoke-agent 47b817e)。
  • 记忆 0.23.4:一次模型错误不再打断整次归纳,连续出错两次跳过;邮件助手 0.15.5 改用记忆的动作名,旧记录照意思映射;自由问题收"以后再说";已知缺陷清空。

2026-10-19

  • 第一版。更正:顶层 ADR 重新编号时误改了 luoke-core 自己的 ADR 编号,已改回。