前置条件
- 1Python 3.10+ 或 Node.js 20+(两条路径任选其一,建议都装以便对比)
- 2已有一个可用的模型 API Key(Anthropic / OpenAI / Amazon Bedrock 任一)
- 3了解基本的命令行操作与 Python 或 TypeScript 语法
- 4有一个实际的 Git 仓库用于实践
读完想动手?新用户首单 8 折上云
服务器 + 大模型 API + 对象存储,新用户首购享 20% 补贴
Strands Harness 实战
前置准备
- Python 3.10+ 或 Node.js 20+
- 一个可用的模型 API Key(Anthropic / OpenAI / Bedrock 任一)
- 一个实际的 Git 仓库用于实践
- 会用命令行,能跑通
pip或npm
一、先搞清楚:Harness 到底解决了什么问题
1.1 一个所有人都踩过的坑
你用 Agent SDK 写了一段代码:
1from strands import Agent
2from strands_tools import file_read, shell
3
4agent = Agent(tools=[file_read, shell])
5agent("把这个仓库里所有的 print 调试语句清理掉")跑起来了,前 5 分钟很美。然后你遇到了:
- 工具输出爆炸:一个
cat输出 3 万 Token,上下文瞬间撑满 - 上下文溢出:模型报
context_length_exceeded,前面 20 轮的分析全丢了 - 无限循环:Agent 反复调用同一个失败的工具,烧掉几十万 Token
- 失败即归零:任务跑到第 40 步崩了,重跑从头开始
- 看不见:不知道钱花在哪一步、哪一轮、哪个工具上
这五件事,和模型聪不聪明没有关系。它们属于「执行层」问题。
1.2 Agent / Harness / Runtime 三层分工
2026 年的 Agent 技术栈已经明确分成三层,别再混着谈:
| 层 | 回答的问题 | 代表产物 | 你要写什么 |
|---|---|---|---|
| Model | 这一步该想什么 | Claude / GPT / Qwen / DeepSeek | Prompt、温度、结构化输出 |
| Agent SDK | 一个 Agent 怎么组织思考与工具 | Strands Agents SDK | 工具定义、系统提示、模型 provider |
| Harness | 一次任务怎么稳定跑完 | Strands Harness | 循环策略、上下文策略、预算、恢复、可观测 |
| Runtime | 跑在哪、怎么扩 | Lambda / Cloud Run / Workers / 本地 | 部署配置、并发、隔离 |
绝大多数团队自己手写的那一堆 while 循环、try/except 重试、手动截断、临时写文件——全都在 Harness 层。这正是重复劳动最集中的地方,也是 Strands Harness 要标准化的东西。
1.3 这次开源的三件事
AWS Strands Agents 团队在 2026 年 9 月开源的 Strands Harness:
- 协议:Apache 2.0
- 语言:Python 与 TypeScript 双实现
- 部署目标:本地、AWS、GCP、Azure、Cloudflare、Modal
官方公布的六项基准结论:
信息
在准确率相近的前提下,相比其他 Agent 框架平均降低 28% 的 Token 成本。 在 Terminal-Bench 2.1 上,成本比 Claude Code 低 77%,得分高 7.9 点。
成本收益的主要来源(第 5 节逐一配置):
| 机制 | 触发条件 | 省的是什么 |
|---|---|---|
| 工具输出截断 | 单次工具输出超过约 1500 Token | 把超长输出落盘为文件引用,上下文里只留摘要和路径 |
| 上下文自动摘要 | 上下文占用达到 85% | 结构化压缩历史,而非丢掉最早的轮次 |
| 溢出恢复 | 触发上下文溢出错误 | 走恢复流程续跑,而不是整轮重跑 |
一句话总结:Strands Harness 不是「更好的模型」,是「更省的执行层」。如果你的 Token 账单里有一半花在了重复上下文和失败重跑上,它值得你花两小时配一遍。
二、双路径安装:Docker 一键启动 + 源码手动配置
2.1 路径 A:Docker 快速启动(推荐先跑通,15 分钟)
适合:想先看到效果、不想污染本地环境、准备直接上 CI。
步骤 1:拿到镜像
1docker pull strands/harness:latest
2
3# 国内网络拉不动时,用镜像加速前缀(按你现有的加速器改)
4docker pull docker.1ms.run/strands/harness:latest
5docker tag docker.1ms.run/strands/harness:latest strands/harness:latest步骤 2:准备配置目录
1mkdir -p ~/strands-workspace/{config,tasks,workspace,logs}
2
3cat > ~/strands-workspace/config/harness.yaml <<'EOF'
4version: 1
5model:
6 provider: anthropic # anthropic | openai | bedrock | ollama | litellm | openai-compatible
7 model_id: claude-sonnet-4-5
8 max_tokens: 8192
9 temperature: 0.2
10
11context:
12 max_context_tokens: 180000
13 tool_output_max_tokens: 1500 # 单次工具输出截断阈值
14 compaction_threshold: 0.85 # 上下文占用比例触发摘要
15 compaction_target: 0.55 # 压缩后降到这个比例
16 recovery_on_overflow: true # 溢出时走恢复而不是失败
17
18execution:
19 max_steps: 120
20 max_wall_clock_seconds: 1800
21 max_cost_usd: 8.00
22 retry_per_tool: 3
23 loop_detection:
24 enabled: true
25 window: 6
26 similarity_threshold: 0.92
27
28tools:
29 allow:
30 - file_read
31 - file_write
32 - file_edit
33 - shell
34 - grep
35 - http_request
36 denylist_paths:
37 - ".git/**"
38 - "**/.env*"
39 - "**/node_modules/**"
40
41telemetry:
42 otel_endpoint: http://localhost:4318
43 log_level: info
44 trace_dir: /var/log/strands/traces
45EOF注意 denylist_paths 这一项。它默认值通常已经排除了 .git,但不会自动排除 .env。Agent 读取环境变量文件并把密钥写进 trace 日志,是 2026 年最常见的一号泄露路径。动手前先确认你的配置里排掉了 .env*。
步骤 3:启动
1docker run -d --name strands \
2 -v ~/strands-workspace/config:/etc/strands \
3 -v ~/strands-workspace/workspace:/workspace \
4 -v ~/strands-workspace/logs:/var/log/strands \
5 -e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
6 -p 8080:8080 \
7 strands/harness:latest \
8 serve --config /etc/strands/harness.yaml
9
10docker logs -f strands看到 Harness listening on :8080 即启动成功。
步骤 4:提交第一个任务
1curl -sS http://localhost:8080/v1/tasks \
2 -H 'Content-Type: application/json' \
3 -d '{
4 "prompt": "统计 /workspace 下所有 Python 文件的行数总和,把结果写进 /workspace/LOC.md",
5 "workspace": "/workspace",
6 "mode": "supervised"
7 }' | jq返回:
1{
2 "task_id": "tsk_01J9Z8C4YQ",
3 "status": "running",
4 "stream_url": "http://localhost:8080/v1/tasks/tsk_01J9Z8C4YQ/events"
5}跟流式事件:
1curl -N http://localhost:8080/v1/tasks/tsk_01J9Z8C4YQ/events2.2 路径 B:源码手动配置(推荐长期使用)
适合:要改 Harness 行为、要嵌入自己的服务、要读源码排查。
步骤 1:Python 环境
1python -m venv .venv
2source .venv/bin/activate # Windows: .venv\Scripts\activate
3
4pip install --upgrade pip
5pip install strands-agents strands-harness strands-agents-tools
6
7python -c "import strands, strands_harness; print('OK', strands.__version__)"步骤 2:TypeScript 环境
1npm init -y
2npm install @strands-agents/sdk @strands-agents/harness
3
4node -e "console.log('OK', require('@strands-agents/harness/package.json').version)"步骤 3:最小可跑示例(Python)
1# run_agent.py
2import asyncio
3from strands import Agent
4from strands.models.anthropic import AnthropicModel
5from strands_tools import file_read, file_write, shell, grep
6
7from strands_harness import Harness, HarnessConfig
8
9config = HarnessConfig(
10 model_provider="anthropic",
11 model_id="claude-sonnet-4-5",
12 max_context_tokens=180_000,
13 tool_output_max_tokens=1500,
14 compaction_threshold=0.85,
15 compaction_target=0.55,
16 recovery_on_overflow=True,
17 max_steps=120,
18 max_wall_clock_seconds=1800,
19 max_cost_usd=8.00,
20)
21
22harness = Harness(config=config)
23
24
25@harness.tool
26def count_python_lines(root: str) -> dict:
27 """统计目录下所有 .py 文件的总行数。"""
28 import pathlib
29
30 total = 0
31 files = 0
32 for p in pathlib.Path(root).rglob("*.py"):
33 if "node_modules" in p.parts or ".venv" in p.parts:
34 continue
35 try:
36 total += len(p.read_text(encoding="utf-8").splitlines())
37 files += 1
38 except Exception:
39 continue
40 return {"files": files, "lines": total}
41
42
43async def main() -> None:
44 agent = Agent(
45 model=AnthropicModel(model_id=config.model_id, max_tokens=8192),
46 tools=[file_read, file_write, shell, grep, count_python_lines],
47 system_prompt=(
48 "你是一个代码工程 Agent。\n"
49 "规则:\n"
50 "1. 先读再改,禁止凭猜测写文件\n"
51 "2. 修改前用 git diff 确认工作区干净\n"
52 "3. 每次修改后必须跑一次相关测试\n"
53 "4. 不确定时停止并报告,不要硬猜"
54 ),
55 )
56
57 result = await harness.run(
58 agent,
59 prompt="统计当前仓库 Python 代码行数,写进 LOC.md,并说明最大的 3 个文件",
60 workspace=".",
61 )
62
63 print("status :", result.status)
64 print("steps :", result.step_count)
65 print("tokens :", result.usage.total_tokens)
66 print("cost :", f"${result.usage.estimated_cost_usd:.4f}")
67 print("output :", result.final_output)
68
69
70if __name__ == "__main__":
71 asyncio.run(main())跑:
1export ANTHROPIC_API_KEY=sk-ant-...
2python run_agent.py步骤 4:最小可跑示例(TypeScript)
1// run-agent.ts
2import { Agent } from '@strands-agents/sdk';
3import { AnthropicModel } from '@strands-agents/sdk/models';
4import { Harness, HarnessConfig } from '@strands-agents/harness';
5
6const config: HarnessConfig = {
7 modelProvider: 'anthropic',
8 modelId: 'claude-sonnet-4-5',
9 maxContextTokens: 180_000,
10 toolOutputMaxTokens: 1500,
11 compactionThreshold: 0.85,
12 compactionTarget: 0.55,
13 recoveryOnOverflow: true,
14 maxSteps: 120,
15 maxWallClockSeconds: 1800,
16 maxCostUsd: 8.0,
17};
18
19const harness = new Harness(config);
20
21async function main() {
22 const agent = new Agent({
23 model: new AnthropicModel({ modelId: config.modelId, maxTokens: 8192 }),
24 systemPrompt: [
25 '你是一个代码工程 Agent。',
26 '规则:',
27 '1. 先读再改,禁止凭猜测写文件',
28 '2. 修改前确认工作区干净',
29 '3. 每次修改后必须跑一次相关测试',
30 '4. 不确定时停止并报告',
31 ].join('\n'),
32 });
33
34 const result = await harness.run(agent, {
35 prompt: '找出 src/ 下所有未使用的导出,列成清单写在 UNUSED.md',
36 workspace: '.',
37 });
38
39 console.log('status :', result.status);
40 console.log('steps :', result.stepCount);
41 console.log('tokens :', result.usage.totalTokens);
42 console.log('cost :', `$${result.usage.estimatedCostUsd.toFixed(4)}`);
43}
44
45main().catch((e) => {
46 console.error(e);
47 process.exit(1);
48});1npx tsx run-agent.ts2.3 两条路径怎么选
| 维度 | Docker 一键 | 源码手动 |
|---|---|---|
| 上手时间 | 15 分钟 | 40 分钟 |
| 改 Harness 行为 | 只能改配置 | 可以改源码、加 hook |
| 自定义工具 | 需打包镜像 | 直接写函数 |
| 生产部署 | 天然适配容器环境 | 需自己打包 |
| 排查问题 | 看日志 | 可打断点 |
| 推荐给 | 先验证收益 / 上 CI | 长期工程化 / 二次开发 |
实际建议:先用 Docker 跑一个真实任务,对比没上 Harness 时的 Token 消耗;确认收益后再切源码路径做深度定制。
三、验证配置自检:别急着跑大任务
3.1 内置自检命令
1# Python
2python -m strands_harness doctor --config config/harness.yaml
3
4# TypeScript
5npx @strands-agents/harness doctor --config config/harness.yaml期望输出:
1[✓] config parsed version=1
2[✓] model provider reachable anthropic / claude-sonnet-4-5 / 142ms
3[✓] workspace writable /workspace
4[✓] tool allowlist resolved 6 tools
5[✓] denylist applied .env* excluded
6[✓] telemetry sink http://localhost:4318
7[!] budget not set for per-tool default unlimited (建议设置 execution.per_tool_budget_usd)
8[✓] hooks loaded 0出现 [!] 不一定是错,但每一条都要读一遍。
3.2 手工验证 5 项关键行为
验证 1:工具输出截断真的生效
1# 造一个大文件
2python - <<'EOF'
3open('/workspace/big.log','w').write(("ERROR something happened\n"*20000))
4EOF
5
6# 让 Agent 去读
7curl -sS http://localhost:8080/v1/tasks -H 'Content-Type: application/json' \
8 -d '{"prompt":"读取 /workspace/big.log 并总结错误类型","workspace":"/workspace"}' | jq -r .task_id然后看 trace:
1grep '"event":"tool_output_truncated"' logs/traces/*.jsonl | tail -1 | jq应看到类似:
1{
2 "event": "tool_output_truncated",
3 "tool": "file_read",
4 "original_tokens": 61200,
5 "kept_tokens": 1480,
6 "artifact_path": "/var/log/strands/traces/artifacts/tsk_xxx/tool_017.txt"
7}没看到这条事件说明截断没生效,回去检查 context.tool_output_max_tokens 是否被上层配置覆盖了。
验证 2:上下文摘要在 85% 触发
1grep '"event":"context_compaction"' logs/traces/*.jsonl | jq -c '{at:.usage_ratio, before:.tokens_before, after:.tokens_after}'期望:at 接近 0.85,after / before 接近 compaction_target / compaction_threshold(即约 0.55/0.85 ≈ 0.65)。
验证 3:循环检测会拦住死循环
故意给一个会卡住的任务:
1curl -sS http://localhost:8080/v1/tasks -H 'Content-Type: application/json' \
2 -d '{"prompt":"反复读取 /workspace/big.log 直到找到不存在的字符串 ZZZ_NOT_EXIST","workspace":"/workspace"}' | jq -r .task_id期望任务在 6 步左右被终止,状态为 aborted,原因 loop_detected。如果它跑满了 120 步,说明 loop_detection 没开。
验证 4:预算硬闸会停
把 max_cost_usd 临时设成 0.05,跑任意一个任务,应看到:
1{"status":"aborted","reason":"budget_exceeded","spent_usd":0.0512}这一步很重要——没验证过的预算上限等于没有上限。
验证 5:密钥不会进 trace
1grep -rIl "$ANTHROPIC_API_KEY" logs/ 2>/dev/null | head必须为空。非空说明 trace 脱敏没开,立刻配 telemetry.redact_patterns。
3.3 一份可抄的自检清单
1[ ] doctor 全绿,[!] 项已逐条读过
2[ ] 工具输出截断事件可见
3[ ] 上下文摘要在 ~0.85 触发
4[ ] 循环检测能在 10 步内终止死循环
5[ ] 预算硬闸真的会 abort
6[ ] trace 里搜不到任何密钥
7[ ] .env / .git / node_modules 已被 denylist 排除
8[ ] 一次真实任务的 cost 已记录,作为后续对比基线四、场景化实战
4.1 场景一:跨 200 个文件的批量重构
任务:把仓库里所有 datetime.utcnow() 换成 datetime.now(timezone.utc)。
为什么适合 Harness:任务边界清晰、可机械验证、重复上下文多(每读一个文件都要带一遍系统提示和项目规范)。
1# batch_refactor.py
2import asyncio
3from strands import Agent
4from strands.models.anthropic import AnthropicModel
5from strands_tools import file_read, file_edit, shell, grep
6
7from strands_harness import Harness, HarnessConfig
8
9config = HarnessConfig(
10 model_provider="anthropic",
11 model_id="claude-sonnet-4-5",
12 max_context_tokens=180_000,
13 tool_output_max_tokens=1200, # 重构任务输出都短,压得更狠
14 compaction_threshold=0.85,
15 compaction_target=0.50,
16 max_steps=400, # 批量任务放宽步数
17 max_wall_clock_seconds=3600,
18 max_cost_usd=25.00,
19)
20
21harness = Harness(config=config)
22
23PROMPT = """把本仓库中所有 Python 代码里的 datetime.utcnow() 替换为 datetime.now(timezone.utc)。
24
25执行流程(严格按顺序):
261. 先跑 `git status --porcelain`,不干净就停止并报告
272. 用 grep 找出所有命中位置,输出文件清单
283. 逐个文件处理:
29 a. 读取文件
30 b. 确认该文件是否已 import timezone;没有则在 datetime 的 import 行补齐
31 c. 替换调用点
32 d. 保存
334. 每处理完 20 个文件,跑一次 `python -m pytest -q` 确认没破坏测试
345. 全部处理完后跑一次完整测试,把结果写进 REFACTOR_REPORT.md
35
36约束:
37- 不要修改测试文件里的断言语义
38- 不要动 .venv 与 node_modules
39- 遇到无法确定的写法,跳过并在报告里单列,不要硬改
40"""
41
42
43async def main():
44 agent = Agent(
45 model=AnthropicModel(model_id=config.model_id, max_tokens=8192),
46 tools=[file_read, file_edit, shell, grep],
47 system_prompt="你是资深 Python 工程师。先读后改,改动必须可测试验证。",
48 )
49 result = await harness.run(agent, prompt=PROMPT, workspace=".")
50 print(result.status, result.usage.total_tokens, result.usage.estimated_cost_usd)
51
52
53asyncio.run(main())关键点解释:
tool_output_max_tokens=1200:重构任务里grep输出往往是命中清单,压到 1200 足够,压狠点直接省一大截compaction_target=0.50:批量任务历史价值低,压缩目标可以比默认更激进- 每 20 个文件跑一次测试:这是把「验证」嵌进循环,比最后一次性跑更容易定位是哪一批改坏的
对照数据(同一任务,同一模型,同一仓库):
| 配置 | 步数 | 总 Token | 估算成本 | 结果 |
|---|---|---|---|---|
| 裸 SDK 手写循环 | 163 | 4,182,000 | $12.55 | 完成,中途 1 次上下文溢出重跑 |
| Strands Harness 默认 | 158 | 3,041,000 | $9.12 | 完成 |
| Harness + 激进压缩 | 149 | 2,466,000 | $7.40 | 完成 |
省下的主要是「每次读文件都重复带上的历史上下文」,以及那次溢出重跑。
4.2 场景二:长任务迁移(跨小时,断点续跑)
任务:把一个 12 万行的 Python 服务迁移到新框架,预计 6 小时以上。
长任务的核心风险不是慢,是中断即归零。Harness 的持久化会话 + 溢出恢复在这里是刚需。
1# long_migration.py
2import asyncio
3from strands import Agent
4from strands.models.anthropic import AnthropicModel
5from strands.session.file_session_manager import FileSessionManager
6from strands_tools import file_read, file_edit, shell, grep
7
8from strands_harness import Harness, HarnessConfig
9
10SESSION_DIR = ".strands/sessions/migration"
11
12config = HarnessConfig(
13 model_provider="anthropic",
14 model_id="claude-sonnet-4-5",
15 max_context_tokens=180_000,
16 compaction_threshold=0.82, # 长任务提前压缩,避免撞墙
17 compaction_target=0.50,
18 recovery_on_overflow=True, # 必须开
19 max_steps=2000,
20 max_wall_clock_seconds=6 * 3600,
21 max_cost_usd=120.00,
22 checkpoint_every_n_steps=25, # 每 25 步落一个检查点
23)
24
25session_manager = FileSessionManager(session_dir=SESSION_DIR)
26harness = Harness(config=config, session_manager=session_manager)
27
28PHASES = [
29 "阶段 1:盘点现状。列出所有入口文件、依赖版本、当前框架的耦合点,写入 MIGRATION_01_inventory.md",
30 "阶段 2:制定迁移顺序。输出依赖拓扑,按拓扑序给出分批计划,写入 MIGRATION_02_plan.md",
31 "阶段 3:执行第一批迁移(底层工具模块)。每改完一个模块跑一次对应单测。",
32 "阶段 4:执行第二批迁移(业务服务层)。",
33 "阶段 5:执行第三批迁移(API 入口层),跑通端到端冒烟。",
34 "阶段 6:清理旧框架残留依赖,输出 MIGRATION_06_report.md",
35]
36
37
38async def main():
39 agent = Agent(
40 model=AnthropicModel(model_id=config.model_id, max_tokens=8192),
41 tools=[file_read, file_edit, shell, grep],
42 system_prompt=(
43 "你负责一次长期迁移工程。\n"
44 "铁律:\n"
45 "1. 每个阶段开始前先读上一阶段的产出文档\n"
46 "2. 每阶段结束必须写一份 markdown 产出文件\n"
47 "3. 任何破坏性操作前先 git commit 一个检查点\n"
48 "4. 连续 3 次工具失败就停下来报告,不要继续瞎试"
49 ),
50 session_manager=session_manager,
51 )
52
53 for i, phase in enumerate(PHASES, 1):
54 print(f"===== {phase[:12]} ... =====")
55 result = await harness.run(agent, prompt=phase, workspace=".", resume=True)
56 print(f" status={result.status} steps={result.step_count} "
57 f"tokens={result.usage.total_tokens} cost=${result.usage.estimated_cost_usd:.2f}")
58 if result.status != "completed":
59 print(" 中断,检查点已保存,修复后重跑本脚本即可续跑")
60 break
61
62
63asyncio.run(main())三个必须做对的点:
- 分阶段,而不是一个大 Prompt。一个大 Prompt 跑到第 5 小时崩了,你不知道该从哪续。分阶段后每个阶段有产出文件,下一个阶段读文件就能接上。
resume=True+ 持久化会话。中断后重跑脚本,Harness 从最近检查点续跑,而不是从头。- 阶段性 git commit。这是最后一道保险——Harness 的恢复机制救不了「代码被改成了一坨但逻辑上完成了」的情况。
4.3 场景三:接进 CI,让 PR 自动生成修复建议
任务:CI 里跑 Harness,对失败的测试自动生成补丁(不自动合并)。
1# .github/workflows/agent-fix.yml
2name: agent-suggest-fix
3on:
4 workflow_run:
5 workflows: ["ci"]
6 types: [completed]
7
8jobs:
9 suggest:
10 if: github.event.workflow_run.conclusion == 'failure'
11 runs-on: ubuntu-latest
12 permissions:
13 contents: read
14 pull-requests: write
15 steps:
16 - uses: actions/checkout@v4
17
18 - name: Run Strands Harness
19 env:
20 ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
21 GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
22 run: |
23 pip install strands-agents strands-harness strands-agents-tools
24 python -m strands_harness ci \
25 --config .strands/ci.yaml \
26 --failure-log ci-output.txt \
27 --max-cost-usd 3.00 \
28 --out suggested-patch.diff
29
30 - name: Comment patch on PR
31 run: |
32 gh pr comment ${{ github.event.pull_request.number }} \
33 --body-file suggested-patch.diffCI 专用配置 .strands/ci.yaml:
1version: 1
2model:
3 provider: anthropic
4 model_id: claude-sonnet-4-5
5 max_tokens: 4096
6
7execution:
8 max_steps: 40
9 max_wall_clock_seconds: 600
10 max_cost_usd: 3.00
11 mode: read-only-until-approved # 关键:不允许直接写文件,只产出 diff
12
13tools:
14 allow: [file_read, grep, shell]
15 shell_allowlist: # CI 里必须白名单化 shell
16 - "pytest"
17 - "npm test"
18 - "git diff"
19 - "git log"
20 denylist_paths: [".git/**", "**/.env*"]
21
22output:
23 format: unified-diff
24 forbid_paths: [".github/**", "ci/", "**/*.lock"]CI 场景的三条红线:
mode: read-only-until-approved:Agent 只能产出 diff,不能直接改仓库。自动合并 Agent 补丁是 2026 年最大的供应链风险来源之一。shell_allowlist:CI 里绝不能给 Agent 无限制 shell。只放行测试与只读 git 命令。forbid_paths:CI 配置、锁文件、工作流文件一律禁止 Agent 碰。改了这些等于让 Agent 修改自己的运行环境。
五、高级配置:把省 Token 的三个机制调明白
5.1 机制一:工具输出截断(约 1500 Token)
原理:工具返回值超过阈值时,Harness 不把它塞进上下文,而是:
- 完整内容落盘到 artifact 目录
- 上下文里只保留头部 N 个 Token + 文件路径 + 行数统计
- Agent 需要更多时,用
artifact_read工具按行区间再取
配置:
1context:
2 tool_output_max_tokens: 1500
3 artifact:
4 dir: .strands/artifacts
5 keep_head_tokens: 600
6 keep_tail_tokens: 200
7 ttl_hours: 72怎么调:
| 任务类型 | 建议值 | 理由 |
|---|---|---|
| 代码重构 / 批量改名 | 800–1200 | 命中和片段都短,压狠点 |
| 日志分析 / 报错排查 | 4000–6000 | 需要看到上下文邻近行,压太狠会丢关键信息 |
| 文档生成 / 长文写作 | 2000–3000 | 中等 |
| 数据分析(CSV/JSON) | 1000 + schema 提示 | 不要直接塞数据,让 Agent 写代码去统计 |
最容易踩的坑:日志排查场景用了默认的 1500,Agent 只看得到错误行的头尾,中间的关键堆栈被截掉,于是它开始瞎猜。症状是 Agent 反复读同一个日志文件。解法:把日志类工具单独配一个大阈值,而不是全局调大。
1context:
2 tool_output_max_tokens: 1500
3 per_tool_overrides:
4 log_read: 6000
5 stack_trace: 80005.2 机制二:上下文自动摘要(85% 触发)
原理:上下文占用达到 compaction_threshold 时,触发一次结构化压缩:
- 保留:系统提示、任务目标、最近 N 轮完整对话
- 压缩:中间历史 → 结构化摘要(已完成步骤 / 关键发现 / 待办 / 已试错的死路)
- 保留:工具产物的文件引用(不丢内容,只是移出上下文)
为什么结构化摘要比「丢最早的轮次」强:丢轮次会丢失「我们试过 A,失败原因是 B」这类负面知识,Agent 会重蹈覆辙。结构化摘要会显式保留死路清单。
1context:
2 compaction_threshold: 0.85
3 compaction_target: 0.55
4 compaction:
5 keep_last_turns: 4
6 summary_model: claude-haiku-4-5 # 用便宜模型做摘要
7 summary_max_tokens: 1500
8 preserve_sections:
9 - system_prompt
10 - task_objective
11 - dead_ends
12 - file_artifacts用便宜模型做摘要是这里最大的杠杆:摘要任务本身不需要强模型,换成 Haiku 级别能把压缩开销降一个数量级。
5.3 机制三:溢出恢复(recovery_on_overflow)
原理:触发 context_length_exceeded 时:
- 捕获错误(不崩溃)
- 强制触发一次压缩到
compaction_target - 如果仍放不下,把当前步的超大工具输出替换为 artifact 引用重试
- 重试成功则续跑,失败 N 次后才判定任务失败
1context:
2 recovery_on_overflow: true
3 recovery:
4 max_attempts: 3
5 strategy: compact-then-evict-then-retry
6 on_failure: checkpoint-and-report关掉它会发生什么:一次溢出 = 整轮重跑 = 前面的钱全白花。长任务必开。
5.4 预算与熔断(防止半夜烧穿信用卡)
1execution:
2 max_steps: 120
3 max_wall_clock_seconds: 1800
4 max_cost_usd: 8.00
5 per_tool_budget_usd: 2.00
6 max_consecutive_tool_failures: 5
7 loop_detection:
8 enabled: true
9 window: 6
10 similarity_threshold: 0.92
11 on_budget_exceeded: checkpoint-and-abort四项预算要一起设:
| 预算 | 防的是什么 |
|---|---|
max_steps | 步数失控的推理循环 |
max_wall_clock_seconds | 卡在某个慢工具上不动 |
max_cost_usd | 总体烧穿 |
per_tool_budget_usd | 单个工具(通常是 shell 或 http)被反复调用 |
只设总预算不设单工具预算,常见后果是:Agent 在某个失败的网络请求上重试 200 次,把 8 美元全花在同一个错误上,然后整体 abort,你既没结果也没钱。
5.5 可观测性:知道钱花在哪
1telemetry:
2 otel_endpoint: http://localhost:4318
3 trace_dir: .strands/traces
4 jsonl: true
5 redact_patterns:
6 - "sk-ant-[A-Za-z0-9_-]+"
7 - "sk-[A-Za-z0-9]{20,}"
8 - "AKIA[0-9A-Z]{16}"
9 - "ghp_[A-Za-z0-9]{36}"
10 metrics:
11 - tokens_by_tool
12 - cost_by_step
13 - compaction_events
14 - loop_detections一次任务跑完,看按工具的 Token 分布:
1python -m strands_harness report --trace .strands/traces/tsk_xxx.jsonl --by tool1tool calls tokens % cost
2file_read 47 612,400 38.2% $1.84
3shell 31 401,200 25.0% $1.20
4file_edit 12 186,000 11.6% $0.56
5grep 23 142,800 8.9% $0.43
6context_compaction 4 98,000 6.1% $0.29
7------------------------------------------------------
8TOTAL 117 1,603,700 100.0% $4.81看到这张表你就知道该优化什么了。如果 file_read 占 38%,说明该让 Agent 用 grep 先定位再读;如果 context_compaction 占 6% 以上,说明压缩太频繁,该调大 compaction_threshold 或减少每步的上下文携带。
5.6 自定义 Hook:在关键节点插手
1from strands_harness import Harness, HookContext
2
3
4@harness.hook("before_tool_call")
5async def guard_shell(ctx: HookContext):
6 """禁止 Agent 在工作区外执行破坏性命令。"""
7 if ctx.tool_name != "shell":
8 return ctx.proceed()
9
10 cmd = ctx.tool_input.get("command", "")
11 banned = ["rm -rf /", "git push --force", "curl | sh", "chmod 777"]
12 for b in banned:
13 if b in cmd:
14 return ctx.block(reason=f"命中禁令: {b}")
15
16 if cmd.strip().startswith("rm ") and ".." in cmd:
17 return ctx.block(reason="禁止在工作区外删除文件")
18
19 return ctx.proceed()
20
21
22@harness.hook("after_step")
23async def cost_guard(ctx: HookContext):
24 if ctx.cumulative_cost_usd > 5.0:
25 ctx.log.warning("成本已过 5 美元,当前步骤 %s", ctx.step_index)
26 return ctx.proceed()Hook 是关键节点上唯一能「硬拦」的地方。**Prompt 里写「不要执行危险命令」是软约束,会被模型偶尔忽略;Hook 是代码级硬约束,绕不过去。**危险操作一律走 Hook,不要只写在提示词里。
5.7 会话持久化与并发
单任务跑通之后,下一步一定是「一次跑很多个」。这里有两件事必须先搞对。
会话存储后端
1from strands.session.file_session_manager import FileSessionManager
2from strands.session.s3_session_manager import S3SessionManager
3
4# 本地:适合单机、调试、CI
5local = FileSessionManager(session_dir=".strands/sessions")
6
7# S3 / 兼容 S3 的对象存储:适合多机、团队共享、需要长期留存
8remote = S3SessionManager(
9 session_id="batch-2026-09-22",
10 bucket="my-strands-sessions",
11 prefix="tasks/",
12)1session:
2 backend: s3 # file | s3 | redis | postgres
3 bucket: my-strands-sessions
4 prefix: tasks/
5 ttl_days: 30
6 checkpoint:
7 every_n_steps: 25
8 include: [messages, artifacts_index, tool_state, cost_so_far]checkpoint.include 里不要放完整工具输出。那些已经在 artifact 里了,重复存一份会让会话体积涨十倍。只存索引。
并发:每个任务独立 workspace
1import asyncio
2from pathlib import Path
3
4async def run_batch(tasks: list[str], concurrency: int = 4):
5 sem = asyncio.Semaphore(concurrency)
6
7 async def one(idx: int, prompt: str):
8 async with sem:
9 ws = Path(f".strands/workspaces/t{idx:03d}")
10 ws.mkdir(parents=True, exist_ok=True)
11 agent = Agent(
12 model=AnthropicModel(model_id="claude-sonnet-4-5", max_tokens=8192),
13 tools=[file_read, file_edit, shell, grep],
14 )
15 return await harness.run(agent, prompt=prompt, workspace=str(ws))
16
17 return await asyncio.gather(*[one(i, p) for i, p in enumerate(tasks)])
18
19
20results = asyncio.run(run_batch([
21 "任务 A 描述",
22 "任务 B 描述",
23 "任务 C 描述",
24], concurrency=4))并发时最容易犯的错:多个任务共用一个 workspace 目录,互相覆盖对方的中间文件。症状是「单跑没问题,并发就乱」——每个任务一个独立目录,这条没有例外。
并发度怎么定:
| 约束 | 建议并发度 |
|---|---|
| 本地单容器,内存 < 4G | 2–3 |
| 本地,内存 8G+ | 4–6 |
| 云端容器,每容器 2 CPU | 每容器 2 |
| 有 API 速率限制 | 按限流配额 ÷ 单任务平均 RPM |
别只看本机资源。并发上去之后先撞的通常是模型 API 的速率限制。先跑小批量测出实际 RPM,再定并发度。
5.8 结构化输出:让 Agent 的输出可被程序消费
Agent 的输出如果只是给人看的,你永远没法自动化。Harness 支持 grammar 约束的结构化输出:
1from pydantic import BaseModel, Field
2from typing import Literal
3
4
5class RefactorPlan(BaseModel):
6 """重构计划的结构化输出。"""
7 summary: str = Field(description="一句话说明这次重构要做什么")
8 affected_files: list[str] = Field(description="将被改动的文件路径清单")
9 risk_level: Literal["low", "medium", "high"]
10 risky_points: list[str] = Field(description="具体风险点,每条给文件路径与原因")
11 verification_commands: list[str] = Field(description="验证重构是否成功的命令")
12 blockers: list[str] = Field(default_factory=list, description="需要人类确认的问题")
13
14
15result = await harness.run(
16 agent,
17 prompt="分析这个仓库的重构需求,输出重构计划",
18 workspace=".",
19 output_schema=RefactorPlan,
20)
21
22plan = result.structured_output # 已校验过的 Pydantic 对象
23if plan.blockers:
24 print("需要人工确认:", plan.blockers)
25else:
26 print("风险等级:", plan.risk_level)
27 for cmd in plan.verification_commands:
28 print(" -", cmd)为什么值得做:
| 对比项 | 自由文本输出 | 结构化输出 |
|---|---|---|
| 下游能否自动消费 | 需要再解析一次(脆弱) | 直接用 |
| 字段缺失 | 静默发生,运行到一半才炸 | 校验阶段就报错 |
| 幻觉字段 | 无法检测 | schema 不认识的字段直接拒 |
| 与 CI 集成 | 难 | 天然适配 |
代价:结构化输出会略微降低模型的表达自由度,且首次生成可能多消耗一些 token。判断标准很简单——输出要给程序用就结构化,给人看就自由文本。
5.9 工具集组织:分层与渐进披露
工具不是越多越好。工具定义本身占上下文,20 个工具的详细 schema 轻松吃掉 8–10K token,且每次请求都要带上。
分层工具集
1tools:
2 # 核心层:始终加载
3 core:
4 - file_read
5 - file_write
6 - file_edit
7 - grep
8 - shell
9
10 # 扩展层:按需加载(Agent 显式请求时才注入 schema)
11 on_demand:
12 - http_request
13 - database_query
14 - docker_exec
15 - git_operations
16 - mcp_bridge
17
18 # 危险层:需要人类批准才能用
19 gated:
20 - npm_publish
21 - git_push
22 - deploy渐进披露的效果:核心 5 个工具约 2.5K token,全部 15 个约 9K token。分层的版本在长任务里能省下可观的固定开销。
gated 工具与批准流
1@harness.hook("before_tool_call")
2async def require_approval(ctx: HookContext):
3 if ctx.tool_name in ("npm_publish", "git_push", "deploy"):
4 approved = await ctx.request_human_approval(
5 reason=f"Agent 请求执行高风险操作: {ctx.tool_name}",
6 detail=ctx.tool_input,
7 timeout_seconds=300,
8 )
9 return ctx.proceed() if approved else ctx.block(reason="人类拒绝")
10 return ctx.proceed()危险工具一定要进 gated 层并用 Hook 拦。把它们和普通工具混在一起,等于把发布权限交给了模型的判断力。
5.10 与 MCP 集成
Harness 可以直接消费 MCP 服务器提供的工具:
1from strands.tools.mcp import MCPClient
2from mcp import StdioServerParameters
3
4beacon = MCPClient(
5 StdioServerParameters(
6 command="npx",
7 args=["-y", "@asymptote-labs/beacon-mcp"],
8 env={"BEACON_HOME": "/Users/you/.beacon"},
9 )
10)
11
12with beacon:
13 mcp_tools = beacon.list_tools_sync()
14 agent = Agent(
15 model=AnthropicModel(model_id="claude-sonnet-4-5", max_tokens=8192),
16 tools=[file_read, file_edit, shell, grep, *mcp_tools],
17 )
18 result = await harness.run(agent, prompt="...", workspace=".")YAML 配置方式:
1mcp:
2 servers:
3 beacon:
4 transport: stdio
5 command: npx
6 args: ["-y", "@asymptote-labs/beacon-mcp"]
7 env:
8 BEACON_HOME: /Users/you/.beacon
9 tool_filter:
10 include: ["beacon_search_memory", "beacon_dead_ends"]
11 context7:
12 transport: stdio
13 command: npx
14 args: ["-y", "@upstash/context7-mcp"]
15 limits:
16 max_servers: 4
17 max_tools_total: 20
18 call_timeout_seconds: 30tool_filter.include 很重要。一个 MCP 服务器可能暴露十几个工具,你通常只需要其中两三个。全量加载会白吃上下文。
max_tools_total: 20 建议别超过。超过之后工具选择准确率会明显下降——模型开始选错工具,反而更慢更贵。
5.11 成本基准:三类任务的实测对比
同一模型、同一仓库,裸 SDK 与 Harness 的对照(每类任务跑 3 次取平均):
| 任务类型 | 方案 | 步数 | 总 Token | 成本 | 结果 |
|---|---|---|---|---|---|
| 批量重构(200 文件) | 裸 SDK | 163 | 4,182,000 | $12.55 | 完成(1 次溢出重跑) |
| Harness 默认 | 158 | 3,041,000 | $9.12 | 完成 | |
| Harness + 激进压缩 | 149 | 2,466,000 | $7.40 | 完成 | |
| 日志排查(需长输出) | 裸 SDK | 22 | 890,000 | $2.67 | 完成 |
| Harness 默认 1500 | 31 | 1,142,000 | $3.43 | 反复重读,变差 | |
| Harness + 日志工具 6000 | 19 | 764,000 | $2.29 | 完成,更优 | |
| 短任务(单文件改动) | 裸 SDK | 6 | 48,000 | $0.14 | 完成 |
| Harness 默认 | 6 | 52,000 | $0.16 | 完成(略贵) |
从这张表能读出三条结论:
- 重复上下文多的任务收益最大(批量重构 -41%)
- 长输出任务必须调
per_tool_overrides,用默认值反而更贵更差 - 短任务别上 Harness,多出来的摘要与 trace 开销超过收益
这就是「28% 平均降幅」的真相:它是按任务分布加权算出来的。你的任务分布不同,实际收益可能是 -15%(变差)也可能是 -45%。先测自己的任务,再决定要不要上。
5.12 提示词分层与上下文预算
Harness 省的是「执行层」的 Token,但上下文里装什么,仍然是你的责任。把提示词拆成三层来管理:
1┌─ L1 系统提示(常驻,每轮都带)───────────┐ 目标 < 800 token
2│ 角色、铁律、输出格式、全局禁区 │
3└────────────────────────────────────────┘
4┌─ L2 项目规范(常驻,来自 AGENTS.md)──────┐ 目标 < 1500 token
5│ 技术栈、编码规范、命令清单、关键禁区 │
6└────────────────────────────────────────┘
7┌─ L3 任务上下文(按需,动态注入)──────────┐ 按需,用完即弃
8│ 当前任务目标、相关文件内容、工具结果 │
9└────────────────────────────────────────┘L1 系统提示的写法
反例(太长、太虚):
1你是一个经验丰富、专业、严谨、乐于助人的高级软件工程师。
2你应该写出高质量、可维护、高性能、安全的代码。
3你应该遵循最佳实践,注意代码质量,考虑边界情况,
4遵循设计模式,写好注释,写出优雅的代码...这段话约 120 token,提供的可执行信息接近 0。每轮都带上,跑 100 步就是白白消耗 12K token。
正例(短、可执行):
1你是代码工程 Agent。
2
3规则:
41. 先读后改,禁止凭猜测写文件
52. 修改前确认工作区干净(git status --porcelain 为空)
63. 每次修改后跑相关测试
74. 不确定时停止并标注 [BLOCKER],不要硬猜
8
9禁止:
10- 不修改 .github/ 与锁文件
11- 不执行 rm -rf / git push --force
12- 不引入新依赖
13
14输出:每步先说要做什么,再做,最后贴验证输出。约 130 token,但每一条都是可判定的。
L2 项目规范的边界
项目规范文件(AGENTS.md)每次会话都会被注入。实测一个 900 行的规范文件,每轮多花约 12K token——在 100 步的任务里是 1.2M token。
控制原则:
| 内容类型 | 放规范文件 | 放 docs/ 用链接指过去 |
|---|---|---|
| 技术栈与版本 | ✅ | |
| 构建/测试命令 | ✅ | |
| 编码风格要点 | ✅(≤ 20 条) | |
| 关键禁区(带原因) | ✅ | |
| 详细架构说明 | ✅ | |
| 完整 API 文档 | ✅ | |
| 历史决策记录 | ✅ |
「不确定的信息用链接,确定的规则用文字」。 Agent 需要时自己去读,不需要时不占上下文。
L3 上下文预算的分配
1context:
2 budget:
3 system_prompt: 800
4 project_spec: 1500
5 task_context: 20000
6 tool_schema: 4000
7 history: dynamic给每层设上限,超了就报警而不是静默吞掉。很多「Agent 后半段变笨」的现象,本质是上下文被某一层悄悄吃满了。
5.13 五个常见反模式
| 反模式 | 表现 | 后果 | 正解 |
|---|---|---|---|
| 系统提示写成作文 | 300 行价值观描述 | 每轮白烧 token,且无可执行信息 | 压到 20 条以内,每条可判定 |
| 工具全量加载 | 20 个工具 schema 常驻 | 固定开销 9K token/轮 | 分层加载 + tool_filter |
| 日志类工具用默认截断 | 堆栈被截掉 | Agent 反复重读同一文件 | per_tool_overrides 调大 |
| 只设总预算 | 单个失败工具重试 200 次 | 钱花在同一个错误上 | 四项预算一起设 |
| trace 不脱敏 | 密钥进了 JSONL | 合规事故 | redact_patterns + 定期自查 |
六、部署:本地与五朵云
6.1 本地 / 自有服务器(国内推荐)
1runtime:
2 type: local
3 workspace_root: /srv/workspaces
4 sandbox:
5 enabled: true
6 backend: container # container | firejail | none
7 image: strands/sandbox:py312
8 network: none # 默认断网,需要联网的工具显式开白名单
9 memory_limit: 2g
10 cpu_limit: 2network: none 是默认正确的选择。Agent 的绝大多数编码任务不需要联网,断网能一次性掐掉数据外传这条路径。需要拉依赖时,用白名单放行特定域名:
1sandbox:
2 network: allowlist
3 network_allowlist:
4 - pypi.org
5 - registry.npmjs.org6.2 AWS
1runtime:
2 type: aws
3 target: lambda # lambda | fargate | bedrock-agentcore
4 region: us-west-2
5 lambda:
6 memory_mb: 2048
7 timeout_seconds: 900
8 ephemeral_storage_mb: 2048
9 session:
10 backend: s3
11 bucket: my-strands-sessions
12 secrets:
13 provider: secrets-manager
14 key_arn: arn:aws:secretsmanager:...Fargate 更适合长任务(Lambda 有 15 分钟硬上限):
1runtime:
2 type: aws
3 target: fargate
4 fargate:
5 cpu: 2048
6 memory: 4096
7 task_timeout_hours: 66.3 GCP Cloud Run
1runtime:
2 type: gcp
3 target: cloud-run
4 region: asia-east1
5 cloud_run:
6 cpu: 2
7 memory: 4Gi
8 timeout_seconds: 3600
9 min_instances: 0
10 model:
11 provider: vertex
12 model_id: gemini-3-pro6.4 Azure Container Apps
1runtime:
2 type: azure
3 target: container-apps
4 container_apps:
5 cpu: 2.0
6 memory: 4.0Gi
7 min_replicas: 0
8 max_replicas: 10
9 model:
10 provider: azure-openai
11 deployment: gpt-5-mini6.5 Cloudflare Workers
1runtime:
2 type: cloudflare
3 target: workers
4 workers:
5 compatibility_date: "2026-09-01"
6 limits:
7 cpu_ms: 30000 # Workers 有 CPU 时间上限
8 model:
9 provider: workers-ai
10 model_id: "@cf/meta/llama-4"注意:Workers 有 CPU 时间与内存硬限制,只适合短任务(步数 < 30)。别拿长迁移任务往 Workers 上放。
6.6 Modal
1runtime:
2 type: modal
3 modal:
4 gpu: null # 自托管模型时用 "A10G"
5 memory_mb: 8192
6 timeout_seconds: 7200
7 volumes:
8 - /workspaceModal 适合需要 GPU 跑自托管模型、或任务时长不可控的场景。
6.7 国内模型接入(openai-compatible)
1model:
2 provider: openai-compatible
3 base_url: https://your-endpoint.example.com/v1
4 api_key_env: MODEL_API_KEY
5 model_id: your-model-name
6 max_tokens: 8192
7 pricing: # 手动填单价,否则成本估算全是 0
8 input_per_mtok: 2.0
9 output_per_mtok: 6.0pricing 必须填。不填的话 Harness 的成本统计和预算熔断全部失效——它会认为一切免费,然后一路跑到 max_steps。
七、与同类方案的选型对比
| 方案 | 形态 | 优势 | 劣势 | 适合 |
|---|---|---|---|---|
| Strands Harness | 可嵌入库(Py/TS) | 双语言、多云、上下文策略透明可调、Apache 2.0 | 需要自己写代码集成 | 自建 Agent 服务 / CI / 批量任务 |
| Claude Code CLI | 终端交互式 | 开箱即用、体验成熟 | 难以程序化嵌入、不便于批量 | 日常交互式编码 |
| Codex CLI | 终端交互式 | 与 OpenAI 生态紧耦合 | 同上 | 同上 |
| OpenAI Agents API | 托管 API | 免运维、有沙箱 | 控制权与数据在厂商侧 | 不想自建基础设施的团队 |
| LangGraph | 图编排库 | 状态机表达力强 | 学习曲线陡、上下文策略要自己写 | 复杂确定性流程 |
| 自写 while 循环 | — | 完全可控 | 五个坑全要自己填一遍 | 学习用途 |
选型一句话:要「Agent 跑在自己的基础设施上、且能精细控制成本」,选 Harness;要「人在终端前写代码」,选 CLI;要「一点都不想运维」,选托管 API。
八、故障排查表
| # | 症状 | 根因 | 排查命令 | 解法 |
|---|---|---|---|---|
| 1 | context_length_exceeded 直接失败 | recovery_on_overflow 未开 | grep context_overflow traces/*.jsonl | 设为 true,配 recovery.max_attempts: 3 |
| 2 | Agent 反复读同一个文件 | 工具输出被截断,看不到想要的内容 | grep tool_output_truncated traces/*.jsonl | tail | 对日志类工具配 per_tool_overrides 调大阈值 |
| 3 | 任务跑满 max_steps 却没结果 | 步数上限过小或陷入隐含循环 | grep loop_detected traces/*.jsonl | 开 loop_detection;或拆分任务 |
| 4 | 成本估算永远显示 $0.00 | 自定义 provider 未填 pricing | 检查配置里 model.pricing | 填 input_per_mtok / output_per_mtok |
| 5 | 预算没生效,跑超了 | 只设了 max_steps,没设 max_cost_usd | 看配置 execution 段 | 四项预算一起设,见 5.4 |
| 6 | Docker 起不来,端口占用 | 8080 被占 | ss -lntp | grep 8080 | -p 8081:8080 换端口 |
| 7 | model provider unreachable | Key 没传进容器 | docker exec strands env | grep API_KEY | -e ANTHROPIC_API_KEY=... 显式传 |
| 8 | trace 里出现明文密钥 | 脱敏未配 | grep -rIl "$KEY" logs/ | 配 telemetry.redact_patterns |
| 9 | Agent 改了 .env 文件 | denylist 没排 .env | 检查 tools.denylist_paths | 加 **/.env* |
| 10 | 摘要后 Agent 忘了关键约定 | 摘要把系统提示压掉了 | 检查 compaction.preserve_sections | 把 system_prompt 加入保留段 |
| 11 | 压缩极其频繁(每 3 步一次) | compaction_threshold 太低或单步上下文太大 | grep context_compaction traces/*.jsonl | wc -l | 阈值调到 0.88;减少工具返回体积 |
| 12 | 摘要本身很贵 | 用了旗舰模型做摘要 | 看 compaction.summary_model | 换成 Haiku / Flash 级模型 |
| 13 | 沙箱里装不了依赖 | network: none | 检查 sandbox.network | 改 allowlist 并加域名 |
| 14 | CI 里 Agent 改了工作流文件 | 没配 output.forbid_paths | 看 .strands/ci.yaml | 加 .github/** |
| 15 | 长任务中断后从头跑 | 没开 resume 或没配 session_manager | 检查 .strands/sessions/ | FileSessionManager + resume=True |
| 16 | Terminal-Bench 类任务得分低 | shell 工具被过度限制 | 看 tools.shell_allowlist | 放通必要命令,但保持白名单制 |
| 17 | Python 与 TS 行为不一致 | 两版 SDK 版本号不同步 | pip show strands-harness / npm ls | 锁同一 major 版本 |
| 18 | 并发跑多任务时互相干扰 | 共用同一 workspace | 检查 workspace_root | 每任务独立 workspace 目录 |
| 19 | artifact 目录撑爆磁盘 | ttl_hours 未设或过大 | du -sh .strands/artifacts | 设 72 小时 TTL 并加定时清理 |
| 20 | 并发跑时结果互相覆盖 | 多任务共用 workspace | 检查 workspace 路径 | 每任务独立目录,见 5.7 |
| 21 | 结构化输出校验失败率高 | schema 字段描述太模糊 | 看 result.validation_errors | 给每个 Field 补 description;减少嵌套层级 |
| 22 | MCP 工具选错 | 工具总数超过 20 | beacon_stats / 看工具 schema 数 | 用 tool_filter.include 裁剪到 15 个以内 |
| 23 | 长任务后半段明显变笨 | 上下文被某一层吃满 | 看 context.budget 各层占用 | 给每层设上限并告警,见 5.12 |
| 24 | 危险命令真的被执行了 | 只在 Prompt 里写了禁令 | grep '"blocked"' traces/*.jsonl | 用 Hook 硬拦,见 5.6 |
九、一条最重要的实践原则
Strands Harness 把执行层的脏活标准化了,这是好事。但它不会替你定义「什么叫做完了」。
跑任何任务前,先把这四件事写成机器可验证的东西:
- 验收标准:跑什么命令、通过什么阈值算完成
- 禁区:哪些文件、哪些命令绝对不能碰(走 Hook 硬拦,不只写进 Prompt)
- 预算:步数、时间、金额三个上限
- 中断点:多久落一次检查点、中断后从哪续
有了这四件事,Harness 才能安全地跑无人值守任务。没有这四件事,它只会更快地把事情做错。
十、下一步
- 把项目规范写进
AGENTS.md,让 Harness 与 CLI 工具共享同一套约束(见本站AGENTS.md 统一配置实战) - 接 MCP 工具扩展能力(见本站 MCP 系列)
- 用 Beacon 之类的跨 Harness 记忆层,把这次任务里学到的经验沉淀给下一次(见本站
Beacon MCP)
常见问题(正文补充)
Q:默认值够用吗?
够用,但三处建议立刻改:denylist_paths 加 .env*;telemetry.redact_patterns 配上;max_cost_usd 设一个你真的能接受的数字。
Q:能不能和 Claude Code 共存?
能,而且推荐共存。日常交互用 CLI,批量与自动化用 Harness,两者共享 AGENTS.md 与工具定义。
Q:28% 是稳定收益吗? 取决于任务形态。重复上下文多、工具输出大的任务收益明显(实测 25%–35%);单轮短任务收益接近 0,甚至因为多了摘要步骤而略微变贵。别指望它治所有病。
常见问题
QStrands Harness 和 Strands Agents SDK 到底什么关系?
SDK 负责「一个 Agent 怎么思考和调工具」,Harness 负责「一次任务怎么跑完」。SDK 里你写 Agent(model=..., tools=[...]) 然后 agent('做件事'),剩下的循环、错误重试、上下文管理归 Harness。换句话说:SDK 是引擎,Harness 是车身加底盘。只用 SDK 的话,循环、截断、摘要、恢复、并发、可观测性全部要你自己写;上了 Harness 之后这些是默认行为,只需要在配置里调参数。本文第 2 节给了两者的职责对照表。
Q那 28% 的 Token 节省到底来自哪里?会不会是牺牲质量换的?
主要来自三个确定性机制,不是靠「让模型少说话」:工具输出超过约 1500 Token 就截断并落盘为引用;上下文占用到 85% 触发结构化摘要而非暴力裁剪;上下文溢出后走恢复流程而不是整轮重跑。官方口径是「准确率相近时」的成本下降,Terminal-Bench 2.1 上得分还高 7.9 点——说明截断策略反而降低了噪音干扰。但要注意:这三个机制默认开启,如果你的任务依赖超长工具输出(比如完整日志分析),需要手动调大截断阈值,见第 5 节。
Q我已经在用 Claude Code / Codex CLI 了,还有必要引入 Strands Harness 吗?
分场景。交互式日常编码,现成的 CLI 体验更好,别换。但如果你要做的是「把 Agent 跑在自己的基础设施上」——批量任务、CI 流水线、需要审计日志、需要自托管模型、需要跨云部署——Harness 这类可嵌入的框架才是正确选择,因为 CLI 工具的设计前提是「人在终端前」。一个实用的折中:日常用 CLI,批量和自动化部分用 Harness,两者共享同一套工具定义与项目规范文件(AGENTS.md)。
Q它能在国内网络环境跑吗?会不会强绑 AWS?
不强绑。Harness 本身是 Apache 2.0 的开源库,本地运行不依赖任何 AWS 服务;模型层通过 provider 抽象接入,Bedrock、Anthropic、OpenAI、Ollama、LiteLLM、以及兼容 OpenAI 协议的国内模型都能配。云端部署适配器里确实有 AWS Lambda / Fargate / Bedrock AgentCore,但同时也有 GCP Cloud Run、Azure Container Apps、Cloudflare Workers、Modal。国内使用建议本地或自有服务器部署,模型走兼容 OpenAI 协议的国内 endpoint,见第 6 节的配置片段。