Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Scoot

Scoot — local-first AI agent daemon and CLI in pure Zig, showing the ReACT loop, built-in tools, and execution policies

Scoot 是一个用纯 Zig 编写的轻量、本地优先的 AI agent 守护进程与 CLI。它通过一个 防御式 ReACT 循环 驱动 OpenAI 兼容的模型后端,校验模型产出的每一个结构化步骤, 在 执行策略门 背后运行本地工具,并把每一步记录为可审计的本地状态。

它专为纯文本环境而构建——服务器、容器、CI runner、嵌入式 Linux——在这些场景下,你 想要一个可自动化、体积小、可预测、完全可检查的 agent,没有 GUI、没有云同步、也没有 明文密钥

工作原理

每个回合都运行同一个防御式循环:

  1. 询问 模型,要求其给出恰好一个结构化步骤(thought + action + action_input)。
  2. 校验 该步骤是否符合严格的 JSON schema(绝不执行自由格式文本)。
  3. 门控 该动作,使其通过当前生效的执行策略(guarded / readonly / unrestricted)。
  4. 运行 选定的内建工具,在带硬超时的沙盒中执行。
  5. 审计 该动作,并写入会话对话记录与审计日志。
  6. 观察——把工具的输出作为下一个观察反馈给模型。

循环不断重复,直到模型给出 final 答复或触及 max_turns

核心能力

  • 两个入口——一次性的 scoot -e "<goal>" 与交互式 REPL。
  • 十二个内建动作——bashfile_readfile_writefile_editgrepgloboutlinehttp_requestskillrecallparallelfinal。这些 结构化工具无需外部命令即可工作,因此在精简系统上行为完全一致。参见 内建工具
  • 三种执行策略——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_readgrepglobhttp_request……)无需任何外部命令。

支持的发布目标:linux-amd64linux-arm64linux-armv7macos-amd64macos-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_VERSIONlatest要安装的 release tag,可带或不带开头的 v
SCOOT_INSTALL_FLAVORsafesafe 安装默认的 ReleaseSafe 产物;small 安装 ReleaseSmall 产物。
SCOOT_INSTALL_BINARYscoot安装后的二进制名称。
SCOOT_INSTALL_REPOjamiesun/scoot下载 release 的 GitHub 仓库。

Safe 与 Small 发布构建

每个 tag 版本会为每个支持目标发布两种二进制:

变体Zig optimize 模式什么时候用
默认ReleaseSafe需要常规 release,保留运行时安全检查和更清晰的 fail-fast 诊断。
smallReleaseSmall需要极小二进制用于探针、边缘设备或极简容器,并接受更少运行时安全检查。

从源码构建

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/amd64linux/arm64linux/arm/v7 的多平台 Linux 容器镜像。

标签规则:

标签形式运行时基础镜像示例
<version><major>.<minor><major>latest极简 BusyBox/musl 运行时ghcr.io/jamiesun/scoot:latest
<version>-alpine<major>.<minor>-alpine<major>-alpinelatest-alpineapk 的 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 runscoot 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.jsonstate/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 按下面顺序优化:

  1. 安全与可控。 模型输出必须先被校验;不安全或非法输出不能直接落到系统。
  2. 可审计。 一次运行结束后,用户应能回看目标、模型步骤、工具调用、策略决定、 观察结果和最终答案。
  3. 本地优先。 配置、会话、技能、日志和 daemon 状态都留在用户机器上。
  4. 小部署面。 一个原生二进制、纯文本配置和尽量少的活动部件,比功能广度更重要。
  5. 长时间运行稳定。 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 和操作系统隔离。

铁律

  1. 先校验,再产生效果。 每个模型步骤必须先解析和检查,工具才能运行。
  2. 所有效果都经过策略门。 shell、写入、网络和原生工具动作必须通过当前策略。
  3. 外部工作必须有超时。 子进程和网络请求不能让 Agent 无限挂住。
  4. 密钥不能进入文本产物。 配置、日志、会话、错误和文档都不能暴露 token。
  5. 无人值守优先 readonly Scheduled guarded job 会被矫正为有效 readonly
  6. Skill 不授予特权。 读取 skill 是原生只读能力;skill 要求 Scoot 执行的动作 仍走正常策略门。
  7. 文档保持双语。 面向用户的文档变更必须同步英文和中文。

看起来像缺陷,其实是选择

你可能看到的现象为什么这样做
没有 GUI。文本界面可脚本化、可审查,也适合小型主机。
daemon run 保持前台运行。后台化、重启、日志和停止应由 systemd 这类 supervisor 负责。
readonly 禁止 shell 和网络。fail-closed 的无人值守模式必须阻止修改和数据外带。
guarded 不被宣传成安全隔离。拒绝清单能挡事故,但不是对抗性沙箱。
没有厂商原生 tool calling 集成。模型边界保持 OpenAI 兼容和 schema 驱动。
Skills 是本地目录,不是插件。指令可以扩展行为,同时不扩大原生可信面。
没有向量记忆子系统。本地 JSONL 状态和 skills 更可检查,也避免重依赖。
网络探针需要显式接受风险。探针有价值,但必须先用 OS / 网络隔离收窄环境,再授予宽权限。

判断一个新功能时,正确的问题不是“能不能做”,而是“能不能在保持本地优先、 可审计、小型、经过策略门的前提下做”。

配置

Scoot 从其运行目录读取配置,并在缺失时回退到内置默认值,因此零配置即可运行。本页是 每个配置节与配置键的完整参考。

文件位置与加载顺序

运行目录默认是 ~/.scoot。可用 --scoot-home(最高优先级)或 SCOOT_HOME 环境变量覆盖。

在该目录内,配置按以下顺序加载:

  1. config.toml
  2. config.json
  3. 内置默认值

合并语义: 加载是 按节、按字段 进行的。任何缺失的节或字段都回退到其内置默认值, 并且 未知字段会被忽略。这意味着部分配置始终有效——你只需指定想要改动的部分。 从 config.example.toml 开始。

随时运行 scoot config 可打印 解析出的 运行目录与后端配置(密钥已脱敏)。

环境变量覆盖

每个非密钥配置字段都可被 SCOOT_* 环境变量覆盖。该覆盖层在 内存中 应用,优先级为:

SCOOT_* 环境变量  >  config.toml / config.json  >  内置默认值

无论配置文件是否存在,环境变量始终胜出,因此你可以在 完全没有配置文件 的情况下运行 Scoot——把 SCOOT_HOME 指向一个临时目录,所有配置经环境变量传入即可。这非常适合 CI 以及 跑完即焚的一次性执行。

环境变量覆盖类型
SCOOT_BACKEND_BASE_URLbackend.base_url字符串
SCOOT_BACKEND_MODELbackend.model字符串
SCOOT_BACKEND_TIMEOUT_MSbackend.timeout_ms整数
SCOOT_BACKEND_API_KEY_ENVbackend.api_key_env字符串(指明持有 token 的变量名)
SCOOT_BACKEND_API_KEY_FILEbackend.api_key_file字符串
SCOOT_BACKEND_API_KEY_CMDbackend.api_key_cmd字符串
SCOOT_BACKEND_CA_FILEbackend.ca_file字符串
SCOOT_BACKEND_STOREbackend.store布尔(true/false/1/0
SCOOT_BACKEND_EXTRA_BODYbackend.extra_bodyJSON 对象
SCOOT_AGENT_DEFAULT_MODEagent.default_mode字符串(goal/plan
SCOOT_AGENT_COMPACTORagent.compactor字符串(drop/extractive/plugin:<name>
SCOOT_AGENT_MAX_TURNSagent.max_turns整数
SCOOT_AGENT_CONTEXT_BUDGET_BYTESagent.context_budget_bytes整数
SCOOT_TOOLS_POLICYtools.policy字符串(guarded/readonly/unrestricted
SCOOT_TOOLS_TIMEOUT_MStools.timeout_ms整数
SCOOT_TOOLS_CONFINE_WRITEStools.confine_writes布尔(true/false/1/0
SCOOT_TOOLS_BLOCK_INTERNAL_HTTPtools.block_internal_http布尔
SCOOT_SKILLS_ENABLEDskills.enabled布尔
SCOOT_SKILLS_INCLUDE_PROJECT_SKILLSskills.include_project_skills布尔
SCOOT_SKILLS_INCLUDE_AGENTS_SKILLSskills.include_agents_skills布尔
SCOOT_AUDIT_LEVELaudit.level字符串
SCOOT_AUDIT_TO_FILEaudit.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_urlstringhttp://127.0.0.1:11434/v1OpenAI 兼容端点的基础 URL。
modelstringqwen2.5发送给后端的模型名。
api_key_envstringOPENAI_API_KEY作为 第一 token 来源的环境变量名。
timeout_msu64120000单次后端 Responses API 调用的硬超时,单位毫秒。0 表示关闭超时。
api_key_filestring?unset → ~/.scoot/token0600 token 文件的路径。在环境变量来源之后使用。
api_key_cmdstring?unset打印 token 的命令(如 pass show openai)。最后使用。它会由 Scoot 执行,因此应视为可信配置。
ca_filestring?unset → system roots用于 HTTPS 的 PEM CA bundle。在缺少根证书的系统上设置它。
storeboolfalse通过 Responses API 的 store 标志请求后端在服务端持久化响应。默认关闭,以保持 scoot 无状态、本地优先。
extra_bodytable?unset合并进每个请求的额外顶层 JSON 字段。

[backend.extra_body]

一个直通表,原样合并进顶层模型请求 JSON。 用它来传递后端专有或较新的字段而无需重新编译——例如 reasoning_effortservice_tiertop_p。只接受 JSON 对象; 非对象值会被忽略。绝不要把密钥放在这里,也不要覆盖 modelmessagesinput 等核心字段。

[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_turnsu3232agent 停止前的最大 ReACT 回合数,用于约束失控循环。
default_modestringgoal认知模式。goal 现已实现;plan 是保留项(见路线图),目前尚不改变执行。
compactorstringextractive上下文压缩策略:extractive 写入确定式纪要;drop 保持旧的计数标记;plugin:<name> 运行外部压缩器包。
context_budget_bytesusize80000累积的提示历史预算,单位 字节0 表示禁用。
compactor_plugintableunset按名称组织的动态插件配置,位于 [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 都会被判为不可用,并进入内置回退链。

类型默认值说明
packagestring必填wasm_tool.validatePackage 校验的目录。
hostlist of stringunset命令 argv 模板。占位符:{package}{component}{entry}。若未设置,Scoot 尝试 {package}/{entry}
timeout_msu64?tools.timeout_ms子进程硬超时。0 表示关闭 deadline。
stdout_limitusize?1048576接受的最大 stdout 字节数。
stderr_limitusize?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_msu6430000每一次 工具调用的硬超时,单位毫秒。
policystringguarded执行策略:guardedreadonlyunrestricted(别名 yolo)。未知值回退到 guarded
confine_writesbooltruefile_write/file_edit 限制在项目根目录内。guarded
block_internal_httpbooltrue阻止 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]

本地技能发现。参见 技能

类型默认值说明
enabledbooltrue启用技能发现与注入。
include_project_skillsboolfalse是否加载 <cwd>/.agents/skills 这个随仓库携带的项目技能目录。仅对可信仓库开启。
include_agents_skillsboolfalse是否加载 ~/.agents/skills 这个跨 agent 的用户级技能目录。
extra_pathslist of string[]额外的技能搜索路径,追加在内置路径之后。

技能按 优先级顺序 发现(名称冲突时靠前者胜出):

  1. <cwd>/.agents/skills——项目本地,仅在 include_project_skills=true 时加载。
  2. ~/.agents/skills——跨 agent 的用户级技能,仅在 include_agents_skills=true 时加载。
  3. ~/.scoot/skills——Scoot 自有的用户级目录。
  4. 此处列出的 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_toolsallowed_tools 为空表示拒绝所有工具。readonly 策略会完全拒绝 mcp_call,因为外部 MCP 工具可能绕过 Scoot 静态工具分类去读写文件或访问网络。 guardedunrestricted 仍然要求显式 server 与工具 allowlist。

类型默认值说明
serversarray[]MCP server 声明列表。

每个 [[mcp.servers]] 条目:

类型默认值说明
namestring""mcp_call.server 使用的名称。
transportstringstdio支持 stdio、Streamable HTTP(http / streamable_http)和 legacy sse
commandstring""stdio transport 要启动的命令。
argslist of string[]传给 command 的参数。
envlist of { name, value }[]子进程环境覆盖块。若设置,请包含子进程需要的一切变量,例如 PATH
allowed_toolslist of string[]显式工具 allowlist。为空即拒绝全部。
policystringreadonly用于审计和未来策略扩展的 server 姿态声明。
urlstring?未设置HTTP/SSE transport 的远程端点 URL。
headersheader object 列表[]远程 transport 的额外 HTTP header。密钥请用 value_env

Header object 支持 namevalue/value_env 二选一,以及可选 prefixAcceptContent-TypeMCP-Protocol-VersionMcp-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]

审计日志。参见 会话与审计

类型默认值说明
levelstringinfo详尽程度:debuginfowarnerror
to_filebooltrue将审计日志写入 ~/.scoot/logs/audit.jsonl
[audit]
level = "info"
to_file = true

[schedule]

无人值守的调度任务。默认禁用——自主执行必须显式开启。参见 调度与守护进程

类型默认值说明
enabledboolfalse启用调度器 / 守护进程循环。
poll_msu641000调度器轮询间隔,单位毫秒。
jobslist of table[]调度任务定义(见下文)。

[[schedule.jobs]]

每个任务是一个 array-of-tables 条目,带 恰好一个 触发器。

类型默认值说明
idstring稳定的任务标识符(必填)。
goalstring""agent 运行的自然语言目标。
every_secu64?unset触发器:固定间隔,单位秒。
at_unixi64?unset触发器:一个固定的 Unix 时间点。
cronstring?unset触发器:5 字段 UTC cron 表达式。
modestringreadonly执行策略: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,按顺序尝试:

  1. 环境变量,由 backend.api_key_env(默认 OPENAI_API_KEY)命名。
  2. token 文件,位于 backend.api_key_file,未设置时为 ~/.scoot/token。该 文件 必须是 0600 权限;若权限过于开放,Scoot 拒绝读取。
  3. 凭证命令,位于 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-homeSCOOT_HOME 覆盖。

全局选项

选项说明
-e, --eval <prompt>运行单个目标至完成,打印答复,然后退出。
--retries <N>-e 模式下针对瞬时后端错误的重试次数(默认 20 禁用)。
--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 servestdin 上的 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 每一行是一条响应;响应会带回同一个 idok,以及 resulterror

支持的方法:

方法参数结果
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_urlmodeltoken 来源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(默认)、readonlyunrestricted

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] 校验结构,不执行 任何技能脚本。一个有效的技能拥有 带非空 namedescriptionSKILL.md;可选的 capabilitiesallowed_toolsscope 元数据也会被校验。
  • skills pack 先校验再导出一个带 .scoot-skill.json 评审 manifest 的 tar。它包含常规的非隐藏文件,拒绝符号链接等不安全类型,且不授予任何策略绕过。

撰写细节参见 技能

wasm-tools check

scoot wasm-tools check <dir>

静态校验本地 Wasm 工具包的边界——manifest.tomlpolicy.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.jsonstate/daemon.pid,安装 SIGTERM/SIGINT 处理器,并 保持无人值守的 readonly 安全规则。它 不会 fork 到后台—— 请用 systemdlaunchdtmux 或 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_*grepglobhttp_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":...}是(原生)
parallel1–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_readgrep。只读;在每种模式下都允许。

outline

{ "path": "src/agent.zig" }

返回单个文件的紧凑结构骨架——函数与类型签名、Markdown 标题, 各自带行号——而不是整文件内容。先用它给陌生文件画出地图,再用 file_readoffset/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 请求(绝不挂起)。methodGET/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(httpstreamable_http)以及 legacy sse transport。远程 transport 使用配置中的 url,复用工具硬超时;如果配置了 backend.ca_file,也会复用该 CA bundle。基于 header 的认证按 server 配置 headers;token 请使用 value_env,可配合 prefix 生成 Bearer ...

MCP 调用按可能具有外部副作用的执行处理。readonly 会拒绝它;guardedunrestricted 仍要求显式 server 与工具 allowlist。MCP 调用像其他工具一样进入审计, 并受同一个硬超时约束。

skill

{ "name": "demo", "path": "SKILL.md" }

已加载技能的 目录中读取一个文件——path 默认为 SKILL.md, 也可指向另一个资源,如 references/guide.md。这是一个 原生、 按设计绕过执行策略的只读能力,因此即使在 readonlybash 被拒绝)下技能仍可使用。

安全位于执行层而非策略层:读取被限制在指定技能的 目录内(绝对路径与 .. 被拒绝),名称必须在已加载集合中 (未知名称返回一个可恢复的观察,列出可用项),并且 每次读取都被审计。内容最多返回约 32 KB。参见 技能

recall

{ "query": "old error text", "limit": 8 }
{ "seq": 12, "context": 2 }

搜索 当前会话的完整 transcript 归档,返回带 seqrolecontent 的 JSONL 风格原文消息行。这是原生只读能力,因此在 readonly 模式下也可用。

当上下文压缩只保留摘要,而模型需要较早的精确观察、命令或用户指令时使用它。 query 做字面量子串匹配;seq 从 1 开始,可带少量前后 contextlimit 会被封顶,避免召回结果重新撑爆上下文。

parallel

{ "calls": [
  { "action": "file_read", "input": "{\"path\":\"README.md\"}" },
  { "action": "grep", "input": "{\"pattern\":\"Scoot\",\"path\":\"AGENT.md\"}" }
] }

并发运行 1–4 个独立的只读调用,保留观察 顺序。仅允许 file_readgrepgloboutline 与 HTTP GET/HEAD—— bash、写入、skillrecall 与嵌套的 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(坏配置绝不能放松策略门)。yolounrestricted 的别名。

各模式的行为

guarded —— 交互式绊线

guarded 是交互式 CLI/REPL 的默认模式。它不是沙盒,而是一条绊线:一份灾难性 shell 命令的拒绝清单。日常工作照常放行,让你在有人值守时真正把活干完。

bash 命令会先归一化(折叠空白、转小写——挫败 rm -RF / 之类的花招),再与一份刻意从紧的灾难性清单比对并拦截,包括:

  • 递归删除根/家目录/*rm -rf /rm -rf ~rm -rf *--no-preserve-root);
  • 磁盘/文件系统摧毁(mkfsdd ... of=/dev/...> /dev/sd...);
  • 管道接 shell 的远程执行(| sh| bash);
  • 电源状态变更(shutdownrebootpoweroffhaltinit 0/6);
  • fork 炸弹,以及鲁莽的 chmod 777 / / 递归 chown

内建工具(file_*grepglobhttp_request)在 guarded 下放行,且受自身的路径/大小/超时上限约束。默认情况下,guarded 还会把 file_write/file_edit 收口在项目根内,并阻止 http_request 访问环回、内网、链路本地和云元数据地址。

readonly —— fail-closed 安全原语

readonly 才是真正的安全边界,也是无人值守任务的结构性前提。它 fail-closed(失败即关闭):

  • bash 一律拒绝 —— shell 组合语义太宽,无法靠白名单精确防住;改用 file_read/grep/glob
  • 所有写一律拒绝file_writefile_edit)。
  • 所有网络一律拒绝 —— 即便是只读的 GET/HEAD,以防本地数据经请求 URL 外带。
  • 本地读放行但受路径收口(见下)。
  • 在全面禁 bash 之上,灾难性 shell 模式仍会被拦截。

readonly 下,本地读路径还会被额外校验:禁绝对路径、禁 ~/$VAR 展开、禁 .. 逃逸,并拒绝常见敏感片段.env.sshid_rsaid_ed25519.netrccredentialssecrettoken 等)。这把读取收口在项目工作目录内,远离明显的密钥文件。

unrestricted —— 不设限,但仍审计

完全不设策略限制(别名 yolo)。每个动作仍会写入审计日志,但不拦截任何东西。仅在你完全信任目标时使用。

skill 动作是原生的

通过 skill 动作 读取技能的指令/资源是一种原生只读能力,刻意绕过策略门 —— 故技能即便在 readonly 下也可用。安全由执行环节把关(目录收口、读取被审计),而非由策略把关。技能随后让模型去执行的一切(shell、写、网络)仍走正常策略门。

正因为它绕过策略门,skill 动作会放宽 readonly 的读取面。在上文 evaluateReadPath 把关的读取(项目工作目录内、非敏感、禁 ../绝对路径)之外,它还能读取任意已注册技能目录下的文件

  1. [skills] include_project_skills = true 时的 <cwd>/.agents/skills
  2. [skills] include_agents_skills = true 时的 ~/.agents/skills
  3. ~/.scoot/skills
  4. [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)。
  • 内建工具能力分类 —— readwritenet_readnet_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 定义版本门为止。

搜索路径

技能按优先级顺序发现(同名时靠前者胜):

  1. <cwd>/.agents/skills —— 项目本地,仅在 [skills] include_project_skills = true 时加载。
  2. ~/.agents/skills —— 跨 agent 的用户级技能,仅在 [skills] include_agents_skills = true 时加载。
  3. ~/.scoot/skills —— Scoot 自有的用户级目录。
  4. 配置 [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/ —— 都和任意普通工具调用走同一套策略校验。技能不获任何特权。

策略门见执行策略与安全,铁律见 Agent 指南

命令

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/minimaldocs/examples/skills/metadata

调度与守护进程

Scoot 可通过前台守护循环运行无人值守的调度任务。自主执行默认关闭 —— 必须显式启用。完整的生命周期/恢复参考见 docs/DAEMON.md

应该使用哪种模式

-eschedule rundaemon run 之间选择时,先看这张表:

模式是否读取配置任务默认是否常驻触发时间由谁负责适用场景
scoot -e "<goal>"调用方人或脚本要执行一个即时任务。
scoot schedule run --ticks 1cron、systemd timer、CI外部调度器周期性唤起 Scoot。
scoot schedule run当前终端或进程管理器简单前台调度循环,不需要 daemon 状态文件。
scoot daemon runScoot 循环 + systemd/launchd 等托管长期无人值守调度,并需要 pid/state/stop/status 支持。

-e 和 scheduled execution 是不同入口。-e 会立即运行命令行传入的 prompt, 并使用普通配置里的工具策略。调度任务来自 [[schedule.jobs]],由 every_secat_unixcron 触发,并使用无人值守安全规则:job 的 mode 默认是 readonlyguarded 会被矫正为有效 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 是调度任务的长驻前台进程。它派生到后台 —— 需要后台托管时请配合 systemdlaunchdtmux 或 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.jsonstate/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.jsonlrepl.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\":\"...\"}"}

rolesystemuserassistant。写入是追加式的,故一个文件累积该会话完整的来回往复,可按序回放。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 打印匹配事件,保留 seqts、可选 run_idkindmsg

详细程度

[audit] level 控制记录量 —— debuginfo(默认)、warnerror。设 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...]

runwasi 执行模块前,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)、readwritenet_readnet_write。独立 host 当前只暴露最小 WASI preview1 的 stdio/args/environ/clock/random/proc-exit 子集;文件与网络权限 尚未实现。

Schema

schema/input.jsonschema/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 是不透明句柄。嵌入者拿不到 AgentSessionConfigpolicyllm.ClienttoolsCompressor。这些都留在内部,这样 Scoot 才能自由演进引擎、配置 schema、压缩、工具、MCP/Wasm 集成和 daemon 内部实现, 而不破坏下游代码。

Options

Options 接收的是配置来源,不是结构化配置:

字段含义
env必填环境变量 map,用于 HOME/SCOOT_HOMESCOOT_* 覆盖和 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 前有效。

稳定边界

稳定:

  • version
  • Options
  • 不透明 Runtime
  • start
  • run
  • stop

不稳定:

  • AgentSessionConfigpolicyllm.ClienttoolsCompressor 和其它所有内部模块;
  • src/ 下的包内名字;
  • build 生成的内部内容;
  • 所有隐藏 runtime 状态的精确布局。

仓库为包根加了白名单测试。意外把 toolsregex 这类内部命名空间导出时, 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 能碰什么:readonlyguarded,还是显式 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、写入和网络。 如果需要 dfsystemctl 或厂商 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,且 namedescription 非空: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 目前接受 goalplan 暂不改变执行。见路线图

怎么更新? 从源码重建(git pull && zig build)或安装更新的发布制品。见安装

还是卡住?--trace 重跑,抓取 scoot doctor 输出,到项目仓库开一个 issue。

Agent 指南

权威英文 Agent 指南:

权威中文 Agent 指南:

关键规则

  • 扩展能力前先读路线图。
  • 代码改动保持外科手术式。
  • 修改 Zig 后运行 zig buildzig 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-amd64
  • linux-arm64
  • linux-armv7
  • macos-amd64
  • macos-arm64

每个目标上传一个 .tar.gz 压缩包和一个 .sha256 校验文件。

Docker release 还会发布面向 linux/amd64linux/arm64linux/arm/v7 的多平台 Linux 镜像。Alpine 运行时标签使用 -alpine 后缀。