文章 · Agent 工具链
让 agent 一次写完整段代码:heredoc 为什么比 REPL 更合适
给 agent 一个终端,它几乎什么都能做。但如果它像人一样一条命令一条命令地试,每一步都是一次模型调用。建联系统跑了几个月之后我越来越确定:把整段任务一次交给运行时,比让它在 REPL 里边走边看要省得多。
这篇回应的是 ego lite 官网的 《Why heredoc-style execution is a better fit for AI agents than a REPL》。观点是我自己的,例子来自我搭的 KOL 建联系统。
Article · Agent tooling
Let the agent write the whole thing at once: why heredoc beats a REPL
Give an agent a terminal and it can do almost anything. But if it works the way a person does, one command at a time, every step is a model call. After months of running my outreach system I am fairly sure of one thing: handing the runtime a complete task in one go is far cheaper than letting the agent feel its way through a REPL.
This piece responds to the ego lite article “Why heredoc-style execution is a better fit for AI agents than a REPL in 2026”. The opinions are mine; the examples come from the KOL outreach system I built.
- CLI 本身不是问题,交互方式才是:命令一条条敲,模型和工具之间的来回次数会随任务长度成倍增长,每一次来回都在烧 token 和时间。
- heredoc 把一整段 JavaScript 当作一次工具调用提交,运行时跑完再返回;REPL 让解释器一直活着,模型就倾向于走一步看一步。
- ego lite 公开的实验里,同样的浏览器任务 heredoc 比 REPL 少 44% 的执行轮次、少 35.5% 的工具调用、便宜 21.6%。我自己的建联脚本也是同一个方向:能写进代码的判断,就别留给模型。
从「CLI is all you need」说起
早期的编程 agent 给了所有人一个启发:只要能用 Bash,agent 就能写代码、查资料、跑测试、管 Git。于是出现了一个很顺口的口号:CLI is all you need。把软件做成命令行,agent 就能用。
这句话有一个隐藏成本。agent 用终端的方式和人很像:敲一条短命令,看结果,再决定下一条。任务一复杂,这种来回就会迅速膨胀。每一次来回都是一次完整的模型调用,上下文越拖越长,等待时间也越来越久。
我第一版建联系统就是这么搭的。让 Claude Code 用一串命令操作浏览器:打开达人库、按条件筛、点进一条记录、读邮件、再决定要不要催。一个二十来人的催信任务,光是模型和工具之间的往返就要几十个回合,大部分时间花在「等结果」上。
CLI 只是入口,代码才是接口
问题不在命令行,而在「用命令表达任务」。对 Git、Docker、FFmpeg 这类老工具,模型在训练时见过无数命令和报错,闭着眼都会用。但一个为 agent 新造的 CLI,对模型来说就是一门新的小语言:子命令、参数、输出格式、错误语义都要重新学,还得通过反复调用才摸清它们怎么拼在一起。
把同样的能力做成一个 JavaScript API,模型要学的只剩领域概念。循环、条件、异常处理、数据加工,用的全是它早就会的东西。所以更好的架构是:保留 CLI 作为统一入口,但用代码作为和软件真正交互的方式。agent 提交一段代码,里面装着操作、判断和数据处理,本地运行时负责执行。
拿一个最简单的任务对比:打开一个页面,读它的主标题。命令式接口大概要三步:
# 第一步:打开页面
ego-browser open https://example.com
# 第二步:找标题
ego-browser find --role heading
# 第三步:读出来
ego-browser read --role heading
每一步后面都藏着一次「等工具返回、模型再想」。换成代码接口,整件事一次写完:
const task = await taskSpace("read example page");
const page = task.page("p1");
await page.goto("https://example.com");
const snap = await page.snapshot();
console.log(snap.match(/heading.*\n\s+text "([^"]+)"/)?.[1]);
两种写法做的是同一件事,差别在于前者把过程拆成好几轮模型与工具的对话,后者让模型把完整过程写一遍,交给本地跑。
heredoc 和 REPL:真正的差别是进程生命周期
一旦 CLI 开始接受代码,下一个问题就是:agent 怎么把这段代码递给执行环境?严格说 heredoc 是 shell 的输入语法,REPL 是解释器的运行模式,两者不在一个层面。这篇文章比较的其实是「通过 heredoc 一次性执行一段代码」和「基于 REPL 的持久交互会话」。下面为了省事就直接叫 heredoc 和 REPL。
heredoc 的用法是把一整段 JavaScript 一次塞给命令:
ego-browser nodejs <<'EOF'
const task = await taskSpace("weekly follow-ups");
const page = task.page("p1");
await page.goto("https://kol-system.example/outreach?due=1");
const rows = await page.evaluate(() => [...document.querySelectorAll("tr.clickable")].length);
console.log({ due: rows });
EOF
shell 把代码交给进程,等它跑完,拿到结果,进程退出。对 agent 来说这和一次普通的工具调用没有区别:提交命令,等待进程,收到结果。
REPL 不一样。解释器一直活着,agent 可以反复输入代码,变量和会话状态都保留着。从表达能力看两者几乎等价:REPL 也能一次跑一个带循环和异常处理的完整程序,heredoc 也可以只提交一行。真正的差别是进程生命周期:heredoc 跑完就退出,REPL 一直等着下一条输入。
这也意味着两者对 agent 工具的要求不同。heredoc 走的是最普通的请求响应模型;REPL 要求工具支持持久进程、会话管理、持续输入、中断和恢复。今天大多数 agent 自带的 Bash 工具没有这些能力,主流产品里只有 Codex 支持得比较完整。而且就算工具支持了,模型会不会好好用它,是另一回事。
模型在两种环境里写出来的代码不一样
理论上模型在两种环境里的编程能力相同。实际观察到的行为却很一致:在 REPL 里,模型倾向于一点点往里敲;在 heredoc 里,它更可能一次生成完整程序。
原因不难猜,是训练数据的分布。REPL 的样本大多来自教程、调试过程和问答,典型模式是探索式的:拿到页面,返回信息;找个元素,返回信息;读内容,返回文本。heredoc 代码块则更像脚本和源文件,有明确的开头和结尾,天然鼓励模型写出一条连贯的工作流。
给人用的软件要讲人体工学,给 agent 用的软件也需要类似的纪律,可以叫它「模型体验工程」:接口应该顺着模型在训练中养成的行为模式走,而不是逆着来。
这种倾向还决定了控制流住在哪里。在 REPL 里,模型更可能把任务拆成几段,每拿到一个结果就重新决策一次;在 heredoc 里,循环、条件、过滤和数据处理会被直接写进程序,交给本地运行时去跑。前者是「执行、观察、继续」,后者是「先组织、再执行」。
数字:ego lite 的实验,和我自己的体感
ego lite 团队在 Codex SDK 上搭了一个自动化基准:同一个 agent 用 REPL 和 heredoc 两种方式完成四类真实的浏览器任务,重复多轮取平均。结果是 heredoc 少了 44% 的执行轮次、少了 35.5% 的工具调用,成本低了 21.6%。这几个数字是他们公开的,不是我测的。
我自己没有做过这么严格的对照,但建联系统的日常给了我足够的体感。系统每天做三件事:该催谁、同步回复、出报表。每件事都是一段 heredoc 脚本。「沉默超过三天且没催过」「这是第几轮话术」「哪些回复要进待审核」这些判断全部写在代码里,模型只负责两件事:决定要不要跑,以及看最后那几行汇总。一次任务通常只有一两次工具调用,而不是几十次。
还有一个意外的好处:脚本是可以审阅的。出了问题我可以把那段 heredoc 原样再跑一遍,或者改一行再跑;REPL 里的对话记录做不到这一点,因为它的状态散落在几十个回合里。
heredoc 不是永久的答案
它也有明显的短板。一段长脚本在中途失败,就得从头再来,没有状态可以接着用。页面出现意料之外的变化时,调试也更费劲,因为你看到的是一次性的输出,不是一步一步的回放。
我的折中是两段式:先用一段很短的 heredoc 拿一次页面快照,确认结构没变;再提交真正干活的那段。长任务则在代码里写检查点,每完成一批就把结果落到文件,失败后从检查点继续。
随着 agent 工具开始支持长生命周期的会话,REPL 的优势会慢慢回来。但原则不会变:尽量减少模型和工具之间的来回,确定性的判断留在代码里,模型只在真正需要判断的地方出手。
如果你要给 agent 做一个 CLI
- 入口保持简单,一个命令接一段代码;不要把能力拆成几十个子命令让模型去背。
- 用模型已经会的语言暴露 API,领域概念越少越好,命名跟着它熟悉的习惯走。
- 每次执行都返回一份能直接当下一步输入的输出,比如页面快照、带 ref 的元素列表,省得模型再去要一次。
- 把「谁该被处理」这类规则放进脚本或配置,而不是提示词;提示词每次都要付 token,代码只写一次。
- The CLI is not the problem; the interaction pattern is. Typing commands one by one multiplies the round trips between model and tool, and each round trip burns tokens and time.
- A heredoc submits a whole block of JavaScript as a single tool call and returns when the runtime finishes; a REPL keeps the interpreter alive, which nudges the model into one step at a time.
- In the experiment ego lite published, heredoc finished the same browser tasks with 44% fewer execution rounds, 35.5% fewer tool calls and 21.6% lower cost. My own outreach scripts point the same way: if a decision can live in code, do not leave it to the model.
Starting from “CLI is all you need”
Early coding agents taught everyone a lesson: give an agent Bash and it can write code, gather context, run tests and manage Git. A catchy slogan followed. CLI is all you need. Ship your software as a command line and agents can use it.
The slogan hides a cost. Agents use a terminal much like people do: run a short command, read the result, decide the next one. As soon as a task gets complex, that loop balloons. Every lap is a full model call, the context keeps growing, and most of the wall-clock time goes to waiting.
My first version of the outreach system worked exactly like that. Claude Code drove the browser with a chain of commands: open the creator library, filter, open a record, read the mail thread, decide whether to chase. A follow-up run for twenty creators took dozens of model-tool round trips, most of them spent waiting for results.
The CLI is the entry point; code is the interface
The issue is not the command line but expressing tasks as commands. For old tools like Git, Docker or FFmpeg the model has seen endless commands and error messages in training and knows them cold. A brand-new CLI built for agents is a new mini-language: subcommands, flags, output formats and error semantics all have to be learned, usually through repeated calls.
Expose the same capability as a JavaScript API and the model only has to learn the domain concepts. Loops, conditions, exception handling and data processing are things it already knows. So the better architecture is: keep the CLI as the universal entry point, but use code as the real way to talk to the software. The agent submits one block that contains the operations, decisions and data handling; the local runtime executes it.
Take the simplest task: open a page and read its main heading. A command-style interface needs roughly three steps:
# step one: open the page
ego-browser open https://example.com
# step two: find the heading
ego-browser find --role heading
# step three: read it
ego-browser read --role heading
Behind each step hides a “wait for the tool, then think again”. With a code interface the whole job is written once:
const task = await taskSpace("read example page");
const page = task.page("p1");
await page.goto("https://example.com");
const snap = await page.snapshot();
console.log(snap.match(/heading.*\n\s+text "([^"]+)"/)?.[1]);
Both get the same thing done. One splits the process into several rounds of model-tool conversation; the other lets the model write the complete process and hand it over.
heredoc vs REPL: the real difference is process lifetime
Once a CLI accepts code, the next question is how the agent hands that code to the execution environment. Strictly speaking, heredoc is shell input syntax and a REPL is an interpreter mode, so they are not the same kind of thing. What this article compares is one-shot execution of a code block through a heredoc versus a persistent interactive session built on a REPL. I will just call them heredoc and REPL from here.
With a heredoc the agent pushes a whole block of JavaScript into one command:
ego-browser nodejs <<'EOF'
const task = await taskSpace("weekly follow-ups");
const page = task.page("p1");
await page.goto("https://kol-system.example/outreach?due=1");
const rows = await page.evaluate(() => [...document.querySelectorAll("tr.clickable")].length);
console.log({ due: rows });
EOF
The shell passes the code to the process, waits for it to finish, collects the result, and the process exits. To the agent this is indistinguishable from an ordinary tool call: submit, wait, receive.
A REPL is different. The interpreter stays alive, the agent can keep typing, and variables and session state persist. In expressive power the two are nearly equivalent: a REPL can run a complete program with loops and error handling in one shot, and a heredoc can submit a single line. The real difference is process lifetime. A heredoc exits when the code finishes; a REPL waits for more input.
That also changes what the agent's tooling must support. A heredoc rides the plain request-response model. A REPL needs persistent processes, session management, continuous input, interruption and recovery. Most agents' built-in Bash tools have none of that; among mainstream products only Codex supports it well. And even when the tool supports it, whether the model uses it well is a separate question.
Models write differently in the two environments
In theory a model has the same programming ability in both. In practice the behaviour is consistent: in a REPL the model tends to type incrementally; in a heredoc it is far more likely to produce a complete program in one pass.
The cause is the training distribution. REPL examples come from tutorials, debugging sessions and Q&A threads, and their pattern is exploratory: get the page, see the output; find an element, see the output; read it, see the text. Heredoc blocks look like scripts and source files, with an explicit beginning and end, which encourages one continuous workflow.
Software for humans has ergonomics. Software for agents needs a similar discipline, call it model-experience engineering: the interface should go with the habits the model picked up in training, not against them.
The tendency also decides where control flow lives. In a REPL the model is more likely to cut the task into stages and re-decide after every result. In a heredoc, loops, conditions, filtering and data processing go straight into the program and run locally. One pattern is execute, observe, continue; the other is organise first, then execute.
The numbers: ego lite's experiment, and what I see day to day
The ego lite team built an automated benchmark on the Codex SDK: the same agent completed four kinds of real browser tasks through a REPL and through heredocs, repeated and averaged. Heredoc needed 44% fewer execution rounds and 35.5% fewer tool calls, at 21.6% lower cost. Those are their published figures, not mine.
I have not run a comparison that strict, but the outreach system gives me a strong feel for it. It does three things every day: who is due for a chase, sync replies, build the report. Each is one heredoc script. The rules, silent for three days and never chased, which round of copy applies, which replies go to review, all live in code. The model does two things: decides whether to run, and reads the few summary lines at the end. A run is usually one or two tool calls, not dozens.
There is a side benefit I did not expect: a script is reviewable. When something goes wrong I can rerun the exact heredoc, or change one line and rerun. A REPL transcript cannot be replayed like that; its state is smeared across dozens of turns.
heredoc is not a permanent answer
It has obvious weaknesses. A long script that fails halfway starts over, with no state to resume from. When a page changes unexpectedly, debugging is harder, because you see one batch of output rather than a step-by-step replay.
My compromise is two-phase: a very short heredoc first to take one snapshot and confirm the structure still holds, then the script that does the work. Long tasks write checkpoints inside the code, flushing results to a file after each batch so a failure resumes from the last checkpoint.
As agent tools grow proper support for long-lived sessions, the REPL will claw some advantage back. The principle will not change: minimise round trips between model and tool, keep deterministic decisions in code, and spend the model only where judgement is actually needed.
If you are building a CLI for agents
- Keep the entry point tiny: one command that accepts one block of code. Do not split capabilities into dozens of subcommands for the model to memorise.
- Expose the API in a language the model already speaks, with as few domain concepts as possible and names that follow its habits.
- Make every run return output that works directly as the next input, such as a page snapshot or an element list with refs, so the model does not have to ask again.
- Put rules like “who should be processed” in scripts or config, not in the prompt. A prompt is paid for on every call; code is written once.
常见问题
不是。heredoc 是所有 POSIX shell 都支持的输入语法,任何接受标准输入的程序都能用。ego-browser 只是围绕这个思路设计了它的 CLI:一个命令接一段 JavaScript。
探索一个陌生页面、调试一条失败的步骤,REPL 仍然更顺手,因为状态还在。这篇文章说的是把任务交给 agent 自动执行的场景,这时候来回次数直接等于成本。
MCP 把每个能力做成一个工具,每次调用也是一次来回,而且工具的 schema 和快照都会进上下文。它和命令式 CLI 的成本结构很像,所以同一个结论基本适用:步骤越多,差距越大。
No. Heredoc is input syntax supported by every POSIX shell, and any program that reads standard input can use it. ego-browser simply designed its CLI around the idea: one command that takes one block of JavaScript.
Exploring an unfamiliar page or debugging one failing step is still easier in a REPL, because the state is right there. This article is about handing tasks to an agent to run on its own, where round trips translate directly into cost.
MCP turns each capability into a tool, so each call is a round trip too, and the tool schemas and snapshots enter the context. Its cost structure resembles a command-style CLI, so the same conclusion mostly applies: the more steps, the wider the gap.
More about me
Wanna know more? 👇