首页
/ Agent Harness:把 GUI 应用改造成 Agent 可用有状态 CLI 的通用 SOP(以 Shotcut/MLT 为例)

Agent Harness:把 GUI 应用改造成 Agent 可用有状态 CLI 的通用 SOP(以 Shotcut/MLT 为例)

2026-09-09 19:33:05作者:傅爽业Veleda

导读

本文基于 CLI-Anything 仓库中 HARNESS.md 这套"标准操作规程(SOP)",系统讲解如何把面向人类操作、依赖显示器和鼠标的开源 GUI 软件,改造成可供编码智能体(Claude Code、Codex 等)直接调用的"有状态 CLI"。文章以 Shotcut 视频编辑器的完整落地实现(SHOTCUT.mdshotcut_cli.py)为佐证,深入剖析渲染断层(Rendering Gap)、滤镜翻译、非整数帧率时间码精度、程序化输出验证等关键工程问题。读完本文,你将掌握一套可复用的四阶段改造方法论,以及一套"改完必须验证"的质量红线,能够独立把任意 GUI 应用接入 Agent 工作流。

一、Harness 的目标与适用对象

HARNESS.md 开宗明义:这是一套面向编码智能体的标准操作规程与工具包,目的是让 AI Agent 能够操作"为人类设计"的软件,而无需显示器与鼠标。其核心断言是:

大多数 GUI 应用都把"呈现层"与"逻辑层"分离,这为 CLI 化改造提供了天然切口——找到底层引擎与原生数据格式,就能绕开界面直接驱动软件。

该 SOP 已在本仓库中落地为多个真实实现,其中 Shotcut 一例最为完整:CLI 直接读写 .mlt(MLT XML)工程文件,驱动 melt 完成渲染,并配套 110+ 项测试(见 TEST.md)。

二、通用 SOP:四阶段把任意 GUI 变成 Agent 可用 CLI

Phase 1:代码库分析(Codebase Analysis)

改造前必须先摸清软件的内在结构,五个步骤缺一不可:

  1. 识别后端引擎——多数 GUI 将表现与逻辑分离,找到核心库/框架(例如 Shotcut 的 MLT、GIMP 的 GEGL)。引擎是 CLI 最终要驱动的对象。
  2. 把 GUI 动作映射为 API 调用——每一个按钮点击、拖拽、菜单项背后都是一个函数调用,需要系统化登记这些映射关系。Shotcut 的完整映射表见 SHOTCUT.md 的"Command Map: GUI Action → CLI Command"。
  3. 识别数据模型——软件使用什么文件格式?工程状态如何表示(XML、JSON、二进制还是数据库)?Shotcut 的答案是 .mlt(MLT XML),这是整个 CLI 的支点。
  4. 寻找现成 CLI 工具——很多后端自带命令行(meltffmpegconvert),这些是现成的积木,不必重复造轮子。
  5. 盘点命令/撤销系统——如果应用有 undo/redo,它大概率采用了命令模式(Command Pattern),这些命令本身就是你的 CLI 操作集。

Phase 2:CLI 架构设计

  1. 选择交互模型

    • 有状态 REPL:适合需要保持上下文的交互式会话;
    • 子命令 CLI:适合一次性脚本与管道操作;
    • 两者兼有(推荐):同一套命令体系同时支撑两种模式。

    Shotcut CLI 即采用"Click 子命令 + REPL"双模式:python3 -m cli.shotcut_cli 不带子命令时直接进入交互式 REPL(见 shotcut_cli.pyclick.group(invoke_without_command=True)ctx.invoke(repl) 的实现)。

  2. 按应用逻辑域定义命令组:工程管理(new/open/save/close)、核心操作(应用的主业)、导入导出(文件 I/O 与格式转换)、配置(设置/偏好/配置文件)、会话与状态管理(undo/redo/history/status)。Shotcut CLI 的命令组为 project / timeline / filter / media / export / transition / composite / session / preview,一一对应。

  3. 设计状态模型:哪些状态需要在命令间持久化(打开的工程、光标位置、选区)?状态存于何处(REPL 用内存、CLI 用文件)?如何序列化(JSON 会话文件)?仓库中 Session 类(session.py)通过 _snapshot() 对工程根节点做快照入栈,从而在任意时刻支持 undo()/redo(),并将会话状态持久化为 JSON(save_session_state/load_session_state/list_sessions)。

  4. 规划输出格式:交互使用人类可读格式(表格、颜色),Agent 消费使用机器可读 JSON,二者由 --json 开关控制。CLI 的全局 output() 函数(shotcut_cli.py)即实现"JSON 模式输出 json.dumps,否则打印字典/列表"的双通道;--json --project p.mlt timeline clips 1 即为典型调用。

Phase 3:实现顺序

HARNESS.md 给出了明确的落地次序,本仓库实现完全遵循:

  1. 先做数据层——XML/JSON 工程文件的解析与改写(对应 utils/mlt_xml.py);
  2. 加探测/信息命令——让 Agent 在修改前先能观察(media probeproject infotimeline showtimeline tracksfilter list 等);
  3. 加变更命令——每个逻辑操作一个命令(add-cliptrimsplitadd-filterset-filter 等);
  4. 加渲染/导出——输出管线(见下文"渲染断层");
  5. 加会话管理——状态持久化、undo/redo;
  6. 加 REPL——用交互模式包装全部子命令。

Phase 4:验证策略

SOP 要求六层验证,由轻到重:单元测试(合成数据、无外部依赖)→ 真实文件 E2E(捕捉单元测试漏掉的格式假设)→ 多步工作流测试(如"剪 3 段、加效果、调色、导出"的组合缺陷)→ 输出验证(见后文)→ 往返测试(CLI 建工程 → GUI 打开核对)→ Agent 测试(让 AI Agent 仅用 CLI 完成真实任务)。

三、最关键的坑:渲染断层(The Rendering Gap)

HARNESS.md 将渲染断层列为头号陷阱(#1 pitfall)。绝大多数 GUI 应用的效果(滤镜/转场)都是在渲染时由引擎实时计算的。当你直接操作工程文件时,必须同时接管渲染——而朴素的渲染方案会静默丢弃所有效果

问题复现路径:CLI 往工程文件里添加了滤镜/效果 → 渲染时如果图省事用简单工具(如 ffmpeg concat demuxer)→ 它直接读取原始媒体文件 → 工程级效果全部被忽略 → 输出与输入几乎一样,用户看不出任何改动发生。

解决方案——滤镜翻译层(filter translation layer),按优先级三选一:

  1. 最佳方案:调用应用原生渲染器。如 MLT 工程就用 melt,它直接读 .mlt 并应用全部效果,零翻译成本;
  2. 备选方案:构建翻译层,把工程格式的效果转成渲染工具的原生语法(如 MLT 滤镜 → ffmpeg -filter_complex);
  3. 兜底方案:生成渲染脚本,交给用户手动运行。

渲染优先级恒为:原生引擎 → 翻译后的滤镜图 → 脚本

从当前仓库源码看,Shotcut CLI 的 render()export.py)采用了最优路线:将当前工程写入临时 .mlt,以 melt <temp.mlt> -consumer avformat:<output> 直接渲染,注释明确写道 "No ffmpeg fallback — melt is the only render path because it natively reads MLT XML and handles all project features"(export.py);而 SHOTCUT.md 中则完整记录了 ffmpeg 翻译层的设计(MLT→ffmpeg 滤镜映射表),作为 melt 不可用时的降级路线,并在 workflow_demo.py 中演示了手工构造 -filter_complex 的完整流程。

四、滤镜翻译的四个经典陷阱

当在两种格式间翻译效果(MLT → ffmpeg)时,HARNESS.md 与 SHOTCUT.md 共同总结出以下易错点:

  1. 重复滤镜类型(Duplicate filter types):ffmpeg 不允许同一链中出现两个相同滤镜。若工程同时有 brightnesssaturation,而二者都映射到 ffmpeg 的 eq=,就必须合并为单个 eq=brightness=X:saturation=Y。SHOTCUT.md 明确警告:eq=brightness=0.06,eq=saturation=1.3 会被拒绝,必须写成 eq=brightness=0.06:saturation=1.3

  2. 排序约束(Ordering constraints):ffmpeg 的 concat 滤镜要求交错流顺序 [v0][a0][v1][a1][v2][a2],而非分组的 [v0][v1][v2][a0][a1][a2]。若顺序写错,报错信息 "media type mismatch between filter output pad" 极具迷惑性。workflow_demo.py 中的 concat 即严格采用 [v0][v1][v2]concat=n=3:v=1:a=0[a0][a1][a2]concat=n=3:v=0:a=1 的分流写法。

  3. 参数空间差异(Parameter space differences):效果参数常使用不同量纲。MLT 的 brightness 1.15 表示 +15%,而 ffmpeg eq=brightness=0.06 基于 -1..1 刻度。每一组映射都必须显式记录换算公式。SHOTCUT.md 给出了已核验的映射表(节选):

    MLT Service ffmpeg Filter 参数翻译
    brightness eq=brightness=X level: 1.0=中性;(level-1)×0.4
    frei0r.saturat0r eq=saturation=X 同刻度(1.0=中性)
    frei0r.hueshift0r hue=h=X shift×360 换算为角度
    sepia colorchannelmixer=... 固定矩阵 rr=0.393 rg=0.769 rb=0.189 等
    charcoal edgedetect,negate 无参数
    frei0r.IIRblur boxblur=X amount×10 得像素半径
    fadein-video/fadeout-video fade=t=in/out 解析关键帧串得时长
    volume volume=X 同刻度(1.0=中性)
  4. 不可映射效果(Unmappable effects):部分效果在渲染工具中没有等价物,应当优雅处理(警告并跳过),而不是崩溃

此外还要注意读取滤镜的作用层级:滤镜可挂在 <producer>(剪辑级)、<playlist>(轨道级)、<tractor>(全局/总线上)三个层级,翻译时若只读一层,效果必然缺失(SHOTCUT.md 明确:"Track-level vs clip-level filters: Read filters from both... Missing one level = missing effects.")。

五、非整数帧率下的时间码精度

29.97fps(即 30000/1001)这类非整数帧率会导致累积舍入误差。HARNESS.md 给出三条铁律,time.py 逐条落实:

  1. 浮点转帧必须用 round() 而非 int()int(9000 * 29.97) 截断会丢帧;round() 才得到正确结果。timecode_to_frames()time.py)对 HH:MM:SS.mmmSS.mmmHH:MM:SS:FF、纯帧号等全部输入统一走 round(total_seconds * fps_num / fps_den)
  2. 时间码显示用整数算术:帧 → 总毫秒用 round(frames * fps_den * 1000 / fps_num),再以整数除法分解出时/分/秒/毫秒,避免长时间跨度下中间浮点漂移。frames_to_timecode()time.py)正是如此实现,注释明确说明"Use integer arithmetic to avoid floating-point drift"。
  3. 非整数帧率的往返测试接受 ±1 帧容差:时间码→帧→时间码严格相等在数学上不可能,测试断言应写成 abs(a - b) <= 1

六、输出验证方法论:不能只看"退出码为 0"

HARNESS.md 反复强调一条纪律:"进程正常退出 ≠ 导出正确"。必须用程序化手段验证输出(详见 HARNESS.md"Output Verification Methodology"):

  • 视频:用 ffmpeg 逐帧探测——第 0 帧应近黑(淡入起点)、中间帧与源对比亮度/饱和度以确认调色生效、最后一帧应近黑(淡出终点);
  • 黑边(letterbox/pillarbox)处理:跨分辨率对比像素时必须排除黑边像素——竖屏视频放进横屏画框约有 40% 黑像素,会严重拉低均值。SHOTCUT.md 记录了实测案例:834×1112 的竖屏源缩入 1920×1080,验证时只取中间约 810px 进行分析;
  • 音频:检查首尾 RMS 电平验证淡入淡出,并与源做频谱对比。

该方法论在仓库中已被"可复现地"落地:workflow_demo.py 的 8 步高光集锦流程产出了实测数据——亮度 +15% 时内容像素均值由 70.8 → 85.5(+14.7 确认)、饱和度 +30% 时色差 58.3 → 71.8(+13.5 确认)、淡入首帧均值 3.3(近黑)、淡出末帧 0.0(纯黑)、Sepia 呈现 R>G>B(24>22>17)通道序(见 SHOTCUT.md "Verified Workflow")。E2E 测试还专门包含 test_render_imported_media_is_not_blacktest_melt_can_load_subclip_projecttest_melt_multiple_subclips_no_loop 等输出正确性测试(test_full_e2e.py)。

七、测试策略:两套互补的测试套件

SOP 规定双套测试体系,本仓库完全照此执行(总 144 项,其中 TEST.md 记录了 110 项单元测试 100% 通过):

  1. 单元测试test_core.py):合成数据、零外部依赖、每个函数隔离测试、快速确定性、适合 CI。覆盖 Timecode(9)、MLT XML(4)、Session(7)、Project(6)、Timeline(17)、Filters(10)、Media(5)、Export(5)、Integration(2)、Transitions(16)、Compositing(16)、Expanded Filters(13)。
  2. E2E 测试test_full_e2e.py):真实媒体文件、跑通全管线(格式解析、编解码、实际渲染),捕捉单元测试覆盖不到的现实问题。

SOP 特别列举了真实世界工作流测试场景清单:多段剪辑(YouTube 式裁剪)、蒙太奇拼装(大量短片段)、画中画合成、调色流水线、音频混音(播客式)、高强度 undo/redo 压力、复杂工程保存/加载往返、迭代精修(增、改、删、再加)——E2E 套件中的 10 个真实工作流测试逐一对应(YouTube edit、montage、multicam、podcast、picture-in-picture、color grading、undo-heavy、save/load complex、iterative refinement、timeline visualization)。

八、关键原则与硬性规则

Key Principles(HARNESS.md):

  • 直接操作原生格式——不要去重实现引擎,解析并修改应用的原生工程文件(MLT XML、PSD 等);
  • 善用既有 CLI 工具——meltffmpegffprobe 作为子进程调用,不要重新发明渲染;
  • 但必须验证渲染确实应用了你的编辑——见"渲染断层",这是最常见也最静默的失败模式;
  • 失败要响亮而清晰——Agent 需要无歧义的错误信息才能自我纠正;
  • 尽可能幂等——同一命令执行两次应当安全;
  • 提供内省能力——infoliststatus 命令对 Agent 理解当前状态至关重要;
  • JSON 输出模式——每个命令都应支持 --json

Rules(硬性规则):

  1. 每个 cli/ 目录必须包含 README.md,说明依赖安装、如何运行、如何测试、基础用法示例——这是用户或 Agent 读到的第一份文档,没有它 CLI 不可用;
  2. 每个导出/渲染函数必须经过程序化输出分析验证后才能标记为可用,"没有报错"不充分;
  3. 注册表中的每个滤镜/效果必须有对应的渲染映射,或明确标注"仅工程内(不渲染)";
  4. 测试套件必须包含真实文件 E2E 测试——现实媒体的格式假设总是在破坏。

九、落地实例:Shotcut CLI 的目录结构与使用

HARNESS.md 描绘了通用结构,本仓库 Shotcut 的实际实现位于 shotcut/agent-harness

shotcut/agent-harness/
├── HARNESS.md              # 本文档——通用 SOP
├── SHOTCUT.md              # Shotcut 专项分析与 SOP
├── TEST.md                 # 测试结果记录
├── setup.py                # 包安装脚本
├── workflow_demo.py        # 完整演示:3 段高光集锦
├── examples/
│   └── workflow_basic.sh   # 基础工作流脚本
└── cli_anything/shotcut/   # 实际 CLI 实现
    ├── README.md           # HOW TO RUN——必备
    ├── shotcut_cli.py      # 主入口(Click + REPL,1630 行)
    ├── core/               # 按域拆分:project/timeline/filters/media/export/session/transitions/compositing/preview
    ├── utils/              # mlt_xml.py、time.py、melt_backend.py、preview_bundle.py、repl_skin.py
    ├── skills/SKILL.md     # Agent 技能说明
    └── tests/              # test_core.py + test_full_e2e.py

安装与运行(详见 cli_anything/shotcut/README.md):依赖 Python 3.10+、clickmelt(渲染必需)、ffmpeg/ffprobe(探测与导出),REPL 可选 prompt_toolkit。系统工具可按发行版安装(pacman -S melt ffmpeg / apt install melt ffmpeg / brew install mlt ffmpeg)。

典型调用(一次性命令与 REPL 两种形态):

# 新建工程(默认 profile: hd1080p30)
python3 -m cli.shotcut_cli project new --profile hd1080p30 -o my_project.mlt
# 打开工程并查看信息(Agent 友好:--json)
python3 -m cli.shotcut_cli --json --project my_project.mlt project info
# 进入交互式 REPL
python3 -m cli.shotcut_cli repl --project my_project.mlt

REPL 内示例会话(README.md):

> new hd1080p30
> add-track video Main
> media import intro.mp4          → Imported intro.mp4 as clip0
> add-clip clip0 1 00:00:00.000 00:00:05.000
> add-filter brightness --track 1 --clip 0 level=1.3
> show
> save
> render output.mp4 --preset h264-high

可用 profile:hd1080p30hd1080p60hd1080p24hd720p304k304k60sd480p。可用转场:dissolvewipe-left/right/down/upbar-horizontal/verticaldiagonalclockiris-circlecrossfade。可用混合模式 17 种(normaladdmultiplyscreenoverlay 等)。CLI 还提供 --dry-run(不落盘试运行)、--session <id>(会话恢复)、命令后自动保存(_auto_save_callback,见 shotcut_cli.py)等增强能力。

导出预设定义于 export.py,共 10 个(源码实际参数值):default(H.264 CRF 21 + AAC 384k,MP4)、h264-high(CRF 15 + preset slow)、h264-fast(CRF 23 + ultrafast)、h265(libx265 CRF 23)、webm-vp9(libvpx-vp9 CRF 30,WebM)、prores(prores_ks profile 2,MOV)、gifaudio-mp3(320k)、audio-wav(pcm_s16le)、png-sequence。渲染命令支持 --width/--height 覆盖与 --overwrite

滤镜注册表定义于 filters.py,CLI 名 → MLT service → 参数规格(类型/默认值/取值范围)一一登记,例如:brightness(level,0.0–2.0,1.0=正常)、volume(level,0.0–5.0,另含 gain dB)、blur(frei0r.IIRblur,amount 0.0–1.0)、crop(left/right/top/bottom 像素)、saturation(frei0r.saturat0r,0.0–3.0)、hue(frei0r.hueshift0r,shift 0.0–1.0=整圆)、sepia(u/v 色度值)、text(dynamictext:argument/size/fgcolour/family/halign/valign)、以及用关键帧串(time=val;time=val)实现的淡入淡出系列。

十、把同一套 SOP 推广到其他软件

HARNESS.md 用一张对照表证明该 SOP 的普适性——模式永远是:找到数据格式、找到引擎、写一个操作前者并驱动后者的 CLI,最后验证输出

软件 后端 原生格式 现有 CLI 渲染断层风险
Shotcut MLT .mlt (XML) melt, ffmpeg ——必须翻译滤镜
GIMP GEGL .xcf gimp -i (script-fu) 中——GEGL 有 CLI
Blender bpy .blend blender --python 低——bpy 原生渲染
Inkscape librsvg .svg (XML) inkscape --actions 低——SVG 即格式
Audacity PortAudio .aup3 (SQLite) ——无 CLI 渲染器
LibreOffice UNO .odt (XML+ZIP) soffice --macro 低——UNO API 可用
OBS Studio libobs scene.json obs-websocket 中——仅实时
Kdenlive MLT .kdenlive (XML) melt ——同 Shotcut

"渲染断层风险"列标识朴素导出方案静默丢效果的概率:风险意味着几乎必然需要滤镜翻译层。本仓库中 Shotcut、Kdenlive、GIMP 等目录正是这一 SOP 的规模化实证。

结语

HARNESS.md 提供的方法论可以浓缩为一句话:不重实现引擎,直接操作原生数据格式,用现成引擎渲染,然后用程序化分析验证输出。四阶段 SOP(分析→设计→实现→验证)解决了"如何把 GUI 变 CLI"的架构问题;渲染断层与滤镜翻译解决了"改完能否真的生效"的正确性问题;时间码精度与双套测试解决了"长期可靠性与 Agent 自纠错"的质量问题。任何想要让开源软件"Agent-Native"的开发者,都可以把这份 SOP 作为起点,并对照 SHOTCUT.mdexport.py 等真实实现逐项核对,让 Agent 真正"无屏操作"你的软件。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.16 K
2.78 K
kernelkernel
deepin linux kernel
C
34
18
docsdocs
暂无描述
Markdown
904
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
932
1.86 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
862
1.36 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.95 K
1.03 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.38 K
1.47 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
535
606
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
549
398
leetcodeleetcode
🔥LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解
Markdown
77
23