# Agent 工作流

> DuckLocal 0.4.0 为 AI agent 新增的能力：受限 SQL、可确认结果的窗口交接、仪表盘修改提议、分析记录和可移动的项目。

Source: https://ducklocal.app/docs/zh/agent-workflows

**[English](../agent-workflows.md)**

::: info 0.4.0 新增
本页内容都随 [v0.4.0](https://github.com/JetSquirrel/DuckLocal/releases/tag/v0.4.0) 发布。如果 `ducklocal check --help` 的输出里没有 `--restricted`，说明你手上的版本较旧，请先[更新](getting-started.md)。
:::

通过 [CLI](cli.md)，agent 已经可以查询文件、搭建仪表盘、读取你的评论。这些新增功能管的是这些工作的前后：限制不是它自己写的 SQL，确认窗口把请求处理成了什么样，由你批准修改，以及记录答案是怎么得出的。

## 限制不是你写的 SQL {#restricted}

`--restricted` 用于从数据里抄来的 SQL，以及别人写的仪表盘。只读模式管不了这些：它只保护数据库文件，`COPY` 仍然能写到你有权限的任何地方，`FROM 'https://...'` 仍然能发出请求。

```bash
ducklocal query --restricted --allow-read ./data --sql "SELECT * FROM 'data/sales.csv'"
ducklocal check report.dash --restricted --allow-read ./data
```

| SQL 想要 | 结果 |
| --- | --- |
| 读取 `--allow-read` 下的文件 | 允许 |
| 读取其他文件，无论用路径、glob 还是 `..` | 拒绝 |
| 写文件，即使在 `--allow-read` 下 | 拒绝 |
| `ATTACH` 或新建数据库 | 拒绝 |
| 访问 `http`、`https`、`s3` 或其他 URL | 拒绝 |
| `INSTALL` 或 `LOAD` 扩展 | 拒绝 |
| 修改设置，包括这些限制本身 | 拒绝 |
| 运行超过 `--timeout`（默认 60 秒） | 被中断，kind 为 `timeout` |
| 使用超过 `--memory-limit`（默认 `2GB`） | 失败，kind 为 `resource_limit` |

不给 `--allow-read` 时，任何文件都读不了，仪表盘自己所在的文件夹也一样。被拒绝的语句退出码为 1，kind 为 `policy_denied`，绝不会退回到不受限的方式运行。`EXPLAIN`、`PRAGMA` 和顶层 `PIVOT` 同样会被拒绝，因为语句必须是单条 SELECT。

**这不是沙箱。** 这些限制来自 DuckDB 本身，DuckLocal 在任何平台上都没有施加操作系统级隔离。限制约束的是 SQL，而不是选择参数的人：能用 shell 的 agent 可以直接去掉 `--restricted`。要约束你不信任的 agent，请把 agent 本身放进容器或虚拟机。`schema`、`profile`、`project check` 和 `open --run` 不受限，窗口打开仪表盘时也不受限。

完整的威胁模型和各平台的测试情况，见 [What limits agent-run SQL](https://github.com/JetSquirrel/DuckLocal/blob/main/docs/agent-security.md)（英文）。

## 与窗口保持同步 {#handoff}

`ducklocal open` 以前只报告“已送达”。现在有两个参数：一个报告请求最后怎么样了，一个防止在窗口已经变化时继续操作。

```bash
ducklocal open --state --active                  # 记下 revision
ducklocal open --if-revision REV --run --wait \
  --title "各渠道收入" --sql "SELECT ..."
```

- `--wait` 最多等 5 分钟，并在输出里加上 `status`、`tab_id`、`result_status`、`error` 和 `revision`。
- `--if-revision REV`：如果窗口显示的已不是这个 revision，就什么都不打开，退出码为 1，kind 为 `stale`。重试前先重新读取状态。有人在别的标签页打字也会让它过期，这是有意设计的。

`--state` 现在还会报告：

| 字段 | 含义 |
| --- | --- |
| `schema_version`、`revision` | 契约版本，以及代表窗口当前内容的令牌 |
| `actions` | 最近请求的结果：`stale`、`queued`、`running`、`opened`、`succeeded` 或 `failed` |
| `limits` | 预览的上限。读取状态不会返回完整结果 |
| `connections` | S3 的 endpoint 和 region，以及每个已挂载数据库的名称、类型、位置和地址。**绝不包含密钥** |
| `result.reach` | `local`、`remote` 或 `unknown`，只根据 SQL 判断；还会说明是否用了你的 S3 密钥 |
| `sql_withheld` | 标签页的 SQL 可能含有凭据时为 `true`，文本会被替换 |
| 每个 plot 上的 `scope` | 这张图画了查询结果的多少 |

`reach` 只看得到 SQL 里写明的内容，所以建在远程数据上的本地视图会显示为 `local`。读取这些信息不会发出网络请求，因此 `location: "remote"` 只说明数据库在哪里，不说明它还能连上。

agent 能看到 S3 已配置，但看不到密钥；不过它用 `open --run` 运行的 SQL 仍然可以使用这个密钥。

所有字段和错误 kind 见[交接契约](https://github.com/JetSquirrel/DuckLocal/blob/main/docs/agent-handoff.md)（英文），状态结构见 [JSON schema](https://github.com/JetSquirrel/DuckLocal/blob/main/docs/agent-state-v1.schema.json)。

## 提议修改仪表盘 {#propose}

如果你希望修改要经过你批准，agent 可以提出提议，而不是直接改文件：

```bash
ducklocal propose sales.dash --status                  # 记下 current_hash
ducklocal propose sales.dash --from new.dash --note "按地区拆分收入" --based-on HASH
ducklocal propose sales.dash --status                  # pending、applied 或 rejected
```

规格文件保持不变，仪表盘标签页会提示有一项修改在等待。点 **审阅** 会运行提议里的 SQL，并显示差异和检查结果，然后点 **应用** 或 **拒绝**。

- 只有你能在窗口里应用提议，没有对应的命令。
- 应用时会把旧版本保存为 `sales.dash.bak`；如果提议之后规格文件变过，应用会被拒绝。
- 拒绝不会改动规格文件。
- 点 **审阅** 会运行提议里的 SQL，所以你不信任的提议能访问仪表盘 SQL 能访问的一切。请先对它运行 `check --restricted`。

## 记录分析过程 {#session}

设置 `DUCKLOCAL_SESSION` 后，`query`、`check`、`propose` 和 `open` 都会记录自己做了什么：

```bash
export DUCKLOCAL_SESSION=analysis.session.jsonl
ducklocal session note --text "EU 收入下降 12%" --refs 3,4
ducklocal session show --last 20
```

不设置这个变量就什么都不记录。文件里有 SQL 和规格文本、每个数据源的大小和修改时间、每个结果的形状，但绝不包含数据行。错误信息最多保留 500 个字符，可能引用数据值，所以除非你有意分享，否则不要把文件放进共享文件夹。

每个步骤和结论都有 `doubts`，在读取时计算：哪些失败了、被截断了，或者之后发生了变化。列表为空表示目前没发现问题，不代表结论一定正确。

可能含有凭据的 SQL 不会被记录。这项检查按 `secret`、`token`、`access_key` 这类词判断，所以列名里有这些词时，查询文本也会被隐去。

## 让分析可以搬走 {#project}

```bash
ducklocal project init             # 写入 ducklocal-project.json
ducklocal project check            # 搬走后什么会失效
ducklocal project relink --source orders --to data/2026/orders.csv
```

`ducklocal-project.json` 是可选的。它列出文件夹里的仪表盘和它们的依赖；数据只引用，绝不复制。在窗口里打开它，就会打开这些仪表盘。

`project check` 报告 `ok`、`portable`、`problems`（每项带 `hint`）和 `limitations`。无论结果如何退出码都是 0，所以要看输出。它不会访问网络，也不会安装任何东西。通过检查不能证明数据没变。`project relink` 会修改你的仪表盘，并把每个旧版本保存为 `NAME.dash.bak`。

## 完整示例 {#worked-example}

[A leaked key, investigated by an agent and a person](https://github.com/JetSquirrel/DuckLocal/tree/main/examples/cloudtrail_investigation)（英文）用一周虚构的 CloudTrail 日志，从第一条查询一直走到审阅完的仪表盘。agent 这一半的步骤，每次改动都有测试运行。窗口这一半还没有人实际点过一遍，它的 README 写明了是哪些步骤。
