UI-TARS Desktop / Agent TARS (ByteDance)

一句话定位

UI-TARS Desktop 是 ByteDance 开源的 GUI / computer-use agent 栈:核心回路是 「截屏 → VLM(UI-TARS / Doubao-UI-TARS 视觉模型)→ 动作 DSL(Thought:/Action:)→ 原生鼠标键盘 / 浏览器 / Android 执行」。仓库同时容纳两代 agent 实现——一代是纯 VLM while 循环、驱动 Electron 桌面 app 的专用 GUI SDK;二代是事件流驱动、含工具 / MCP / 多 tool-call-engine / 多存储后端 / 快照回放的通用 agent 内核 Tarko,其上再长出 Agent TARS (通用多模态 agent)与 Omni(单 agent 多 environment 组合)。它与 bytedance-trae(AI IDE) 是完全不同的产品线,勿混。

与多数「代码 agent」harness 的最大区别:它是 GUI-agent,协同设计的重心不在 planner / sub-agent 编排,而在逐模型定制的 action-space prompt + 坐标编码 + DSL 解析器

核心架构总览(目录结构关键路径 + 引用的 commit)

分析基于 git clone --depth 1 拉取的单一 commit c2ad42e3eb9b27830db41a3e6f51ca7179d9b168(commit message: fix(mcp-http-server): default host to 127.0.0.1, not all interfaces (#1918), 时间 2026-07-01 11:03:17 +0800,2026-07-11 clone;License:Apache-2.0)。这是一个 pnpm + turbo 的 monorepo,一个仓库里含两代 agent 栈加一个桌面壳

UI-TARS-desktop/
├── packages/ui-tars/          — 一代 SDK("UI-TARS-desktop" 本体)
│   └── sdk/src/GUIAgent.ts     经典 while(true) GUI loop,纯 VLM,无 MCP/工具体系
├── apps/ui-tars/               — Electron 桌面 app,用的就是一代 SDK
├── multimodal/
│   ├── tarko/                  — 二代 Agent 内核(Tarko framework)
│   │   ├── agent/               loop-executor / message-history / tool-manager / tool-call-engine
│   │   ├── agent-server/        会话持久化(SQLite/File/Mongo/Memory)
│   │   ├── agent-snapshot/      轨迹录制 / 回放(确定性回归测试)
│   │   ├── agio/               面向运维的可观测协议
│   │   └── mcp-agent/          MCP 客户端接入
│   ├── agent-tars/             — Agent TARS(基于 tarko,CLI + Web UI 通用多模态 agent)
│   ├── gui-agent/              — 新一代 GUI agent SDK(operator 抽象:nutjs/browser/adb/aio)
│   ├── omni-tars/              — Omni(code/mcp/gui 三 environment 组合进单 agent)
│   └── benchmark/              — 评测

务必分清一代与二代packages/ui-tars/(一代,纯 VLM while loop)与 multimodal/tarko/(二代,事件流驱动)两者的 loop / 记忆 / 工具机制完全不同,下面逐维度分别标注归属。

Agent Loop(主循环 / 何时继续何时停)

存在两套 loop

一代(GUI 专用,packages/ui-tars/sdk/src/GUIAgent.ts L130–433)——经典感知-决策-执行 while(true)。每轮:① 检查 pause/stop/abort(L133–160)② 截屏 operator.screenshot()(L185) ③ 把截图作为 from:'human' + IMAGE_PLACEHOLDER 塞进 conversations(L214),toVlmModelFormat 转成 VLM 消息(含图像滑窗 L246)④ model.invoke()(L270)⑤ 解析出 parsedPredictionsThought/Action)⑥ 逐 action operator.execute()(L385)。停机判据:action 为 finished()status=END(L418–420);call_user()CALL_USER(L415–417); loopCnt>=maxLoopCount → ERROR REACH_MAXLOOP(L162);连续截图失败达 MAX_SNAPSHOT_ERR_CNT → ERROR。每轮末有 loopIntervalInMs sleep(L424)。

二代(Tarko,multimodal/tarko/agent/src/agent/runner/loop-executor.ts L50–227)——事件流 驱动的 for(iteration<maxIterations)。每轮调 llmProcessor.processRequest()停机判据(已在 留档源码逐条核对行号):

  • 最新 assistant 事件没有 toolCalls 即视为 final answer 而停(L163: if (!latestAssistantEvent.toolCalls || latestAssistantEvent.toolCalls.length === 0));
  • abortSignal 触发 → finishReason:'abort'(L72);
  • 上层 agent 请求终止 isLoopTerminationRequested()(L81)→ finishReason:'stop'(L96);
  • onBeforeLoopTermination 钩子可否决终止(L108):高阶 agent 能拦截「想收尾」这一步、 注入 system 事件让 loop 继续跑(异常时 L133 记 error 兜底);
  • 达到 maxIterations 强制终止,发 finishReason:'max_iterations'(L209)。

新 GUI SDK(multimodal/gui-agent/agent-sdk/src/GUIAgent.ts)复用二代 loop,但通过 onAfterToolCall(L147–210)在每次 operator 动作后 doScreenshot(),把新截图作为 environment_input(“Browser Screenshot”)事件回灌——形成「截屏 = 下一轮观察」的闭环,是把一代 GUI 感知模式移植到二代事件流内核的手法。

记忆与上下文管理(压缩、长期记忆、会话持久化)

核心结论:只有图像滑动窗口,无文本压缩 / 摘要 / 长期记忆 / 向量检索。

  • 二代(multimodal/tarko/agent/src/agent/message-history.ts:核心是事件流 → LLM 消息 的投影。唯一的上下文控制是图像滑动窗口 maxImagesCount(构造参数 L50): getImagesToOmit(L174)保留最新 N 张截图,更旧的图替换为文本占位符(L129 日志: ... images replaced with placeholders)——对每轮产生一张截图的 GUI agent 是关键省 token 手段。 plan_update 事件被投影为 system 消息(L118)。
  • 一代toVlmModelFormat + processVlmParams 做同样的 image 滑窗(L246 注释 “sliding images window to vlm model”),历史用 historyMessages 累积;并用 previousResponseId(L259/304)走 stateful Responses API(服务端保留上下文,减少重复传历史)。
  • 会话持久化multimodal/tarko/agent-server/src/storage/ 提供 SQLiteStorageProvider / FileStorageProvider / MongoDBStorageProvider / MemoryStorageProvider 多后端可插拔,持久化 session 与 event stream(供 UI 回看,非用于压缩)。
  • 压缩 / 长期记忆 / 经验回放:未实现(无 summarizer、无 memory store)。

工具体系(定义/调用协议/注册/权限)

(均属二代 Tarko;一代无工具体系,只有 operator 动作。)

  • 定义Tool@tarko/agent-interface),schema 支持 zod 或 JSON Schema (tool-processor.ts getToolSchema L400–403:hasJsonSchema?() ? schema : zodToJsonSchema)。
  • 注册ToolManagermultimodal/tarko/agent/src/agent/tool-manager.ts)本质是 Map<string,Tool>,提供 registerTool / getTool / executeTool。另有 per-execution 覆盖: ToolProcessor.setExecutionTools(L42)可为单次执行替换工具集。
  • 调用协议(3 种 ToolCallEngine,可切换)NativeToolCallEngine(走 provider 原生 function calling)、StructuredOutputsToolCallEngine、以及 PromptEngineeringToolCallEngine (无原生 FC 的模型:system prompt 里塞 <tool_call>{json}</tool_call> 格式说明,用状态机 ParserState 流式解析 tag,见留档 src-tarko/PromptEngineeringToolCallEngine.ts L31/L98–119)。
  • 执行 + hook 链tool-processor.ts::processToolCalls L154–379):串行执行每个 toolCall, 每步发 tool_call/tool_result 事件;钩子 onProcessToolCalls(整体拦截替换,可做 mock/审批)、 onBeforeToolCall(改参数)、onAfterToolCall(改结果,GUI SDK 用它回灌截图)、onToolCallError; 支持 abortSignal 中途中断,结构化日志带 duration/toolCallId(L114–125)。
  • GUI agent 特例:只注册一个 operator 工具(GUI_ADAPTED_TOOL_NAMEgui-agent/agent-sdk/src/GUIAgent.ts L86–122),参数为空;模型输出的 Action: DSL 由 action-parser 解析成 operator_action 再执行,坐标经 normalizeActionCoords 归一化。
  • 权限:工具级无强制审批门(见「安全与权限」)。

Prompt 设计(系统提示结构、动态组装)

  • Agent TARS(multimodal/agent-tars/core/src/prompt.tsDEFAULT_SYSTEM_PROMPT 注释标注 “Inspired and modified from Manus ❤️”,采用结构化 XML-ish 分节: <intro><language_settings><multimodal_understanding><system_capability><agent_loop> <file_rules><shell_rules><writing_rules><report_rules>动态组装generateBrowserRulesPrompt(control)(L123)按浏览器控制模式(hybrid/dom/visual-grounding) 拼装不同的 <browser_rules>,说明各工具何时用。
  • GUI 模型(multimodal/gui-agent/agent-sdk/src/prompts.ts逐模型版本各一套 prompt—— getSystemPromptUITARS_1_0/1_5getSystemPromptDoubao_15_15B/20BSYSTEM_PROMPTSYSTEM_PROMPT_LATEST;都用 Thought:/Action: 输出格式,但 action space 与坐标编码随模型不同 (详见「与模型的协同设计」)。20B 版内嵌大量中/英 Thought few-shot 示例(L122–146)。 getSystemPromptForModel(uiTarsVersion, operator)(L199)按版本派发; assembleSystemPrompt(template, operator.getSupportedActions()) 把 operator 实际支持的动作 填进模板——prompt 与 operator 能力绑定。
  • Omni(omni-tars/core/src/AgentComposer.ts::generateSystemPrompt L44–64):base prompt + 各 plugin 的 environmentSection 拼接 + generateUsageInstructions 生成 <code_env>/<mcp_env>/<computer_env> 使用说明。

Router / 编排(任务分解、多 agent、子 agent)

  • 无经典多 agent / sub-agent 派发,无 planner-executor 分离。
  • Omni-TARS 的「编排」= 单 agent 多 environment 路由AgentComposer + ComposableToolCallEngine): 把 code / mcp / gui 三个 AgentPlugin 组合,system prompt 要求模型每轮用 <environment_name>...</environment_name> 标签声明本轮用哪个环境(L182–214), ComposableToolCallEngine 据标签分派到对应 parser。是同一个模型自己在多环境间切换,不是多模型 / 多 agent。
  • plan_update 事件类型(message-history 会投影为计划状态 system 消息),即模型可维护 TODO 计划, 但无独立的规划器组件

Skill / 插件体系

  • omni-tars/core/src/AgentPlugin.ts:plugin 抽象——每个 plugin 贡献 environmentSection (prompt 片段)+ getTools() + 生命周期钩子(onLLMRequest/onEachAgentLoopStart/ onAfterToolCall 等),由 AgentComposer 聚合所有 plugin 的工具与提示。这是本仓最接近「插件体系」 的机制(code-agent / mcp-agent / gui-agent 各是一个 plugin)。
  • MCP = 主要外部能力扩展点tarko/mcp-agentmcp-agent.ts / mcp-client-v2.ts)把 MCP servers 的 tools 注入 agent;Agent TARS 在 agent-tars/core/src/agent-tars.ts L90 以 mcpServers: environment.getMCPServerRegistry() 注册。AIO Sandbox 模式下全部工具都来自远程 sandbox 的 MCP(见「沙箱与执行隔离」)。
  • 无独立「skill 市场 / 热加载」机制;扩展靠 MCP server 或 AgentPlugin。

自进化能力(自我改进 / 学习型记忆 / eval 驱动纠错)

未实现。 全仓(排除无关的 gemini 示例 jsonl)grep self.?evolv|self.?improv|learn from|reflection memory|experience replay|fine.?tune 无源码命中。 无 eval 驱动的自我纠错回路、无学习型记忆、无从轨迹自动改 prompt/权重的机制。快照系统(见「轨迹利用」) 是回归测试 / 评测用途,不反哺训练或 prompt。

可观测性(日志 / trace 格式)

设计上明确分两层multimodal/tarko/agio/src/index.ts 头部注释):

  • Agent Event Stream:内部机制,既是记忆又驱动 UI(user_message/assistant_message/ tool_call/tool_result/environment_input/plan_update/system 等事件)。
  • Agio(Agent Insights and Observations)面向运维的标准化监控协议,事件类型(已核对留档 agio-index.ts)含 agent_run_start(L154)、agent_ttft(首 token 时延,L201)、 agent_tps(吞吐,L215)、agent_loop_start/endtool_call/tool_resultuser_feedback(L313)。面向私有化部署与跨 session 运营指标,与 event stream 解耦。

此外全链路用 getLogger()@tarko/shared-utils)分级日志; agent-tars/core/src/shared/message-history-dumper.ts 可 dump 消息历史供调试。

安全与权限(审批门、密钥管理)

  • OS 级权限门(真实存在,apps/ui-tars/src/main/utils/systemPermissions.ts:macOS 通过 @computer-use/node-mac-permissions 检查 / 申请 accessibility(控制鼠标键盘)与 screen recording(截屏)权限,未授权则包一层 warning。这是桌面 app 能操作系统的前置硬门。
  • HEAD commit 本身就是一处安全修复fix(mcp-http-server): default host to 127.0.0.1, not all interfaces (#1918)——MCP HTTP server 默认只绑本地回环,不暴露到所有网卡。
  • 人在环(human-in-the-loop):一代 StatusEnumPAUSE(loop 中可暂停 / 恢复,L133–149)、 CALL_USER(模型主动 call_user() 求助,L415)、USER_STOPPED。二代 / Agent TARS 靠 prompt 建议敏感操作请用户接管浏览器(prompt.ts L213 suggest user to take over the browser for sensitive operations)。
  • 工具级审批门:核心 loop 无强制 approval gate——onBeforeToolCall 钩子给了插入审批的位置, 但默认不拦截;GUI 动作默认自动执行,靠 PAUSE / call_user / OS 权限做兜底,而非每步确认。
  • 密钥:模型 apiKey 由 config / env 传入;仓库有 .secretlintrc.json + secretlint 防提交泄密。

沙箱与执行隔离

  • 本地 operator 无隔离NutJSOperatormultimodal/gui-agent/operator-nutjs/,留档 src-gui-sdk/NutJSOperator.ts)直接用 @computer-use/nut-js 驱动真实鼠标键盘、screen.grab() 抓真屏——直接操作用户真机,无沙箱。桌面 app 即此模式。
  • AIO Sandbox(远程隔离,agent-tars/core/src/environments/aio/:给定 aioSandbox endpoint 后, 禁用所有本地资源操作,一切工具走远程 sandbox 的 MCP(${aioSandbox}/mcpenv/index.ts L61)。 对应 operator 在 gui-agent/operator-aio/AIOComputer/AIOBrowser/AIOHybridOperator/ AIOGameOperator)——远程容器里的 computer / browser。
  • 其他 operator:operator-browser(本地 / 远程 Chrome,LocalBrowserOperator/RemoteBrowserOperator)、 operator-adb(Android 设备)。

与模型的协同设计

这是 GUI-agent 类 harness 的核心,也是本仓协同设计最深的地方。

  • harness 强绑定 UI-TARS / Doubao-UI-TARS VLM 家族,prompt 与解析器逐模型版本定制gui-agent/agent-sdk/src/prompts.ts):
    • ui-tars-1.0/1.5:坐标 <|box_start|>(x1,y1)<|box_end|>
    • doubao-1.5-ui-tars-15b:坐标 [x1,y1,x2,y2](含框);
    • doubao-1.5-ui-tars-20b / SYSTEM_PROMPT:坐标 <point>x1 y1</point>,20B 还多 press()/release()(按住修饰键)、navigate() 等浏览器动作。
  • 动作 DSL 而非 JSON tool call:模型输出 Thought: ...\nAction: click(point='<point>100 200</point>'),由 DefaultActionParser.parsePredictionmultimodal/gui-agent/action-parser/FormatParserChain 多格式兼容)解析成结构化 BaseAction,再 normalizeActionCoords 按屏幕分辨率 / scaleFactor 反归一化到真实像素(NutJSOperatorscreenContext 记录 scaleX/scaleY,L42–47); model.factors 参与坐标缩放(一代 GUIAgent.ts L391)。
  • 内置动作即控制信号finished()/call_user()/wait()(sleep 5s 再截图)直接映射到 loop 状态机——模型用动作空间自己控制停机与求助。
  • stateful 推理:一代用 previousResponseId(Responses API)让服务端保留上下文。
  • 图像 detail 自适应detailCalculator(w,h) 决定截图传给 VLM 的 detail 级别(GUI SDK L184)。

轨迹利用(session/trajectory 是否反哺训练/评测)

  • multimodal/tarko/agent-snapshot:核心是录制 + 回放AgentSnapshot 两模式—— ① generate:真实 LLM 调用 + 埋点,把 LLM 请求 / 响应与整条 event stream 落盘为快照; ② replay:用已录快照做确定性回归测试AgentReplaySnapshotHook 拦截 LLM 调用返回录制响应)。 有 snapshot-normalizer 抹平时间戳等非确定项。
  • multimodal/benchmark:评测目录。
  • 用途 = 评测 / 回归测试,不反哺训练:仓库内无用轨迹做 SFT/RL 的代码路径(呼应「自进化」一节)。 轨迹(event stream)也持久化在 storage 供 UI 回看。
  • 一代对话数据结构(ComputerUseUserData / Conversationpackages/ui-tars/shared/src/types/data.ts) 含 screenshotBase64 + predictionParsed + timing/tokens,是天然可导出的训练 / 评测样本格式, 但导出 / 训练动作不在本仓

与同类 harness 的关键差异(1-3 条)

  1. GUI-agent 而非 code-agent:不靠 shell/文件工具解题,而靠「截屏→VLM→鼠标键盘动作」直接操作 真实桌面 / 浏览器;协同设计重心在 action-space prompt 与坐标编码,而非工具 schema 或 planner。
  2. 动作 DSL 逐模型定制到坐标编码级<|box_start|> / [x1,y1,x2,y2] / <point> 三套坐标格式随 模型版本切换,prompt / parser / operator 三者版本对齐——比多数 harness 与模型的耦合深得多,是「harness 随模型一起演进」的典型样本。
  3. 一仓两代且都保留:一代纯 VLM while loop(面向桌面 app)与二代事件流 Tarko 内核(通用 agent framework)机制完全不同却共存,可直接对比「专用 GUI loop」与「事件流通用内核」两种设计。

原始源码定位

  • repo: https://github.com/bytedance/UI-TARS-desktop
  • commit/version analyzed: c2ad42e3eb9b27830db41a3e6f51ca7179d9b1682026-07-01 11:03:17 +0800,Apache-2.0,2026-07-11 --depth 1 clone)
  • 关键文件列表(相对 repo 根):
    • packages/ui-tars/sdk/src/GUIAgent.ts — 一代 GUI loop(L130–433)
    • packages/ui-tars/shared/src/types/agent.tsStatusEnum / ErrorStatusEnum
    • packages/ui-tars/shared/src/types/data.ts — 一代对话 / 截图数据结构
    • multimodal/tarko/agent/src/agent/runner/loop-executor.ts — 二代 loop(L50–227)
    • multimodal/tarko/agent/src/agent/runner/tool-processor.ts — 工具执行 + hook 链
    • multimodal/tarko/agent/src/agent/tool-manager.ts — 工具注册表
    • multimodal/tarko/agent/src/agent/message-history.ts — 事件流→消息、图像滑窗
    • multimodal/tarko/agent/src/tool-call-engine/{Native,StructuredOutputs,PromptEngineering}ToolCallEngine.ts
    • multimodal/tarko/agio/src/index.ts — 可观测协议 Agio
    • multimodal/tarko/agent-server/src/storage/* — 会话持久化多后端
    • multimodal/tarko/agent-snapshot/src/agent-snapshot.ts — 轨迹录制 / 回放
    • multimodal/gui-agent/agent-sdk/src/GUIAgent.ts — 新 GUI loop(operator 抽象)
    • multimodal/gui-agent/agent-sdk/src/prompts.ts — 逐模型 system prompt / action space
    • multimodal/gui-agent/operator-nutjs/src/NutJSOperator.ts — 原生鼠标键盘 operator
    • multimodal/gui-agent/action-parser/src/DefaultActionParser.tsThought:/Action: DSL 解析
    • multimodal/agent-tars/core/src/prompt.ts — Agent TARS 系统提示 + 浏览器规则动态组装
    • multimodal/agent-tars/core/src/agent-tars.ts — MCP server registry 注入(L90)
    • multimodal/omni-tars/core/src/AgentComposer.ts — 多 environment 组合系统提示
    • multimodal/omni-tars/core/src/AgentPlugin.ts — plugin 抽象(本仓最接近插件体系)
    • apps/ui-tars/src/main/utils/systemPermissions.ts — macOS 权限门

一手源存档(sources/)

存于 /Users/zhao/projects/self-wiki/ai-research/sources/harness/ui-tars-desktop/(约 176K):

  • NOTES.md — 第一阶段源码级调研笔记(含各文件行号)
  • src-legacy-sdk/GUIAgent.ts — 一代 GUI loop 留档
  • src-tarko/loop-executor.ts — 二代 loop 留档
  • src-tarko/message-history.ts — 事件流→消息 / 图像滑窗留档
  • src-tarko/tool-manager.ts — 工具注册表留档
  • src-tarko/tool-processor.ts — 工具执行 + hook 链留档
  • src-tarko/PromptEngineeringToolCallEngine.ts<tool_call> 标签解析引擎留档
  • src-tarko/agio-index.ts — Agio 可观测协议留档
  • src-gui-sdk/GUIAgent.ts — 新 GUI loop 留档
  • src-gui-sdk/prompts.ts — 逐模型 prompt / action space 留档
  • src-gui-sdk/NutJSOperator.ts — 原生鼠标键盘 operator 留档
  • src-omni/AgentComposer.ts — Omni 多 environment 组合留档
  • src-agent-tars/prompt.ts — Agent TARS 系统提示留档