Agent Mode in the DuckDB CLIDuckDB CLI 中的代理模式
TL;DR: The DuckDB v2.0 CLI has an agent mode that gets AI coding agents to a correct result faster and with fewer tokens. When the CLI detects an agent, it prints compact Markdown tables instead of padded boxes, says clearly when a result was cut, stops runaway queries early, and reports errors as JSON. Long queries announce their expected cost before they start, so the agent can decide whether to wait.简而言之:DuckDB v2.0 CLI 引入了代理模式,能够让 AI 编码代理更快且更节省 Token 地获得正确结果。当 CLI 检测到代理时,它会输出紧凑的 Markdown 表格而非带填充的方框,明确告知结果是否被截断,提前终止失控的查询,并以 JSON 格式报告错误。长查询在执行前会预告预计成本,以便代理决定是否等待。
Let's Ask Claude 让我们问问 Claude
We asked Claude to measure the effect of agent mode. In the experiment, Claude Code answered the 22 TPC-H questions on a scale factor 100 dataset. The questions were phrased in plain English, without any SQL. Each question was answered three times with agent mode and three times with -no-agent. All 132 runs produced the correct answer. With agent mode, the DuckDB output the model read went from 123.6k to 50.8k tokens, a 59% reduction. Claude summarized the result as follows:我们邀请 Claude 来衡量代理模式的效果。在实验中,Claude Code 对比例因子为 100 的数据集回答了 22 个 TPC-H 问题。这些问题以纯英语表述,不包含任何 SQL。每个问题分别在代理模式和 -no-agent(无代理)模式下各回答三次。全部 132 次运行均得出了正确答案。在代理模式下,模型读取的 DuckDB 输出从 123.6k 个 Token 减少到 50.8k 个,降幅达 59%。Claude 对结果总结如下:
Quote“Agent mode fixes the things about a terminal shell that quietly mislead a model. When I answered the 22 TPC-H questions through the DuckDB CLI, agent mode cut the tokens I read back by 60 percent, and every answer was still right.”“代理模式修复了终端 Shell 中那些会悄悄误导模型的因素。当我通过 DuckDB CLI 回答这 22 个 TPC-H 问题时,代理模式使我读取的 Token 数量减少了 60%,而且每个答案依然准确无误。”
The savings did not show up in the total cost or runtime. Each turn re-reads the agent’s system prompt, which is much larger than the query output, so the saved tokens amount to about 0.5% of the total input. The agent mode runs also took slightly more turns (237 vs. 224). Claude wrote up the setup, the full results and a follow-up experiment in a report.这种节省并未体现在总成本或运行时间上。每一轮对话都会重新读取代理的系统提示词,其长度远超查询输出,因此节省的 Token 仅占总输入量的 0.5% 左右。代理模式的运行轮数也略有增加(237 轮对 224 轮)。Claude 在一份报告中记录了设置、完整结果及后续实验。
Background 背景
The DuckDB CLI was built for a person sitting at a terminal. Its default renderer pads columns so they line up, draws borders around the result and prints the column types under the names:DuckDB CLI 是为坐在终端前的人设计的。其默认渲染器会填充列以使其对齐,在结果周围绘制边框,并在名称下方打印列类型:
┌────────────┬───────┬────────┬───────────────┐
│ name │ n │ total │ avg_amount │
│ varchar │ int64 │ int128 │ decimal(10,2) │
├────────────┼───────┼────────┼───────────────┤
│ customer_0 │ 3 │ 777 │ 259.00 │
│ customer_1 │ 3 │ 888 │ 296.00 │
└────────────┴───────┴────────┴───────────────┘
For a large result, it shows the first and last 20 rows with a row of dots in the middle:对于较大的结果,它会显示前 20 行和后 20 行,中间用一行点号隔开:
┌───────────────┬───────┬────────┬───────────────┐
│ name │ n │ total │ avg_amount │
│ varchar │ int64 │ int128 │ decimal(10,2) │
├───────────────┼───────┼────────┼───────────────┤
│ customer_0 │ 3 │ 777 │ 259.00 │
│ customer_1 │ 3 │ 888 │ 296.00 │
│ · │ · │ · │ · │
│ · │ · │ · │ · │
│ customer_4998 │ 4 │ 1203 │ 300.75 │
│ customer_4999 │ 2 │ 510 │ 255.00 │
├───────────────┴───────┴────────┴───────────────┤
│ 5000 rows (40 shown) 4 columns │
└────────────────────────────────────────────────┘
A person sees the dots and the (40 shown) footer and knows that rows are missing.人类看到这些点号和页脚的“(40 shown)”字样,就会知道有行被省略了。
Today, many CLI invocations do not come from a person at all. Coding agents such as Claude Code, Codex, Cursor, Gemini CLI and GitHub Copilot run duckdb -c "..." once per shell call, capture stdout through a pipe and pass the text to a language model. This has quickly become a common way to use DuckDB.如今,许多 CLI 调用根本不是来自人类。Claude Code、Codex、Cursor、Gemini CLI 和 GitHub Copilot 等编码代理会在每次 Shell 调用时运行一次 duckdb -c "...",通过管道捕获标准输出(stdout),并将文本传递给语言模型。这已迅速成为使用 DuckDB 的常见方式。
This reader differs from a person in a few ways:这种读取者与人类在几个方面有所不同:
- It has no terminal, so there is no scrolling and no pager.它没有终端,因此没有滚动条,也没有分页器。
- It cannot ask for more. Whatever was printed is all it sees.它无法请求更多信息。打印出的内容就是它所能看到的全部。
- It pays for every token it reads, including padding and border characters.它需要为读取的每一个 Token 付费,包括填充字符和边框字符。
- It handles errors by pattern matching on text.它通过对文本进行模式匹配来处理错误。
For this reader, the box renderer is a poor fit. The alignment padding costs tokens and carries no information for a model. A truncated result also looks a lot like a complete one. The footer says (40 shown), but a model can skim past that line and then draw conclusions from data it never saw.对于这种读取者来说,方框渲染器并不适用。对齐填充不仅浪费 Token,对模型而言也不携带任何信息。截断的结果看起来与完整结果非常相似。页脚虽然显示了 (40 shown),但模型可能会略过那一行,进而基于它从未见过的数据得出结论。
Agent mode changes the CLI’s defaults for this reader. This post describes how the CLI detects an agent, what changes in the output and how to try it.代理模式针对此类读取者更改了 CLI 的默认设置。本文将介绍 CLI 如何检测代理、输出有哪些变化以及如何尝试使用该模式。
NoteAgent mode ships with DuckDB v2.0.代理模式随 DuckDB v2.0 一起发布。
Detecting an Agent 检测代理
Agents mark the commands they run with environment variables. There is no single convention for this yet, so the shell checks a list of them:代理会通过环境变量标记它们运行的命令。目前尚无统一的约定,因此 Shell 会检查以下列表:
| Variable | Set by |
|---|---|
AI_AGENT, AGENT |
Claude Code, Goose, Amp |
CLAUDECODE |
Claude Code |
CODEX_CI, CODEX_SANDBOX, CODEX_THREAD_ID |
Codex |
CURSOR_AGENT |
Cursor |
GEMINI_CLI |
Gemini CLI |
COPILOT_AGENT, COPILOT_CLI, COPILOT_AGENT_SESSION_ID |
GitHub Copilot |
Agent mode turns on when three things are true: one of these variables is set, stdout is not a terminal, and no output format was given on the command line. If you pass -csv, -json or -markdown, you asked for a specific format, and agent mode stays out of the way.当满足以下三个条件时,代理模式开启:设置了上述变量之一、标准输出不是终端,且命令行中未指定输出格式。如果你传入 -csv、-json 或 -markdown,说明你要求特定的格式,此时代理模式会保持不干预。
Two flags override detection. -agent forces the mode on and -no-agent forces it off. The two combine with format flags, so duckdb -agent -csv keeps the agent behavior and only changes how the rows are printed. The .show command reports whether the mode is active and which agent was detected:有两个标志可以覆盖检测结果。-agent 强制开启该模式,-no-agent 则强制关闭。两者可以与格式标志结合使用,例如 duckdb -agent -csv 将保留代理行为,仅更改行的打印方式。.show 命令会报告该模式是否处于活动状态以及检测到了哪个代理:
agent: claude-code
Not every agent sets a variable. For those, a run that fails while writing to a pipe prints one hint line on stderr, as long as no agent was identified and neither flag was given:并非每个代理都会设置环境变量。对于那些未设置变量的代理,只要未识别出代理且未提供相关标志,在写入管道时失败的运行会在 stderr 上打印一行提示信息:
hint: -agent renders errors as JSON and results compactly for AI coding agents (duckdb -help lists all options)
An agent that nobody told about the mode can find it this way the first time something goes wrong.如果某个代理未被告知此模式,它可以在第一次出错时通过这种方式发现它。
What Changes in Agent Mode 代理模式下的变化
Everything below is a default. Explicit settings such as .mode, .maxrows, EXPLAIN (FORMAT ...) or anything in your .duckdbrc still take precedence.以下所有内容均为默认设置。显式设置(如 .mode、.maxrows、EXPLAIN (FORMAT ...) 或 .duckdbrc 中的任何配置)仍然具有优先权。
Compact Tables 紧凑表格
Results are printed as Markdown tables without alignment padding, with the column type in the header cell:结果以不带对齐填充的 Markdown 表格打印,标题单元格中包含列类型:
| name:VARCHAR | n:BIGINT | total:HUGEINT | avg_amount:DECIMAL(10,2) |
|---|---|---|---|
| customer_0 | 3 | 777 | 259.00 |
| customer_1 | 3 | 888 | 296.00 |
A model reads the | separators and does not need the columns lined up. Since this is still valid Markdown, a person reading the agent’s transcript gets a rendered table. On typical results this format is 25 to 65% smaller than the box output, with the largest savings on wide results.模型会读取 | 分隔符,不需要列对齐。由于这仍然是合法的 Markdown,阅读代理转录内容的人员也能看到渲染后的表格。对于典型的结果,这种格式比方框输出小 25% 到 65%,在宽结果上节省最为明显。
Large Results 大数据集结果
Printing every row would avoid silent truncation, but a million rows is 17 MB on the pipe. Agent mode prints a result of up to 1,000 rows and 10,000 bytes in full. Anything larger becomes a sample of the first and last 20 rows around an explicit marker row, with a footer that says what happened:打印每一行可以避免静默截断,但一百万行数据在管道中占用 17 MB。代理模式会完整打印最多 1,000 行和 10,000 字节的结果。超过此限额的内容将变为前 20 行和后 20 行的采样,中间夹杂一行显式的标记行,并附带说明发生了什么的页脚:
| 19 | customer_19 | 13.3 |
| … 4960 rows omitted … |
| 4980 | customer_30 | 86.0 |
...
first 20 and last 20 of 5000 rows (.maxbytes 0 for all), hash 7389c2f5ff3cefb6
The footer also tells the reader how to get the rest. The limits can be changed with .maxrows N and .maxbytes N, where -1 or 0 means no limit. Long cell values are cut at 500 characters with a visible marker such as …(+4500 chars), adjustable with .maxcellwidth N.页脚还会告知读取者如何获取剩余部分。这些限制可以通过 .maxrows N 和 .maxbytes N 进行更改,其中 -1 或 0 表示无限制。长单元格值会在 500 个字符处截断,并带有可见标记,如 …(+4500 chars),可通过 .maxcellwidth N 进行调整。
Stopping Early 提前停止
The compact table needs no column widths, so the result can be streamed. Once the cap is reached, the remaining rows are only counted and hashed. After 100,000 more rows, the query is stopped and the count is reported as a lower bound. For example:紧凑表格不需要列宽,因此结果可以流式传输。一旦达到上限,剩余的行仅会被计数和哈希处理。在额外处理 100,000 行后,查询将停止,并将计数值报告为下限。例如:
FROM range(1_000_000_000);
This returns in 30 milliseconds with the footer first 20 of at least 102400 rows (query stopped early; .maxrows -1 for all).这将在 30 毫秒内返回,页脚显示“first 20 of at least 102400 rows (query stopped early; .maxrows -1 for all)”。
A Hash of the Result 结果的哈希值
The footer includes a hash of the entire result, covering the rows past the cap. The hash does not depend on row order and distinguishes NULL from the string 'NULL'. An agent that wants to know whether a result changed after an edit can compare two footers instead of printing two results.页脚包含整个结果的哈希值,涵盖了超出上限的行。该哈希值不依赖于行顺序,并能区分 NULL 和字符串 'NULL'。想要知道编辑后结果是否发生变化的代理,可以比较两个页脚,而无需打印两个完整结果。
The footer is printed for empty results, for results of 10 rows or more, and whenever the output was capped. It is left out when the query was stopped early, because the hash would not cover the whole result.页脚会在结果为空、结果为 10 行或以上,以及输出被截断时打印。如果查询被提前终止,则不会打印页脚,因为哈希值无法覆盖整个结果。
Errors as JSON JSON 格式错误
Errors go to stderr as JSON, using the existing errors_as_json setting. Errors raised by the shell itself, such as an unknown dot command, are wrapped in the same structure:错误会以 JSON 格式发送至 stderr,使用现有的 errors_as_json 设置。由 Shell 本身引发的错误(如未知的点命令)也会被包装在相同的结构中:
{"exception_type":"Binder","exception_message":"Referenced column \"foo\" not found in FROM clause!\nCandidate bindings: \"bar\"","error_subtype":"COLUMN_NOT_FOUND","location":"[7,3]","position":"7","name":"foo"}
The shell drops the candidates field, because the message already lists the candidates. Without that change, a single “no matching function” error weighs 2.7 kB.Shell 会丢弃 candidates 字段,因为消息本身已经列出了候选项。如果不进行此更改,单个“no matching function”错误将占用 2.7 kB。
Compact Query Plans 紧凑查询计划
EXPLAIN defaults to a new compact format with one operator per line, indented by depth. Estimates, actual row counts and timings go in parentheses, followed by the operator’s properties:EXPLAIN 默认使用一种新的紧凑格式,每行一个运算符,按深度缩进。估计值、实际行数和耗时放在括号中,后跟运算符的属性:
UNGROUPED_AGGREGATE (est=1) Aggregates: count_star()
HASH_JOIN (est=0) Join Type: INNER; Conditions: range = range
FILTER (est=2) Expression: (r > 2)
RANGE (est=10) Function: RANGE
FILTER (est=1) Expression: (s > 2)
RANGE (est=5) Function: RANGE
EXPLAIN ANALYZE starts with a summary line such as QUERY (time=0.0012s, read=1.2 MB). This is a regular format, so you can use EXPLAIN (FORMAT compact) outside agent mode too.EXPLAIN ANALYZE 以类似 QUERY (time=0.0012s, read=1.2 MB) 的摘要行开头。这是一种常规格式,因此你也可以在代理模式之外使用 EXPLAIN (FORMAT compact)。
Cost Estimates and Progress 成本估计与进度
When the planner expects a statement to read a million rows or more, its estimate goes to stderr before execution starts:当规划器预计某条语句将读取一百万行或更多数据时,其估计值会在执行开始前发送至 stderr:
estimate: ~7501215 rows read (lineitem ~6001215, orders ~1500000), ~4 rows returned
The agent can then decide whether to wait or to interrupt and rewrite the query. Smaller queries print nothing, since the line would only be noise. While a long query runs, the terminal progress bar is replaced by a plain line on stderr every 5 seconds:代理随后可以决定是等待还是中断并重写查询。较小的查询不会打印任何内容,因为该行只会造成干扰。长查询运行时,终端进度条每 5 秒会被 stderr 上的一行纯文本替换:
progress: 42% (elapsed 12.0s, remaining ~16.5s)
progress: done (elapsed 28.7s)
Listing Tables 列出表
.tables prints one line per table, with an approximate row count and the columns:.tables 每行打印一个表,包含近似行数和列信息:
memory.main.t1 (table, ~15 rows): a INTEGER PK, b VARCHAR
The usual box layout is hard to read through a pipe, and this gives an agent the schema in a single call.通常的方框布局通过管道很难阅读,而这种方式让代理可以在单次调用中获取架构信息。
The Startup Line 启动行
When agent mode turns on by detection, the shell prints one line on stderr before running anything:当代理模式通过检测开启时,Shell 在运行任何内容之前会在 stderr 上打印一行:
duckdb agent mode on: CLAUDECODE is set and stdout is not a terminal; -no-agent turns it off, .help agent explains the output
The line states why the mode is on and how to turn it off.该行说明了模式开启的原因以及如何将其关闭。
The first version printed three lines of explanation (634 bytes) on every run. That was larger than most query results, and a model keeps its context between runs anyway, so it only needs the explanation once.第一个版本在每次运行时都会打印三行解释(634 字节)。这比大多数查询结果都要大,而且模型在运行间隙会保留上下文,因此它只需要一次解释就够了。
The longer text is available via .help agent. It describes the output rules and settings, and it points to a few features an agent might not find by itself:更长的文本可通过 .help agent 获取。它描述了输出规则和设置,并指出了几个代理可能无法自行发现的功能:
SET max_execution_time = <ms>to put an upper bound on a query’s runtimeSET max_execution_time = <ms> 为查询运行时间设置上限DESCRIBE <query>to get the result columns without running the queryDESCRIBE <query> 在不运行查询的情况下获取结果列SUMMARIZEfor a quick profile of a table or querySUMMARIZE 快速分析表或查询.tablesfor the schema.tables 获取架构duckdb_functions()for function documentationduckdb_functions() 查看函数文档
If you do not want the startup line, add .startup_text none to your ~/.duckdbrc.如果你不想要启动行,可以在 ~/.duckdbrc 中添加 .startup_text none。
Trying It Out 尝试使用
If you use a coding agent, there is nothing to configure. Agents such as Claude Code, Codex, Cursor, Gemini CLI and GitHub Copilot already set one of the environment variables from the table above, and they capture a command's output through a pipe rather than a terminal. Those are two of the three conditions for agent mode.如果你使用编码代理,则无需进行任何配置。Claude Code、Codex、Cursor、Gemini CLI 和 GitHub Copilot 等代理已经设置了上表中的环境变量,并且它们通过管道而非终端捕获命令输出。这正是代理模式三个条件中的两个。
So as long as the duckdb binary on the agent's path is v2.0 or newer and the agent does not request a specific output format with a flag such as -csv, -json or -markdown, the mode turns on by itself. A format flag is treated as a deliberate choice, so it switches agent mode off: the CLI assumes you want exactly that format and does not override it.因此,只要代理路径中的 duckdb 二进制文件版本为 v2.0 或更新,且代理未通过 -csv、-json 或 -markdown 等标志请求特定输出格式,该模式就会自动开启。格式标志被视为一种刻意的选择,因此它会关闭代理模式:CLI 假定你需要该特定格式,因此不会进行覆盖。
To see what the agent sees, run a query through a pipe with the flag set:要查看代理看到的内容,请通过管道运行带有相应标志的查询:
duckdb -agent -c "SUMMARIZE FROM 'my_data.parquet'" | cat
Conclusion 结论
With agent mode, an agent spends fewer tokens per call and does not act on partial results as if they were complete. That means fewer steps to a correct result.有了代理模式,代理在每次调用中消耗的 Token 更少,且不会将部分结果误认为完整结果。这意味着能以更少的步骤获得正确结果。
The current behavior is a first attempt, and the conventions around agents are a work in progress. If your agent is not detected, or the output confuses it, please open an issue or comment on the pull request. Transcripts of an agent being confused are especially useful.目前的行为是初步尝试,围绕代理的约定仍在不断完善中。如果你的代理未被检测到,或者输出使其感到困惑,请提交 Issue 或在 Pull Request 上发表评论。记录代理感到困惑的转录内容尤其有用。
Further Reading 延伸阅读
- Shell: agent mode, render for AI coding agents when one is detected (pull request #26167)Shell: 代理模式,在检测到 AI 编码代理时为其渲染输出 (Pull Request #26167)
- Shell: proposal to add
.mode llm(Carlo Piovesan’s proposal, the basis for the byte budget and early stop)Shell: 增加 .mode llm 的提案(Carlo Piovesan 的提案,字节预算和提前终止的基础)