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)⑤ 解析出 parsedPredictions
(Thought/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.tsgetToolSchemaL400–403:hasJsonSchema?() ? schema : zodToJsonSchema)。 - 注册:
ToolManager(multimodal/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.tsL31/L98–119)。 - 执行 + hook 链(
tool-processor.ts::processToolCallsL154–379):串行执行每个 toolCall, 每步发tool_call/tool_result事件;钩子onProcessToolCalls(整体拦截替换,可做 mock/审批)、onBeforeToolCall(改参数)、onAfterToolCall(改结果,GUI SDK 用它回灌截图)、onToolCallError; 支持 abortSignal 中途中断,结构化日志带 duration/toolCallId(L114–125)。 - GUI agent 特例:只注册一个 operator 工具(
GUI_ADAPTED_TOOL_NAME,gui-agent/agent-sdk/src/GUIAgent.tsL86–122),参数为空;模型输出的Action:DSL 由 action-parser 解析成operator_action再执行,坐标经normalizeActionCoords归一化。 - 权限:工具级无强制审批门(见「安全与权限」)。
Prompt 设计(系统提示结构、动态组装)
- Agent TARS(
multimodal/agent-tars/core/src/prompt.ts):DEFAULT_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_5、getSystemPromptDoubao_15_15B/20B、SYSTEM_PROMPT、SYSTEM_PROMPT_LATEST;都用Thought:/Action:输出格式,但 action space 与坐标编码随模型不同 (详见「与模型的协同设计」)。20B 版内嵌大量中/英Thoughtfew-shot 示例(L122–146)。getSystemPromptForModel(uiTarsVersion, operator)(L199)按版本派发;assembleSystemPrompt(template, operator.getSupportedActions())把 operator 实际支持的动作 填进模板——prompt 与 operator 能力绑定。 - Omni(
omni-tars/core/src/AgentComposer.ts::generateSystemPromptL44–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-agent(mcp-agent.ts/mcp-client-v2.ts)把 MCP servers 的 tools 注入 agent;Agent TARS 在agent-tars/core/src/agent-tars.tsL90 以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/end、tool_call/tool_result、user_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):一代
StatusEnum含PAUSE(loop 中可暂停 / 恢复,L133–149)、CALL_USER(模型主动call_user()求助,L415)、USER_STOPPED。二代 / Agent TARS 靠 prompt 建议敏感操作请用户接管浏览器(prompt.tsL213suggest 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 无隔离:
NutJSOperator(multimodal/gui-agent/operator-nutjs/,留档src-gui-sdk/NutJSOperator.ts)直接用@computer-use/nut-js驱动真实鼠标键盘、screen.grab()抓真屏——直接操作用户真机,无沙箱。桌面 app 即此模式。 - AIO Sandbox(远程隔离,
agent-tars/core/src/environments/aio/):给定aioSandboxendpoint 后, 禁用所有本地资源操作,一切工具走远程 sandbox 的 MCP(${aioSandbox}/mcp,env/index.tsL61)。 对应 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.parsePrediction(multimodal/gui-agent/action-parser/,FormatParserChain多格式兼容)解析成结构化BaseAction,再normalizeActionCoords按屏幕分辨率 / scaleFactor 反归一化到真实像素(NutJSOperator的screenContext记录 scaleX/scaleY,L42–47);model.factors参与坐标缩放(一代GUIAgent.tsL391)。 - 内置动作即控制信号:
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/Conversation,packages/ui-tars/shared/src/types/data.ts) 含screenshotBase64+predictionParsed+ timing/tokens,是天然可导出的训练 / 评测样本格式, 但导出 / 训练动作不在本仓。
与同类 harness 的关键差异(1-3 条)
- GUI-agent 而非 code-agent:不靠 shell/文件工具解题,而靠「截屏→VLM→鼠标键盘动作」直接操作 真实桌面 / 浏览器;协同设计重心在 action-space prompt 与坐标编码,而非工具 schema 或 planner。
- 动作 DSL 逐模型定制到坐标编码级:
<|box_start|>/[x1,y1,x2,y2]/<point>三套坐标格式随 模型版本切换,prompt / parser / operator 三者版本对齐——比多数 harness 与模型的耦合深得多,是「harness 随模型一起演进」的典型样本。 - 一仓两代且都保留:一代纯 VLM
whileloop(面向桌面 app)与二代事件流 Tarko 内核(通用 agent framework)机制完全不同却共存,可直接对比「专用 GUI loop」与「事件流通用内核」两种设计。
原始源码定位
- repo: https://github.com/bytedance/UI-TARS-desktop
- commit/version analyzed:
c2ad42e3eb9b27830db41a3e6f51ca7179d9b168(2026-07-01 11:03:17 +0800,Apache-2.0,2026-07-11--depth 1clone) - 关键文件列表(相对 repo 根):
packages/ui-tars/sdk/src/GUIAgent.ts— 一代 GUI loop(L130–433)packages/ui-tars/shared/src/types/agent.ts—StatusEnum/ErrorStatusEnumpackages/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.tsmultimodal/tarko/agio/src/index.ts— 可观测协议 Agiomultimodal/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 spacemultimodal/gui-agent/operator-nutjs/src/NutJSOperator.ts— 原生鼠标键盘 operatormultimodal/gui-agent/action-parser/src/DefaultActionParser.ts—Thought:/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 系统提示留档