🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI
← 全部攻略

Martty 的双进程边界:终端界面与 Agent 为什么分开跑

桌面端类文章2026/10/18 发布1 次阅读

Martty 是 DSH-first 的 Agent TUI:Agent 在 Host 里,界面侧只做客户端。dsh --profile martty 拉起两个进程——Host 跑 Base Cordis tree、ACP Plugin 与 Agent,独立的 Client 跑 UI、Theme、Slot、Overlay 与 Session Service;两边只通过 ACP 通信,不共享 Plugin Fiber 与 Service 对象。

问题是这条边界买到什么、又付出什么。

两种时间尺度装不进一个事件循环

Agent 一回合是分钟级:流式回复、工具调用、Plan、token 用量。界面一帧是毫秒级的:按键、光标、主题预览。挤进一个事件循环就会互相牵制——Agent 侧一段同步计算,键盘掉帧;painter 等一次终端写入,ACP 读循环后移。

分开后各有独立循环。Agent 卡住,Client 仍能响应 esc、仍能画运行状态;Client 崩了,Host 的会话不一定跟着死。

代价是跨边界调用变成序列化加异步:原来一次调用能拿到的东西,现在要么走 ACP,要么在 Client 缓存;Client 要自己的 Session Service,就是因为它读不到 Host 的内存对象。

ACP 走 stdin/stdout,用户 TTY 走 fd 3/4

Rust/ratatui painter 由 Client 驱动并独占用户 TTY;ACP 走 Client 进程的 stdin/stdout;用户 TTY 用 fd 3/4。两条通道互不混用。

若都挤在 stdout,问题不是「可能」,而是必然:重绘字节会落进 ACP 的帧,协议消息也会被终端当成转义序列。TTY 挪到 fd 3/4 后,重绘流量物理上不经过协议通道。

fd 3/4 要启动方接出描述符,启动方式会影响它是否就绪。它还反向约束插件:通道靠约定维持,插件就不能碰 TTY、raw mode 与终端坐标,只能用公开 Service 与语义节点。

一个 Package,两半代码

Cordis Plugin Package 有两个可选字段:code.host 与 code.client。只有 host 是 Host-only,只有 client 是 Client-only,两者都有是双向 Plugin,仍算一个 run——Client half 可用 host.call(method, args) 调同一 run 的 Host half。绑成一个 run 是为了生命周期不错位:两半独立时,Client 热重载一次,Host 可能还挂着旧回调。

能力却不对称。Client-only 的 Artifact 能存进 $MARTTY_HOME/plugins/<artifact-id>/plugin.json;含 code.host 的不能写进 .oma,得整理成普通 Cordis npm Package,用 dsh plugin --profile martty add <package-or-path> 装进 profile,且没有一键转静态的 promote。原因很直接:Host half 能碰的比界面多,绕过安装流程就绕过了依赖检查。

六个 Slot 是一种收敛

Client half 用 tuiSlots.register 提供内容,位置只有六个:welcome.hero 与 welcome.info(single·root)在欢迎页;chrome.right(list·root)是主界面右栏;conversation.input.dock(list·session)在输入框上方;conversation.navigation.dock(list·session)在 composer 内部;conversation.composer.dock(list·session)在 composer 外层底部。

single 只容一个,list 可排队;root 跟界面走,session 跟会话走。这把「插件能改哪儿」变成可枚举的契约,也让换 UI 与配色时插件不必知道绝对坐标;代价是想浮一个可拖拽面板时,六个 Slot 里没有位置。

闭环里最容易被跳过的一步是 save

流程六步:inspect → generate → run → observe → update / rollback → save。

前五步让 Agent 看见自己改了什么:先读真实的 Service、Slot 与 Schema,再生成、再运行,装载错误与绘制结果都反馈进下一次修改。前提是只能用公开 Service 与语义节点——能直接写坐标的话,观察到的只是字节。

第六步最克制。Cordis 模式的 Plugin 默认只属于当前进程,/liang on、/liang off 显式控制且缺省关闭;Client-only 走 cordis_define → cordis_run → tui_plugin_save → $MARTTY_HOME/plugins,含 code.host 的走不了。不自动落盘的理由很实际:临时 run 没经过依赖检查,写进长期配置,最坏是下次启动加载失败,而你面对一个起不来的界面,连回滚命令都没处敲。

这条边界在什么时候会失效

版本是一处。engines.dsh 是下限 >=0.1.7-rc.2,不是固定版本表;比已测试最高版本(当前 0.2.1-rc.2)新时只警告并继续。低于下限或 host 缺必需能力,ACP 插件不挂载——但界面仍启动,并在会话里说明版本、下限与可选做法。

还有一层:站点扫描标为 🔵「含敏感能力」——可执行外部命令、读写删除本地文件、读取剪贴板或密钥类环境变量;尚无人工评估,站点也未做风险分级。能在运行时生成并执行界面代码的插件系统本身就是代码执行面,这是固有属性。

总结

双进程加 fd 3/4 的分离,换来的是界面与 Agent 可以各自失败而不互相带走,插件改界面时也有可观察、可回滚的路径;想对照同类插件的中文清单见 DeepSeek Harness Hub 插件清单。

适合与不适合

适合:已经在用 DSH、想让终端 Agent 拥有可编程界面的开发者;要给 Agent 加监控面板或 token 统计的团队;想研究自进化闭环的人。

不适合:只想要开箱即用图形界面的人,终端 TUI 这个前提就不匹配;DSH 低于 0.1.7-rc.2 又不想升级的环境,ACP 插件不会挂载;需要长期常驻自定义界面的场景,临时 Plugin 重启即失,含 code.host 的 Package 不能走 .oma 短路径。

标签:Martty、DeepSeek Harness、终端架构

本文由 DeepSeek Harness Hub 自动整理,数据来源于插件详情页。

回到顶部