Skip to content

12. 非交互入口 ​

agentao 不只是一套 REPL。顶层命令还支持初始化、一次性 prompt、会话恢复、ACP server 模式,以及 skill / plugin 管理。

agentao init — 写 .env ​

第一次在项目里使用时,可以让向导生成 .env:

bash
agentao init

向导会询问 provider、API key、base URL、model,然后写入:

bash
LLM_PROVIDER=OPENAI
OPENAI_API_KEY=...
OPENAI_BASE_URL=https://api.openai.com/v1
OPENAI_MODEL=gpt-5.4

已有 .env 时会先询问是否覆盖。

agentao -p / --print — 一次性运行 ​

打印模式发送一个 prompt,输出回答,然后退出。

bash
agentao -p "总结 README"
cat issue.md | agentao --print "根据下面内容生成修复计划"

从 0.4.x 起,-p 是 agentao run --format text --prompt … 的薄壳,两者共用下文 agentao run 一节里的统一退出码表。

升级提示(0.3.x → 0.4.x): 旧版 -p 把"达到最大工具迭代数"映射为退出码 2。0.4.x 起这种情况是 4;2 现在表示"用法或 spec 校验失败"。

agentao run — 自动化结构化入口 ​

agentao run 是面向自动化的稳定面:把结构化 spec(来自 stdin 或 --spec)与显式 CLI 覆盖合并,跑一个 Agentao turn,输出机器可读的结果。

bash
# spec 从 stdin 进入
agentao run --format json < task.yaml

# spec 从文件进入,并用 flag 覆盖
agentao run --spec .agentao/tasks/review.yaml --model gpt-5.5 --format json

# 不写 spec 文件,直接给 prompt
agentao run --prompt "总结当前目录" --format json

--spec 与管道 stdin 互斥,同时给会以退出码 2 失败。

M0 spec 结构 ​

yaml
prompt: string                 # 必填(或用 --prompt 传入)。可模板化。
instructions: string           # 追加到 system prompt。可模板化。`.strip()` 后非空时
                               # 经 Agentao(project_instructions=…) 注入,并跳过
                               # AGENTAO.md 的磁盘读取。
parameters:                    # spec 级 --param 替换所用的类型化参数槽
  - name: string               # ASCII 标识符;不能是 Jinja 保留名
    required: boolean          # 默认 false;与 `default` 互斥
    default: string            # v1 仅支持字符串;若有 `choices` 必须在其中
    choices: [string]          # 可选枚举
cwd: string                    # 这次 run 的工作目录
model: string                  # 覆盖环境里的 LLM model
base_url: string               # 覆盖环境里的 base URL
permission_mode: read-only | workspace-write | full-access | plan
interaction_policy: reject | deny   # 默认 reject;见下文
max_denials: int               # 仅 deny;默认 5
permissions:
  allow:
    - tool: string             # glob — 与 ~/.agentao/permissions.json 同语法
      args: { ... }            # 可选参数模式
      domain:                  # 可选 URL/domain 匹配
        url_arg: string
        allowlist: [string]
        blocklist: [string]
  deny:
    - tool: string
      args: { ... }
      domain: { ... }
max_iterations: int            # 默认 100
skills: [string]               # 在自动发现的激活 skills 之上追加
thinking: string               # 思考深度(0.5.15):minimal | low | medium
                               # | high、模型接受的其他档位,或 "off"
replay: boolean                # 这次 run 启用 ReplayManager
output:
  format: text | json

extra="forbid" —— 未知 spec 字段会以退出码 2 失败。secrets(api_key)绝不在 spec 里接收,必须留在环境变量或宿主注入的 client 里。

CLI flag 只在用户显式提供时才覆盖 spec 值,argparse 默认值不会抹掉 spec 字段。

思考深度(thinking、--thinking) ​

thinking(或 --thinking LEVEL,后者优先于 spec)设置思考深度,与交互式的 /thinking 是同一个设置。Agentao 把档位写进该线路读取的请求字段:

线路请求字段档位
openai-completionsreasoning_effortminimal、low、medium、high。其他单个词原样发送,由 provider 校验
anthropic-messagesoutput_config.effortlow、medium、high、xhigh、max。其他词在发出任何请求前以退出码 2 失败
openai-responsesreasoning.effort同 openai-completions

off 移除显式设置的档位(例如 LLM_EXTRA_BODY 设置的),恢复 provider 的默认行为。它不承诺关闭模型的推理:默认就推理的模型仍会推理。请加引号写成 thinking: "off"。YAML 会把裸写的 off、false、no 读成布尔值 false,spec 也把它当作 off。true 和数字会被拒绝(退出码 2)。加了引号的 "false"、"no" 仍是字符串,会作为档位发送。多于一个词以退出码 2 失败。宿主的 LLM client 没有可写入的 extra_body dict 时,以退出码 1 失败(runtime_error)。结果 envelope 里的 thinking 给出该线路读取的字段里的档位(其他线路遗留的 reasoning_effort 不算),无论它来自 thinking 还是 LLM_EXTRA_BODY。它不保证请求里没有另一条线路的字段:宿主在 LLM_EXTRA_BODY 里设置的字段会原样发送。没有设置档位时不输出该字段。没有自动恢复:模型拒绝该档位时,每个请求都会失败。其他 provider 字段仍通过 LLM_EXTRA_BODY 发送。

交互策略(interaction_policy、--interaction-policy) ​

运行时没有人能批准需要审批的工具调用(规则判为 ask 的调用,例如不在只读白名单里的 shell 命令)。策略决定这个调用的结果:

策略需要审批的调用ask_user
reject(默认)结束运行:permission_required,退出码 3。此前的写入留在磁盘上,但这一轮到不了收尾步骤。interaction_required,退出码 3
deny(0.5.16)不执行。模型收到一条工具结果:非交互模式下无法审批,不要重试这个调用,可以改用当前权限规则允许的替代方式。这一轮继续。interaction_required,退出码 3,与 reject 相同

由 MCP Skills gate 发起的确认(同意使用服务器 skill 的内容,或在 skill 已加载时执行的调用)改为返回普通的“未获批准”结果,不提供替代路径,但仍计入 max_denials。deny 只改变 ask 的情形。被规则或 read-only 模式拒绝(deny)的调用,在两种策略下都仍以 permission_denied 结束运行。

max_denials(或 --max-denials N,默认 5)给 deny 设上限。它累计整个运行中每一次被拒绝的调用,前台子代理的调用也计入。(运行中没有后台子代理,见下文“输出契约”。)前 N 次拒绝返回给模型。第 N + 1 次与 reject 一样结束运行(permission_required,退出码 3,消息中注明 max_denials),同一批次的其他调用被取消。0 让 deny 等同于 reject。不设 interaction_policy: deny 却设了 max_denials 时退出码为 2,负值也是。

结果的 denials 列出每一个被拒绝的调用(tool_name、tool_call_id,由引擎规则发起 ask 时还有 matched_rule),成功和失败时都有。子代理的调用记为 [<agent>] <tool>,带子代理自己的 tool_call_id,没有 matched_rule(它的权限事件留在子代理那里)。tool_call_id 是 provider 给这次调用的 id,跨轮次不保证唯一:有的 provider 在每次响应里都把调用编号为 call_0、call_1……--format text 下每条写一行到 stderr。deny 下的退出码 0 表示模型给出了最终回答,不表示任务已经完成:模型想做的某一步可能在 denials 里,并且没有任何回滚。把结果当作完成之前,先检查 denials。

参数与模板渲染(--param) ​

prompt 与 instructions 是 Jinja2 模板,对类型化参数值做替换。其他 spec 字段不做模板化(给 permissions 或 skills 加模板容易引发陷阱)。

yaml
# .agentao/runs/review-pr.yaml
parameters:
  - name: pr_number
    required: true
  - name: depth
    default: shallow
    choices: [shallow, deep]
instructions: |
  你正在审查 PR #{{ pr_number }}。
  使用 {{ depth }} 模式:shallow = 只看表层;deep = 跨文件追踪数据流。
prompt: 审查 PR #{{ pr_number }},专注于正确性 bug。
permission_mode: workspace-write
max_iterations: 30
bash
agentao run --spec .agentao/runs/review-pr.yaml \
            --param pr_number=142 --param depth=deep

--param KEY=VALUE 可重复。只在第一个 = 处切分 —— value 可以包含更多 =(如 --param expr=a=b → expr → a=b)。

触发规则。 当 spec.parameters 与 --param 两侧均为空时,渲染器完全不调用 —— 没声明 parameters 的 spec 里的字面 {{ }} 会原样透传给 LLM。当 spec 没有 parameters 但 CLI 传了 --param 时,运行退出码 2,确保拼写错误不会被吃掉。

错误(一律退出码 2 / invalid_spec):

  • 缺 required 参数、未知参数、不在 choices 中。
  • --param 形态错误(expected KEY=VALUE、重复 key、非标识符 key)。
  • 模板里用到未声明变量(StrictUndefined):template uses undefined variable 'X' (declare it in spec.parameters)。
  • Jinja 渲染期抛出的其他异常({{ 1/0 }} → ZeroDivisionError、缺 loader 的 {% include %} 等)会被捕获并报为 template error in spec.<field>。
  • 沙箱拒绝:渲染器使用 jinja2.sandbox.SandboxedEnvironment,所以走属性链的逃逸(如 {{ ''.__class__.__mro__ }})会被拒绝 —— 共享 / 不可信来源的 recipe 无法在 permission_mode 与工具权限生效之前访问 Python 内部。

保留参数名。 看起来是 ASCII 标识符、但被 Jinja 保留的名字会在 spec 校验阶段被拒:常量(true/True/false/False/none/None)、关键字(for/if/in/set/is/not/or/…)以及 Jinja runtime 注入的 self / parent。完整清单见 agentao/cli/run_models.py::_JINJA_RESERVED_NAMES。

Instructions 优先级。 渲染后的 spec.instructions 非空、且至少包含一个非空白字符时,会经 Agentao(project_instructions=…) 注入并跳过 AGENTAO.md 磁盘读取。全空白的输出(例如 YAML block scalar 渲染成 "\n")会回落到 AGENTAO.md,从而保证渲染成空的模板不会静默把项目说明给抹掉。

完整设计动机 —— 包括 v1 范围、延迟落地的 number / boolean 类型,以及 goose-recipes 的对比 —— 见 docs/design/run-spec-parameters.zh.md。

输出契约 ​

--format text:stdout 上只写最终 assistant 文本;诊断信息走 stderr。最接近老 agentao -p 的形态。

--format json:run 结束后输出一个 envelope:

json
{
  "status": "ok",
  "session_id": "...",
  "turn_id": "...",
  "cwd": "/abs/path/to/project",
  "model": "gpt-5.5",
  "final_text": "...",
  "replay_path": ".agentao/replays/<id>.jsonl",
  "usage": {
    "prompt_tokens": 12000,
    "completion_tokens": 900,
    "total_tokens": 12900,
    "cache_read_tokens": 9500,
    "cache_creation_tokens": 0,
    "reasoning_tokens": 600
  },
  "tool_calls": 7,
  "thinking": "high",
  "denials": [],
  "warnings": []
}

usage 是本次运行各个 LLM 请求上报用量的总和:agent 自己的调用、它的压缩摘要请求,以及——自 0.5.1 起——它委派出去的每个前台子代理(此前子代理的请求不计入任何地方,所以有委派的运行会少报)。usage 是一份快照,在写出 envelope 的那一刻读取,写出之后不会再修订。自 0.5.6 起,agentao run 完全不提供后台子代理(它的工厂传入 bg_store=None,schema 里没有 run_in_background):这一轮结束进程就退出,而后台 worker 是 daemon 线程,会随之被截断。因此一次运行委派出去的子代理都是前台的,在写出 envelope 之前就已结束并计入。需要按子代理拆分的数字时,宿主应改读 SubagentLifecycleEvent.usage。prompt_tokens 含缓存命中的输入,而 provider 对这部分按另一个价格计费;cache_read_tokens 与 cache_creation_tokens(0.5.1)说明其中有多少 —— 它们是 prompt_tokens 之中的一部分,不是额外相加的量,也不会在 total_tokens 里再算一次。reasoning_tokens(0.5.15)同样是 completion_tokens 之中的一部分:模型用于思考的输出,推理模式的 provider 按输出计费但不显示。Chat Completions(completion_tokens_details.reasoning_tokens)和 openai-responses(output_tokens_details.reasoning_tokens)会给出它。anthropic-messages 上为 0,因为 output_tokens 已含思考部分,没有单独的计数。Agentao 只报这些量,不套任何价格。流中失败的请求按服务端当时已上报的量计入(0.5.2):在 anthropic-messages 线路上那是完整的输入量,流的第一个事件里就给了;Chat Completions 的 usage 只在最后一个 chunk 里,流在那之前断掉就什么也加不上。一次失败的尝试和随后的重试是两个请求,都会计入。上报不等于计费 —— 请把 usage 当用量,不要当账单。

失败时 final_text 为 null,error 字段携带 { type, message, reason?, tool_name?, tool_call_id?, question?, matched_rule? }。type 取值:permission_required、permission_denied、interaction_required、max_iterations、runtime_error、invalid_spec、interrupted、empty_response、length_truncated、doom_loop、hook_stop。消费方应把 envelope 视为前向兼容(多余字段直接忽略)。

下面这些 type 表示 turn 结束但没有可交回的答案,且它们的 reason 都携带运行时的精确分类——请基于 reason 分支,而不是 message:

typereason含义
empty_responseno_output模型什么都没输出。
empty_responsereasoning_only只有推理内容,没有答案正文。
length_truncatedlength_truncated输出在 token 上限处被截断——要么是工具调用的参数被截断(harness 随即中止该 turn),要么是最终答案在半句处被切断。
doom_loopdoom_loop模型重复同一个调用,直至检测器 halt 了该 turn。
hook_stophook_stop插件 hook 结束了该 turn。PreToolUse 与 PostToolUse / PostToolUseFailure 经由 continue: false 走到这里;UserPromptSubmit 在调用模型之前拦下或停止提示词时也走到这里。message 里带 hook 给出的原因。
runtime_errorllm_errorLLM API 调用失败(provider 5xx / 限流 / 认证)且重试后仍失败;message 里带 provider 的真实报错。

这样拆分是刻意的:empty_response 表示模型什么都没说,length_truncated / doom_loop 表示 harness 停掉了一个不收敛的 turn(往往值得用更小的任务重试,而前者通常不值得),hook_stop 表示你自己的配置有意停止了它,llm_error 表示 provider 调用失败。它们的退出码均为 1,且这套词表是封闭的——每一个没有完整答案就结束的 turn 都会被分类,流水线绝不会在退出码 0 处把它读成成功。

注意:turn 可能在执行过工具之后以这种方式结束——模型可能已经完成了工作,只是没有做总结。同一 envelope 上的 tool_calls 会报告实际执行了多少次工具调用,其副作用已经落盘——empty_response 失败不代表工作区未被改动。

统一退出码(agentao run 与 agentao -p 共用) ​

退出码含义
0正常完成。interaction_policy: deny 下请检查 denials:退出码 0 不表示每个调用都执行了
1运行时错误,或 turn 没有产出答案(runtime_error、empty_response、length_truncated、doom_loop、hook_stop)
2用法错误 / spec 校验失败 / 未知 spec 字段
3需要权限或交互(非交互环境无人审批;deny 下超过 max_denials)
4达到最大工具迭代数,回答可能不完整
130被中断(SIGINT / SIGTERM)

完整 M0 设计 —— 合并规则、Non-goals、Post-MVP 范围(jsonl 事件流、attachments、provider、每次 run 的 plugins、session resume)—— 见 docs/history/implementation/non-interactive-run-plan.md。

--resume — 启动即恢复会话 ​

bash
agentao --resume
agentao --resume a1b2c3

不带 id 恢复最近会话;带 id 时按前缀匹配。REPL 内同等命令是 /sessions resume <id>。

与 --acp 同时使用时,--resume 改为在第一个 session/new 上恢复——见下方 --acp --stdio。

--acp --stdio — 作为 ACP Server ​

bash
agentao --acp --stdio
agentao --acp --resume               # 第一个 session/new 恢复最近会话
agentao --acp --resume <SESSION_ID>  # 第一个 session/new 恢复指定会话

这会把 Agentao 作为 ACP stdio JSON-RPC server 启动,供 IDE、宿主进程或其他 agent 调用。--stdio 当前只在 --acp 下有效。加上 --resume 会装载一个一次性指令,由第一个 session/new 消费来注入并重放持久化会话(任何未命中都降级为全新会话)。见 3.2 Agentao 作为 ACP Server → 启动时恢复会话。

--plugin-dir — 临时加载插件 ​

bash
agentao --plugin-dir ./my-plugin
agentao plugin --plugin-dir ./my-plugin list

--plugin-dir 可重复。适合本地开发插件时不安装包,直接指向目录。

agentao skill ... ​

顶层 skill 子命令用于管理受管安装的 skills:

bash
agentao skill install owner/repo[:path][@ref]
agentao skill list
agentao skill list --installed
agentao skill remove <name>
agentao skill update <name>
agentao skill update --all

REPL 内的 /skills 管“当前会话看见什么、激活什么”;顶层 agentao skill ... 管“磁盘上安装了什么”。

agentao plugin list ​

bash
agentao plugin list
agentao plugin list --json

这会加载插件并输出诊断,适合 CI 或发布前检查。REPL 内的 /plugins 是同一类诊断的交互版。

agentao doctor —— 健康快照 ​

bash
agentao doctor
agentao doctor --json

把 harness 已经能给出的所有健康信号汇总到一份报告里:.env 的 provider 检查(只看 API key 是否存在,绝不输出 key 值)、settings.json、permissions、MCP、replay 配置、ACP schema 导出状态、项目 + 用户 memory store、插件诊断、可选依赖探测。--json 是给 CI / 宿主用的契约面,人类可读输出是终端默认。

输出契约:

  • {"ok": bool, "sections": {...}, "findings": [...]}
  • ok 为 false 当且仅当至少一条 finding 的 "level": "error",此时退出码为 1。Warning(例如刚 clone 时的 "API key not set")保持 ok=true、退出 0,不会误绊 CI。
  • 每条 finding 含 level(info / warning / error)、area(所属 section)、可读 message、source(路径或环境变量标签)。
  • Doctor 是只读的:不会建 memory DB、不会改文件。探测一个不存在的 memory.db 会报 "absent",绝不顺手把空文件创出来。
  • 未知 flag 退出码 2(与 agentao run 一致)—— 一旦 CI 写错(如 --jsno)会立即报错,不会用错误参数静默 exit 0。

agentao config validate —— 显式配置校验 ​

bash
agentao config validate
agentao config validate --json

doctor 的窄版伙伴,只校验用户可编辑配置:settings.json、.env provider 变量、permissions.json、mcp.json、settings.json 的 replay 段、memory-store 探测。插件发现刻意排除(用 doctor 或 plugin list)。

会显式报出运行时通常会默默吞掉的问题:

  • 上述文件的 JSON 损坏 / 顶层非 object 形态;
  • LLM_TEMPERATURE / LLM_MAX_TOKENS 解析失败;
  • MCP 单条 server 条目非 object,或其 env / headers / args 含非 string(运行时 expand_env_vars 会抛 TypeError);
  • 用户级与项目级 mcp.json 同名碰撞(运行时会丢掉项目级条目,validate 会 warn 让你知道);
  • ReplayConfig.from_mapping 会静默吞掉的 replay 设置(max_instances: 0、非 bool 的 capture flag、未知 flag 键等)。

运行时行为不变——agentao config validate 是显式校验面。factory 仍是 best-effort,这样宿主带着可选配置坏掉时也不会被卡住启动。

两个命令都支持 --plugin-dir DIR(doctor 用它来发现插件),且都依赖 [cli] extra;缺依赖时会走标准 missing-dep 守卫并打印安装提示。


真相源头

顶层参数 parser 在 agentao/cli/entrypoints.py:_build_parser。非交互 print 模式在同文件的 run_print_mode(薄壳,转发到 agentao/cli/run.py:execute)。Spec 模型在 agentao/cli/run_models.py;Jinja2 沙箱化渲染器(负责 prompt 与 instructions)在 agentao/cli/run_template.py。Skill / plugin 子命令在 agentao/cli/subcommands.py。