AI学习吧
📍 源码七号站 开源解码 Tutti深度拆解:一款让多个AI Agent共享上下文、实时协作的开源工作台

Tutti深度拆解:一款让多个AI Agent共享上下文、实时协作的开源工作台

摘要:2026年,AI Agent从单兵作战走向军团协作,但跨Agent切换时上下文丢失、重复搬运等问题让开发者沦为“人肉传话筒”。Tutti开源项目提供了一个全新解法:它不是又一个Agent工具,而是一层“环境层”,让Claude Code、Codex等多个Agent共用实时工作空间。通过CLI工具安装,使用tmux管理多Agent会话,并在一个Agent终端里用“@”符号即可引用另一个Agent的全部上下文与任务。简单来说,Tutti让Agent们“共脑”协作,告别手动传话。
字号 100%
行距 2.05
当前可见 60% 的内容
本文由 莫潇羽@源码七号站(www.fuyuan7.com)撰写,转载请注明出处。

快速摘要:2026年,AI Agent正从单兵作战走向军团协作。市面上的Claude Code、Codex、Canvas等工具各自能打,但一旦涉及跨Agent接力,开发者就沦为"人肉传话筒"——手动搬运上下文、截图、粘贴需求。Tutti开源项目提供了一个全新的解法:它不是一个简单的Agent聚合器,而是一层"环境层"(Environment Layer),让多个Agent共用同一个实时工作空间,上下文、文件、任务全部打通。本文从设计理念、核心功能、实操复现到对比选型,完整拆解Tutti的底层逻辑——它是一个CLI工具(通过cargo install tutti安装),使用tmux管理多Agent会话,并提供一个Web仪表盘方便可视化查看。你只需要在一个Agent终端里通过@符号,就能引用另一个Agent的全部上下文、历史对话甚至正在跑的任务,彻底告别重复搬运。想看完整拆解,往下翻。全文约13000字,建议收藏后阅读。

一、从"单兵作战"到"军团协作"的必然转折

2026年,AI Agent已经不是一个新鲜词了。

我自己从2024年开始接触各类Agent工具,从最早的ChatGPT插件生态,到后来的Claude Code、Codex、Canvas、OpenClaw,一路用下来,最大的感受是:每个Agent单独拎出来都很强,但真要让它们配合干活,就立刻暴露了问题。

打个比方。你有一个顶尖的建筑师(负责设计图纸),一个顶级的施工队(负责搭建结构),还有一个专业的装修团队(负责软装搭配)。单看任何一方都是行业翘楚,但你让他们协作的时候呢?建筑师出了一版图纸,得打印出来交给施工队;施工队干到一半发现图纸有问题,得找人去问建筑师;装修团队进场后不知道前两方已经做了什么——整个链条里最忙的反而是那个负责传话的项目经理。

在AI Agent的世界里,那个"项目经理"就是我们自己。

莫潇羽自己在过去半年里踩过不少坑。有一次用Claude Code写一个数据看板的后端API,写到一半API额度用完了,切到Codex继续。结果光是交代项目背景、解释Claude Code已经改了哪些文件、当前的代码状态,就花了我十来分钟。更崩溃的是,Codex接手后读了一下代码,发现有个逻辑Claude Code已经改了一半但没提交,直接报错了。我又得回头翻Claude Code的历史会话,找到那个改动,截图、粘贴、解释……一套操作下来,真正写代码的时间还没"传话"的时间多。

这不是个例。但凡同时用过两个以上Agent工具的开发者,大概率都经历过这种"人肉上下文搬运"的折磨。

也正因为如此,多Agent协作(Multi-Agent Collaboration) 在2026年成了一个被反复讨论的热门方向。Gartner的数据显示,2024到2025年间,关于多智能体系统的企业咨询量激增了1400%以上;2026年预计有70%的企业级AI应用会采用多智能体架构。市场不缺需求,缺的是一个真正能把多个Agent"捏合"在一起的工具。

而Tutti,恰好在这个时间节点出现了。

2026年,Tutti在GitHub上开源,发布后迅速获得数百颗星并持续增长(写稿时仓库数据,开源项目变化较快,请以实时数据为准)。它的README里写了一句非常精准的话:"Tutti不是替代你的Coding Agent,而是Agent与Agent实时共享的工作空间。" 这句话点出了关键——市面上缺的不是更强的单兵Agent,而是一个能让Agent们"同频"协作的中间层。

这篇文章,莫潇羽会把Tutti从设计理念到实操体验,从头到尾拆一遍。全文会覆盖这几个方面:Tutti到底解决了一个什么问题(痛点分析)、它的三种核心能力分别怎么用(功能拆解)、用一个真实的Web项目走一遍完整流程(实操复现)、以及它和LangGraph、CrewAI这些主流框架比有什么差异(选型对比)。不管你是刚接触Agent的开发者,还是已经在用多个Agent的老手,这篇应该都能给到你一些实操层面的参考。如果你也被多Agent切换的问题困扰过,花十分钟读完,说不定能省下后面几十个小时的搬运时间。

二、多Agent协作的三大痛点——为什么我们急需一层"环境层"

在正式介绍Tutti之前,我想先把问题摊开来聊一聊。为什么多个Agent协作起来这么费劲? 或者说,Agent之间到底差了什么?

我自己总结了三个核心痛点,各位可以对照看看自己中了几个。

痛点一:上下文丢失——每次切换都是"从零开始"

这是最普遍、也最让人抓狂的问题。

每个Agent工具本质上都是一个独立的"会话容器"。你在Claude Code里跟AI聊了两个小时,它已经掌握了你的项目结构、代码风格偏好、当前正在改哪个模块——这些信息都存储在Claude Code的上下文窗口里。但当你切到Codex时,Codex的上下文窗口是空的,它对你刚才的一切一无所知。

对比维度

同一Agent内连续对话

跨Agent切换

上下文连续性

完整保留历史对话

完全丢失

项目结构了解程度

逐步积累

需要重新说明

当前工作进度

AI自己知道改到哪

需要人工交代

文件变动记录

可追溯

不可见

耗时成本

零额外开销

每次5到15分钟

这个表格一列出来就清楚了。跨Agent切换时,开发者要做的"交代工作"不是一次性的,而是一种高频重复劳动——每切换一次,就要从头讲一遍。

更隐性的一点是:人转述的信息往往有遗漏。 我自己就有过这样的情况——跟Claude Code聊到某个技术细节时,我随口说了一句"这里用WebSocket代替轮询",Codex那边并不知道这个决策,结果它接手后又给我改回了轮询方案。不是Codex不行,是它根本不知道前面已经做过这个判断了。

痛点二:工具孤岛——每个Agent是一个"信息黑箱"

2026年的Agent生态已经相当丰富了。有擅长写代码的Agent(Claude Code、Codex、Cursor)、擅长设计的Agent(Claude Design、Midjourney)、擅长写作的Agent(Claude、ChatGPT)、擅长数据分析的Agent……

但问题是,这些工具以产品为边界,各自形成了孤岛。

你可以把每个Agent理解成一个独立的"部门"——设计部出了一张图,研发部不知道;研发部改了一段代码,测试部不知道。而你作为那个"CEO",需要在各个部门之间跑断腿,把信息从一个部门搬到另一个部门。

Agent类别

代表工具

擅长领域

与其他Agent的信息互通程度

代码Agent

Claude Code, Codex, Cursor

编程开发

几乎为零,需手动传递

设计Agent

Claude Design, Midjourney

UI/UX、配图

需导出截图再上传

文档Agent

Notion AI, Claude

文档撰写

需复制粘贴

数据分析Agent

ChatGPT Advanced Data

数据处理

需导出CSV再导入

这种工具孤岛带来的后果是:工作流越长,搬运成本越高,出错的概率也越大。

我做一个小项目,完整链路可能是这样的:

  1. 在Claude Code里写需求文档(PRD)
  2. 把PRD复制出来,贴到Claude Design里生成UI原型
  3. 把原型截图保存到本地
  4. 回到Claude Code,上传截图,告诉它"按这个设计开发"
  5. 开发过程中发现需要配图,打开Midjourney或DALL-E生成图片
  6. 下载图片,再上传回Claude Code,告诉它插入到页面中

每一步都在做"导出→导入→解释"的重复劳动。这还没算上中间可能出现的格式问题、图片压缩问题、文件命名混乱问题。真正创造价值的时间,被大量非创造性的搬运工作吞噬了。

痛点三:任务编排缺失——串行和并行全靠人工判断

2026年的企业级AI应用,往往涉及多个Agent的并行或串行协作。比如一个电商推荐系统:

  • 数据分析Agent需要拉取用户行为数据
  • 策略Agent需要基于数据制定推荐规则
  • 开发Agent需要将规则代码实现
  • 测试Agent需要验证推荐效果
  • 运维Agent需要部署到生产环境

这是一个典型的多Agent流水线。但现实是,目前绝大多数的Agent工具没有任何任务编排能力。哪个Agent先做、哪个后做、哪些可以并行、中间产出如何流转——全得靠人盯着、催着、搬运着。

更麻烦的是,如果Agent A和Agent B需要修改同一个文件,处理不好就会产生冲突。传统软件工程里Git已经解决了多人协作的冲突问题,但在Agent协作领域,这类基础设施几乎空白。

任务编排需求

当前现状

理想状态

依赖关系管理

全靠人工记忆和跟进

Agent自动识别依赖并调度

并行执行

逐个手动切换执行

多Agent同时运行

冲突处理

人工发现并协调

自动检测冲突并处理

状态追踪

靠笔记或飞书记录

实时可视化的任务面板

中间产物流转

手动导出再导入

自动共享和引用

下一章,我们来正式认识一下Tutti。

三、Tutti是什么:一套全新的多Agent协作范式

先直白地说一句:Tutti不是一个Coding Agent,也不是又一个AI聊天工具。它是一个实时共享的工作空间——帮你把多个Agent拉到同一个"房间"里,让它们能看到彼此的上下文、文件、运行中的任务以及应用产出。

如果你用过Figma或者飞书文档的多人协作文档,就比较容易理解这个思路。Figma里,设计师A和设计师B可以同时编辑同一个画板,实时看到对方的改动。Tutti做的其实是类似的事情,只不过协作的双方从"人"变成了"AI Agent"。

3.1 一句话理解Tutti的设计哲学

Tutti的README里有这样一段话,我觉得非常精准:

Tutti不是替代你的Coding Agent,而是Agent与Agent实时共享的工作空间。 你的Codex能看到Claude改了什么、正在运行什么、项目当前处于什么状态。

这句话翻译成人话就是:过去你需要在Agent之间当传话筒,现在Agent们自己"共脑"了。

3.2 传统方案 vs Tutti方案

用一个具体的对比来说明。

传统的工作流是这样走的:

你 → [Claude Code写代码] → 手动复制上下文/截图/文件 → [Codex接手续写]
                                       ↑
                             你在这里当翻译官

Tutti的工作流是这样走的:

你 → [Claude Code写代码] ←→ [Codex接手续写]
         ↑  共享工作空间  ↑
         └────────────────┘
         (上下文/文件/任务全部实时可见)

在Tutti里,你只需要在Codex的输入框里输入 @Claude Code,就可以直接引用Claude Code的历史对话、文件改动和正在运行的任务。Codex拿到的不只是你口头转述的"摘要",而是完整的上下文——包括Claude Code之前做的每一步、改的每一行代码、最终的决策理由。

3.3 核心设计概念图解

下面这张Mermaid图,展示Tutti的分层设计思路:

graph TB
    subgraph "用户层"
        User[开发者/用户]
    end

    subgraph "Tutti 工作空间(编排层)"
        WS[实时共享工作空间<br/>tmux多面板 + Web仪表盘]
        CFG[配置层<br/>tutti.toml 角色/工作流定义]
        CTX[上下文共享<br/>工作目录/任务状态/审计日志]
    end

    subgraph "Agent层"
        CC[Claude Code]
        CX[Codex]
        AD[Aider]
        OC[OpenClaw]
    end

    User --> WS
    WS --> CTX
    WS --> CFG
    CTX --> CC
    CTX --> CX
    CTX --> AD
    CTX --> OC
    CC --> CFG
    CX --> CFG

这个架构最关键的一点是"上下文总线"(Context Bus)这一层。它相当于一个全局共享的内存区域,所有Agent都可以从这里读写上下文信息,不需要经过人来中转。

3.4 它跟"把Agent都放在一个文件夹里"有什么区别?

有人可能会问:那我直接把所有Agent的聊天记录导出,放到同一个文件夹里,是不是也达到了类似的效果?

区别非常大。我列个表:

对比维度

传统"文件夹方案"

Tutti方案

上下文获取方式

手动打开文件阅读

直接在终端通过@引用

文件变动追踪

手动对比版本

通过共享工作目录实时可见

正在运行的任务

看不到

通过tmux分屏实时可见

Agent产出引用

需手动复制路径

直接在终端引用文件路径

跨Agent调用

不适用

通过@或配置工作流实现调度

冲突处理

靠人肉检查

通过worktree隔离减少冲突

说白了,文件夹方案解决的只是"存储"的问题,而Tutti解决的是"流通"的问题。上下文不仅要存下来,还要能在Agent之间自然地流动和引用,这才是Tutti真正的价值所在。

3.5 开箱体验:CLI工具与Web仪表盘的双重视角

第一次上手Tutti时,最直接的感受是:它跟我想象的"桌面应用"完全不是一回事。

Tutti本质上是一个命令行工具(CLI),通过 cargo install tutti 安装,依赖 Rust 工具链和 tmux(终端多路复用器)。安装完成后,你在终端里通过 tt 命令来操作它。

基本的操作流程是这样的:

# 安装(需要已安装Rust工具链)
cargo install tutti

# 进入项目目录,初始化配置
cd your-project
tt init --template minimal

# 查看可用工作流
tt run --list

# 启动Agent团队
tt up

# 启动Web仪表盘(可视化查看)
tt serve --port 4040

初次接触时,你会在终端里看到 tmux 分割出的多个面板——每个面板运行着不同的 Agent(比如 Claude Code 在一个面板、Codex 在另一个面板),所有 Agent 的输出和状态在同一个终端窗口里同时可见。

如果你不太习惯纯终端操作,Tutti 也提供了一个 Web 仪表盘(通过 tt serve --port 4040 启动),在浏览器里可以看到 Agent 的运行状态、token 消耗、工作流进度等信息。仪表盘上还有一个"调度面板"(dispatch panel),方便你从浏览器触发任务。

这个设计让我一开始有点不习惯——毕竟习惯了点击图标的操作方式。但用了一段时间后就理解了:Tutti 选择 CLI 作为主要交互方式是有原因的。 开发者日常本来就在终端里工作,Agent 输出也是文本为主,CLI 方式反而让 Agent 的接入更直接、更可控。tmux 的分屏让多个 Agent 的实时输出一目了然,Web 仪表盘则提供了更高层级的可视化视角。

值得一提的是,无论是通过 CLI 直接与 Agent 交互,还是通过 Web 仪表盘监控全局,所有操作都围绕同一个共享工作目录展开。到第五章的实操环节你会看到,Agent 产出的 HTML 原型文件就保存在这个共享目录中,你可以在浏览器里直接打开预览——这种流程上的连贯性,正是 Tutti "环境层"设计最直观的体现。

用Tutti官方文档里的一句话来收尾这一章最合适不过:

"Tutti不是替代你的Coding Agent,而是Agent与Agent实时共享的工作空间。"

这就是Tutti的核心设计哲学——让上下文流动起来,而不是让它停在某个Agent的会话窗口里。

四、核心能力深度拆解

通过上一章的介绍,大家应该对Tutti有了一个整体的认知。这一章我们把镜头拉近,逐个拆解它的核心能力:实时共享工作空间编排层与Agent调度任务编排与多项目构建

4.1 核心能力一:实时共享的工作空间

这是Tutti所有功能的地基。没有这一层,后面的一切都无从谈起。

什么叫"实时共享的工作空间"?用一句话概括:在Tutti里,任何一个Agent能看到的东西,其他Agent也能看到。

具体来说,共享的内容包括四个方面:

1)共享上下文(Session Context)

你在Claude Code里跟AI聊了两小时,所有的对话记录、代码diff、决策理由,都会实时同步到工作空间里。当你切换到Codex时,不需要重新说明"我之前做了什么",直接在Codex的输入框里输入 @ 符号,就能看到Claude Code的历史会话列表,点选其中一条,Codex就能读取全部内容。

这个功能底层实现时,Tutti维护了一个全局的上下文索引,每个Agent的对话记录、文件变更都会自动写入这个索引。其他Agent通过@操作符查询索引,就能拿到完整的上下文信息。

用伪代码表示这个流程:

# Tutti上下文共享的伪代码逻辑

class SharedWorkspace:
    def __init__(self):
        self.context_index = {}     # 上下文索引
        self.file_system = {}       # 共享文件系统
        self.task_registry = {}     # 任务注册表

    def write_context(self, agent_id, context_data):
        """Agent向工作空间写入上下文"""
        self.context_index[agent_id] = {
            "conversations": context_data.history,
            "file_changes": context_data.diff,
            "current_status": context_data.status,
            "timestamp": now()
        }
        notify_other_agents(agent_id, "context_updated")

    def query_context(self, agent_id, query_agent_id):
        """Agent通过@查询其他Agent的上下文"""
        if query_agent_id in self.context_index:
            return self.context_index[query_agent_id]
        return None

# 具体场景
claude_code_agent = Agent("Claude Code")
codex_agent = Agent("Codex")

# Claude Code写入上下文
SharedWorkspace.write_context("claude_code", claude_code_agent.context)

# Codex通过@查询
context = SharedWorkspace.query_context("codex", "claude_code")
# Codex得到的不只是摘要,而是完整的对话历史和文件改动

2)共享文件(Shared Files)

Agent在开发过程中会创建和修改大量文件。在传统的模式下,你需要在不同工具之间手动导出和导入文件。但在Tutti里,所有文件都放在共享工作空间中。

这意味着:Claude Code创建了一个 api_handler.py,Codex可以立刻看到它、读取它、修改它。改完之后,Claude Code也能看到Codex的改动。不需要任何手动操作,文件变动是实时同步的。

这个"实时同步"具体是怎么做到的?我研究了一下它的工作机制,大致分为三层:

第一层是文件系统监听层。Tutti在本地启动了一个文件监听服务(类似nodemon或chokidar),当任何一个Agent在工作目录里创建、修改或删除文件时,监听器会捕获到变更事件,并把变更内容记录到内存中的变更日志里。

第二层是变更广播层。变更日志生成后,Tutti会向工作空间里所有已连接的Agent广播一条消息,内容类似于"文件X发生了Y类型的变化"。Agent收到广播后,会决定是否需要重新读取这个文件。如果Agent当前正在进行的任务跟这个文件有关,它会主动拉取最新版本;如果无关,就忽略这条广播,避免不必要的Token消耗。

第三层是按需拉取层。Agent并不会被动接收所有文件内容——那样太消耗上下文了。而是先收到"文件有变动"的通知,然后按需决定要不要拉取最新内容。这样一来,既保证了信息的实时性,又避免了上下文被无关信息撑爆。

三层机制配合起来,实现了"该同步的实时同步,不该同步的不浪费资源"的体验。

文件操作类型

传统方式

Tutti方式

查看Agent创建的文件

需要Agent告诉你文件在哪,自己去打开

文件自动出现在共享空间,可直接引用

跨Agent修改同一文件

各自维护一份,人工合并

基于共享文件系统,变更实时可见

引用文件内容

复制粘贴或上传

点击「+」直接引用

文件版本追溯

依赖外部Git

共享空间自动记录变更历史

3)共享运行中的任务(Running Tasks)

这是一个容易被忽略但非常实用的能力。在Tutti里,你不仅能看到Agent做了什么,还能看到Agent正在做什么

假如Claude Code正在执行一个耗时的重构任务,Codex可以看到它的执行进度、当前卡在哪一步、输出了什么中间结果。如果Codex发现Claude Code有一个步骤可能有问题,它甚至可以在旁边提出建议——有点像结对编程。

4)共享审计与状态信息

Tutti会自动记录每个Agent的操作日志和状态变更,这些信息同样在共享空间中可见。你通过 tt log 命令或Web仪表盘可以查看到:

  • 每个Agent读写了哪些文件
  • Token消耗统计
  • 已完成和正在执行的任务列表
  • 工作流的整体进度

这些信息对排查问题和把握项目全局进度非常有帮助。

4.2 核心能力二:通过编排层实现Agent间的灵活调度

Tutti的另一个核心能力,是它提供了一层编排层(Orchestration Layer),让你可以在一个统一的配置文件中定义Agent的角色、工作流和协作规则。

这个编排层最实用的地方在于——你可以在一个地方定义好整个Agent团队的分工,然后一键启动,所有Agent按照你预设的规则并行或串行工作。

具体来说,编排层提供了几个关键能力:

1)角色定义(Role Definition)

你可以在 tutti.toml 配置文件中给每个Agent分配一个明确的角色。比如:

# tutti.toml — 定义Agent团队角色

[agents.frontend]
name = "前端开发"
backend = "claude-code"
worktree = "./frontend"
role = "负责前端页面开发与样式实现"

[agents.backend]
name = "后端开发"
backend = "codex"
worktree = "./backend"
role = "负责API开发与数据层实现"

[agents.design]
name = "设计"
backend = "openclaw"
worktree = "./design"
role = "负责UI素材生成与页面配图"

每个Agent有自己独立的工作目录(worktree),互不干扰,产出又都在同一个项目下。

2)工作流编排(Workflow Orchestration)

Tutti允许你将多个阶段编排成一个完整的工作流,每个阶段可以指定由哪个Agent执行,以及上下游依赖关系。举个例子:

# 一个典型的多Agent工作流
阶段1:需求分析 → 由Claude Code执行
阶段2:UI设计 → 由设计Agent执行(依赖阶段1的产出)
阶段3:前端开发 → 由前端Agent执行(依赖阶段2的产出)
阶段4:后端开发 → 由后端Agent执行(可以与阶段3并行)
阶段5:联调测试 → 由Claude Code执行(依赖阶段3和4)

这种编排方式让多个Agent可以按照预设的节奏协作,而不需要你在中间来回协调。

3)审计追踪(Audit Trail)

Tutti会自动记录每个Agent的每一次操作——它读写了哪些文件、输出了什么结果、消耗了多少Token。这些记录会保存在本地数据库中,你可以通过 tt log 命令或Web仪表盘随时查看。

编排能力

具体表现

体验提升

角色定义

每个Agent分配专属角色和工作目录

分工清晰,减少冲突

工作流编排

定义阶段依赖与执行顺序

减少人工协调

审计追踪

记录Agent的每一步操作

随时掌握全局

并行执行

无依赖的多Agent可同时运行

缩短整体耗时

需要说明的是:Tutti本身不内置像"产品原型设计""AI生图""PPT生成"这类应用。它的定位是编排层——你需要在各个Agent(Claude Code、Codex、Aider等)中分别完成各自擅长的任务,Tutti负责让它们"看到彼此"的上下文和进度。如果你想在开发中用到UI原型或配图,可以在Claude Code里写需求,然后通过Tutti的共享上下文让Codex或设计相关的Agent接力完成。这些Agent各自调用自己擅长的API,Tutti只做信息流转的通道。

打个比方——Tutti不是"应用商店",而是"机场的行李传送带"。你的Agent们各带各的行李(任务),传送带保证每个人都能看到别人带了什么、到哪了,但传送带本身不带行李。

4.3 核心能力三:Big「@」与「+」引用机制

如果说共享工作空间是Tutti的"硬件基础",那么@+就是它的"操作指令"。这两个符号构成了Tutti里最核心的交互方式。

Big「@」:上下文引用的万能钥匙

@的功能很直白:在任何一个Agent的对话框里输入@,系统会弹出可引用的对象列表。当前支持的引用对象包括:

@引用对象

示例

效果

另一个Agent的历史会话

@Claude Code 最近的对话

Codex直接读取Claude Code的完整会话

共享文件

@api_handler.py

将文件内容注入当前对话

其他Agent生成的文件

@assets/stadium.png

将其他Agent产出的文件注入到当前Agent

运行中的任务

@task_001

读取某个Agent正在执行的任务状态和中间输出

这个功能的精髓在于:Agent拿到的不只是文件名或摘要,而是完整的结构化数据。 比如Codex @ 了Claude Code的对话,它看到的不仅仅是"Claude Code在重构用户模块"这句话,而是看到了每一行代码的改动diff、每一次提交的commit message、甚至Claude Code在中间做决策时的推理过程。

「+」:引用本地文件和其他Agent产出

在Agent的终端输入框(或通过 tt 命令的参数)中,你可以用 + 前缀来引用文件级别的资源:

  • 引用本地文件:把自己电脑上的文件路径直接传给Agent处理
  • 引用其他Agent生成的产物:比如Codex生成了一张图片写入项目目录,Claude Code可以直接引用这个文件路径

用一张流程图来总结两种引用方式的适用场景:

flowchart LR
    A[你想引用什么] --> B{引用类型判断}
    B --> C[Agent的历史/任务/状态]
    B --> D[其他Agent的文件产出]
    B --> E[本地文件]
    C --> F["用@ 符号"]
    D --> G["用+ 或直接引用路径"]
    E --> H["用+ 符号"]
    F --> I[注入完整上下文]
    G --> I
    H --> I
    I --> J[目标Agent直接使用]

4.4 任务编排与冲突处理机制

最后提一个容易被忽视但很实用的能力:多Agent的任务编排。

当多个Agent在同一个工作空间里干活时,不可避免地会遇到资源竞争的问题——两个Agent同时修改同一个文件怎么办?Agent A的执行结果被Agent B依赖,但Agent A还没跑完怎么办?

Tutti在这块做了一个轻量级的编排机制,核心思路是:

  1. 自动检测冲突:如果两个Agent要修改同一个文件,工作空间会发出冲突警告
  2. 串行与并行判断:Agent会自行判断任务是该串行还是并行执行
  3. 跨服务交互:不同API提供方的Agent(比如Claude和Codex)之间,一样可以互相沟通,不会因为API来源不同而"语言不通"

编排能力

具体表现

体验提升

冲突检测

两个Agent改同一文件时告警

避免互相覆盖

依赖管理

Agent B自动等待Agent A完成后再启动

减少人工协调

并行执行

无依赖的多任务同时推进

缩短整体耗时

状态可视化

实时查看每个Agent的执行进度

随时掌握全局

当然,这套编排机制目前还偏"提醒式"而非"自动化"——系统会告诉你可能有冲突,但最终怎么处理还需要你来决策。用莫潇羽自己的话说,这更像是一个"带提示的协作空间",而不是一个"全自动的流水线管理系统"。但从实际使用的体验来看,这种"轻编排"恰恰是开发者在现阶段最需要的——既没有过度自动化带来的不可控感,又解决了最疼的"上下文搬运"问题。

五、实操复现:用Tutti从零完成一个完整的Web项目

理论讲再多,不如上手跑一遍。这一章莫潇羽带大家走完一个完整项目的开发流程——从需求梳理到原型设计,从代码开发到配图生成,全部在Tutti里完成,中间不离开工作空间一步。

我做了一个体育赛事数据看板项目,包含赛程展示、实时比分、球队信息、积分榜等模块。项目不算复杂,但正好能覆盖"多Agent接力"的典型场景。更重要的是,整个过程我会记录每一步的操作方式,这样大家跟着走一遍就能上手。

5.1 第一步:需求梳理——在Claude Code里写PRD

在终端里运行 tt up 启动Tutti工作空间后,tmux会分割出多个面板。我在Claude Code所在的面板中启动一个新的会话。

我的输入是这样的:

我想做一个体育赛事数据看板应用,包含以下功能模块:
赛程页面:展示所有比赛的时间、对阵双方、当前比分
积分榜:按积分排序展示各支球队的排名
球队详情页:点击球队后展示该队的所有比赛记录
数据来源:使用免费公开API获取实时数据
请帮我梳理完整的PRD(产品需求文档),包含功能列表、页面结构、数据字段定义和技术选型建议。

Claude Code在Tutti里的交互体验比终端版本清晰很多——它会把回复结构化成Markdown,代码块、列表、表格都能正确渲染。不到两分钟,一份结构完整的PRD就出来了。

下面是Claude Code生成的PRD中关于数据字段定义的摘录(我简化了一下):

{
  "比赛数据字段": {
    "match_id": "字符串,唯一标识",
    "home_team": "主队名称",
    "away_team": "客队名称",
    "match_time": "ISO 8601格式时间戳",
    "home_score": "主队比分(可空)",
    "away_score": "客队比分(可空)",
    "status": "枚举值:scheduled / live / finished",
    "venue": "比赛场馆名"
  },
  "球队数据字段": {
    "team_id": "字符串,唯一标识",
    "team_name": "球队名称",
    "logo_url": "队标图片URL",
    "group_name": "小组名称"
  },
  "积分榜数据字段": {
    "rank": "排名",
    "team_id": "球队ID",
    "played": "已赛场次",
    "wins": "胜场",
    "draws": "平场",
    "losses": "负场",
    "goals_for": "进球数",
    "goals_against": "失球数",
    "goal_diff": "净胜球",
    "points": "积分"
  }
}

这一步做完,PRD已经自动保存在Tutti的共享工作空间中了。后面所有Agent都能直接读取它,不需要再手动导出。

5.2 第二步:原型设计——让Claude Code先产出UI原型HTML

PRD写完了,接下来需要做UI原型。如果是传统模式,我现在要做的是:

  1. 把PRD复制出来
  2. 打开Claude Design或Figma
  3. 把需求粘贴进去
  4. 等设计稿出来
  5. 截图保存
  6. 回到Claude Code上传截图

在Tutti里,上面的步骤可以更顺畅——因为工作空间共享上下文,Claude Code可以直接基于它自己写的PRD来产出原型HTML。

我直接在Claude Code的对话框里输入:

基于刚才的PRD,先帮我生成一版页面原型HTML,包含赛程列表页、积分榜页和球队详情页的布局。用纯HTML+Tailwind CDN实现,让我可以在浏览器中直接预览交互效果。

Claude Code读取工作空间中已有的PRD文档,开始生成UI原型HTML文件,并写入共享工作目录。

大概等了三十秒左右,三页原型就生成好了。我可以在浏览器中直接打开这些HTML文件预览原型——不是静态截图,而是可交互的HTML页面,可以点击按钮、切换标签页,体验跟真实的网页几乎一样。

操作步骤

传统方式耗时

Tutti方式耗时

省时

从PRD到原型生成

15到30分钟

30秒

90%以上

前后端信息同步

5到10分钟(人工传递)

0秒(自动同步)

100%

修改反馈迭代

每轮5到10分钟

每轮30秒到1分钟

80%以上

如果对原型有修改意见,直接在对话里说"赛程列表页的比分区放大一些"或者"积分榜增加小组筛选器",Claude Code会实时更新HTML文件。

5.3 第三步:代码开发——Claude Code根据设计稿写页面

UI原型确认后,进入开发阶段。

这一步同样不需要离开Tutti。我直接在Claude Code里输入:

根据刚才确认的产品原型,开始开发这个体育赛事数据看板。技术栈选React + TypeScript + Tailwind CSS。API层用免费的体育数据API(比如OpenSportsDB)。先搭项目框架,然后把首页的赛程列表做出来。

Claude Code直接在共享工作空间里创建了项目文件夹,开始写代码。

这里有一个细节值得注意:因为设计稿已经是工作空间里的产物,Claude Code可以直接读取它。 它不需要我上传截图,也不需要我口头描述"顶部是什么颜色、左边栏多宽、卡片间距是多少"。它自己打开之前生成的HTML原型文件,分析UI布局,然后照着写样式。

这一点在实际开发中节省了大量时间。以前我跟Claude Code说"按这张图做",Claude Code会说"请上传图片"或者"请描述设计稿的内容"。现在,它自己就能把设计稿"看"一遍。

在开发过程中,Claude Code会自动将每一步的改动diff同步到工作空间。举个例子,它创建了这样一个组件文件:

// src/components/MatchCard.tsx
// 比赛卡片组件,展示单场比赛的缩略信息

interface Match {
  matchId: string;
  homeTeam: string;
  awayTeam: string;
  homeScore?: number;
  awayScore?: number;
  status: 'scheduled' | 'live' | 'finished';
  matchTime: string;
}

export function MatchCard({ match }: { match: Match }) {
  const isLive = match.status === 'live';
  const isFinished = match.status === 'finished';

  return (
    <div className={`rounded-lg p-4 shadow-md ${isLive ? 'border-l-4 border-red-500 bg-red-50' : 'bg-white'}`}>
      <div className="flex items-center justify-between">
        <span className="text-lg font-semibold">{match.homeTeam}</span>
        <div className="text-center">
          {isLive && <span className="text-xs text-red-600 font-bold">● 直播中</span>}
          <div className="text-2xl font-bold">
            {isFinished || isLive
              ? `${match.homeScore ?? '-'} : ${match.awayScore ?? '-'}`
              : 'VS'}
          </div>
          {!isLive && !isFinished && (
            <span className="text-xs text-gray-500">
              {new Date(match.matchTime).toLocaleString('zh-CN')}
            </span>
          )}
        </div>
        <span className="text-lg font-semibold">{match.awayTeam}</span>
      </div>
    </div>
  );
}

Claude Code继续推进开发,接下来创建了积分榜页面组件和API数据服务层。下面是我截取的几个关键文件——积分榜表格组件和数据获取的API服务层代码:

// src/components/StandingsTable.tsx
// 积分榜表格组件,展示球队排名数据

interface TeamStanding {
  rank: number;
  teamId: string;
  teamName: string;
  played: number;
  wins: number;
  draws: number;
  losses: number;
  goalsFor: number;
  goalsAgainst: number;
  goalDiff: number;
  points: number;
}

export function StandingsTable({ data }: { data: TeamStanding[] }) {
  const sorted = [...data].sort((a, b) => b.points - a.points || (b.goalDiff - a.goalDiff));

  return (
    <div className="overflow-x-auto rounded-lg shadow">
      <table className="min-w-full bg-white">
        <thead className="bg-gray-100">
          <tr>
            <th className="px-4 py-2 text-left">排名</th>
            <th className="px-4 py-2 text-left">球队</th>
            <th className="px-4 py-2 text-center">场次</th>
            <th className="px-4 py-2 text-center">胜</th>
            <th className="px-4 py-2 text-center">平</th>
            <th className="px-4 py-2 text-center">负</th>
            <th className="px-4 py-2 text-center">进球</th>
            <th className="px-4 py-2 text-center">失球</th>
            <th className="px-4 py-2 text-center">净胜球</th>
            <th className="px-4 py-2 text-center font-bold">积分</th>
          </tr>
        </thead>
        <tbody>
          {sorted.map((team, idx) => (
            <tr key={team.teamId} className={idx % 2 === 0 ? 'bg-white' : 'bg-gray-50'}>
              <td className="px-4 py-2">{team.rank}</td>
              <td className="px-4 py-2 font-medium">{team.teamName}</td>
              <td className="px-4 py-2 text-center">{team.played}</td>
              <td className="px-4 py-2 text-center text-green-600">{team.wins}</td>
              <td className="px-4 py-2 text-center text-yellow-600">{team.draws}</td>
              <td className="px-4 py-2 text-center text-red-600">{team.losses}</td>
              <td className="px-4 py-2 text-center">{team.goalsFor}</td>
              <td className="px-4 py-2 text-center">{team.goalsAgainst}</td>
              <td className="px-4 py-2 text-center">{team.goalDiff > 0 ? `+${team.goalDiff}` : team.goalDiff}</td>
              <td className="px-4 py-2 text-center font-bold text-lg">{team.points}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

数据层方面,Claude Code写了一个轻量的API服务模块,通过免费体育数据接口拉取赛程和积分信息:

// src/services/api.ts
// 体育赛事数据API服务层

const BASE_URL = 'https://api.opensportsdb.com/v1';

interface ApiConfig {
  sport: string;
  season: string;
}

export async function fetchMatches(config: ApiConfig): Promise<Match[]> {
  const response = await fetch(
    `${BASE_URL}/matches?sport=${config.sport}&season=${config.season}`
  );
  if (!response.ok) throw new Error(`API error: ${response.status}`);
  const data = await response.json();
  return data.matches.map(normalizeMatch);
}

export async function fetchStandings(config: ApiConfig): Promise<TeamStanding[]> {
  const response = await fetch(
    `${BASE_URL}/standings?sport=${config.sport}&season=${config.season}`
  );
  if (!response.ok) throw new Error(`API error: ${response.status}`);
  const data = await response.json();
  return data.standings.map(normalizeStanding);
}

function normalizeMatch(raw: any): Match {
  return {
    matchId: raw.id,
    homeTeam: raw.home_team,
    awayTeam: raw.away_team,
    homeScore: raw.home_score,
    awayScore: raw.away_score,
    status: raw.status,
    matchTime: raw.date,
  };
}

function normalizeStanding(raw: any): TeamStanding {
  return {
    teamId: raw.team_id,
    teamName: raw.team_name,
    played: raw.played,
    wins: r
🔒
🔒 该内容仅对更高等级用户组开放,请升级您的账户等级以查看完整内容。
您当前:游客 · 可见 60% 内容 · 升级至 注册用户 可见 70%
👀
游客
可见 60%
✓ 当前
注册用户
注册用户
可见 70%
社区精英
社区精英
可见 100%
社区守护
社区守护
可见 100%
仅解锁本文,永久有效。如需PDF珍藏版,请联系站长获取。 当前单篇价格 ¥9.9
✏️ 发表评论

请先登录后发表评论

前往登录
📊 站点统计
今日发布2 篇
文章总数1289 篇
昨日发布0 篇
本月发布67 篇
建站时间407 天
🔍 搜索
📅 日历
« 2026 » « 08 »
     12
3456789
10111213141516
17181920212223
24252627282930
31      
站长微语

联系站长

微信:165255185
AIGC 技术社区
致力于解码 AI前沿技术 与经验分享
纯粹的技术交流社区

💡 欢迎您的建议与反馈,让社区变得更好

快速通道
联系站长
站长微信二维码
AI交流群
AI交流群二维码
仍在路上

那些寒夜里追赶过的方向

那些冷眼下没放弃的理想

一篇一篇写到现在

仍在路上

"不羁放纵爱自由"

—— 致敬 Beyond
持续创作中 莫潇羽 · 源码七号站