Scoot
Scoot 是一个用纯 Zig 编写的轻量、本地优先的 AI agent 守护进程与 CLI。它通过一个 防御式 ReACT 循环 驱动 OpenAI 兼容的模型后端,校验模型产出的每一个结构化步骤, 在 执行策略门 背后运行本地工具,并把每一步记录为可审计的本地状态。
它专为纯文本环境而构建——服务器、容器、CI runner、嵌入式 Linux——在这些场景下,你 想要一个可自动化、体积小、可预测、完全可检查的 agent,没有 GUI、没有云同步、也没有 明文密钥。
工作原理
每个回合都运行同一个防御式循环:
- 询问 模型,要求其给出恰好一个结构化步骤(
thought+action+action_input)。 - 校验 该步骤是否符合严格的 JSON schema(绝不执行自由格式文本)。
- 门控 该动作,使其通过当前生效的执行策略(
guarded/readonly/unrestricted)。 - 运行 选定的内建工具,在带硬超时的沙盒中执行。
- 审计 该动作,并写入会话对话记录与审计日志。
- 观察——把工具的输出作为下一个观察反馈给模型。
循环不断重复,直到模型给出 final 答复或触及 max_turns。
核心能力
- 两个入口——一次性的
scoot -e "<goal>"与交互式 REPL。 - 十二个内建动作——
bash、file_read、file_write、file_edit、grep、glob、outline、http_request、skill、recall、parallel与final。这些 结构化工具无需外部命令即可工作,因此在精简系统上行为完全一致。参见 内建工具。 - 三种执行策略——
guarded(交互式绊线)、readonly(fail-closed(失败即关闭)),以及unrestricted(有审计但不设限),外加可选的 写入限制与 SSRF 加固。参见 执行策略与安全。 - 本地技能 配合渐进式披露——面向具体任务的指令包,从项目目录与用户目录中发现,
通过原生只读的
skill动作读取。参见 技能。 - 调度与守护进程模式——无人值守的任务始终以 fail-closed 的
readonly安全级别运行, 除非你主动选择放宽。参见 调度与守护进程。 - 可审计状态——会话与审计事件以仅追加(append-only)的 JSONL 持久化。参见 会话与审计。
- 灵活的配置与密钥——TOML 优先,JSON 兜底,密钥从环境变量、
0600的 token 文件, 或凭证命令加载——绝不内联写入。参见 配置。
快速开始
# 1. Build (Zig 0.16+).
zig build
zig build test
# 2. Point Scoot at a backend (defaults to a local Ollama-compatible endpoint).
export OPENAI_API_KEY="sk-..." # only if your backend needs a key
# 3. Inspect the resolved runtime and health.
./zig-out/bin/scoot config
./zig-out/bin/scoot doctor
# 4. Run a one-shot goal, or start the REPL.
./zig-out/bin/scoot -e "count the Zig source files in this repository"
./zig-out/bin/scoot # interactive REPL; /exit to leave
初次接触 Scoot?请按顺序阅读 安装 → 配置 → CLI 参考。想了解 agent 究竟能 做 什么?参见 内建工具 与 执行策略与安全。
运行目录
Scoot 默认把所有内容放在 ~/.scoot 下。可用 --scoot-home 标志或
SCOOT_HOME 环境变量覆盖(标志优先)。
~/.scoot/
config.toml # configuration (config.json is the fallback)
token # optional 0600 API token file
skills/ # user-level skills
logs/ # audit / run logs (audit.jsonl)
state/ # sessions, daemon lifecycle, scheduler state
从 config.example.toml 开始——把它复制到
~/.scoot/config.toml 再编辑。
设计原则
Scoot 刻意保持保守。以下是不可妥协的边界,而非偏好:
- 本地优先的运行时状态,一个小巧的二进制,没有 GUI;
- 仅支持 OpenAI 兼容后端——不蔓延到各家厂商专有协议;
- 提交的配置、日志或审计输出中没有明文密钥;
- 绝不执行未经校验的模型输出;
- 技能只增加指令与数据,绝不增加特权执行路径。
完整的演进规则集请参见 路线图 与 Agent 指南, 它们规定了 Scoot 如何演进。
安装
Scoot 以单个自包含二进制分发。你可以从源码构建,也可以下载某个 tag 版本的发布产物。
环境要求
- Zig 0.16.0 或更新版本 用于从源码构建。没有其他构建依赖。
- 一个可达的 OpenAI 兼容 Responses API(
/v1/responses)后端(本地或远程)。 - 一个 POSIX shell(
/bin/sh)供bash工具使用。结构化工具 (file_read、grep、glob、http_request……)无需任何外部命令。
支持的发布目标:linux-amd64、linux-arm64、linux-armv7、
macos-amd64、macos-arm64。
安装 latest release
安装脚本会识别当前主机的 OS/CPU,下载匹配的 latest release 压缩包和 .sha256
文件,校验通过后安装 scoot 二进制。
curl -fsSL https://raw.githubusercontent.com/jamiesun/scoot/main/install.sh | sh
默认安装到 /usr/local/bin,必要时会使用 sudo。如果想避免 sudo,请指定一个
已经在 PATH 中的用户可写目录:
curl -fsSL https://raw.githubusercontent.com/jamiesun/scoot/main/install.sh | env SCOOT_INSTALL_DIR="$HOME/.local/bin" sh
需要可复现安装时,可以固定版本:
curl -fsSL https://raw.githubusercontent.com/jamiesun/scoot/main/install.sh | env SCOOT_INSTALL_VERSION=v0.2.0 sh
当体积比运行时安全检查更重要时,可以安装更小的 ReleaseSmall 构建:
curl -fsSL https://raw.githubusercontent.com/jamiesun/scoot/main/install.sh | env SCOOT_INSTALL_FLAVOR=small sh
安装脚本支持的环境变量:
| 变量 | 默认值 | 作用 |
|---|---|---|
SCOOT_INSTALL_DIR | /usr/local/bin | 二进制安装目录。 |
SCOOT_INSTALL_VERSION | latest | 要安装的 release tag,可带或不带开头的 v。 |
SCOOT_INSTALL_FLAVOR | safe | safe 安装默认的 ReleaseSafe 产物;small 安装 ReleaseSmall 产物。 |
SCOOT_INSTALL_BINARY | scoot | 安装后的二进制名称。 |
SCOOT_INSTALL_REPO | jamiesun/scoot | 下载 release 的 GitHub 仓库。 |
Safe 与 Small 发布构建
每个 tag 版本会为每个支持目标发布两种二进制:
| 变体 | Zig optimize 模式 | 什么时候用 |
|---|---|---|
| 默认 | ReleaseSafe | 需要常规 release,保留运行时安全检查和更清晰的 fail-fast 诊断。 |
small | ReleaseSmall | 需要极小二进制用于探针、边缘设备或极简容器,并接受更少运行时安全检查。 |
从源码构建
git clone https://github.com/jamiesun/scoot.git
cd scoot
zig build # produces ./zig-out/bin/scoot
zig build test # run the full test suite
zig build run -- --version
用于生产 / 嵌入式构建时,优先使用某种 release 优化模式:
zig build -Doptimize=ReleaseSafe # recommended: keeps safety checks
zig build -Doptimize=ReleaseFast # fastest, fewer safety checks
zig build -Doptimize=ReleaseSmall # smallest, fewer safety checks
如果愿意,可以把二进制放到 PATH 上:
install -m 0755 zig-out/bin/scoot /usr/local/bin/scoot
安装发布产物
每个 tag 版本会为每个目标发布默认 .tar.gz、一个 -small 变体,以及对应的
.sha256 校验和。
# Pick the archive for your platform from the Releases page, then:
sha256sum -c scoot-<target>.tar.gz.sha256
tar -xzf scoot-<target>.tar.gz
install -m 0755 scoot/scoot /usr/local/bin/scoot
scoot --version
用 Docker 运行
每个 tag release 还会发布面向 linux/amd64、linux/arm64 和
linux/arm/v7 的多平台 Linux 容器镜像。
标签规则:
| 标签形式 | 运行时基础镜像 | 示例 |
|---|---|---|
<version>、<major>.<minor>、<major>、latest | 极简 BusyBox/musl 运行时 | ghcr.io/jamiesun/scoot:latest |
<version>-alpine、<major>.<minor>-alpine、<major>-alpine、latest-alpine | 带 apk 的 Alpine 运行时 | ghcr.io/jamiesun/scoot:latest-alpine |
镜像的 entrypoint 是 scoot,因此镜像名后面的参数就是普通 Scoot CLI 参数。
容器里建议始终显式设置 SCOOT_HOME 并挂载一个运行目录,避免 config.toml、
状态、会话、技能和日志留在镜像文件系统里:
mkdir -p scoot-data
cp config.example.toml scoot-data/config.toml
docker run --rm \
-e SCOOT_HOME=/scoot \
-e OPENAI_API_KEY \
-v "$PWD/scoot-data:/scoot" \
ghcr.io/jamiesun/scoot:latest \
--version
如果后端运行在 Docker 宿主机上,容器内的 127.0.0.1 指的是容器自身。请把挂载
配置里的 [backend] base_url 改成容器可访问的地址:
[backend]
base_url = "http://host.docker.internal:11434/v1"
model = "qwen2.5"
api_key_env = "OPENAI_API_KEY"
Docker Desktop 和 OrbStack 通常内置 host.docker.internal。Linux Docker
Engine 可以给 docker run 增加:
--add-host=host.docker.internal:host-gateway
或者直接使用后端真实的 LAN / 容器网络地址。
一次性容器运行
当人、CI 或脚本只想立即执行一个目标时,使用一次性容器:
docker run --rm \
-e SCOOT_HOME=/scoot \
-e OPENAI_API_KEY \
-v "$PWD/scoot-data:/scoot" \
ghcr.io/jamiesun/scoot:latest \
-e "Inspect the mounted project and summarize obvious risks."
无人值守调度容器
config.example.toml 默认关闭调度:
[schedule]
enabled = false
这是有意的安全默认值。scoot schedule run 和 scoot daemon run 会在挂载配置
没有显式启用调度时 fail-closed 退出。容器化调度任务需要编辑
scoot-data/config.toml:
[schedule]
enabled = true
poll_ms = 1000
[[schedule.jobs]]
id = "disk-check"
goal = "Inspect disk usage and summarize anomalies"
every_sec = 300
mode = "readonly"
当外部调度器每次拉起一个新容器时,例如宿主机 cron、CI、systemd timer 或
Kubernetes CronJob,使用 schedule run --ticks 1:
docker run --rm \
-e SCOOT_HOME=/scoot \
-e OPENAI_API_KEY \
-v "$PWD/scoot-data:/scoot" \
ghcr.io/jamiesun/scoot:latest \
schedule run --ticks 1
因为每次容器退出后调度器运行时内存都会重置,every_sec 任务在每个新容器的第一轮
都会到期。如果需要严格日历时间,建议使用与外部调度频率一致的 cron 触发器。
当容器本身要长驻并负责持续轮询任务时,使用 daemon run。它保持前台运行,写入
state/daemon.json 与 state/daemon.pid,并处理 SIGTERM/SIGINT 以便容器干净停止:
docker run -d --name scoot \
-e SCOOT_HOME=/scoot \
-e OPENAI_API_KEY \
-v "$PWD/scoot-data:/scoot" \
ghcr.io/jamiesun/scoot:latest \
daemon run
docker compose 示例:
services:
scoot:
image: ghcr.io/jamiesun/scoot:latest
command: ["daemon", "run"]
restart: unless-stopped
environment:
SCOOT_HOME: /scoot
OPENAI_API_KEY: ${OPENAI_API_KEY}
volumes:
- ./scoot-data:/scoot
运行 daemon run 时,/scoot 挂载目录需要可写,因为 Scoot 要写入 state、session
和 audit 文件。如果希望配置文件本身只读,可以让部署系统管理 config.toml,同时保留
可写的 state/、logs/ 和 skills/ 子目录。
首次运行设置
Scoot 在内置默认值下即可工作,但你通常会把它指向自己的后端与 token。
1. 创建运行目录与配置。 Scoot 默认使用 ~/.scoot;把示例配置复制到那里:
mkdir -p ~/.scoot
cp config.example.toml ~/.scoot/config.toml
2. 选择一个后端。 编辑 ~/.scoot/config.toml 中的 [backend]:
[backend]
# Local Ollama-compatible endpoint (the default):
base_url = "http://127.0.0.1:11434/v1"
model = "qwen2.5"
# Or a hosted OpenAI-compatible endpoint:
# base_url = "https://api.openai.com/v1"
# model = "gpt-4o-mini"
3. 提供 token,但不要写进配置。 Scoot 先从环境变量解析密钥,再尝试 0600
的 token 文件,最后是凭证命令。最简单的方式:
export OPENAI_API_KEY="sk-..."
或者使用一个私密的 token 文件:
umask 077
printf '%s' "sk-..." > ~/.scoot/token # must be mode 0600
完整的解析顺序与凭证命令选项参见 配置 → 密钥。
4. 验证。 config 打印解析出的运行目录与后端(密钥已脱敏);
doctor 运行本地健康检查:
scoot config
scoot doctor
doctor 会报告运行目录、配置来源、后端可达性前置条件、解析出的密钥 来源
(绝不报告值本身)、技能发现,以及审计日志路径。在运行目标前,先修复它标记出的所有问题。
后端示例
Scoot 只讲 OpenAI 兼容 Responses API(/v1/responses)。Ollama ≥ 0.13.3 与 vLLM
支持这种无状态接口;其他后端需置于 Responses 兼容网关之后。
Ollama(本地,默认)
[backend]
base_url = "http://127.0.0.1:11434/v1"
model = "qwen2.5"
# No api key needed for a local Ollama; leave OPENAI_API_KEY unset.
OpenAI
[backend]
base_url = "https://api.openai.com/v1"
model = "gpt-4o-mini"
api_key_env = "OPENAI_API_KEY"
Azure / 其他带额外字段的厂商
使用 [backend.extra_body] 传递厂商专有的顶层请求字段,无需重新编译。绝不要把密钥放在这里。
[backend]
base_url = "https://your-resource.openai.azure.com/openai/v1"
model = "gpt-4o"
[backend.extra_body]
reasoning_effort = "high"
service_tier = "priority"
自定义 CA bundle(精简 / 嵌入式系统)
如果系统根证书缺失(在最小化 Linux 镜像上很常见),把 ca_file 指向随固件附带的 PEM bundle:
[backend]
ca_file = "/etc/ssl/certs/ca-certificates.crt"
下一步
设计理念
Scoot 刻意保守。它不追求成为功能最多的 AI 自动化平台,而是要成为一个小型、 本地、可检查的 Agent 运行时,能以可控方式触碰真实机器。
有些看起来像缺陷的地方,其实是选择。没有 GUI、daemon 前台运行、readonly
非常严格、不做各厂商私有协议适配,这些都不是偶然遗漏,而是为了让系统保持小、
可审计、可预测。真正的 bug 当然应该修;但如果需求跨过下面这些边界,就需要先
做项目级决策。
Scoot 优先追求什么
Scoot 按下面顺序优化:
- 安全与可控。 模型输出必须先被校验;不安全或非法输出不能直接落到系统。
- 可审计。 一次运行结束后,用户应能回看目标、模型步骤、工具调用、策略决定、 观察结果和最终答案。
- 本地优先。 配置、会话、技能、日志和 daemon 状态都留在用户机器上。
- 小部署面。 一个原生二进制、纯文本配置和尽量少的活动部件,比功能广度更重要。
- 长时间运行稳定。 daemon 和 scheduled workloads 必须有边界,并采用保守恢复。
这些目标冲突时,Scoot 选择优先级更高的一项。因此,一些更激进的 Agent 会尝试的 工作,Scoot 可能会拒绝。
目标
Scoot 应该是:
- 终端原生的 Agent。 通过 shell 使用
-e、REPL、schedule 和 daemon 模式。 - 带策略门的本地执行器。 文件、搜索、shell、HTTP、skill 和 parallel 动作都 先校验,再经过明确策略决策。
- 边界上 OpenAI 兼容。 本地或云端后端只要提供 OpenAI 兼容
Responses API(
/v1/responses)即可接入。 - 适合小机器。 Zig 实现、低依赖、显式内存分配和交叉编译能力,是为了适配 边缘主机、NAS、实验室机器和小型服务器。
- 通过指令扩展,而不是原生插件扩展。 Skills 通过可审查的指令包和资源扩展 行为,不需要重新编译 Scoot。
- 足够适合无人值守只读任务。 Scheduled work 默认
readonly,无人值守的guarded会被矫正为有效readonly。
非目标
这些不是 backlog,而是边界:
- 不做 GUI 或 Web Dashboard。 Scoot 是 CLI 和 daemon,不是桌面应用或浏览器控制台。
- 不扩散到厂商私有协议。 Scoot 不为每个模型厂商维护一个适配器;厂商差异应由 OpenAI 兼容网关抹平。
- 不做复杂云同步。 运行状态留在本地;Scoot 不是托管式多设备控制平面。
- 不执行未经校验的模型输出。 自由文本不会直接变成 shell 命令或工具调用。
- 不做原生插件运行时。 Skills 是指令和资源,不是带新权限的动态原生代码。
- 不为了方便牺牲密钥安全。 token 不应出现在提交的配置、日志、审计输出或示例里。
- 不把
guarded伪装成安全沙箱。guarded是交互式绊线;无人值守或敌对环境 应使用readonly和操作系统隔离。
铁律
- 先校验,再产生效果。 每个模型步骤必须先解析和检查,工具才能运行。
- 所有效果都经过策略门。 shell、写入、网络和原生工具动作必须通过当前策略。
- 外部工作必须有超时。 子进程和网络请求不能让 Agent 无限挂住。
- 密钥不能进入文本产物。 配置、日志、会话、错误和文档都不能暴露 token。
- 无人值守优先
readonly。 Scheduledguardedjob 会被矫正为有效readonly。 - Skill 不授予特权。 读取 skill 是原生只读能力;skill 要求 Scoot 执行的动作 仍走正常策略门。
- 文档保持双语。 面向用户的文档变更必须同步英文和中文。
看起来像缺陷,其实是选择
| 你可能看到的现象 | 为什么这样做 |
|---|---|
| 没有 GUI。 | 文本界面可脚本化、可审查,也适合小型主机。 |
daemon run 保持前台运行。 | 后台化、重启、日志和停止应由 systemd 这类 supervisor 负责。 |
readonly 禁止 shell 和网络。 | fail-closed 的无人值守模式必须阻止修改和数据外带。 |
guarded 不被宣传成安全隔离。 | 拒绝清单能挡事故,但不是对抗性沙箱。 |
| 没有厂商原生 tool calling 集成。 | 模型边界保持 OpenAI 兼容和 schema 驱动。 |
| Skills 是本地目录,不是插件。 | 指令可以扩展行为,同时不扩大原生可信面。 |
| 没有向量记忆子系统。 | 本地 JSONL 状态和 skills 更可检查,也避免重依赖。 |
| 网络探针需要显式接受风险。 | 探针有价值,但必须先用 OS / 网络隔离收窄环境,再授予宽权限。 |
判断一个新功能时,正确的问题不是“能不能做”,而是“能不能在保持本地优先、 可审计、小型、经过策略门的前提下做”。
配置
Scoot 从其运行目录读取配置,并在缺失时回退到内置默认值,因此零配置即可运行。本页是 每个配置节与配置键的完整参考。
文件位置与加载顺序
运行目录默认是 ~/.scoot。可用 --scoot-home(最高优先级)或
SCOOT_HOME 环境变量覆盖。
在该目录内,配置按以下顺序加载:
config.tomlconfig.json- 内置默认值
合并语义: 加载是 按节、按字段 进行的。任何缺失的节或字段都回退到其内置默认值,
并且 未知字段会被忽略。这意味着部分配置始终有效——你只需指定想要改动的部分。
从 config.example.toml 开始。
随时运行 scoot config 可打印 解析出的 运行目录与后端配置(密钥已脱敏)。
环境变量覆盖
每个非密钥配置字段都可被 SCOOT_* 环境变量覆盖。该覆盖层在 内存中 应用,优先级为:
SCOOT_* 环境变量 > config.toml / config.json > 内置默认值
无论配置文件是否存在,环境变量始终胜出,因此你可以在 完全没有配置文件 的情况下运行
Scoot——把 SCOOT_HOME 指向一个临时目录,所有配置经环境变量传入即可。这非常适合 CI 以及
跑完即焚的一次性执行。
| 环境变量 | 覆盖 | 类型 |
|---|---|---|
SCOOT_BACKEND_BASE_URL | backend.base_url | 字符串 |
SCOOT_BACKEND_MODEL | backend.model | 字符串 |
SCOOT_BACKEND_TIMEOUT_MS | backend.timeout_ms | 整数 |
SCOOT_BACKEND_API_KEY_ENV | backend.api_key_env | 字符串(指明持有 token 的变量名) |
SCOOT_BACKEND_API_KEY_FILE | backend.api_key_file | 字符串 |
SCOOT_BACKEND_API_KEY_CMD | backend.api_key_cmd | 字符串 |
SCOOT_BACKEND_CA_FILE | backend.ca_file | 字符串 |
SCOOT_BACKEND_STORE | backend.store | 布尔(true/false/1/0) |
SCOOT_BACKEND_EXTRA_BODY | backend.extra_body | JSON 对象 |
SCOOT_AGENT_DEFAULT_MODE | agent.default_mode | 字符串(goal/plan) |
SCOOT_AGENT_COMPACTOR | agent.compactor | 字符串(drop/extractive/plugin:<name>) |
SCOOT_AGENT_MAX_TURNS | agent.max_turns | 整数 |
SCOOT_AGENT_CONTEXT_BUDGET_BYTES | agent.context_budget_bytes | 整数 |
SCOOT_TOOLS_POLICY | tools.policy | 字符串(guarded/readonly/unrestricted) |
SCOOT_TOOLS_TIMEOUT_MS | tools.timeout_ms | 整数 |
SCOOT_TOOLS_CONFINE_WRITES | tools.confine_writes | 布尔(true/false/1/0) |
SCOOT_TOOLS_BLOCK_INTERNAL_HTTP | tools.block_internal_http | 布尔 |
SCOOT_SKILLS_ENABLED | skills.enabled | 布尔 |
SCOOT_SKILLS_INCLUDE_PROJECT_SKILLS | skills.include_project_skills | 布尔 |
SCOOT_SKILLS_INCLUDE_AGENTS_SKILLS | skills.include_agents_skills | 布尔 |
SCOOT_AUDIT_LEVEL | audit.level | 字符串 |
SCOOT_AUDIT_TO_FILE | audit.to_file | 布尔 |
说明:
- 空值(
"")视为 未设置,不会覆盖默认值——便于 CI 中的可选输入。 - 类型错误 的值(例如给
SCOOT_AGENT_MAX_TURNS传非整数)会被 忽略,字段保留原值, 并向 stderr 打印告警(绝不写 stdout,从而保证-e管道输出干净)。 - 绝不从
SCOOT_*直接读取密钥。 token 仍只来自backend.api_key_env指明的来源 (默认OPENAI_API_KEY),遵循下文 密钥 铁律。SCOOT_BACKEND_API_KEY_ENV只改变 查询哪个 变量,而非 token 本身。
在 GitHub Actions 中零配置运行
把 token 存为 GitHub secret,其余经 env 传入。无需提交或写出 config.toml;运行目录
在 runner 临时空间下即时创建,随任务一并销毁。
jobs:
ask:
runs-on: ubuntu-latest
env:
SCOOT_HOME: ${{ runner.temp }}/scoot
OPENAI_API_KEY: ${{ secrets.LLM_KEY }} # token 值(机密)
SCOOT_BACKEND_API_KEY_ENV: OPENAI_API_KEY # 由哪个变量持有
SCOOT_BACKEND_BASE_URL: https://api.openai.com/v1
SCOOT_BACKEND_MODEL: gpt-4o-mini
SCOOT_TOOLS_POLICY: readonly # CI 的安全默认
steps:
- uses: actions/checkout@v4
- name: Install scoot
run: |
# 下载对应平台的 release 资产,然后:
install -m755 scoot /usr/local/bin/scoot
- name: Ask
run: scoot -e "总结本仓库的最新改动"
scoot -e 会检查 SCOOT_HOME:目录不存在则用内置默认创建;已存在则在其之上叠加 SCOOT_*
覆盖。无论哪种情况,密钥都不会落盘。
配置节概览
| 配置节 | 用途 |
|---|---|
[backend] | LLM 端点、模型、API key 来源、TLS、额外请求字段 |
[agent] | ReACT 回合上限、认知模式、上下文预算、压缩插件 |
[tools] | 工具超时、执行策略、guarded 加固 |
[skills] | 技能发现开关与额外搜索路径 |
[mcp] | mcp_call 可调用的外部 MCP server 声明 |
[audit] | 审计日志级别与文件输出 |
[schedule] | 无人值守的调度任务与轮询间隔 |
[backend]
LLM 后端。Scoot 只 讲 OpenAI 兼容 Responses API(/v1/responses)。
默认情况下,Scoot 每回合都会重发完整 input,以保持本地上下文压缩有效,并约束
token 用量。
| 键 | 类型 | 默认值 | 说明 |
|---|---|---|---|
base_url | string | http://127.0.0.1:11434/v1 | OpenAI 兼容端点的基础 URL。 |
model | string | qwen2.5 | 发送给后端的模型名。 |
api_key_env | string | OPENAI_API_KEY | 作为 第一 token 来源的环境变量名。 |
timeout_ms | u64 | 120000 | 单次后端 Responses API 调用的硬超时,单位毫秒。0 表示关闭超时。 |
api_key_file | string? | unset → ~/.scoot/token | 0600 token 文件的路径。在环境变量来源之后使用。 |
api_key_cmd | string? | unset | 打印 token 的命令(如 pass show openai)。最后使用。它会由 Scoot 执行,因此应视为可信配置。 |
ca_file | string? | unset → system roots | 用于 HTTPS 的 PEM CA bundle。在缺少根证书的系统上设置它。 |
store | bool | false | 通过 Responses API 的 store 标志请求后端在服务端持久化响应。默认关闭,以保持 scoot 无状态、本地优先。 |
extra_body | table? | unset | 合并进每个请求的额外顶层 JSON 字段。 |
[backend.extra_body]
一个直通表,原样合并进顶层模型请求 JSON。
用它来传递后端专有或较新的字段而无需重新编译——例如
reasoning_effort、service_tier、top_p。只接受 JSON 对象;
非对象值会被忽略。绝不要把密钥放在这里,也不要覆盖
model、messages 或 input 等核心字段。
[backend]
base_url = "https://api.openai.com/v1"
model = "gpt-4o-mini"
# store = false
api_key_env = "OPENAI_API_KEY"
[backend.extra_body]
top_p = 0.9
reasoning_effort = "high"
[agent]
认知引擎。
| 键 | 类型 | 默认值 | 说明 |
|---|---|---|---|
max_turns | u32 | 32 | agent 停止前的最大 ReACT 回合数,用于约束失控循环。 |
default_mode | string | goal | 认知模式。goal 现已实现;plan 是保留项(见路线图),目前尚不改变执行。 |
compactor | string | extractive | 上下文压缩策略:extractive 写入确定式纪要;drop 保持旧的计数标记;plugin:<name> 运行外部压缩器包。 |
context_budget_bytes | usize | 80000 | 累积的提示历史预算,单位 字节。0 表示禁用。 |
compactor_plugin | table | unset | 按名称组织的动态插件配置,位于 [agent.compactor_plugin.<name>]。 |
context_budget_bytes 用于保护小上下文后端。当运行中的对话记录将超过该大小时,
agent 会先按 agent.compactor 压缩历史。默认 extractive 会写入确定式导航纪要,
例如读过/改过的文件、命令与退出码、策略拒绝和明显的 TODO 观察。drop 是最小兜底行为:
把更早的工具记录替换为计数标记。plugin:<name> 会先运行已配置的外部压缩器;若包无效、
策略拒绝、超时、输出格式错误,或生成的 marker 仍会超预算,则回退到 extractive 再到
drop。仅当压缩后对话记录仍超预算
(预算过小、连最小保留集都放不下)时,才在下一次后端调用 之前 以清晰的错误 fail-fast。
字节是 token 的粗略代理;默认值只是可靠性护栏,不是精确模型窗口保证。应把它设为低于后端
上下文窗口的保守值,或设为 0 显式关闭(回合数仍由 max_turns 约束)。
[agent]
max_turns = 32
default_mode = "goal"
compactor = "extractive" # 或 "drop"
context_budget_bytes = 80000 # 0 表示关闭;按后端窗口调小
[agent.compactor_plugin.<name>]
外部压缩插件复用 Wasm 工具包的静态描述符边界,但 manifest.toml 必须设置
kind = "compressor"。Scoot 不内嵌 Wasm runtime;压缩由有界子进程完成。插件从 stdin
接收 JSON CompactionRequest,并在 stdout 输出类似 {"marker":"..."} 的 JSON 对象。
包策略只能授予 compute。任何非 compute 能力、坏输出、超时、非零退出码或超预算 marker
都会被判为不可用,并进入内置回退链。
| 键 | 类型 | 默认值 | 说明 |
|---|---|---|---|
package | string | 必填 | 由 wasm_tool.validatePackage 校验的目录。 |
host | list of string | unset | 命令 argv 模板。占位符:{package}、{component}、{entry}。若未设置,Scoot 尝试 {package}/{entry}。 |
timeout_ms | u64? | tools.timeout_ms | 子进程硬超时。0 表示关闭 deadline。 |
stdout_limit | usize? | 1048576 | 接受的最大 stdout 字节数。 |
stderr_limit | usize? | 262144 | 接受的最大 stderr 字节数。 |
如果可选的独立 host 已安装在 PATH 上,可直接使用 scoot-wasm wasi {component}
(否则把 scoot-wasm 换成绝对路径):
[agent]
compactor = "plugin:tiny"
[agent.compactor_plugin.tiny]
package = "/opt/scoot/compressors/tiny"
host = ["scoot-wasm", "wasi", "{component}"]
timeout_ms = 30000
stdout_limit = 1048576
stderr_limit = 262144
[tools]
工具沙盒与执行策略。完整模型参见 执行策略与安全。
| 键 | 类型 | 默认值 | 说明 |
|---|---|---|---|
timeout_ms | u64 | 30000 | 每一次 工具调用的硬超时,单位毫秒。 |
policy | string | guarded | 执行策略:guarded、readonly 或 unrestricted(别名 yolo)。未知值回退到 guarded。 |
confine_writes | bool | true | 把 file_write/file_edit 限制在项目根目录内。仅 guarded。 |
block_internal_http | bool | true | 阻止 http_request 访问内部/元数据主机(SSRF 防护)。仅 guarded。 |
两个加固标志 仅在 guarded 模式下生效——readonly 已经
fail-closes 写入与网络。confine_writes 拒绝绝对路径、..
逃逸,以及 shell 风格的 ~/$VAR 展开。block_internal_http 是一个
基于字面 IP 范围与已知内部名称的启发式判断;它 不 解析 DNS,因此 DNS 重绑定仍可绕过它——
若需真正隔离,请使用 readonly 或网络沙盒。
[tools]
timeout_ms = 30000
policy = "guarded"
confine_writes = true
block_internal_http = true
[skills]
本地技能发现。参见 技能。
| 键 | 类型 | 默认值 | 说明 |
|---|---|---|---|
enabled | bool | true | 启用技能发现与注入。 |
include_project_skills | bool | false | 是否加载 <cwd>/.agents/skills 这个随仓库携带的项目技能目录。仅对可信仓库开启。 |
include_agents_skills | bool | false | 是否加载 ~/.agents/skills 这个跨 agent 的用户级技能目录。 |
extra_paths | list of string | [] | 额外的技能搜索路径,追加在内置路径之后。 |
技能按 优先级顺序 发现(名称冲突时靠前者胜出):
<cwd>/.agents/skills——项目本地,仅在include_project_skills=true时加载。~/.agents/skills——跨 agent 的用户级技能,仅在include_agents_skills=true时加载。~/.scoot/skills——Scoot 自有的用户级目录。- 此处列出的
extra_paths。
项目本地技能默认关闭,因为仓库可以携带不可信指令。只应在可信工作区内显式开启。
读取技能的指令是一种原生只读能力,即使在 readonly 模式下也可工作;技能随后让模型运行的内容仍受策略门控。
[skills]
enabled = true
include_project_skills = false
include_agents_skills = false
extra_paths = ["/opt/scoot/skills", "./skills"]
[mcp]
外部 Model Context Protocol server 声明,通过 mcp_call 这个元动作调用。Scoot
这里只做 MCP client:启动或连接已配置的 server 并调用其工具,不对外暴露 MCP server。
调用默认 fail-closed。目标 server 必须存在于 [[mcp.servers]],请求的 tool
也必须出现在 allowed_tools;allowed_tools 为空表示拒绝所有工具。readonly
策略会完全拒绝 mcp_call,因为外部 MCP 工具可能绕过 Scoot 静态工具分类去读写文件或访问网络。
guarded 与 unrestricted 仍然要求显式 server 与工具 allowlist。
| 键 | 类型 | 默认值 | 说明 |
|---|---|---|---|
servers | array | [] | MCP server 声明列表。 |
每个 [[mcp.servers]] 条目:
| 键 | 类型 | 默认值 | 说明 |
|---|---|---|---|
name | string | "" | mcp_call.server 使用的名称。 |
transport | string | stdio | 支持 stdio、Streamable HTTP(http / streamable_http)和 legacy sse。 |
command | string | "" | stdio transport 要启动的命令。 |
args | list of string | [] | 传给 command 的参数。 |
env | list of { name, value } | [] | 子进程环境覆盖块。若设置,请包含子进程需要的一切变量,例如 PATH。 |
allowed_tools | list of string | [] | 显式工具 allowlist。为空即拒绝全部。 |
policy | string | readonly | 用于审计和未来策略扩展的 server 姿态声明。 |
url | string? | 未设置 | HTTP/SSE transport 的远程端点 URL。 |
headers | header object 列表 | [] | 远程 transport 的额外 HTTP header。密钥请用 value_env。 |
Header object 支持 name、value/value_env 二选一,以及可选 prefix。
Accept、Content-Type、MCP-Protocol-Version、Mcp-Session-Id 等协议头由
Scoot 管理,不能覆盖。value_env 缺失或为空时,调用会 fail-closed。
[[mcp.servers]]
name = "demo"
transport = "stdio"
command = "/path/to/mcp-server"
args = ["--flag", "value"]
env = [{ name = "SERVER_MODE", value = "readonly" }]
allowed_tools = ["lookup", "read_resource"]
policy = "readonly"
[[mcp.servers]]
name = "remote-demo"
transport = "http"
url = "https://example.com/mcp"
allowed_tools = ["lookup"]
headers = [
{ name = "Authorization", value_env = "REMOTE_MCP_TOKEN", prefix = "Bearer " },
]
[[mcp.servers]]
name = "legacy-sse-demo"
transport = "sse"
url = "https://example.com/sse"
allowed_tools = ["lookup"]
headers = [
{ name = "X-API-Key", value_env = "REMOTE_MCP_API_KEY" },
]
[audit]
审计日志。参见 会话与审计。
| 键 | 类型 | 默认值 | 说明 |
|---|---|---|---|
level | string | info | 详尽程度:debug、info、warn 或 error。 |
to_file | bool | true | 将审计日志写入 ~/.scoot/logs/audit.jsonl。 |
[audit]
level = "info"
to_file = true
[schedule]
无人值守的调度任务。默认禁用——自主执行必须显式开启。参见 调度与守护进程。
| 键 | 类型 | 默认值 | 说明 |
|---|---|---|---|
enabled | bool | false | 启用调度器 / 守护进程循环。 |
poll_ms | u64 | 1000 | 调度器轮询间隔,单位毫秒。 |
jobs | list of table | [] | 调度任务定义(见下文)。 |
[[schedule.jobs]]
每个任务是一个 array-of-tables 条目,带 恰好一个 触发器。
| 键 | 类型 | 默认值 | 说明 |
|---|---|---|---|
id | string | — | 稳定的任务标识符(必填)。 |
goal | string | "" | agent 运行的自然语言目标。 |
every_sec | u64? | unset | 触发器:固定间隔,单位秒。 |
at_unix | i64? | unset | 触发器:一个固定的 Unix 时间点。 |
cron | string? | unset | 触发器:5 字段 UTC cron 表达式。 |
mode | string | readonly | 执行策略:readonly(默认,安全)或 unrestricted。 |
every_sec / at_unix / cron 中必须设置 恰好一个;否则该任务无效并被跳过并伴随警告。
cron 支持分钟/小时/日/月/周字段,以及 *、逗号列表、范围和 /step。
安全: 调度任务默认 readonly,且 guarded 在执行时会被矫正为等效
readonly。仅在你刻意接受无人值守的写入/网络风险时才使用 unrestricted。
[schedule]
enabled = true
poll_ms = 1000
[[schedule.jobs]]
id = "disk-check"
goal = "Inspect disk usage and summarize anomalies"
every_sec = 300
mode = "readonly"
[[schedule.jobs]]
id = "morning-brief"
goal = "Prepare today's task brief"
at_unix = 1893456000
mode = "readonly"
密钥
绝不要把明文 API key 放进 config.toml/config.json。 Scoot 从三个来源解析
后端 token,按顺序尝试:
- 环境变量,由
backend.api_key_env(默认OPENAI_API_KEY)命名。 - token 文件,位于
backend.api_key_file,未设置时为~/.scoot/token。该 文件 必须是0600权限;若权限过于开放,Scoot 拒绝读取。 - 凭证命令,位于
backend.api_key_cmd(如pass show openai)。 请保持其有界且非交互。
解析出的值绝不会回写磁盘、被 config/doctor 打印,
或被记录进审计日志——只报告其 来源。密钥处理的铁律参见 Agent
指南。
# Source 1 — environment:
export OPENAI_API_KEY="sk-..."
# Source 2 — private token file:
umask 077
printf '%s' "sk-..." > ~/.scoot/token
# Source 3 — credential command (in config):
# api_key_cmd = "pass show openai"
JSON 配置
如果你偏好 JSON,可在运行目录中创建 config.json(仅在
config.toml 缺失时使用)。其结构与 TOML 各节一一对应:
{
"backend": { "base_url": "https://api.openai.com/v1", "model": "gpt-4o-mini" },
"agent": { "max_turns": 32 },
"tools": { "policy": "guarded", "timeout_ms": 30000 }
}
带注释的示例
仓库附带一份完整注释的 config.example.toml。
复制它并编辑:
cp config.example.toml ~/.scoot/config.toml
CLI 参考
scoot [options] [command]
不带命令时,Scoot 启动交互式 REPL。全局选项可以位于命令之前或之后。运行目录默认是
~/.scoot,可用 --scoot-home 或 SCOOT_HOME 覆盖。
全局选项
| 选项 | 说明 |
|---|---|
-e, --eval <prompt> | 运行单个目标至完成,打印答复,然后退出。 |
--retries <N> | -e 模式下针对瞬时后端错误的重试次数(默认 2,0 禁用)。 |
--scoot-home <dir> | 覆盖运行目录。优先于 SCOOT_HOME。 |
--trace | 把 ReACT 执行轨迹打印到 stderr(答复/对话仍在 stdout)。-e 与交互式 REPL 模式均可用。 |
--ticks <N> | 用于 schedule run / daemon run:运行 N 个轮询周期后退出(默认 0 = 永久运行)。 |
-h, --help | 显示用法。 |
-v, --version | 显示版本。 |
命令
选择运行模式
| 模式 | 工作来源 | 退出行为 | 什么时候用 |
|---|---|---|---|
scoot -e "<goal>" | 命令行 prompt。 | 返回一个答案后退出。 | 要立即执行一个任务。 |
scoot serve | stdin 上的 NDJSON 请求。 | 一直运行到 stdin 关闭。 | 本地 app 需要长期 stdio peer。 |
scoot schedule run --ticks 1 | 配置里的 [[schedule.jobs]]。 | 轮询一次调度器后退出。 | cron、systemd timer 或 CI 负责调度时间。 |
scoot daemon run | 配置里的 [[schedule.jobs]]。 | 默认持续运行。 | Scoot 负责调度循环,外部 supervisor 负责保活。 |
daemon run 不是 -e 的快捷写法:它不接收命令行里的临时 prompt,而是加载
配置任务、检查触发器、写入 daemon pid/state 文件,并使用无人值守任务的安全规则。
repl(默认)
scoot # or: scoot repl
启动交互式的读取-求值-打印循环(Read-Eval-Print loop)。输入一个目标,看着 agent 工作,
得到答复,再循环。输入 /exit 退出。每个提示都会在所配置的策略下运行完整的 ReACT 循环。
加上 --trace 可把每一轮的 ReACT 轨迹流式输出到 stderr,对话仍保留在 stdout:
scoot --trace # 交互式 REPL,执行轨迹输出到 stderr
-e, --eval — 一次性
scoot -e "count the Zig source files in this repository"
scoot --retries 4 -e "summarize README.md"
scoot --trace -e "list the largest files under src/"
运行一个目标,并 仅把最终答复 打印到 stdout——非常适合脚本与管道。--trace 会在
stderr 上附加逐步轨迹,便于调试而不污染答复。轨迹会在每个阻塞步骤之前先打印实时进度
标记——调用模型前打印 thinking:,执行工具前打印 running: <工具>——这样等待期间也能看到
agent 当前在做什么,轨迹不会显得卡死。--retries 控制对瞬时后端失败(限流、5xx)的重试。
serve — stdio app-server
printf '%s\n' '{"id":"1","method":"session.list","params":{}}' | scoot serve
以前台进程运行本地 app 集成用的 stdio 协议。协议是换行分隔 JSON:stdin 每一行
是一条请求,stdout 每一行是一条响应;响应会带回同一个 id、ok,以及
result 或 error。
支持的方法:
| 方法 | 参数 | 结果 |
|---|---|---|
run | { "goal": "..." } | { "session_id": "...", "reply": "..." } |
session.list | {} | { "sessions": [...] } |
session.get | { "id": "..." } | { "id": "...", "messages": [...] } |
audit.query | { "session_id": "..." } | { "session_id": "...", "events": [...] } |
serve 不打开 TCP/UDS,不做鉴权,不把自己后台化,也不做多任务并发状态机。
进程生命周期、重启和日志归调用方或 supervisor 管理。
setup
scoot setup
scoot --scoot-home /opt/scoot/instance-a setup
通过几步交互式提问生成配置目录,让你无需手写 TOML 即可快速搭建一个实例。它会询问
配置目录(默认 ~/.scoot,或解析出的 --scoot-home / SCOOT_HOME)、后端的
base_url 与 model、token 来源(env、一个 0600 文件,或一条命令)、max_turns
以及工具 policy。随后创建运行目录树(skills/、logs/、state/sessions/)并写出
config.toml。
token 值本身 绝不会写入 config.toml——只记录其来源。如果选择文件来源并粘贴了
token,Scoot 会把它写入 token 文件并收紧为 0600,以便密钥加载能够接受。
若 config.toml 已存在,会先请你确认再覆盖。提示未覆盖到的选项,可在生成后直接编辑该文件
(参见 config.example.toml)。
由于每个生成的目录都是自包含的,setup 是在同一台主机上运行 多个隔离实例 的快捷路径——
让每个实例各自指向自己的 --scoot-home / SCOOT_HOME。每个运行目录只允许一个守护进程的规则
参见调度与守护进程。
config
scoot config
打印解析出的运行目录与后端配置。密钥被 脱敏——只显示解析出的来源,绝不显示 token 值。 用它来确认当前生效的是哪个配置文件与运行目录。
doctor
scoot doctor
scoot --scoot-home /tmp/scoot-test doctor
运行本地健康检查且不打印任何密钥:运行目录与权限、配置来源、后端前置条件、解析出的 密钥来源、技能发现、调度状态,以及审计日志路径。出现异常时先运行它。
policy check
scoot policy check <action> <input> [--mode <mode>]
针对某个策略模式对工具动作进行试运行(dry-run),并解释它会被 允许 还是 拒绝,
而不实际执行任何东西。<mode> 为 guarded(默认)、readonly 或 unrestricted。
scoot policy check bash "rm -rf /" --mode guarded # deny
scoot policy check bash "ls -la" --mode readonly # deny (no shell in readonly)
scoot policy check file_read '{"path":"README.md"}' --mode readonly # allow
scoot policy check skill '{"name":"demo"}' --mode readonly # allow (native)
scoot policy check recall '{"query":"old"}' --mode readonly # allow (native)
这是理解策略模型最快的方式——参见 执行策略与安全。
skills
scoot skills # list discovered skills (name / description / dir)
scoot skills check [dir] # validate a skill dir, or all search paths if omitted
scoot skills pack <dir> [out.tar] # validate and export a reviewable tar package
skills打印解析出的搜索路径与每个被发现的技能。skills check [dir]校验结构,不执行 任何技能脚本。一个有效的技能拥有 带非空name与description的SKILL.md;可选的capabilities、allowed_tools与scope元数据也会被校验。skills pack先校验再导出一个带.scoot-skill.json评审 manifest 的 tar。它包含常规的非隐藏文件,拒绝符号链接等不安全类型,且不授予任何策略绕过。
撰写细节参见 技能。
wasm-tools check
scoot wasm-tools check <dir>
静态校验本地 Wasm 工具包的边界——manifest.toml、
policy.toml、被引用的 JSON schema,以及安全的相对路径。它 绝不
加载或执行 Wasm。参见 Wasm 工具包。
schedule
scoot schedule list # show configured jobs and their state
scoot schedule run # run the scheduler loop (foreground)
scoot schedule run --ticks 1 # run one poll cycle then exit
列出或运行调度任务。无人值守的运行强制执行 fail-closed 的 readonly
安全级别。运行需要 schedule.enabled = true。参见
调度与守护进程。
daemon
scoot daemon status # print last recorded daemon state
scoot daemon run # foreground long-running scheduler
scoot daemon run --ticks 3 # run three poll cycles then exit
scoot daemon stop # state/pid 一致时才发送 SIGTERM
面向调度任务的前台长运行模式。它写入
state/daemon.json 与 state/daemon.pid,安装 SIGTERM/SIGINT 处理器,并
保持无人值守的 readonly 安全规则。它 不会 fork 到后台——
请用 systemd、launchd、tmux 或 shell 作业来实现。每个运行目录只允许一个守护进程;
若要在同一主机上运行多个,请为每个实例分配各自的 --scoot-home / SCOOT_HOME
(可用 scoot setup 来搭建)。参见
调度与守护进程。
退出行为与管道
-e 模式把最终答复写入 stdout,把诊断/轨迹写入
stderr,因此你可以把 Scoot 组合进 shell 管道:
answer=$(scoot -e "print today's date in ISO 8601")
scoot --trace -e "audit open ports" 2> trace.log
内建工具
每个回合,模型必须给出恰好一个 JSON 步骤:
{ "thought": "one-line reasoning", "action": "<action>", "action_input": "<input>" }
action 必须是下面十三个内建动作之一——Scoot 绝不执行
自由格式文本。每个工具都在带 硬超时(tools.timeout_ms,默认 30 秒)的沙盒中运行,
其输出作为下一个 观察 返回给模型(会被裁剪以保持上下文精简)。某个动作是否
被允许,取决于当前生效的 执行策略。
结构化工具(file_*、grep、glob、http_request)无需
外部命令,因此在最小化/嵌入式系统上行为完全一致。优先使用它们,而非外壳调用。
动作概览
| 动作 | 用途 | action_input | 只读 |
|---|---|---|---|
bash | 运行一条 POSIX shell 命令 | 命令字符串 | 否 |
file_read | 读取文件 | {"path":...} | 是 |
file_write | 覆盖/创建文件 | {"path":...,"content":...} | 否 |
file_edit | 替换一段精确文本 | {"path":...,"old":...,"new":...} | 否 |
grep | 在文件内做正则搜索 | {"pattern":...,"path":...} | 是 |
glob | 按 glob 模式列出文件 | {"pattern":...,"root":"."} | 是 |
outline | 文件结构骨架 | {"path":...} | 是 |
http_request | 一次 HTTP/HTTPS 请求 | {"method":...,"url":...,"body":...} | 取决于方法 |
mcp_call | 调用已配置 MCP server 的工具 | {"server":...,"tool":...,"args":{...}} | 否 |
skill | 读取已加载技能的文件 | {"name":...,"path":"SKILL.md"} | 是(原生) |
recall | 搜索当前会话 transcript 归档 | {"query":...} 或 {"seq":...} | 是(原生) |
parallel | 1–4 个并发只读调用 | {"calls":[...]} | 是 |
final | 返回答复并停止 | 答复文本 | — |
bash
在带硬超时的沙盒中、于 POSIX sh(/bin/sh)下运行一条 shell 命令。
action_input 是原始命令字符串;其合并输出成为下一个观察。
- 仅使用可移植的 POSIX 语法——避免 bash 特有写法,如
[[ ]]、数组、 花括号展开{1..10}或$'...'。 - stdout 与 stderr 各自最多捕获 1 MiB;观察会被裁剪。
- 用于非交互、可自行终止的命令。在
readonly模式下被完全 拒绝,在guarded模式下会针对灾难性命令做筛查。
对于文件、搜索与 HTTP,优先使用结构化工具——bash 用于处理其他一切。
file_read
{ "path": "src/main.zig" }
读取一个文件(最多 1 MiB)并返回其内容。观察会被裁剪
到约 8 KB,以免大文件淹没上下文;对大文件请读取定向范围或使用
grep。在每种策略模式下都允许。
file_write
{ "path": "notes.txt", "content": "full new file contents" }
用 完整 的新内容覆盖文件(不存在则创建)。
这是一个变更性动作:在 readonly 下被拒绝,在 guarded 模式下可
通过 confine_writes 限制在项目根目录内。参见 策略。
file_edit
{ "path": "README.md", "old": "exact unique text", "new": "replacement text" }
替换一段精确文本。old 必须在文件中恰好出现一次——若
不确定,先 file_read 查看精确文本。歧义或缺失的匹配会干净地
失败且不做任何更改。策略处理与 file_write 相同。
grep
{ "pattern": "fn main", "path": "src/main.zig" }
在单个文件内逐行做正则搜索;返回匹配的行号与
文本。支持的正则子集:. ^ $ * + ? [] () | \d \w
\s。不 支持:捕获组反向引用、环视、惰性
量词。只读;在每种模式下都允许。
可加可选的 context,同时返回每个命中前后各 N 行(类似 grep -C),
这样无需再整读文件即可理解命中点:
{ "pattern": "fn main", "path": "src/main.zig", "context": 3 }
命中行标注为 行号:原文,上下文行标注为 行号-原文;相邻/重叠的命中会合并,
块之间以 -- 分隔。context 会被夹到 0..20。
glob
{ "pattern": "src/**/*.zig", "root": "." }
列出 root(默认 .)下匹配某个 glob 的文件路径。* ? [] 不
跨越 /;** 跨越目录层级。返回的路径可直接喂给
file_read 或 grep。只读;在每种模式下都允许。
outline
{ "path": "src/agent.zig" }
返回单个文件的紧凑结构骨架——函数与类型签名、Markdown 标题,
各自带行号——而不是整文件内容。先用它给陌生文件画出地图,再用
file_read 的 offset/limit 窗口读真正需要的片段;以此避免把大文件
整块灌进上下文。
语言识别是零依赖的行启发式(不引入 AST / 外部解析器,保持 Scoot
单一自包含二进制):Zig 与 Markdown 走精确规则;其余语言回退到关键字
引导的启发式(def/class/func/function/struct/type/interface/…),
属 best-effort,可能漏掉类型引导的定义(如 C/C++)。输出上限 400 条
(超出即标注已截断)。只读;在每种模式下都允许。
http_request
{ "method": "GET", "url": "https://example.com/api", "body": "optional" }
发起一次带硬超时的 HTTP/HTTPS 请求(绝不挂起)。method 是
GET/POST/PUT/DELETE/HEAD/PATCH 之一;HTTPS 会自动协商
(自定义根证书参见 backend.ca_file)。返回响应状态与正文(最多
1 MiB,观察会被裁剪)。
策略处理按方法划分:读式(GET/HEAD)vs 写式
(其他一切)。readonly 阻止网络变更;guarded 可通过
block_internal_http 阻止内部/元数据主机。参见 策略。
mcp_call
{ "server": "demo", "tool": "lookup", "args": { "query": "example" } }
调用一个已配置 MCP server 上的工具。server 配置位于 [[mcp.servers]];
server 名称必须存在,tool 必须显式列在该 server 的 allowed_tools 中。
allowed_tools 为空会拒绝全部 MCP 工具。
当前支持 stdio、Streamable HTTP(http 或 streamable_http)以及 legacy
sse transport。远程 transport 使用配置中的 url,复用工具硬超时;如果配置了
backend.ca_file,也会复用该 CA bundle。基于 header 的认证按 server 配置
headers;token 请使用 value_env,可配合 prefix 生成 Bearer ...。
MCP 调用按可能具有外部副作用的执行处理。readonly 会拒绝它;guarded 与
unrestricted 仍要求显式 server 与工具 allowlist。MCP 调用像其他工具一样进入审计,
并受同一个硬超时约束。
skill
{ "name": "demo", "path": "SKILL.md" }
从 已加载技能的 目录中读取一个文件——path 默认为 SKILL.md,
也可指向另一个资源,如 references/guide.md。这是一个 原生、
按设计绕过执行策略的只读能力,因此即使在 readonly(bash 被拒绝)下技能仍可使用。
安全位于执行层而非策略层:读取被限制在指定技能的
目录内(绝对路径与 .. 被拒绝),名称必须在已加载集合中
(未知名称返回一个可恢复的观察,列出可用项),并且
每次读取都被审计。内容最多返回约 32 KB。参见 技能。
recall
{ "query": "old error text", "limit": 8 }
{ "seq": 12, "context": 2 }
搜索 当前会话的完整 transcript 归档,返回带 seq、role 与
content 的 JSONL 风格原文消息行。这是原生只读能力,因此在 readonly
模式下也可用。
当上下文压缩只保留摘要,而模型需要较早的精确观察、命令或用户指令时使用它。
query 做字面量子串匹配;seq 从 1 开始,可带少量前后 context。
limit 会被封顶,避免召回结果重新撑爆上下文。
parallel
{ "calls": [
{ "action": "file_read", "input": "{\"path\":\"README.md\"}" },
{ "action": "grep", "input": "{\"pattern\":\"Scoot\",\"path\":\"AGENT.md\"}" }
] }
并发运行 1–4 个独立的只读调用,保留观察
顺序。仅允许 file_read、grep、glob、outline 与 HTTP GET/HEAD——
bash、写入、skill、recall 与嵌套的 parallel 都被拒绝。每个子调用
仍会经过正常的策略门。用它在一个回合中并行扇出独立的读取。
final
action_input 是给用户的答复文本。给出 final 即结束 ReACT
循环。在 -e 模式下,这段文本就是打印到 stdout 的内容。
观察与截断
工具输出会作为观察反馈,但每个都会被 裁剪 以约束
上下文增长(大致为:bash ~2 KB,file_read/http_request ~8 KB,
parallel ~12 KB,skill ~32 KB,recall ~16 KB)。对于大数据,请收窄
你的读取——使用 grep、glob 路径、定向范围,或更精确的 recall 查询,
而不是倾倒整个文件。
执行策略与安全
Scoot 绝不让未经校验的模型输出直接落到你的系统上。每个工具动作在执行前都要经过策略门(policy gate)。本页讲清三种模式、判定模型、默认 guarded 加固,并坦诚说明该策略能保护什么、不能保护什么。
三种模式
按限制力从弱到强排列:unrestricted < guarded < readonly。
| 模式 | Shell(bash) | 本地写 | 网络 | 本地读 | 适用场景 |
|---|---|---|---|---|---|
unrestricted | 允许 | 允许 | 允许 | 允许 | 完全信任目标;仍会审计。 |
guarded (默认) | 除灾难性外允许 | 允许,默认收口在项目根内 | 允许,默认带内部地址防护 | 允许 | 有人值守的交互式使用。 |
readonly | 拒绝 | 拒绝 | 拒绝 | 允许(受限) | 无人值守/不可信;fail-closed 安全。 |
在配置里设定模式([tools] policy = "..."),或用 scoot policy check 测试任意动作。未知值回落到 guarded(坏配置绝不能放松策略门)。yolo 是 unrestricted 的别名。
各模式的行为
guarded —— 交互式绊线
guarded 是交互式 CLI/REPL 的默认模式。它不是沙盒,而是一条绊线:一份灾难性 shell 命令的拒绝清单。日常工作照常放行,让你在有人值守时真正把活干完。
bash 命令会先归一化(折叠空白、转小写——挫败 rm -RF / 之类的花招),再与一份刻意从紧的灾难性清单比对并拦截,包括:
- 递归删除根/家目录/
*(rm -rf /、rm -rf ~、rm -rf *、--no-preserve-root); - 磁盘/文件系统摧毁(
mkfs、dd ... of=/dev/...、> /dev/sd...); - 管道接 shell 的远程执行(
| sh、| bash); - 电源状态变更(
shutdown、reboot、poweroff、halt、init 0/6); - fork 炸弹,以及鲁莽的
chmod 777 // 递归chown。
内建工具(file_*、grep、glob、http_request)在 guarded 下放行,且受自身的路径/大小/超时上限约束。默认情况下,guarded 还会把 file_write/file_edit 收口在项目根内,并阻止 http_request 访问环回、内网、链路本地和云元数据地址。
readonly —— fail-closed 安全原语
readonly 才是真正的安全边界,也是无人值守任务的结构性前提。它 fail-closed(失败即关闭):
bash一律拒绝 —— shell 组合语义太宽,无法靠白名单精确防住;改用file_read/grep/glob。- 所有写一律拒绝(
file_write、file_edit)。 - 所有网络一律拒绝 —— 即便是只读的
GET/HEAD,以防本地数据经请求 URL 外带。 - 本地读放行但受路径收口(见下)。
- 在全面禁
bash之上,灾难性 shell 模式仍会被拦截。
readonly 下,本地读路径还会被额外校验:禁绝对路径、禁 ~/$VAR 展开、禁 .. 逃逸,并拒绝常见敏感片段(.env、.ssh、id_rsa、id_ed25519、.netrc、credentials、secret、token 等)。这把读取收口在项目工作目录内,远离明显的密钥文件。
unrestricted —— 不设限,但仍审计
完全不设策略限制(别名 yolo)。每个动作仍会写入审计日志,但不拦截任何东西。仅在你完全信任目标时使用。
skill 动作是原生的
通过 skill 动作 读取技能的指令/资源是一种原生只读能力,刻意绕过策略门 —— 故技能即便在 readonly 下也可用。安全由执行环节把关(目录收口、读取被审计),而非由策略把关。技能随后让模型去执行的一切(shell、写、网络)仍走正常策略门。
正因为它绕过策略门,skill 动作会放宽 readonly 的读取面。在上文 evaluateReadPath 把关的读取(项目工作目录内、非敏感、禁 ../绝对路径)之外,它还能读取任意已注册技能目录下的文件:
[skills] include_project_skills = true时的<cwd>/.agents/skills[skills] include_agents_skills = true时的~/.agents/skills~/.scoot/skills[skills]中声明的extra_paths
每次读取仍被收口在命中的那个技能自身目录内(拒绝绝对路径、..,以及解析后逃逸该目录的 symlink),并被审计。对无人值守 / readonly 运行的现实含义:只安装你信任的技能。 被污染或恶意的技能包即便在 readonly 下也能把它自身目录的内容暴露给模型 —— 这是读取边界的既定组成部分,而非绕过,因此不要把 readonly 当成对抗不可信技能的沙盒。
recall 动作是原生的
recall 只读取当前会话的 transcript 归档,没有文件、网络或进程副作用,因此
同样是原生能力,并在 readonly 下放行。它不像 skill 那样扩大文件系统读取面;
它只能返回本会话 transcript 中已经存在的内容。
默认 guarded 加固
两个开关用于收紧 guarded 模式。二者默认 true,且仅在 guarded 生效(readonly 已对写与网络 fail-closed)。只有在明确接受更宽的写入或网络面时才应关闭。
confine_writes
把 file_write/file_edit 收口在项目根内:禁绝对路径、.. 逃逸、shell 风格的 ~/$VAR 展开。这能挡住不可信模型写入诸如 $HOME/.ssh/authorized_keys。它不拒绝敏感文件名 —— 项目内的风险是位置逃逸,而非命名。
[tools]
policy = "guarded"
confine_writes = true
block_internal_http
一道 SSRF 防护:拒绝 http_request 访问环回、内网、链路本地与云元数据地址。这是基于字面 IP 段与已知内网名的启发式判断 —— 它不解析 DNS,故 DNS 重绑定仍可绕过。要真正的网络隔离,请用 readonly 或外部网络沙盒。
[tools]
policy = "guarded"
block_internal_http = true
判定模型
两条互补的校验共享同一套 Mode 语义:
- Shell 命令(
bash)按字符串分析:归一化、与灾难性拒绝清单比对,然后放行(guarded)或拒绝(readonly)。 - 内建工具按能力分类 ——
read、write、net_read、net_write—— 因为它们的语义无需解析命令字符串即静态已知。这正是策略门不随工具增多而变复杂的原因:新增的读工具复用同一条read判定。它也保证内建工具无法绕过readonly。
坦诚的威胁模型
在敌对环境中依赖 Scoot 前请先读这段:
guarded不是安全边界。 拒绝清单总能被有决心或敌对的提示绕过。别据此产生虚假信心 —— 它是为了在有人值守时拦住意外与明显灾难。readonly才是 fail-closed 原语。 它在构造上禁 shell、禁写、禁网络,是让无人值守执行站得住脚的依据。任何不可信目标、调度任务或守护进程都应优先选它。- 真正的隔离仍要靠操作系统。 要强保证,请把
readonly与 OS 级沙盒(容器、seccomp、网络命名空间、只读挂载)结合。Scoot 的策略是纵深防御,不是牢笼。
调度任务会被矫正
无人值守任务在结构上强制安全:配置为 guarded 的任务在执行时会被矫正为等效 readonly。若你接受风险,必须在任务配置里显式写 unrestricted。见调度与守护进程。
检视判定
用 policy check 对任意动作在任意模式下做演练 —— 不会真正执行:
scoot policy check bash "rm -rf /" --mode guarded # deny
scoot policy check bash "ls -la" --mode readonly # deny
scoot policy check file_write '{"path":"/etc/x"}' --mode readonly # deny
scoot policy check file_read '{"path":"README.md"}' --mode readonly # allow
scoot policy check http_request '{"method":"GET","url":"http://169.254.169.254/"}' --mode guarded
技能
技能(skill)是一个本地目录,装着特定任务的指令,扩展代理「会做什么」 —— 但不引入任何特权执行通道。规范参考(front matter 字段、打包、校验规则)见 docs/SKILLS.md;本页是实操概览。
技能长什么样
my-skill/
SKILL.md # required: front matter + instructions
scripts/ # optional helper scripts
references/ # optional reference material
只有 SKILL.md 是必需的。scripts/ 与 references/ 可选,且被使用时和其它动作一样照常经过工具策略门。
SKILL.md 以 YAML 风格的 front matter 开头:
---
name: metadata
description: Demonstrates review metadata for a local Scoot skill.
capabilities: [instructions, references]
allowed_tools: [file_read, grep, glob]
scope: workflow
---
# Instructions
...the full operating instructions the model loads on demand...
name(必填):ASCII 字母、数字、.、_、-,至多 64 字节。description(必填):发现阶段使用的简短、非空摘要。capabilities/allowed_tools/scope(可选):声明式的审查元数据。allowed_tools是给审查者看的预期工具用途 —— 它不授予任何权限。
scoot_version / requires_scoot 等兼容性字段被刻意拒绝,直到 Scoot 定义版本门为止。
搜索路径
技能按优先级顺序发现(同名时靠前者胜):
<cwd>/.agents/skills—— 项目本地,仅在[skills] include_project_skills = true时加载。~/.agents/skills—— 跨 agent 的用户级技能,仅在[skills] include_agents_skills = true时加载。~/.scoot/skills—— Scoot 自有的用户级目录。- 配置
[skills]中的任意extra_paths。
项目本地技能默认关闭,因为仓库可以携带不可信指令。只应在可信工作区内开启。
scoot skills 会打印解析后的路径和所有已发现技能。额外位置通过 [skills] 配置。
渐进式披露
为保持上下文精简,发现阶段只注入每个技能的 name + description,绝不预载 SKILL.md 正文。当某技能相关时,模型用原生 skill 动作按需加载:
{ "name": "my-skill" } // reads SKILL.md
{ "name": "my-skill", "path": "references/guide.md" } // reads another file
读取是原生的;执行受策略门
这是核心安全属性:
- 读取技能是免费的。
skill动作是原生只读能力,按设计绕过执行策略,故技能即便在readonly(此时禁bash)下也能用。读取收口在技能自身目录内(拒绝绝对路径、..,以及解析后逃逸该目录的 symlink),未知技能名返回可纠错的观察,且每次读取都被审计。 - 对技能采取行动则受策略门。 技能随后让模型去做的一切 —— 跑
bash、写文件、发网络请求、执行scripts/—— 都和任意普通工具调用走同一套策略校验。技能不获任何特权。
命令
scoot skills # list discovered skills + search paths
scoot skills check path/to/my-skill # validate one skill (no scripts run)
scoot skills check # validate all configured search paths
scoot skills pack path/to/my-skill my-skill.scoot-skill.tar
skills check 只校验结构,不执行任何东西。skills pack 导出一个带 .scoot-skill.json 审查清单的 tar(元数据、文件条目、大小,以及一条策略说明:技能脚本不绕过策略门,而读取指令是一次原生的、受收口的读取)。
起步模板:docs/examples/skills/minimal 与 docs/examples/skills/metadata。
调度与守护进程
Scoot 可通过前台守护循环运行无人值守的调度任务。自主执行默认关闭 —— 必须显式启用。完整的生命周期/恢复参考见 docs/DAEMON.md。
应该使用哪种模式
在 -e、schedule run 和 daemon run 之间选择时,先看这张表:
| 模式 | 是否读取配置任务 | 默认是否常驻 | 触发时间由谁负责 | 适用场景 |
|---|---|---|---|---|
scoot -e "<goal>" | 否 | 否 | 调用方 | 人或脚本要执行一个即时任务。 |
scoot schedule run --ticks 1 | 是 | 否 | cron、systemd timer、CI | 外部调度器周期性唤起 Scoot。 |
scoot schedule run | 是 | 是 | 当前终端或进程管理器 | 简单前台调度循环,不需要 daemon 状态文件。 |
scoot daemon run | 是 | 是 | Scoot 循环 + systemd/launchd 等托管 | 长期无人值守调度,并需要 pid/state/stop/status 支持。 |
-e 和 scheduled execution 是不同入口。-e 会立即运行命令行传入的 prompt,
并使用普通配置里的工具策略。调度任务来自 [[schedule.jobs]],由 every_sec、
at_unix 或 cron 触发,并使用无人值守安全规则:job 的 mode 默认是
readonly,guarded 会被矫正为有效 readonly。
只有当你需要进程托管时,systemd 才有意义。使用 scoot daemon run 时,
Scoot 负责调度循环,systemd 负责启动、重启、日志、环境变量、资源限制和
SIGTERM 停止。如果你希望 systemd 也负责触发时间,请使用 systemd timer 调用
scoot schedule run --ticks 1。
启用调度
[schedule]
enabled = true
poll_ms = 1000
[[schedule.jobs]]
id = "disk-check"
goal = "Inspect disk usage and summarize anomalies"
every_sec = 300
mode = "readonly"
每个任务需要恰好一个触发器:
| 触发器 | 含义 |
|---|---|
every_sec | 按固定间隔触发(秒)。 |
at_unix | 在某个固定 Unix 时间点触发一次。 |
cron | 按 5 字段 UTC cron 表达式触发。 |
触发器为零个或多个的任务非法,会被跳过并告警。每个字段见配置 → [[schedule.jobs]]。
无人值守安全
调度任务在结构上强制安全,而非靠约定:
- 任务的
mode默认为readonly; guarded任务在执行时被矫正为等效readonly;- 只有显式设置
unrestricted才会生效,意味着你接受无人值守的写/网络风险。
这意味着无人值守任务不会意外写盘或触网,除非你刻意选择。见执行策略与安全。
运行调度器
scoot schedule list # show jobs and whether each is ACTIVE/INACTIVE
scoot schedule run # run the loop in the foreground
scoot schedule run --ticks 1 # run exactly one poll cycle, then exit
--ticks N 便于测试和 cron 驱动的一次性调用:轮询 N 次后退出(0 = 持续运行)。
守护进程模式
daemon 是调度任务的长驻前台进程。它不派生到后台 —— 需要后台托管时请配合 systemd、launchd、tmux 或 shell 作业。
scoot daemon run # foreground; requires schedule.enabled = true
scoot daemon run --ticks 3 # run three poll cycles then exit
scoot daemon status # print the last recorded daemon state
scoot daemon stop # running state 与 pid 一致时才 SIGTERM
daemon run 加载有效任务、写入生命周期状态、安装 SIGTERM/SIGINT 处理器,并运行与 schedule run 相同的循环。stop 时,Scoot 只有在 state/daemon.json 显示 running 且匹配 state/daemon.pid 时才发信号;否则把 pid 文件视为陈旧文件。运行中的守护进程会跑完当前一轮、写入 stopped 状态、删除其 pid 文件。
每个运行目录只允许一个守护进程
守护进程的存活通过每个运行目录下的 state/daemon.json 与 state/daemon.pid 跟踪。当同一目录已有守护进程存活时再启动 daemon run 会被拒绝,因此两个守护进程绝不会共享同一套调度与状态目录:
[scoot] refusing to start: detected daemon already running (pid=… started_at=…).
Run `scoot daemon stop` first.
该守卫用信号 0 探测所记录的 pid;崩溃残留的过期 pid 会被视为非正常停止,并在下一次运行时恢复。
要在同一主机上运行多个守护进程,给每个实例分配各自的运行目录,它们便完全隔离 —— 配置、任务、会话、日志与生命周期文件各自独立:
scoot --scoot-home /opt/scoot/web setup # 搭建实例 "web"
scoot --scoot-home /opt/scoot/batch setup # 搭建实例 "batch"
SCOOT_HOME=/opt/scoot/web scoot daemon run &
SCOOT_HOME=/opt/scoot/batch scoot daemon run &
scoot setup 是搭建每个目录的最快方式。由于单守护进程守卫是按目录隔离的,不同的 home 永不冲突。
生命周期文件
~/.scoot/
logs/audit.jsonl # audit events
state/daemon.json # status, pid, timestamps, stop reason, job count, poll interval
state/daemon.pid # present while running; removed on clean shutdown
state/sessions/ # per-run session transcripts
若进程崩溃,下一次 daemon run 会发现上次状态仍为 running,并在写入新状态前打印一条重启恢复告警。
恢复契约
恢复刻意保守 —— Scoot 在进程死亡后不续跑进行到一半的模型回合:
- 已完成的会话保留在
state/sessions/; - 已落盘的审计事件保留在
logs/audit.jsonl; every_sec/at_unix的运行时计时在重启后重置;- 配置仍是「有哪些任务」的唯一真相源;
- 残留的
running状态被视为不干净的停止并被覆盖。
示例:一个 systemd 单元
[Unit]
Description=Scoot daemon
After=network-online.target
[Service]
ExecStart=/usr/local/bin/scoot daemon run
Restart=on-failure
Environment=SCOOT_HOME=%h/.scoot
[Install]
WantedBy=default.target
本版本中日志与会话文件均为追加写;长期部署请在外部轮转或清理 logs/ 与 state/sessions/。
会话与审计
Scoot 把自己做过的事以追加写 JSONL 持久化到本地磁盘 —— 短期会话记录与逐步审计日志。两者都是纯文本,便于回放、grep 或喂给其它工具。按设计没有长期语义记忆或向量库(见路线图)。
会话
会话是一次交互的消息记录。-e 与 REPL 每个进程都会获得新的 id,例如
cli-<ms>-<pid> 或 repl-<ms>-<pid>,因此独立运行不会再追加进同一个
cli.jsonl 或 repl.jsonl。调度任务仍保留稳定的 job-<id>,因为它代表
一个持续的无人值守任务。
它持久化到:
~/.scoot/state/sessions/<id>.jsonl
每行一条消息:
{"role":"system","content":"..."}
{"role":"user","content":"count the Zig files"}
{"role":"assistant","content":"{\"thought\":\"...\",\"action\":\"glob\",\"action_input\":\"...\"}"}
role 为 system、user 或 assistant。写入是追加式的,故一个文件累积该会话完整的来回往复,可按序回放。resume / 载入旧 transcript 是独立能力,不会因为文件命名修复而自动开启。
会话只是短期记忆。它不会跨运行被索引或汇总;持久化是为了可审计与可检视,而非用于回忆。
检视会话
可以用只读 CLI 命令检视已经持久化的会话文件,不会启动 agent:
scoot sessions list
scoot session show <id>
sessions list 会列出本地 session id、修改时间戳、消息数与首条用户消息摘要。session show <id> 会把该会话 transcript 以 JSONL 打印出来,便于继续 pipe 给其它工具。
审计日志
当 [audit] to_file = true(默认)时,每个有意义的步骤都会记入审计日志:
~/.scoot/logs/audit.jsonl
每行一个事件:
{"seq":0,"ts":1718600000123,"session_id":"cli-1718600000000-4242","kind":"run","msg":"goal: count the Zig files"}
{"seq":1,"ts":1718600000456,"session_id":"cli-1718600000000-4242","kind":"thought","msg":"..."}
{"seq":2,"ts":1718600000789,"session_id":"cli-1718600000000-4242","kind":"tool_call","msg":"glob {\"pattern\":\"**/*.zig\"}"}
{"seq":3,"ts":1718600000900,"session_id":"cli-1718600000000-4242","kind":"observation","msg":"..."}
{"seq":4,"ts":1718600001000,"session_id":"cli-1718600000000-4242","kind":"final","msg":"There are 23 Zig files."}
| 字段 | 含义 |
|---|---|
seq | 单调递增的事件序号(每个 logger 实例从 0 起)。 |
ts | 墙钟时间戳,Unix 毫秒。 |
session_id | 本地会话 id,用于把审计事件关联到 state/sessions/<id>.jsonl。 |
run_id | 可选的更细粒度运行关联字段。 |
kind | 事件类型(见下)。 |
msg | 消息文本,密钥已脱敏。 |
事件类型
kind | 何时写入 |
|---|---|
run | 一次运行的起点,携带用户目标(在日志里分隔多次运行)。 |
thought | 模型对某步的一句话推理。 |
tool_call | 即将执行的动作及其输入。 |
observation | 回灌给模型的工具结果。 |
final | 终态答复。 |
policy_deny | 被策略门拒绝的动作。 |
system_error | 内部/可恢复错误。 |
run 标记让你把单个追加文件切分成一次次运行,seq + ts 让你回放时间线并关联事件。policy_deny 条目正是策略门拦截了什么的审计轨迹。
要查看某个会话对应的审计事件:
scoot audit show <session-id>
该命令会按 session_id 过滤 logs/audit.jsonl,并以 JSONL 打印匹配事件,保留 seq、ts、可选 run_id、kind 与 msg。
详细程度
用 [audit] level 控制记录量 —— debug、info(默认)、warn 或 error。设 to_file = false 可完全关闭文件日志。
[audit]
level = "info"
to_file = true
密钥绝不入日志
后端 token 的值绝不写入会话或审计日志 —— 只会报告其来源(由 config/doctor)。审计消息写入前先经脱敏。见 Agent 指南的密钥规则。
留存
会话与审计文件是追加式 JSONL。单个 JSONL 文件达到内置大小上限后,Scoot 会在追加前把它轮转为 .1,避免 daemon 长跑时单文件无界增长。
Wasm 工具包
状态:核心静态校验 + 独立 host。 核心 scoot 二进制依旧不加载或执行 Wasm;可选的
scoot-wasm 二进制在使用 -Dwasm-host=true 构建后,可以执行当前的整数/WASI host 子集。
完整参考见 docs/WASM_TOOLS.md;本页是概览。
目标是为第三方工具提供一个小巧、本地、可审查的边界 —— 刻意比 MCP 或 Wassette 更小 —— 使一个包在引入任何运行时之前就能被检视、其请求的权限被理解。
包布局
tool/
component.wasm
manifest.toml
policy.toml
schema/
input.json
output.json
校验一个包 —— 只读,绝不运行 Wasm:
scoot wasm-tools check path/to/tool
该校验解析元数据与 schema、核验被引用文件存在、拒绝不安全路径(绝对路径、..、隐藏段、盘符前缀、空段),
并校验 component.wasm 的字节码结构(magic、version、section、LEB128 长度与基础索引/数量一致性);
不会执行 Wasm。
需要执行时显式构建独立 host:
zig build -Dwasm-host=true
scoot-wasm check path/to/module.wasm
scoot-wasm run path/to/module.wasm add 2 40
scoot-wasm wasi path/to/module.wasm [args...]
在 run 或 wasi 执行模块前,host 会验证当前支持的函数体子集:operand/control stack
形状、block/loop/if 签名、分支 label、调用签名、local/global 访问、memory/table 是否存在,
以及不可变 global 写入。
仓库内置了一个完整压缩插件示例、一个可复制模板,以及第二个确定性的 redactor compressor:
zig build wasm-compressor-example wasm-plugin-template wasm-redactor-compressor
scoot wasm-tools check examples/wasm-compressor
scoot wasm-tools check examples/wasm-plugin-template
scoot wasm-tools check examples/wasm-redactor-compressor
printf '%s\n' '{"version":1,"kind":"compressor","keep_recent":2,"elided_count":3,"elided_bytes":1200,"messages":[]}' \
| scoot-wasm wasi examples/wasm-compressor/component.wasm
scoot-wasm wasi examples/wasm-redactor-compressor/component.wasm \
< examples/wasm-redactor-compressor/fixtures/request.json
新增 compressor 包时优先从 examples/wasm-plugin-template 复制。scripts/check-wasm-examples.sh
会构建 host 与示例、校验包边界,并运行代表性的 WASI 执行 smoke check。
Manifest 与 Policy
manifest.toml 声明身份、入口、schema 和请求的能力:
kind = "tool"
name = "calculator"
description = "Evaluate simple math expressions"
entry = "call"
component = "component.wasm"
input_schema = "schema/input.json"
output_schema = "schema/output.json"
capabilities = ["compute"]
为兼容旧包,kind 默认是 tool。外部上下文压缩器复用同一个静态包边界,但需要设置
kind = "compressor";Scoot 核心仍不会加载或执行 Wasm。
policy.toml 声明实际授予的能力,且必须是 manifest 的子集 —— 包不能悄悄获得它未声明的权限:
capabilities = ["compute"]
能力名:compute(纯 CPU,无 I/O)、read、write、net_read、net_write。独立 host
当前只暴露最小 WASI preview1 的 stdio/args/environ/clock/random/proc-exit 子集;文件与网络权限
尚未实现。
Schema
schema/input.json 与 schema/output.json 是工具 I/O 的 JSON Schema。校验器当前只检查两者存在且为合法 JSON;运行时强制将基于同样的文件构建。计划中的模型调用形态:
{ "action": "wasm_tool", "action_input": "{\"tool\":\"calculator\",\"input\":{\"expr\":\"1+2\"}}" }
非目标(v0)
不做 OCI registry 或远程安装、不依赖 MCP/Wassette、不做授权 UI,默认不给文件/网络/环境访问。先用 JSON 字符串,WIT 绑定靠后。Scoot 自己掌握发现、策略映射与审计身份 —— 为日后采用 Component Model/WIT 留出空间,而不把它变成审查的前置条件。
嵌入 API
Scoot 可以作为 Zig 包被其他可执行文件嵌入,但公共包根刻意只是一个 生命周期门面,不是一组内部类型工具箱。
公共 API 只有:
pub const version: []const u8;
pub const Runtime = opaque {};
pub const Options = struct { ... };
pub fn start(gpa: std.mem.Allocator, io: std.Io, options: Options) !*Runtime;
pub fn run(rt: *Runtime, goal: []const u8) ![]const u8;
pub fn stop(rt: *Runtime) void;
Runtime 是不透明句柄。嵌入者拿不到 Agent、Session、Config、
policy、llm.Client、tools 或 Compressor。这些都留在内部,这样
Scoot 才能自由演进引擎、配置 schema、压缩、工具、MCP/Wasm 集成和 daemon 内部实现,
而不破坏下游代码。
Options
Options 接收的是配置来源,不是结构化配置:
| 字段 | 含义 |
|---|---|
env | 必填环境变量 map,用于 HOME/SCOOT_HOME、SCOOT_* 覆盖和 API token 环境变量查找。 |
scoot_home | 可选运行目录覆盖,语义类似 CLI 的 --scoot-home。 |
config_file | 可选显式配置文件。.toml 按 TOML 解析,其它扩展按 JSON 解析。 |
所有具体配置 struct 都保持内部。要改变模型、策略、压缩器、skills 或工具行为, 使用与 CLI 相同的配置文件和环境变量。
最小示例
见 examples/embed/minimal.zig。
该示例会随 zig build test 编译,因此公共 API 漂移会被测试捕获。
const scoot = @import("scoot");
const rt = try scoot.start(arena, init.io, .{
.env = init.environ_map,
});
defer scoot.stop(rt);
const reply = try scoot.run(rt, "Return a short greeting.");
返回的 reply 由 runtime 持有,在 stop 前有效。
稳定边界
稳定:
versionOptions- 不透明
Runtime startrunstop
不稳定:
Agent、Session、Config、policy、llm.Client、tools、Compressor和其它所有内部模块;src/下的包内名字;- build 生成的内部内容;
- 所有隐藏 runtime 状态的精确布局。
仓库为包根加了白名单测试。意外把 tools、regex 这类内部命名空间导出时,
zig build test 会失败。
Zig 兼容性
Scoot 的公共 API 是 Zig 源码级 API,不是 ABI。Zig 仍未到 1.0,因此 Scoot 的 semver 承诺默认要求使用本仓库支持的 Zig 版本。嵌入 Scoot 时,请锁定与 Scoot CI/release workflow 相同的 Zig toolchain。
最佳实践案例
Scoot 最适合作为一个小型、可审计的 Agent 运行时,而不是泛用自动化平台。好的 部署方式要先把三条边界讲清楚:
- 谁负责触发:人、CI、cron/systemd timer,还是
scoot daemon run; - Agent 能碰什么:
readonly、guarded,还是显式unrestricted; - 密钥和状态放哪里:环境变量 / 文件 / 命令密钥,本地 JSONL 会话与审计日志。
下面 7 个案例是最值得优先支持和推荐的场景。
1. GitHub Actions 评审助手
当你需要只读总结、release note 草稿、changelog 检查或文档漂移报告时,CI 是
非常适合 Scoot 的场景:触发器已经由 GitHub 负责,checkout 是临时的,
readonly 可以避免意外写入,也能阻断 agent 工具通过网络外带。
这里用 scoot -e,不要用 daemon run。
name: Scoot review
on:
pull_request:
workflow_dispatch:
jobs:
review:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
env:
SCOOT_HOME: ${{ runner.temp }}/scoot
OPENAI_API_KEY: ${{ secrets.LLM_KEY }}
SCOOT_BACKEND_API_KEY_ENV: OPENAI_API_KEY
SCOOT_BACKEND_BASE_URL: https://api.openai.com/v1
SCOOT_BACKEND_MODEL: gpt-4o-mini
SCOOT_TOOLS_POLICY: readonly
SCOOT_AUDIT_TO_FILE: "true"
steps:
- uses: actions/checkout@v4
- name: Install Scoot
run: |
tar -xzf scoot-linux-amd64.tar.gz
install -m755 scoot/scoot /usr/local/bin/scoot
- name: Generate review brief
run: |
scoot -e "Review this checkout. Summarize behavior changes, risky files, and missing docs/tests. Do not modify files." \
| tee scoot-review.md
- uses: actions/upload-artifact@v4
with:
name: scoot-review
path: |
scoot-review.md
${{ runner.temp }}/scoot/logs/
${{ runner.temp }}/scoot/state/sessions/
如果以后要把结果写回 PR 评论,建议作为单独的显式步骤处理。Scoot 负责生成分析 产物,GitHub 权限仍应保持最小化。
2. 无人值守运维简报
当你想每天或每小时从日志、配置文件、预生成状态快照中生成本地报告时,用这个
模式。Scoot 负责调度循环,systemd 只负责托管这个前台进程。
[schedule]
enabled = true
poll_ms = 1000
[[schedule.jobs]]
id = "ops-brief"
goal = "Inspect local logs, config files, and pre-generated status snapshots. Summarize anomalies and likely next checks. Do not write files or call the network."
cron = "0 8 * * *"
mode = "readonly"
[Unit]
Description=Scoot operations brief
After=network-online.target
[Service]
ExecStart=/usr/local/bin/scoot daemon run
Restart=on-failure
Environment=SCOOT_HOME=/var/lib/scoot
[Install]
WantedBy=multi-user.target
这是无人值守的默认推荐形态:guarded job 会被矫正为有效 readonly,
而 readonly 会拒绝 shell、写入和网络。
如果需要 df、systemctl 或厂商 CLI 的输出,请让外部固定任务先生成纯文本
状态快照,再让这个 readonly job 读取快照。
3. RouterOS 或容器探针
这个场景有价值,但不是默认安全场景。RouterOS 和容器探针通常需要访问网络,
而 scheduled readonly job 会按设计拒绝网络。如果要用 Scoot 做这类探针,
应先隔离运行环境,再显式授予网络能力。
推荐形态:
- 把 Scoot 放在只能访问目标管理网络的容器、VM 或 network namespace 中;
- 除
SCOOT_HOME外,文件系统尽量只读挂载; - RouterOS/API 凭据放在环境变量、token 文件或凭证命令中,不写进 goal;
- 探针命令必须有明确超时;
- 只有确实需要网络的那个 job 设置
mode = "unrestricted"。
[schedule]
enabled = true
[[schedule.jobs]]
id = "routeros-probe"
goal = "Run the existing read-only RouterOS/container probe script, interpret its output, and report anomalies. Do not change device configuration."
every_sec = 300
mode = "unrestricted"
关键点是:unrestricted 的权限很宽。必须先用操作系统和网络隔离把运行环境收窄,
再授予它。
4. Release 与 Changelog 预检
发布前可以用 scoot -e 检查当前 checkout,生成给人看的预检报告。通常应使用
readonly。
SCOOT_TOOLS_POLICY=readonly \
scoot -e "Prepare a release preflight: summarize commits since the last tag, check README/changelog consistency, list risky changes, and identify missing release notes."
好的输出应该包括:
- 用户可见行为变化;
- 应同步更新的文档;
- 可能缺失的测试;
- 打包或发布目标风险。
实际版本号 bump、打 tag、发布产物等动作应留在这个只读预检之外,除非你明确决定 再运行一个独立的 guarded / unrestricted 发布自动化。
5. 配置与安全姿态审计
当你想定期检查 Scoot 自己的运行姿态有没有漂移时,用这个模式。任务应读取配置、
运行 doctor、检查权限,并解释弱配置。
scoot doctor
scoot policy check bash "rm -rf /" --mode guarded
scoot policy check http_request '{"method":"GET","url":"http://169.254.169.254/"}' --mode guarded
也可以配置成定时本地简报:
[[schedule.jobs]]
id = "scoot-posture"
goal = "Inspect Scoot config, doctor output, and runtime files. Report weak permissions, disabled hardening, unknown config keys, and risky scheduled jobs."
cron = "30 7 * * *"
mode = "readonly"
这样可以发现配置漂移,同时不给 agent 写入权限。
6. Edge / NAS 健康看护
Scoot 的小型原生部署形态适合低资源主机:NAS、边缘 Linux 设备、实验室机器、
小型常开服务器。能用本地模型后端时优先使用本地后端,job 保持只读。由于
readonly 会拒绝 shell,应把日志和状态快照文件喂给 Scoot,而不是让它直接跑
系统探针命令。
[backend]
base_url = "http://127.0.0.1:11434/v1"
model = "qwen2.5"
[agent]
compactor = "extractive"
context_budget_bytes = 80000
[schedule]
enabled = true
[[schedule.jobs]]
id = "edge-health"
goal = "Inspect local logs, service files, and status snapshots. Summarize health risks for this edge host. Do not write files or call the network."
every_sec = 1800
mode = "readonly"
如果设备缺少系统根证书,又必须访问 HTTPS 后端,请设置 ca_file。
7. 项目本地 Runbook Skills
把项目内可复用的操作流程做成本地 skills:事故排查、发布检查清单解释、数据保留 审查、厂商专用诊断等。把说明放进仓库,让 runbook 和代码一起评审。
.agents/skills/
incident-triage/
SKILL.md
references/
service-map.md
escalation.md
scoot skills check .agents/skills/incident-triage
SCOOT_SKILLS_INCLUDE_PROJECT_SKILLS=1 \
scoot -e "Use the incident-triage skill to inspect this checkout and prepare a triage brief."
最佳实践:
- skill 指令要具体、可审查;
- 不要把密钥写进 skill 文件;
- 只在可信仓库中开启项目本地 skills;
- 生产工作优先使用项目本地 skills,而不是宽泛的用户全局 skills;
- 读取 skill 文件即使在
readonly下也可用,但 skill 要求 Scoot 执行的任何动作 仍然会经过正常 policy gate。
选择指南
| 需求 | 推荐模式 |
|---|---|
| 立即做一次分析 | scoot -e |
| CI 总结、PR / release 预检 | SCOOT_TOOLS_POLICY=readonly + scoot -e |
| 外部调度器负责触发时间 | scoot schedule run --ticks 1 |
| Scoot 自己负责周期性本地任务 | systemd/launchd 托管 scoot daemon run |
| 网络探针 | 显式 unrestricted + OS / 网络隔离 |
| 不可信或无人值守本地检查 | readonly |
故障排查与 FAQ
出问题时,先运行 scoot doctor —— 它会在不打印任何密钥的前提下检查运行目录、配置来源、密钥来源、技能发现、调度状态和审计路径。
诊断命令
scoot doctor # local health checks
scoot config # resolved runtime dir + backend (redacted)
scoot --trace -e "your goal" # full ReACT trace on stderr
scoot policy check <action> <input> --mode <mode> # why was this allowed/denied?
常见问题
「No home directory」/ 运行目录不对
Scoot 需要 $HOME(或 SCOOT_HOME)来定位 ~/.scoot。在 $HOME 未设的精简环境里,传 --scoot-home:
scoot --scoot-home /var/lib/scoot doctor
--scoot-home 总是优先于 SCOOT_HOME。运行 scoot config 确认实际生效的目录。
后端认证失败 / 没有 token
Scoot 按 env → 0600 token 文件 → 凭证命令的顺序解析 token。先看 doctor 报告的是哪个来源,然后:
- 确保
OPENAI_API_KEY(或你的api_key_env)在同一个 shell 里已导出; - 若用 token 文件,它必须是
0600权限,否则 Scoot 拒绝读取:chmod 600 ~/.scoot/token; - 若用
api_key_cmd,确认该命令会打印 token 且非交互。
切勿把 key 放进 config.toml。见配置 → 密钥。
HTTPS 后端的 TLS / 证书错误
精简/嵌入式镜像常缺系统根证书。把 ca_file 指向一个 PEM bundle:
[backend]
ca_file = "/etc/ssl/certs/ca-certificates.crt"
后端「Connection refused」
默认 base_url 是本地 Ollama 端点(http://127.0.0.1:11434/v1)。若你不跑 Ollama,请把 base_url/model 设成你真实的后端。确认该端点从 Scoot 所在主机/网络可达。
代理说它「不能」执行某命令
这通常是策略门,而非 bug。readonly 下按设计禁 bash、禁写、禁网络;guarded 下灾难性命令被拦。用 policy check 确认:
scoot policy check bash "the command" --mode readonly
如合适,把 [tools] policy 切到 guarded(交互式)或 unrestricted(完全信任)—— 见执行策略与安全。
file_edit 报匹配歧义/未找到
file_edit 要求 old 在文件中恰好出现一次。先 file_read 看清文件,再把更长、唯一的上下文片段拷进 old。
某个技能未被发现
- 看
scoot skills,确认解析出的搜索路径与发现结果。 - 确保
[skills] enabled = true。 - 对随仓库携带的项目技能,只在可信工作区设置
[skills] include_project_skills = true。 - 校验该目录有合法
SKILL.md,且name与description非空:scoot skills check path/to/skill。 - 记住优先级 —— 同名时靠前者胜(可选的
<cwd>/.agents/skills> 可选的~/.agents/skills>~/.scoot/skills>extra_paths)。
技能在 readonly 下用不了
能用 —— 读取技能是原生的、与策略无关。技能随后让模型去执行的部分仍受策略门。若某技能的动作在 readonly 下被拦,那是预期行为;加载它的指令则不会。
调度任务从不触发
[schedule] enabled必须为true。- 每个任务需要恰好一个触发器;
schedule list会把非法任务标为INACTIVE。 - cron 是 5 字段 UTC 计划,每个匹配分钟最多触发一次。
- 设为
guarded的任务以等效readonly运行;若它似乎无法写或触网,那正是无人值守安全矫正。
运行因上下文预算提前中止
对话记录在压缩历史后仍超过 [agent] context_budget_bytes——也就是预算过小,连最小保留集(system 提示 + 原始任务 + 最近若干回合)都放不下。调大预算(保持低于后端上下文窗口),或设为 0 关闭该检查(回合数仍受 max_turns 约束)。
代理循环不收尾
它撞到了 max_turns(默认 32)。调大 [agent] max_turns,或收窄目标。用 --trace 看它在哪打转。
FAQ
Scoot 会把我的代码发给第三方吗?
只发给你配置的模型后端(base_url)。没有遥测、没有云同步,密钥绝不入日志。指向本地后端即可完全在端上运行。
能完全离线用吗? 能 —— 配一个本地 OpenAI 兼容后端(如 Ollama)。结构化工具不依赖任何外部命令。
guarded 模式是沙盒吗?
不是。它是拦意外的绊线。readonly 才是 fail-closed 安全原语;面对敌对输入请与 OS 级隔离结合。见坦诚的威胁模型。
日志和历史在哪?
~/.scoot/logs/audit.jsonl 与 ~/.scoot/state/sessions/<id>.jsonl。见会话与审计。
什么是「plan 模式」?
保留中,尚未实现。default_mode 目前接受 goal;plan 暂不改变执行。见路线图。
怎么更新?
从源码重建(git pull && zig build)或安装更新的发布制品。见安装。
还是卡住?
带 --trace 重跑,抓取 scoot doctor 输出,到项目仓库开一个 issue。
Agent 指南
权威英文 Agent 指南:
权威中文 Agent 指南:
关键规则
- 扩展能力前先读路线图。
- 代码改动保持外科手术式。
- 修改 Zig 后运行
zig build与zig build test。 - 所有项目文档保持中英双语同步。
- 不执行未经校验的模型输出。
- skill 的执行不得绕过工具沙盒(读取技能指令是原生只读能力,刻意不受策略门控制)。
- 不把密钥写进配置、日志或审计输出。
路线图
权威英文路线图:
权威中文路线图:
简版
Scoot 应该保持小型、可审计、本地优先的自动化核心:
- 轻量单体二进制,
- CLI 与配置文件交互,
- 只对接 OpenAI 兼容后端,
- 本地状态与审计日志,
- 执行前防御性校验,
- 不做 GUI,
- 不做云同步,
- 不泄露密钥,
- skill 不越权。
近期最值得补的是错误诊断、每次运行摘要、目录权限硬化、日志生命周期,以及后续的 plan 模式。
CI、发布与文档
本仓库包含 GitHub Actions 工作流:
- CI:构建并测试 Zig 项目。
- Release:推送版本标签时构建发布产物。
- mdBook:构建并发布双语文档站点。
本地检查
zig build
zig build test
mdbook build book/en
mdbook build book/zh
mkdir -p site
cp book/site-index.html site/index.html
mkdir -p site/assets
cp docs/assets/scoot-logo.svg docs/assets/scoot-favicon.svg docs/assets/scoot-favicon.png site/assets/
文档站点
英文书构建到 site/en,中文书构建到 site/zh。共享入口页是 book/site-index.html。
每本书顶部都提供语言切换链接。
发布产物
推送版本标签后会发布这些目标:
linux-amd64linux-arm64linux-armv7macos-amd64macos-arm64
每个目标上传一个 .tar.gz 压缩包和一个 .sha256 校验文件。
Docker release 还会发布面向 linux/amd64、linux/arm64 和 linux/arm/v7
的多平台 Linux 镜像。Alpine 运行时标签使用 -alpine 后缀。