Harness 概念提出者宣布创业,瞄准的是一个正在变得具体的基础设施缺口:交互式开发、CI 自动化和多个 AI Agent 并行执行,目前各自维护进程、工作目录与日志,但它们本质上都在操作同一种东西,即一段可以持续运行、断线重连、转交控制权的开发会话。
为什么普通终端不够用
人类使用终端时,可以记住刚才执行过什么、当前目录在哪里,以及某个失败是否值得重试。AI Agent 没有这种天然连续性。一旦模型调用结束、网络连接中断或调度器重新分配任务,终端里的隐含状态就可能丢失。
一个面向 Agent 的持久化会话至少需要保存以下信息:
- 稳定的会话 ID,而不是依赖某次 SSH 或 WebSocket 连接
- PTY、环境变量、当前目录和仍在运行的子进程
- 标准输入输出记录,以及可以按位置继续读取的事件流
- 工作区、Git 分支和构建产物之间的对应关系
- 当前控制者、租约期限、权限和密钥使用范围
- 超时、退出码、资源消耗与人工接管记录
这里要区分两个概念。终端持久化意味着客户端断开后进程仍然运行;任务持久化则意味着主机重启后,系统还能根据日志、检查点和工作区恢复任务。tmux 能解决前者的一部分,却不能单独解决后者。
把开发、CI 和 Agent 放进同一种会话模型
统一会话体系的价值,不是让所有工具共享一个巨大的 shell,而是让它们遵守同一套生命周期协议。例如,一次会话可以依次经历:
created -> running -> waiting_for_input -> detached -> resumed -> completed
\-> cancelled
\-> expired
开发者可以在本地创建会话,Agent 接手执行测试,CI 在同一工作区验证结果;遇到需要判断的错误时,会话进入 waiting_for_input,再由人类附加终端处理。控制权可以变化,但会话 ID、输出记录和文件状态保持连续。
并行 Agent 则不应直接写入同一个工作目录。更稳妥的边界是“一项任务一个会话、一个 Git worktree、一个资源配额”。共享仓库历史没有问题,共享未提交文件通常会制造覆盖和竞态。
可以这样实践:用 tmux 做一个最小原型
下面的脚本不是来源中产品的接口,而是一个可运行的本地原型。它用 tmux 保持进程,用目录保存日志,并为会话提供 start、run、attach、tail 和 stop 操作。
运行前需要安装 tmux。将下面内容保存为 agent-session,然后执行 chmod +x agent-session。
#!/usr/bin/env bash
set -euo pipefail
STATE_DIR="${AGENT_SESSION_HOME:-$HOME/.agent-sessions}"
mkdir -p "$STATE_DIR"
usage() {
echo "usage: $0 {start|run|attach|tail|list|stop} [name] [args...]" >&2
exit 2
}
validate_name() {
[[ "$1" =~ ^[A-Za-z0-9._-]+$ ]] || {
echo "invalid session name: $1" >&2
exit 2
}
}
command="${1:-}"
case "$command" in
start)
name="${2:-}"; workspace="${3:-$PWD}"
[[ -n "$name" ]] || usage
validate_name "$name"
workspace="$(cd "$workspace" && pwd)"
session="agent-$name"
log_file="$STATE_DIR/$name.log"
tmux new-session -d -s "$session" -c "$workspace"
printf -v log_command 'cat >> %q' "$log_file"
tmux pipe-pane -o -t "$session" "$log_command"
printf '%s\n' "$workspace" > "$STATE_DIR/$name.workspace"
echo "started $name in $workspace"
;;
run)
name="${2:-}"; shift 2 || true
[[ -n "$name" && "$#" -gt 0 ]] || usage
validate_name "$name"
tmux send-keys -t "agent-$name" -- "$*" C-m
;;
attach)
name="${2:-}"; [[ -n "$name" ]] || usage
validate_name "$name"
exec tmux attach-session -t "agent-$name"
;;
tail)
name="${2:-}"; [[ -n "$name" ]] || usage
validate_name "$name"
touch "$STATE_DIR/$name.log"
exec tail -f "$STATE_DIR/$name.log"
;;
list)
tmux list-sessions -F '#S #{session_created_string}' 2>/dev/null || true
;;
stop)
name="${2:-}"; [[ -n "$name" ]] || usage
validate_name "$name"
tmux kill-session -t "agent-$name"
;;
*) usage ;;
esac
可以先用普通命令验证会话确实能在客户端退出后继续运行:
./agent-session start build "$PWD"
./agent-session run build 'while true; do date; sleep 5; done'
./agent-session tail build
按 Ctrl+C 只会退出日志查看,后台循环仍在运行。执行 ./agent-session attach build 可以接管终端,执行 ./agent-session stop build 才会结束会话。
并行任务可以结合 Git worktree 隔离:
git worktree add ../project-agent-a -b agent/a
git worktree add ../project-agent-b -b agent/b
./agent-session start agent-a ../project-agent-a
./agent-session start agent-b ../project-agent-b
./agent-session run agent-a 'npm test'
./agent-session run agent-b 'npm run lint'
接入真实 Agent 时,可以把 npm test 替换成团队使用的 Agent CLI,但不要把未经审查的模型输出直接拼接成高权限 shell 命令。命令参数、工作目录和环境变量都应经过策略校验。
从原型走向生产系统
生产级持久化终端还需要处理 tmux 没有覆盖的问题:主机故障恢复、跨节点调度、输出背压、密钥轮换、文件快照、审计保留和租约冲突。尤其需要防止两个 Agent 同时获得写控制权,或已经取消的任务继续占用凭据。
落地时可以按以下顺序推进:
- 先统一会话 ID、状态机和日志格式,不急着统一所有执行环境。
- 为每个 Agent 分配独立 worktree、容器或虚拟机,并设置 CPU、内存和执行时间上限。
- 将终端输出写入追加式事件流,同时把重要结果保存为结构化产物。
- 使用短期凭据和命令策略,默认阻止访问宿主机、生产网络与全局密钥。
- 设计人工接管流程,明确接管后 Agent 是暂停、只读还是继续运行。
- 用故障演练验证断网、模型超时、进程僵死和节点重启后的行为。
持久化终端真正要统一的不是界面,而是执行上下文。只有当会话可以被定位、观察、暂停、恢复和审计时,交互式开发、CI 与 AI Agent 才可能安全地使用同一套基础设施。