拓冰建站拓冰建站
首页 / 资讯中心 / 正文

终端多AI Agent管理:tmux会话池与agent-hub脚本实践

1. 为什么要在终端里同时管理多个 AI Agent1.1 从单开窗口到总管模式的真实痛点最开始用 Claude Code 的时候我的工作流特别朴素开一个终端窗口cd 到项目目录敲claude然后就开始对话。一个项目一个窗口感觉还挺清爽。但很快问题就来了——手上同时有三四个项目在跑有的是前端重构有的是后端接口调试还有一个是临时帮朋友看的一个脚本。每个项目都要开一个终端标签页每个标签页里跑一个 Claude Code 实例切来切去的时候经常搞混这个窗口是哪个项目的刚才那个改动是在哪个 Agent 里做的更麻烦的是Claude Code 本身是有上下文记忆的你在一个会话里聊了半小时它记住了你的项目结构、你的编码习惯、你之前让它改过哪些文件。如果你不小心关掉了那个窗口或者切错了目录重新起了一个实例这些上下文就全没了。你得重新跟它解释一遍我这个项目用的是 pnpm 不是 npm数据库连接在 .env.local 里别动 legacy 目录下的代码。这种重复劳动一天下来能吃掉你不少时间。所以我就想能不能有一个总管式的东西让我在一个终端界面里就能看到所有正在跑的 AI Agent随时切换、随时查看状态而不用在十几个标签页里玩找不同。这个需求听起来简单但真做起来涉及到终端复用、会话管理、Agent 生命周期控制这几个层面的东西。1.2 核心概念澄清Agent、LLM、AI 模型到底啥关系在往下聊之前得先把几个词捋清楚不然容易越听越糊涂。我刚开始接触的时候也迷糊后来用生活化的方式理解就通了。AI 模型比如 DeepSeek、GPT、Claude 这些就像是大脑它本身是一个巨大的神经网络经过海量数据训练具备了理解和生成文本的能力。但大脑本身不会自己干活你得给它一个身体和任务。LLMLarge Language Model大语言模型就是 AI 模型里专门处理语言的那一类。DeepSeek 属于 LLMClaude 也是 LLM它们都是会说话的大脑。AI Agent则是大脑 手脚 记忆的完整组合。它不光会聊天还能调用工具比如读写文件、执行命令、搜索网页能记住之前做过什么能根据目标自主规划下一步。Claude Code 就是一个典型的 AI Agent——它底层用的是 Claude 这个 LLM但它外面套了一层终端操作能力所以它能直接在你的项目里改代码、跑测试、提交 git。打个比方LLM 是一个很聪明的顾问你问他什么他都能答AI Agent 是这个顾问加上一台电脑加上一个工位你告诉他把这个项目的登录 bug 修了他能自己打开文件、找到问题、改完、跑测试、告诉你结果。Claude Code、Codex 这些都是 Agent 层面的产品。1.3 终端复用工具选型为什么我最终选了 tmux要实现一个终端管多个 Agent核心工具就是终端复用器。这类工具能让你在一个物理终端窗口里创建多个虚拟会话每个会话可以独立运行程序随时切换、分离、重新连接。市面上常见的几个选择工具特点适合场景tmux老牌、稳定、配置灵活、社区大长期跑后台任务、多会话管理screen更老、功能少但极简只想要最基础的会话保持Zellij新秀、自带布局和提示、对新手友好喜欢开箱即用、不想写配置Tabby图形化终端工具、支持分屏和插件喜欢 GUI 但又要终端功能我最后选的是tmux原因有几个第一它足够稳定我有个会话跑了三天没掉过第二它的send-keys功能可以让我用脚本往指定会话里发命令这为后面做总管自动化打下了基础第三它的状态栏可以自定义我能把当前所有 Agent 的状态直接显示在底部一眼就能看到谁在忙谁闲着。Zellij 我也试过界面确实好看自带的状态提示对新手很友好但它的脚本化能力不如 tmux 成熟我想做自动化的时候发现有些操作得绕路。Tabby 适合喜欢图形界面的人但它的会话管理更偏向标签页而不是后台常驻关掉窗口会话就没了不太符合我Agent 要一直跑着的需求。提示如果你只是偶尔用用Zellij 的体验确实更舒服但如果你打算把 Agent 当成日常开发的一部分长期跑tmux 的稳定性和可脚本化能力更值得投入时间学习。1.4 整体架构一个总管脚本 tmux 会话池我的方案说起来不复杂用 tmux 创建多个命名会话每个会话里跑一个 Claude Code 实例对应一个项目目录。然后写一个 shell 脚本作为总管负责列出所有会话、显示状态、快速切换、批量启动。这个总管脚本我给它起名叫agent-hub放在~/bin/下面随时可以调用。它的核心功能就四个list列出所有正在跑的 Agent 会话显示项目名和运行状态new新建一个 Agent 会话指定项目目录和名称attach切换到某个 Agent 会话kill关闭某个 Agent 会话听起来简单但每个功能背后都有一些细节要处理。比如显示运行状态这件事Claude Code 在跑的时候终端里是有输出的你怎么判断它是正在思考还是等待输入我一开始想用进程状态判断后来发现不准最后用的是最后输出时间加是否有等待输入的提示符两个条件综合判断。再比如批量启动我早上到工位第一件事就是跑agent-hub start-all它会读取一个配置文件把昨天没关的几个项目重新拉起来。这个配置文件就是一个简单的文本文件每行一个项目路径和名称改起来很方便。2. 核心细节解析与实操要点2.1 tmux 会话命名规范别用默认的数字编号tmux 默认的会话名是 0、1、2 这样的数字你tmux ls看到的就是一堆数字根本分不清谁是谁。所以第一件事就是给会话起有意义的名字。我的命名规范是agent-项目名比如agent-blog、agent-api、agent-script。这样在tmux ls里一眼就能看出来哪个是哪个。创建的时候用tmux new-session -d -s agent-blog -c ~/projects/blog这里-d表示后台创建不立即进入-s指定会话名-c指定工作目录。创建完之后再往里面发命令启动 Claude Codetmux send-keys -t agent-blog claude Enter注意Enter是单独一个参数不是字符串的一部分。我第一次写的时候写成了claude\n结果 tmux 把\n当成了普通字符发过去Claude Code 没启动起来排查了半天才发现是这个问题。注意send-keys发送特殊按键时按键名要作为独立参数传入比如Enter、C-cCtrlC、Escape。不要试图用转义字符模拟。2.2 会话保活为什么你的 Agent 总是掉线很多人用 tmux 会遇到一个问题SSH 断开之后tmux 会话里的程序好像卡住了重新连上去发现 Claude Code 还在跑但输出停在了断开前的那一刻。这不是 tmux 的问题而是程序本身对终端断开的处理方式不同。Claude Code 这类交互式程序在检测到终端断开时可能会进入一种暂停状态等待终端重新连接。解决办法是在启动 Claude Code 之前先设置好终端环境变量让它知道即使终端断了我也要继续跑。我在agent-hub脚本里加了一行export TERMxterm-256color这行看起来不起眼但它确保了 Claude Code 在 tmux 会话里能正确识别终端类型不会因为终端类型不识别而进入降级模式。另外tmux 本身有个remain-on-exit选项我设成了on这样即使 Claude Code 因为某种原因退出了会话窗口也不会立刻关闭我能看到最后的错误信息。tmux set-option -g remain-on-exit on这个设置救过我好几次。有一次 Claude Code 因为 API 配额用完直接退出了如果窗口直接关掉我根本不知道发生了什么。设了remain-on-exit之后窗口还在最后一行显示quota exceeded我一看就明白了。2.3 状态检测怎么知道 Agent 是在忙还是闲着这是整个总管里最麻烦的部分。Claude Code 在跑的时候终端里会有各种输出有时候是它在思考显示一个转圈或者省略号有时候是它在等你输入显示一个输入提示符有时候是它在执行工具调用显示Reading file...之类的。我试过几种检测方案方案一看进程 CPU 占用。用ps查 Claude Code 进程的 CPU 使用率高就是忙低就是闲。但这个不准因为 Claude Code 在等待 API 响应的时候 CPU 也很低但那时候它其实是在忙。方案二看最后输出时间。用tmux capture-pane抓取会话最后几行看时间戳。如果最后输出在 5 秒内说明它在活跃如果超过 30 秒没输出可能是在等你输入。这个方案比第一个准但有个问题Claude Code 在思考的时候可能 10 秒都不输出你会误判成闲着。方案三综合判断。我最后用的是这个先抓取最后 5 行看有没有等待输入的提示符比如或者?如果有就是等待输入如果没有再看最后输出时间30 秒内有输出就是运行中超过 30 秒就是可能卡住。抓取命令是tmux capture-pane -t agent-blog -p | tail -5-p表示输出到标准输出而不是保存到缓冲区。然后我用 grep 匹配提示符if tmux capture-pane -t agent-blog -p | tail -3 | grep -q ^ ; then echo 等待输入 fi这个判断逻辑不是 100% 准确但实际用下来误判率很低足够我日常使用了。2.4 配置文件设计让总管知道有哪些项目agent-hub需要一个配置文件来知道有哪些项目可以管理。我用的是一个简单的文本文件~/.agent-hub.conf格式是每行名称:路径blog:/home/user/projects/blog api:/home/user/projects/api-server script:/home/user/projects/temp-script解析的时候用cut或者awk按冒号分割就行。这个设计的好处是简单改起来不用动脚本直接编辑文本文件就行。我也考虑过用 JSON 或者 YAML但觉得没必要杀鸡用牛刀。实操心得配置文件里不要写~要写绝对路径。因为脚本执行的时候工作目录可能不是你想象的那个~展开可能会出问题。我踩过这个坑脚本在 cron 里跑的时候~展开成了/root结果找不到项目目录。3. 实操过程与核心环节实现3.1 环境准备从零开始装好 tmux 和 Claude Code如果你还没装 tmux在 Ubuntu 或者 Debian 系上直接sudo apt update sudo apt install tmux -ymacOS 上用 Homebrewbrew install tmux装完之后验证一下tmux -V应该输出类似tmux 3.3a的版本号。建议用 3.0 以上的版本因为一些新特性比如更好的脚本化支持在老版本上没有。Claude Code 的安装官方推荐的方式是用 npm 全局安装npm install -g anthropic-ai/claude-code装完之后在终端里敲claude应该能看到欢迎界面。第一次运行会引导你登录或者配置 API Key。如果你是在 WSL 2 的 Ubuntu 里用注意要在 WSL 的终端里装不是在 Windows 的 PowerShell 里装。注意Claude Code 需要 Node.js 18 以上版本。如果你node -v看到的是 16 或者更低先升级 Node。我见过有人卡在这一步以为是 Claude Code 的问题其实是 Node 版本太老。3.2 编写 agent-hub 脚本核心逻辑逐段拆解脚本我放在~/bin/agent-hub用 bash 写。开头是标准的 shebang 和配置加载#!/bin/bash CONF$HOME/.agent-hub.conf然后是list功能的实现list_agents() { echo 当前 Agent 会话 echo ---------------- while IFS: read -r name path; do if tmux has-session -t agent-$name 2/dev/null; then status$(check_status agent-$name) echo $name [$status] $path fi done $CONF }check_status函数就是前面说的综合判断逻辑check_status() { local session$1 local last_lines last_lines$(tmux capture-pane -t $session -p | tail -5) if echo $last_lines | grep -q ^ ; then echo 等待输入 elif echo $last_lines | grep -q Thinking\|Working\|Reading; then echo 运行中 else echo 空闲 fi }new功能创建新会话并启动 Claude Codenew_agent() { local name$1 local path$2 if tmux has-session -t agent-$name 2/dev/null; then echo 会话 agent-$name 已存在 return 1 fi tmux new-session -d -s agent-$name -c $path tmux send-keys -t agent-$name claude Enter echo 已创建 agent-$name目录$path }attach功能就是简单的tmux attachattach_agent() { local name$1 if ! tmux has-session -t agent-$name 2/dev/null; then echo 会话 agent-$name 不存在 return 1 fi tmux attach -t agent-$name }kill功能关闭会话kill_agent() { local name$1 tmux kill-session -t agent-$name 2/dev/null echo 已关闭 agent-$name }最后是命令分发case $1 in list) list_agents ;; new) new_agent $2 $3 ;; attach) attach_agent $2 ;; kill) kill_agent $2 ;; *) echo 用法: agent-hub {list|new|attach|kill} [参数] ;; esac给脚本加执行权限chmod x ~/bin/agent-hub确保~/bin在 PATH 里如果不在在.bashrc或.zshrc里加一行export PATH$HOME/bin:$PATH3.3 批量启动早上到工位一键拉起所有 Agent我写了一个start-all子命令读取配置文件把所有项目都拉起来start_all() { while IFS: read -r name path; do if ! tmux has-session -t agent-$name 2/dev/null; then new_agent $name $path sleep 1 fi done $CONF list_agents }注意那个sleep 1这是为了让每个 Claude Code 实例有足够时间启动不然同时发太多send-keys可能会乱。我试过不加 sleep结果有几个会话的claude命令没发进去原因是 tmux 还在初始化会话send-keys发早了。实操心得批量操作时每个会话之间加 1 秒延迟看起来慢但比出问题后排查快得多。我一开始追求秒起结果三个会话里有一个没起来又得手动补反而更费时间。3.4 状态栏定制把 Agent 状态显示在 tmux 底部tmux 的状态栏可以自定义我把当前所有 Agent 的状态直接显示在底部这样不用敲list也能看到概况。在~/.tmux.conf里加set -g status-right #(~/bin/agent-hub status-bar) set -g status-interval 5status-bar是脚本里的一个子命令输出简短的统计信息比如Agents: 3 running, 1 idle。status-interval 5表示每 5 秒刷新一次。这个功能不是必须的但用起来很舒服。你正在一个会话里干活余光扫一眼底部就知道其他 Agent 有没有在等你。4. 常见问题与排查技巧实录4.1 Claude Code 启动后没反应终端里一片空白这是最常见的问题原因通常有三个原因一Node 版本不对。Claude Code 需要 Node 18如果你系统里默认的 Node 是 16启动会静默失败。排查方法在 tmux 会话里手动敲node -v看看版本。原因二API Key 没配置。第一次用 Claude Code 需要配置 API Key 或者登录。如果你在非交互环境下启动它可能卡在登录提示那里。排查方法tmux attach进去看看最后一行显示什么。原因三工作目录不存在。tmux new-session -c指定的目录如果不存在tmux 会创建会话但工作目录是默认的Claude Code 启动后可能因为找不到项目文件而行为异常。排查方法tmux attach进去后敲pwd看看当前目录。4.2 会话里的中文显示乱码这个问题在 WSL 2 的 Ubuntu 里特别常见。原因是 locale 没设置好。解决办法是在~/.bashrc里加export LANGen_US.UTF-8 export LC_ALLen_US.UTF-8如果系统里没有en_US.UTF-8先安装sudo apt install locales -y sudo locale-gen en_US.UTF-8然后在 tmux 里也要设置set -g default-terminal screen-256color注意改完.bashrc后要重新加载或者新开终端才生效。在 tmux 会话里改的话要source ~/.bashrc或者重启会话。4.3 send-keys 发送的命令没有执行除了前面说的Enter要单独传参之外还有一个常见原因是会话还没准备好。tmux 创建会话是异步的new-session -d返回的时候会话可能还在初始化。如果你紧接着就send-keys命令可能发到了一个还没准备好的终端里。解决办法就是加延迟tmux new-session -d -s agent-$name -c $path sleep 0.5 tmux send-keys -t agent-$name claude Enter0.5 秒通常够了如果机器慢可以加到 1 秒。4.4 常见问题速查表现象可能原因排查方法解决Claude Code 启动无反应Node 版本低node -v升级到 18中文乱码locale 未设置locale设置 UTF-8send-keys 无效会话未就绪加 sleep延迟 0.5-1 秒会话断开后程序暂停终端类型问题echo $TERM设xterm-256color状态检测误判提示符不匹配抓取最后几行看调整 grep 模式批量启动部分失败并发冲突逐个检查加延迟逐个启动4.5 独家避坑不要把 Agent 会话和日常终端混在一起我一开始图方便把 Claude Code 会话和我日常敲命令的终端放在同一个 tmux 服务器里。结果有一次我手滑tmux kill-server把所有会话都关了包括跑了两个小时的 Claude Code 会话上下文全丢。后来我学乖了给 Agent 单独用一个 tmux sockettmux -L agent-socket new-session -d -s agent-$name -c $path-L指定 socket 名称这样 Agent 的 tmux 服务器和日常用的完全隔离。你tmux kill-server只会杀掉默认 socket 的会话不会影响 Agent。要管理 Agent 的时候加-L agent-socket就行。这个技巧在脚本里就是统一加一个变量TMUX_SOCKET-L agent-socket tmux $TMUX_SOCKET new-session -d -s agent-$name -c $path所有 tmux 命令都带上这个变量就实现了隔离。4.6 关于 MCP 和 Codex 的补充说明有朋友问能不能把 Codex 也纳入这个总管体系。理论上是可以的因为 Codex 也是终端里的交互式程序tmux 一样能管。但 Codex 的启动命令和 Claude Code 不一样你需要改一下new_agent函数里的启动命令或者加一个参数来指定用哪个 Agent。MCPModel Context Protocol是另一个层面的东西它解决的是Agent 怎么调用外部工具的问题和怎么管理多个 Agent是两个维度。你可以理解为MCP 是给单个 Agent 扩展能力的而 tmux 总管是管理多个 Agent 实例的。两者不冲突可以同时用。如果你在用 MCP 的时候遇到连接问题比如某个 MCP Server 启动失败排查思路是先单独在终端里跑那个 MCP Server 的命令看能不能正常启动如果能再检查 Claude Code 的 MCP 配置里路径和参数对不对。MCP 的问题通常出在路径和权限上和 tmux 没关系。5. 进阶玩法让总管更聪明一点5.1 自动记录每个 Agent 的操作日志Claude Code 在 tmux 会话里的所有输出都可以用pipe-pane记录到文件tmux pipe-pane -t agent-$name -o cat ~/agent-logs/$name.log-o表示只在新输出时追加不会重复记录已有内容。这样每个 Agent 的操作历史都有日志可查万一它改错了什么你可以翻日志看它到底做了什么。日志文件建议按日期分目录不然时间长了会很大LOG_DIR$HOME/agent-logs/$(date %Y-%m-%d) mkdir -p $LOG_DIR tmux pipe-pane -t agent-$name -o cat $LOG_DIR/$name.log5.2 用 fzf 做交互式选择如果你觉得敲agent-hub attach blog还是太麻烦可以结合fzf做一个交互式选择select_agent() { local selected selected$(list_agents | fzf | awk {print $1}) if [ -n $selected ]; then attach_agent $selected fi }这样你敲agent-hub select会弹出一个列表用方向键选一个回车就切过去了。fzf的安装很简单sudo apt install fzf或者brew install fzf就行。5.3 定时检查卡住的 Agent有时候 Claude Code 会因为网络问题或者 API 问题卡住你如果不主动去看它可能卡在那里半小时。我加了一个定时检查每 5 分钟跑一次发现超过 10 分钟没输出的会话就标记出来check_stuck() { while IFS: read -r name path; do local sessionagent-$name if tmux has-session -t $session 2/dev/null; then local last_output last_output$(tmux capture-pane -t $session -p | tail -1) if [ -z $last_output ]; then echo 警告$name 可能卡住 fi fi done $CONF }这个检查逻辑比较粗糙但能抓到大部分明显卡住的情况。更精细的做法是记录每次输出的时间戳然后算差值但那样脚本会复杂很多我觉得没必要。5.4 会话迁移换电脑时怎么把 Agent 带走tmux 会话本身不能跨机器迁移但你可以把配置文件、日志、以及每个项目的 git 状态带走。我的做法是把~/.agent-hub.conf和~/bin/agent-hub同步到新机器把~/agent-logs/目录打包带走在新机器上重新start-allClaude Code 会重新启动但项目文件通过 git 同步过来上下文虽然丢了但代码状态是一致的如果你想让 Claude Code 记住之前的对话那得看它本身有没有导出功能。目前 Claude Code 的会话上下文是存在本地的具体位置取决于版本你可以查一下文档。但我的经验是与其纠结迁移上下文不如把重要的决策和约定写进项目的CLAUDE.md文件里这样新会话启动时 Claude Code 会自动读取相当于持久化的上下文。实操心得CLAUDE.md是 Claude Code 的一个很实用的功能你可以在项目根目录放一个里面写清楚项目结构、编码规范、常用命令。这样每次新启动一个会话它都会先读这个文件省去你重复解释的时间。我每个项目都有一个内容不多但很管用。5.5 资源占用监控别让 Agent 把机器跑满同时跑多个 Claude Code 实例每个实例本身占的资源不多但如果它们同时在跑测试或者构建CPU 和内存会飙升。我加了一个简单的监控monitor_resources() { echo Agent 资源占用 for session in $(tmux $TMUX_SOCKET list-sessions -F #{session_name} 2/dev/null); do local pid pid$(tmux $TMUX_SOCKET list-panes -t $session -F #{pane_pid} 2/dev/null | head -1) if [ -n $pid ]; then local cpu mem cpu$(ps -p $pid -o %cpu 2/dev/null | tr -d ) mem$(ps -p $pid -o %mem 2/dev/null | tr -d ) echo $session: CPU ${cpu}% MEM ${mem}% fi done }这个输出能让你快速看到哪个 Agent 在吃资源。如果某个 Agent 的 CPU 一直很高可能是它在跑一个死循环或者大构建你可以attach进去看看情况。6. 我踩过的几个印象深刻的坑6.1 那个让我丢了半小时上下文的 kill-server前面提过一次但值得再强调永远不要在日常终端和 Agent 终端混用同一个 tmux socket。我那次kill-server之后两个小时的对话上下文全没了重新跟 Claude Code 解释项目背景花了半小时。从那以后我就用-L agent-socket隔离再也没出过这个问题。6.2 send-keys 里的特殊字符陷阱有一次我想往会话里发一个带引号的命令结果引号被 shell 提前解析了发过去的命令少了一半。后来学乖了复杂命令先写到临时文件然后发source /tmp/cmd.sh。或者用send-keys -lliteral 模式逐字发送避免特殊字符被解释。6.3 状态检测的假空闲Claude Code 在等待 API 响应的时候终端可能十几秒没输出我的状态检测会误判成空闲。后来我调整了逻辑把最后输出时间的阈值从 10 秒放宽到 30 秒并且加了一个如果最后一行是省略号或者转圈字符就算运行中的判断。这个调整之后误判少了很多。6.4 WSL 2 里的终端大小问题在 WSL 2 里用 tmux有时候会发现终端大小不对Claude Code 的界面显示错位。原因是 WSL 的终端大小没有正确传递给 tmux。解决办法是在~/.tmux.conf里加set -g aggressive-resize on set -g window-size latestaggressive-resize让 tmux 根据当前连接的客户端调整大小window-size latest让窗口大小跟随最新连接的客户端。这两个设置加上之后WSL 里的显示就正常了。6.5 配置文件路径的坑前面提过~展开的问题这里再补充一个如果你在脚本里用cd切换了目录后面的相对路径就会基于新目录。我的建议是脚本里所有路径都用绝对路径或者在最开头就把配置文件的绝对路径算好CONF$(cd $(dirname $0) pwd)/.agent-hub.conf这样不管从哪里执行脚本配置文件路径都是对的。7. 后续可以怎么扩展这套总管目前够我用了但如果你想让它在团队里共享可以考虑几个方向。一是把配置文件放到共享目录团队成员各自维护自己的项目列表二是加一个 Web 界面用简单的 HTTP 服务把状态暴露出来这样在浏览器里也能看三是结合通知系统某个 Agent 完成任务或者卡住的时候发个提醒。不过我得说工具这东西够用就行别为了完善而过度工程化。我一开始也想做 Web 界面后来发现终端里agent-hub list已经够快了没必要再开个浏览器。把时间花在写代码上比花在折腾工具上划算。最后分享一个我最近才发现的技巧Claude Code 支持在项目里放一个.claude/settings.json可以配置一些项目级别的行为比如默认的模型、是否自动确认某些操作。如果你同时管多个项目每个项目的配置可以不一样这样总管拉起来的每个 Agent 都会自动读取自己项目的配置不用你手动调整。这个功能我还在摸索但初步用下来感觉很实用。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门