“高级能力与最佳实践”不是单个运行时功能,而是一组让 Background Agents 在真实团队里跑得更快、更稳、更安全的工程约定,主要覆盖这些场景:
这些能力通常一起使用:先用预构建镜像提供稳定基础环境,再用仓库快照减少重复安装成本,任务运行时按需拆分子任务,所有环境准备动作通过生命周期脚本统一管理,Secrets 只在运行时按作用域注入。
预构建镜像适合放入变化慢、体积大、安装耗时长的内容,例如:
不适合放入:
.git 目录推荐把镜像分成两层:
textbase image ├─ OS 工具 ├─ Node.js / Python / git / browser └─ Agent 运行时 repo image / snapshot ├─ 仓库代码 ├─ node_modules / venv / build cache └─ 生命周期脚本执行结果
典型接口可以抽象成:
tstype PrebuildRequest = {
repository: string
ref?: string
dockerfile?: string
context?: string
imageTag?: string
buildArgs?: Record<string, string>
secrets?: string[]
}
type PrebuildResult = {
image: string
digest: string
cacheKey: string
logsUrl?: string
}缓存 key 一般至少包含:
textbase image digest + Dockerfile 内容 + package-lock.json / pnpm-lock.yaml / poetry.lock / requirements.txt + 生命周期脚本内容 + 运行时版本
如果需要在构建阶段访问私有包 registry,应使用 BuildKit secret mount,避免通过 ARG 或 ENV 把 token 固化进镜像层。
快照适合解决这类启动慢的问题:
textclone repo 5s ~ 60s install deps 30s ~ 10min install browser 1min ~ 5min generate cache 10s ~ 3min
快照保存的是一个可恢复的工作目录模板。新 session 创建时,不需要重新执行完整初始化,而是:
textrestore snapshot → git fetch → checkout target ref → run pre-task hooks → start agent
推荐快照 key 包含:
textrepo identifier + target base ref + prebuild image digest + lockfiles + .background-agents/hooks/* + package manager version + runtime version
伪接口:
tstype SnapshotKey = string
type CreateSnapshotRequest = {
repository: string
ref: string
imageDigest: string
setupCommands?: string[]
lifecycleScriptsHash?: string
}
type Snapshot = {
id: string
key: SnapshotKey
createdAt: string
sizeBytes: number
}
type RestoreSnapshotRequest = {
snapshotId: string
sessionId: string
targetRef: string
}快照恢复后仍应执行一次轻量同步:
bashgit fetch origin
git checkout "$TARGET_REF"
git reset --hard "origin/$TARGET_REF"这样可以避免快照太旧导致代码基线偏移。
Background Agents 支持把一个复杂任务拆成多个并行子任务。每个子任务应该运行在独立 sandbox 中,拥有自己的:
主任务只负责任务拆解和结果汇总,不应该让多个子任务直接写同一个目录。
建议的子任务接口:
tstype CreateSubtaskRequest = {
parentSessionId: string
title: string
prompt: string
baseRef?: string
files?: string[]
model?: string
}
type CreateSubtaskResult = {
childSessionId: string
sandboxId: string
status: "queued" | "running"
}典型拆分方式:
text主任务:给订单页增加退款能力 子任务 A:后端增加 refund API 子任务 B:前端订单详情页增加退款入口 子任务 C:补充单元测试和 e2e 用例 子任务 D:更新文档和变更日志
最后由主任务汇总:
textcollect child results → review patches → resolve conflicts → run test suite → create PR
建议每个仓库把 Background Agents 相关脚本放在固定目录,例如:
text.background-agents/ Dockerfile hooks/ post-clone.sh pre-install.sh post-install.sh pre-task.sh post-task.sh
推荐语义:
| 脚本 | 执行时机 | 适合做什么 |
|---|---|---|
post-clone.sh |
仓库 clone 后 | 设置 git hooks、初始化子模块 |
pre-install.sh |
安装依赖前 | 配置 registry、检查 runtime |
post-install.sh |
安装依赖后 | 生成 ORM client、安装浏览器 |
pre-task.sh |
Agent 开始任务前 | 打印项目信息、检查工作区 |
post-task.sh |
Agent 结束任务后 | 清理临时文件、导出报告 |
建议注入的环境变量:
bashBA_REPO_DIR=/workspace/repo
BA_SESSION_ID=session_123
BA_TASK_ID=task_456
BA_BASE_REF=main
BA_HEAD_REF=agent/task-456
BA_IS_SUBTASK=false
BA_SANDBOX_TOKEN=...脚本要求:
一个安全的脚本模板:
bash#!/usr/bin/env bash
set -euo pipefail
cd "${BA_REPO_DIR:?BA_REPO_DIR is required}"
echo "repo: $(basename "$BA_REPO_DIR")"
echo "session: ${BA_SESSION_ID:-unknown}"
echo "base ref: ${BA_BASE_REF:-unknown}"
if [ -f package.json ]; then
echo "node project detected"
fiREADME 中的 token 架构可以概括为:
| Token 类型 | 用途 | 范围 |
|---|---|---|
| GitHub App Token | git clone / fetch / push | GitHub App 安装过的仓库 |
| User OAuth Token | 创建 PR、读取用户信息 | 用户自己有权限的仓库 |
| Sandbox Auth Token | sandbox 调 control plane | 单个 session |
| WebSocket Token | 实时会话认证 | 单个 session |
生产环境建议按作用域管理 Secrets:
textorg secret 组织级,例如内部 npm registry token repo secret 仓库级,例如部署测试环境 key session secret 单次任务级,例如临时云服务凭证
注入方式优先级:
text短生命周期 token > 文件挂载 > 环境变量 > 构建参数
避免:
dockerfile# 不推荐:token 会留在镜像层历史里
ARG NPM_TOKEN
RUN npm config set //registry.npmjs.org/:_authToken=$NPM_TOKEN推荐使用运行时注入或 BuildKit secret:
dockerfile# 推荐:secret 不进入镜像层
RUN --mount=type=secret,id=npm_token \
NPM_TOKEN="$(cat /run/secrets/npm_token)" && \
npm config set //registry.npmjs.org/:_authToken="$NPM_TOKEN" && \
npm ci日志侧需要做 masking:
textsk-live-abc123 → **** ghp_xxx → ****
这个例子演示把依赖安装进镜像层。需要本机安装 Docker。
bashmkdir -p /tmp/ba-prebuild-demo
cd /tmp/ba-prebuild-demo
cat > package.json <<'EOF'
{
"name": "ba-prebuild-demo",
"version": "1.0.0",
"dependencies": {
"left-pad": "1.3.0"
}
}
EOF
cat > Dockerfile <<'EOF'
# syntax=docker/dockerfile:1.7
FROM node:20-alpine
WORKDIR /workspace
RUN node -v > /opt/prebuilt-node-version
COPY package.json ./
RUN --mount=type=cache,target=/root/.npm \
npm install --package-lock=false --ignore-scripts
CMD ["sh", "-lc", "cat /opt/prebuilt-node-version && node -e \"console.log(require('left-pad')('ok', 4, '.'))\""]
EOF
DOCKER_BUILDKIT=1 docker build -t ba-prebuild-demo:dev .
docker run --rm ba-prebuild-demo:dev预期输出类似:
textv20.x.x ..ok
在真实 Background Agents 部署中,这个镜像可以作为某类 Node 仓库的基础镜像,后续 session 直接从该镜像启动,避免重复安装通用依赖。
这个例子不依赖 Docker,用本地目录和 tar 模拟快照创建与恢复。
bashcat > /tmp/ba-snapshot-demo.sh <<'SH'
#!/usr/bin/env bash
set -euo pipefail
ROOT=/tmp/ba-snapshot-demo
REPO="$ROOT/repo"
CACHE="$ROOT/cache"
SANDBOX="$ROOT/sandbox"
rm -rf "$ROOT"
mkdir -p "$REPO/.background-agents/hooks" "$CACHE"
cat > "$REPO/package.json" <<'EOF'
{
"name": "snapshot-demo",
"version": "1.0.0"
}
EOF
cat > "$REPO/.background-agents/hooks/post-install.sh" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
echo "warming dependencies..."
mkdir -p .cache
date > .cache/deps-ready.txt
EOF
chmod +x "$REPO/.background-agents/hooks/post-install.sh"
hash_stdin() {
if command -v sha256sum >/dev/null 2>&1; then
sha256sum | awk '{print $1}'
else
shasum -a 256 | awk '{print $1}'
fi
}
KEY="$(
{
cat "$REPO/package.json"
cat "$REPO/.background-agents/hooks/post-install.sh"
} | hash_stdin
)"
SNAPSHOT="$CACHE/$KEY.tar.gz"
if [ ! -f "$SNAPSHOT" ]; then
echo "snapshot miss: $KEY"
(
cd "$REPO"
./.background-agents/hooks/post-install.sh
)
tar -C "$REPO" -czf "$SNAPSHOT" .
else
echo "snapshot hit: $KEY"
fi
mkdir -p "$SANDBOX"
tar -C "$SANDBOX" -xzf "$SNAPSHOT"
echo "restored files:"
find "$SANDBOX" -maxdepth 3 -type f | sort
echo
echo "cache content:"
cat "$SANDBOX/.cache/deps-ready.txt"
SH
bash /tmp/ba-snapshot-demo.sh
bash /tmp/ba-snapshot-demo.sh第一次运行会看到 snapshot miss,第二次运行会看到 snapshot hit。
这个例子用 Python 模拟 3 个子任务并行执行,每个任务都有自己的工作目录,最后主任务汇总结果。
bashcat > /tmp/ba-subtasks-demo.py <<'PY'
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
import shutil
import time
root = Path("/tmp/ba-subtasks-demo")
repo = root / "repo"
sandboxes = root / "sandboxes"
if root.exists():
shutil.rmtree(root)
repo.mkdir(parents=True)
(repo / "README.md").write_text("# demo repo\n", encoding="utf-8")
tasks = [
("backend", "add refund API"),
("frontend", "add refund button"),
("tests", "add refund test cases"),
]
def run_subtask(name: str, goal: str):
sandbox = sandboxes / name
shutil.copytree(repo, sandbox)
time.sleep(1)
result = sandbox / f"{name}.md"
result.write_text(
f"# {name}\n\nGoal: {goal}\nSandbox: {sandbox}\n",
encoding="utf-8",
)
return name, result.read_text(encoding="utf-8")
with ThreadPoolExecutor(max_workers=3) as pool:
futures = [pool.submit(run_subtask, name, goal) for name, goal in tasks]
for future in as_completed(futures):
name, output = future.result()
print(f"=== subtask finished: {name} ===")
print(output)
print("all subtasks done")
PY
python3 /tmp/ba-subtasks-demo.py重点是:子任务只共享初始仓库状态,不共享可写工作目录。真实系统里,每个子任务对应一个独立 sandbox 和独立 session token。
这个例子演示运行 pre-task.sh 时注入 secret,并在日志里做脱敏。
bashcat > /tmp/ba-lifecycle-secrets-demo.sh <<'SH'
#!/usr/bin/env bash
set -euo pipefail
ROOT=/tmp/ba-lifecycle-secrets-demo
REPO="$ROOT/repo"
rm -rf "$ROOT"
mkdir -p "$REPO/.background-agents/hooks"
cat > "$REPO/.background-agents/hooks/pre-task.sh" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
cd "${BA_REPO_DIR:?BA_REPO_DIR is required}"
echo "running pre-task hook"
echo "session: ${BA_SESSION_ID:-unknown}"
# 真实脚本里不应该打印 secret;这里故意打印,用来演示日志脱敏。
echo "using token: ${SECRET_API_TOKEN:?SECRET_API_TOKEN is required}"
EOF
chmod +x "$REPO/.background-agents/hooks/pre-task.sh"
export BA_REPO_DIR="$REPO"
export BA_SESSION_ID="session_demo_123"
export SECRET_API_TOKEN="sk_demo_should_be_masked"
LOG="$("$REPO/.background-agents/hooks/pre-task.sh" 2>&1 || true)"
MASKED_LOG="${LOG//$SECRET_API_TOKEN/****}"
echo "$MASKED_LOG"
SH
bash /tmp/ba-lifecycle-secrets-demo.sh预期输出:
textrunning pre-task hook session: session_demo_123 using token: ****
真实环境中应尽量避免打印 secret,masking 是最后一道防线,不是主要防线。
Background Agents 的安全模型是单租户。也就是说,系统假设所有用户都是同一组织内可信成员,并且对组织仓库有相近的访问范围。
需要重点注意:
生产建议:
textWeb UI 放在 SSO / VPN 后面 GitHub App 只安装到明确需要的仓库 配置 ALLOWED_GITHUB_ORGS 限制组织成员 安装 GitHub App 时选择 specific repositories 记录 session、repo、actor、token mint、PR creation 审计日志
如果要做多租户,需要额外实现:
textper-tenant GitHub App installation session 创建时的仓库访问校验 租户隔离的数据模型 Secrets 按租户隔离 sandbox 网络隔离 审计日志按租户隔离
ARG TOKEN=... 传敏感信息。linux/amd64 和 linux/arm64 的原生依赖不能混用。git fetch 并 reset 到目标 ref。.git-credentials、SSH key、token 文件打进快照。set -euo pipefail。rm -rf "$DIR" 前确认变量非空。ALLOWED_GITHUB_ORGS 或等价机制限制登录来源。