13  第 11 章 Agent 自动化

13.1 概述

前面几章我们一直在手动操作 Herdr——用键盘切窗格、用鼠标点 Agent、用对话框输入指令。这很好,但远不是 Herdr 的全部实力。

Herdr 的真正威力在于可编程:你可以用脚本控制 Agent,甚至让 Agent 控制 Agent。本章将深入 Herdr 的自动化层,涵盖三大原语、控制面选择、Agent 生命周期管理和完整的编排 Recipes。

读完本章,你将能够:

  • 理解 Layout / Pane / Agent 三大原语在自动化中的角色
  • 精准选择 pane runagent startagent prompt 等控制原语
  • 编写可靠的多 Agent 编排脚本
  • 处理超时、阻塞、异常等边界情况

13.2 三大原语回顾

在进入自动化之前,我们需要重新审视 Herdr 的三大核心原语。在日常交互中,它们的边界很模糊;但在自动化场景中,理解它们的分工至关重要。

graph TB
    subgraph "Layout 层"
        L["Layout<br/>空间结构"]
    end
    subgraph "Pane 层"
        P1["Pane<br/>终端窗格"]
        P2["Pane<br/>终端窗格"]
        P3["Pane<br/>终端窗格"]
    end
    subgraph "Agent 层"
        A1["Agent<br/>AI 代理"]
        A2["Agent<br/>AI 代理"]
    end
    L --> P1
    L --> P2
    L --> P3
    P1 --> A1
    P2 --> A2
    P3 -.->|"无 Agent<br/>普通 Shell"| N[普通进程]

13.2.1 Layout(布局)

Layout 是对空间结构的描述——窗格怎么分割、比例多少、谁在左谁在右。它是声明式的:你定义「长什么样」,而不是「怎么做到」。

# 导出当前布局
herdr layout export > my-layout.json

# 应用布局
herdr layout apply my-layout.json

在自动化中,Layout 用于初始化工作空间的结构。

13.2.2 Pane(窗格)

Pane 是终端进程的容器。每个 Pane 有唯一的 pane_id(如 p1p2),可以运行任何东西——Shell、TUI 程序、AI Agent。

Pane 层的控制原语:

原语 作用 示例
pane run 在窗格中执行命令(阻塞直到完成) herdr pane run p1 -- "git status"
pane send-text 向窗格发送文本(不回车) herdr pane send-text p1 -- "npm test"
pane send-keys 发送特殊按键 herdr pane send-keys p1 -- Enter
pane wait-output 等待输出匹配模式 herdr pane wait-output p1 --pattern "done"
pane read 读取窗格当前可见内容 herdr pane read p1

13.2.3 Agent(代理)

Agent 是 Pane 中运行的 AI 代理实例。它有名字、有状态、有生命周期。Agent 层的控制原语更高级——它理解 Agent 的语义,知道什么时候在「工作」、什么时候在「等待」。

原语 作用 示例
agent start 在指定窗格启动 Agent herdr agent start --pane p2 --kind claude my-helper
agent prompt 向 Agent 发送提示 herdr agent prompt my-helper -- "修复这个 bug"
agent send-keys 向 Agent 窗格发送按键 herdr agent send-keys my-helper -- Escape
agent wait 等待 Agent 进入指定状态 herdr agent wait my-helper --until done
agent read 读取 Agent 输出 herdr agent read my-helper --source recent
Note

关键区别:Pane 层原语操作的是「终端」——它不关心里面跑的是什么。Agent 层原语操作的是「AI 代理」——它理解 idle/working/blocked/done 等状态。选择哪一层取决于你需要什么粒度的控制。


13.3 Agent 身份与启动

13.3.1 Pane ID vs Agent Name

每个 Agent 有两种标识方式:

  • Pane ID:空间位置标识(p1p2),随布局变化可能改变
  • Agent Name:语义名称(my-helperreviewer),全局唯一,与位置无关
# 用 Pane ID 操作
herdr agent prompt --pane p2 -- "hello"

# 用 Agent Name 操作(推荐)
herdr agent prompt my-helper -- "hello"
Tip

最佳实践:在自动化脚本中,始终使用 Agent Name 而非 Pane ID。布局变化时 Pane ID 可能重新编号,但 Agent Name 保持稳定。

13.3.2 命名规则

Agent 名称必须符合以下正则表达式:

^[a-z][a-z0-9_-]{0,31}$

即:

  • 以小写字母开头
  • 只包含小写字母、数字、下划线和连字符
  • 长度 1-32 个字符

✅ 合法名称:helpermy-helpercode-reviewer-2a
❌ 非法名称:Helper(大写)、1helper(数字开头)、my.helper(点号)、超长名称超过三十二个字符的名称(中文 + 超长)

13.3.3 agent start 语法

herdr agent start [--pane <pane_id>] [--kind <kind>] [--timeout <seconds>] <name> [-- <agent-args>...]

13.3.3.1 选项详解

选项 说明 默认值
--pane 在哪个窗格启动 Agent。省略则使用当前焦点窗格 当前焦点
--kind Agent 类型(决定底层模型) 配置文件默认值
--timeout Agent 空闲超时(秒),超时后自动停止 配置文件默认值
-- <args> 透传给 Agent 的额外参数

13.3.3.2 支持的 Kind 完整列表

Kind 说明 典型用途
claude Claude Code (Anthropic) 通用编程、架构设计
codex Codex CLI (OpenAI) 推理、数学密集任务
gemini Gemini CLI (Google) 长上下文任务
kimi Kimi Code (Moonshot) 中文场景
grok Grok CLI (xAI) 快速原型
qwen Qwen Code (Alibaba) 混合中英文
custom 自定义 Agent 任何符合 Herdr Agent 协议的程序
# 在 p2 窗格启动名为 helper 的 Claude Agent
herdr agent start --pane p2 --kind claude helper

# 启动 Codex Agent,并透传参数
herdr agent start --pane p3 --kind codex reviewer -- --model o4-mini

# 在当前窗格启动,5 分钟空闲超时
herdr agent start --kind claude --timeout 300 my-agent
Warning

透传参数的 ---- 之后的所有参数会原样传递给 Agent 程序。如果忘了加 --,参数会被 Herdr 解析,可能导致意外的行为。


13.4 控制面选择指南

面对如此多的控制原语,初学者常困惑「该用哪个」。下面的决策表帮你快速选择。

13.4.1 决策流程图

flowchart TD
    A[需要控制什么?] -->|普通进程/Shell| B{需要等待输出吗?}
    A -->|AI Agent| C{需要语义理解吗?}
    
    B -->|否,只发送| D[pane send-text]
    B -->|是,等输出匹配| E[pane wait-output]
    B -->|是,执行并等待| F[pane run]
    
    C -->|否,模拟按键| G[agent send-keys]
    C -->|是,发送提示| H[agent prompt]
    C -->|是,等待状态| I[agent wait]
    
    H --> J{需要同步等待回复吗?}
    J -->|是| K[agent prompt --wait]
    J -->|否| L[agent prompt 异步]

13.4.2 完整对照表

场景 推荐原语 替代方案 原因
在窗格执行命令并获取结果 pane run pane send-text + pane wait-output 一步到位
向窗格发送命令但不等待 pane send-text 非阻塞
发送特殊按键(Ctrl+C 等) pane send-keys send-text 不处理特殊键
等待窗格出现特定输出 pane wait-output 轮询 pane read 原子操作更可靠
启动 AI Agent agent start 初始化 Agent 生命周期
向 Agent 发送提示 agent prompt pane send-text prompt 理解 Agent 语义
同步等待 Agent 回复 agent prompt --wait agent prompt + agent wait 减少 TOCTOU 窗口
等待 Agent 完成工作 agent wait --until done 轮询 agent get 原子等待
打断 Agent(Escape) agent send-keys pane send-keys 语义一致
读取 Agent 当前输出 agent read pane read 支持 source 参数
Note

一般原则:如果操作对象是 Agent,优先用 Agent 层原语。它们理解 Agent 的生命周期,能在合适的时机做合适的事。


13.5 agent prompt –wait 详解

agent prompt --wait 是自动化中最常用、也最容易出问题的原语之一。它向 Agent 发送提示,并阻塞直到 Agent 回复(回到 idle 状态)。

13.5.1 工作原理

sequenceDiagram
    participant Script as 你的脚本
    participant H as Herdr
    participant A as Agent
    
    Script->>H: agent prompt --wait my-agent "修复 bug"
    H->>A: 发送提示文本
    A->>A: 处理中(working)
    Note over A: Agent 思考、写代码
    A->>H: 回复完成(idle)
    H->>Script: 返回结果

13.5.2 生命周期观察

如果 Agent 当前不在工作状态(idle),--wait 会先发送提示,然后等待 Agent 进入 working 状态再回到 idle。Herdr 要求这个状态转换在 5 秒内发生,否则触发 agent_prompt_stalled 错误。

正常流程:idle → working → idle ✅
超时情况:idle → (5s 仍在 idle) ❌ agent_prompt_stalled

13.5.3 错误处理

# 可能超时的场景
herdr agent prompt --wait my-agent "分析这个 1000 行的文件" --timeout 120

# 处理 stalled 错误
result=$(herdr agent prompt --wait my-agent "hello" 2>&1)
if echo "$result" | grep -q "agent_prompt_stalled"; then
    echo "Agent 未响应,可能已挂起"
    # 重试或检查 Agent 状态
    herdr agent get my-agent
fi
Warning

agent_prompt_stalled 常见原因: 1. Agent 进程已崩溃但 Pane 仍存在 2. Agent 正在等待交互式输入(如确认对话框) 3. Agent 模型 API 限流或网络超时 4. Agent 尚未完全启动(需要更长预热时间)

解决方案:先检查 Agent 状态(agent get),必要时重启。

13.5.4 –timeout 参数

# 设置 60 秒超时
herdr agent prompt --wait my-agent "修复 bug" --timeout 60

# 不设置 timeout 时,使用默认值(通常 300 秒)
herdr agent prompt --wait my-agent "hello"

超时后命令返回非零退出码,但 Agent 仍在后台运行——它可能只是需要更多时间。


13.6 agent wait –until 详解

agent wait 用于等待 Agent 进入指定状态,不发送任何提示。这在你已经用 agent prompt(不带 –wait)异步发送了任务后特别有用。

13.6.1 支持的状态

状态 说明 典型场景
idle Agent 空闲,等待指令 等待 Agent 处理完毕
done Agent 标记任务完成 等待 Agent 明确报告完成
blocked Agent 被阻塞,需要人类介入 等待需要审批的场景

13.6.2 使用示例

# 异步发送任务
herdr agent prompt my-helper -- "重构 auth 模块"

# 等待 Agent 完成(最多 10 分钟)
herdr agent wait my-helper --until done --timeout 600

# 等待 Agent 被阻塞(需要审批时)
herdr agent wait my-helper --until blocked --timeout 300

13.6.3 多状态等待

你可以同时等待多个状态:

# 等待完成或阻塞(任一满足即返回)
herdr agent wait my-helper --until done --until blocked

13.6.4 超时处理

# 超时返回退出码 124
if ! herdr agent wait my-helper --until done --timeout 300; then
    echo "Agent 超时未完成"
    # 检查当前状态
    status=$(herdr agent get my-helper --format json | jq -r '.state')
    echo "当前状态: $status"
    
    if [ "$status" = "working" ]; then
        echo "Agent 仍在工作,继续等待..."
        herdr agent wait my-helper --until done --timeout 300
    elif [ "$status" = "blocked" ]; then
        echo "Agent 被阻塞,需要介入"
        herdr agent read my-helper --source recent
    fi
fi
Tip

组合模式:先 agent prompt(异步)→ agent wait(等状态),比 agent prompt --wait 更灵活。你可以在等待期间做其他事情——读取中间输出、检查进度等。


13.7 输出读取策略

正确读取 Agent(或 Pane)的输出是自动化的关键。Herdr 提供四种读取源(source),各有适用场景。

13.7.1 四种 Source

Source 说明 包含内容 典型用途
visible 当前屏幕可见内容 视口范围内的文本 快速检查当前状态
recent 最近的输出 滚动缓冲区的最近部分 读取 Agent 的回复
recent-unwrapped 最近的输出(不去除换行) 原始 ANSI 文本 调试终端控制序列
detection Agent 检测用文本 去除噪声的纯净文本 程序化判断 Agent 状态
# 读取可见区域(人眼看到的)
herdr agent read my-helper --source visible

# 读取最近输出(最常用)
herdr agent read my-helper --source recent

# 调试用:原始未处理输出
herdr agent read my-helper --source recent-unwrapped

# 程序判断用
herdr agent read my-helper --source detection

13.7.2 –lines 参数

--lines 参数控制读取的行数,行为因 source 不同而略有差异:

# recent + --lines 50:读取最近 50 行
herdr agent read my-helper --source recent --lines 50

# visible + --lines 50:读取可见区域,最多 50 行
herdr agent read my-helper --source visible --lines 50

# 不指定 --lines:使用默认值(recent 默认 80 行,visible 默认视口高度)
herdr agent read my-helper --source recent

13.7.3 alternate-screen 限制

许多 TUI 程序(vim、less、Claude Code 的全屏界面)使用 alternate screen。当程序在 alternate screen 中运行时:

Warning

alternate-screen 限制: - visible 能看到 alternate screen 的内容 - recent 可能无法捕获 alternate screen 中的交互——因为切换回主屏幕时 alternate screen 内容会被丢弃 - 如果 Agent 使用了 alternate screen,recent 可能只包含切换前的旧内容

建议:对于 Claude Code 等使用全屏界面的 Agent,优先使用 visible source,或让 Agent 以非交互模式运行(减少 alternate screen 的使用)。


13.8 完整编排 Recipes

以下三个实战示例展示了从简单到复杂的多 Agent 编排模式。

13.8.1 Recipe 1:启动 Helper + 分配任务 + 等待 + 读取结果

这是最经典的编排模式:主脚本启动一个 Agent,给它任务,等它完成,读取结果。

#!/bin/bash
set -euo pipefail

# ============================================
# Recipe 1: 单 Agent 任务编排
# 场景:让 helper Agent 修复一个 bug
# ============================================

AGENT_NAME="bug-fixer"
BUG_DESCRIPTION="用户登录时偶发 500 错误,怀疑是 race condition"

echo "→ 启动 Agent..."
herdr agent start \
    --pane p2 \
    --kind claude \
    "$AGENT_NAME"

echo "→ 等待 Agent 就绪..."
# 新启动的 Agent 需要几秒初始化
sleep 3

echo "→ 发送任务..."
herdr agent prompt "$AGENT_NAME" -- "$BUG_DESCRIPTION"

echo "→ 等待完成..."
# 等待 Agent 进入 done 或 idle 状态
herdr agent wait "$AGENT_NAME" --until done --timeout 600

echo "→ 读取结果..."
output=$(herdr agent read "$AGENT_NAME" --source recent --lines 100)
echo "$output"

echo "→ 清理..."
herdr agent send-keys "$AGENT_NAME" -- q  # 退出 Agent
echo "✅ 完成"

13.8.2 Recipe 2:等待 Agent 请求输入 + 检查 + 交互

Agent 在工作过程中可能需要人类确认(如「是否执行这个命令?」)。这个 Recipe 展示了如何检测并响应这种场景。

#!/bin/bash
set -euo pipefail

# ============================================
# Recipe 2: 响应 Agent 的输入请求
# 场景:Agent 需要确认才能执行危险操作
# ============================================

AGENT_NAME="code-reviewer"

# 发送代码审查任务
herdr agent prompt "$AGENT_NAME" -- "审查 src/auth/ 目录的安全性"

# 主循环:持续监控 Agent 状态
while true; do
    # 等待 Agent 进入 blocked 或 done
    if herdr agent wait "$AGENT_NAME" \
        --until blocked --until done \
        --timeout 300; then
        
        # 检查当前状态
        status=$(herdr agent get "$AGENT_NAME" --format json | jq -r '.state')
        
        if [ "$status" = "done" ]; then
            echo "✅ Agent 已完成"
            herdr agent read "$AGENT_NAME" --source recent --lines 80
            break
        fi
        
        if [ "$status" = "blocked" ]; then
            echo "⚠️ Agent 请求输入"
            # 读取 Agent 的问题
            question=$(herdr agent read "$AGENT_NAME" --source recent --lines 20)
            echo "Agent 问: $question"
            
            # 自动批准安全操作
            if echo "$question" | grep -qiE "run.*(test|lint|format)"; then
                echo "→ 自动批准测试/lint 操作"
                herdr agent send-keys "$AGENT_NAME" -- y
            elif echo "$question" | grep -qiE "run.*(deploy|delete|drop)"; then
                echo "→ 危险操作,拒绝"
                herdr agent send-keys "$AGENT_NAME" -- n
            else
                # 交给人类决定
                echo "→ 需要人工确认,查看 Agent 输出:"
                herdr agent read "$AGENT_NAME" --source visible
                read -p "批准?(y/n) " answer
                herdr agent send-keys "$AGENT_NAME" -- "$answer"
            fi
        fi
    else
        echo "❌ 超时"
        break
    fi
done

13.8.3 Recipe 3:运行普通进程 + 等待输出(非 Agent)

并非所有自动化都涉及 Agent。有时你只需要在窗格中运行普通命令并等待结果。

#!/bin/bash
set -euo pipefail

# ============================================
# Recipe 3: 运行普通进程并等待输出
# 场景:在指定窗格运行测试,等待结果
# ============================================

PANE_ID="p3"
TEST_PATTERN="PASS|FAIL|Error"

echo "→ 在 $PANE_ID 中运行测试..."

# 方法 1:pane run(阻塞执行,获取 stdout)
echo "--- 方法 1: pane run ---"
result=$(herdr pane run "$PANE_ID" -- "npm test 2>&1")
echo "$result"

# 方法 2:send-text + wait-output(异步模式,适合长时间任务)
echo "--- 方法 2: send-text + wait-output ---"
herdr pane send-text "$PANE_ID" -- "npm test"
herdr pane send-keys "$PANE_ID" -- Enter

# 等待测试结果出现
echo "→ 等待测试完成..."
if herdr pane wait-output "$PANE_ID" \
    --pattern "$TEST_PATTERN" \
    --timeout 300; then
    
    output=$(herdr pane read "$PANE_ID" --source recent --lines 30)
    
    if echo "$output" | grep -q "PASS"; then
        echo "✅ 测试通过"
    elif echo "$output" | grep -q "FAIL"; then
        echo "❌ 测试失败"
        echo "$output"
        exit 1
    fi
else
    echo "❌ 测试超时"
    exit 124
fi

13.9 自动化脚本最佳实践

13.9.1 1. 始终设置超时

# ❌ 危险:没有超时,可能永远挂起
herdr agent wait my-helper --until done

# ✅ 安全:设置合理超时
herdr agent wait my-helper --until done --timeout 600

13.9.2 2. 使用 set -euo pipefail

#!/bin/bash
set -euo pipefail

# 任何命令失败立即退出
# 未定义变量报错
# 管道中任何环节失败都算失败

13.9.3 3. 清理策略

# 定义清理函数
cleanup() {
    echo "→ 清理资源..."
    herdr agent send-keys "$AGENT_NAME" -- q 2>/dev/null || true
    # 关闭临时窗格
    herdr pane close p3 2>/dev/null || true
}

# 注册退出时清理
trap cleanup EXIT ERR INT TERM

13.9.4 4. 结构化输出处理

# 使用 JSON 格式便于解析
status=$(herdr agent get my-helper --format json)
state=$(echo "$status" | jq -r '.state')
kind=$(echo "$status" | jq -r '.kind')
pane=$(echo "$status" | jq -r '.pane_id')

echo "Agent: $AGENT_NAME | State: $state | Kind: $kind | Pane: $pane"

13.9.5 5. 日志记录

LOG_FILE="/tmp/herdr-automation-$(date +%Y%m%d-%H%M%S).log"
log() { echo "[$(date '+%H:%M:%S')] $*" | tee -a "$LOG_FILE"; }

log "启动 Agent $AGENT_NAME"
herdr agent start --pane p2 --kind claude "$AGENT_NAME"
log "发送任务"
herdr agent prompt "$AGENT_NAME" -- "$TASK"
herdr agent wait "$AGENT_NAME" --until done --timeout 600
log "Agent 完成"

13.9.6 6. 幂等性设计

# 检查 Agent 是否已存在,避免重复创建
if ! herdr agent get "$AGENT_NAME" 2>/dev/null; then
    herdr agent start --pane p2 --kind claude "$AGENT_NAME"
    sleep 3  # 等待初始化
fi

13.9.7 7. 组合使用 Layout 和 Agent

```bash # 先应用布局,再在对应窗格中启动 Agent herdr layout apply layouts/code-review.json herdr agent start –pane p1 –kind claude orchestrator herdr agent start –pane p2 –kind codex reviewer herdr agent start –pane p3 –kind claude fixer sleep 5 # 等待就绪 herdr agent prompt orchestrator – “分析代码库并分配审查任务” herdr agent wait orchestrator –until idle –timeout 120