Browse by type
Local-First · 轻量级 · 功能完备 · 生产可用的通用 Agent 框架


大多数 Agent 框架要么过于臃肿(满屏抽象层、数百个依赖),要么过于简陋(仅能跑个 demo)。harness9 走中间路线:
| 原则 | 说明 |
|---|---|
| Local-First | 数据全部存储在本机(SQLite、tool_results、plans),工具在本地 Docker 容器内执行,无云端依赖,代码不离机 |
| 简洁 | 最小化抽象层,代码直白易读,极少的直接依赖 |
| 完备 | 覆盖 Agent 运行所需的全部核心模块 |
| 生产可用 | 错误恢复、上下文管理、超时控制、并发工具执行等生产级特性 |
# 安装
curl -fsSL https://raw.githubusercontent.com/ZhangShenao/harness9/master/scripts/install.sh | bash
# 配置 API Key
export OPENAI_API_KEY="sk-..."
# 进入你的项目目录并启动
cd /your/project && harness9
# 查看所有 CLI 参数与说明
harness9 --help
# 查看版本号
harness9 --version
完整安装选项、Anthropic/OpenRouter 配置、AGENTS.md 设置和常见问题,见 快速启动指南。
在 TTY 中直接运行,自动进入全屏 TUI:欢迎页 → 对话页双 Phase 切换,流式输出逐 token 追加,工具执行期间实时 Spinner + 耗时计数。
Tab 键补全命令和 Skill 名称Ctrl-C 中断 Agent,再按一次退出详见 TUI 交互界面实现原理。
! 前缀)在对话框中直接运行 Bash 命令,无需切换终端:
› !git log --oneline -5
$ git log --oneline -5
a1b2c3d feat: add shell execution
...
✓ 完成 — 12ms
输入 ! 时 TUI 自动切换 Shell 模式(状态栏变深绿、输入区显示 [SHELL] $ 徽章)。命令输出追加到对话流,并在下次向 LLM 发送消息时自动注入上下文,Agent 可直接引用命令结果进行推理。
Esc 退出 Shell 模式,Enter 执行命令详见 Shell 执行功能技术方案。
对话历史自动持久化到 SQLite(~/.harness9/sessions.db),进程重启后可通过 /resume 恢复。
ctx: 45.2K/128K (35%) ← 绿色:正常
ctx: 92.1K/128K (72%) ← 黄色:警告,即将压缩
LLM 摘要压缩(默认):上下文超过 80% 时自动调用 LLM 生成结构化摘要,保留关键语义后继续会话,远优于简单截断;Provider 不可用时自动回退到 TokenBudgetCompactor。
⚡ 上下文已压缩 — 12.5K → 6.2K tokens(45 → 22 条消息)
会话命令:/new 开启新会话,/resume 恢复历史。
短期记忆只在单次会话内有效。Long-Term Memory 让 Agent 跨会话积累知识、记住用户偏好、复用历史决策——记忆持久化到 SQLite(long_term_memories 表 + FTS5 全文索引,复用 state.db,零新增依赖)。
› 记住:我偏好简洁的中文回复,测试用标准库 testing
✓ memory_write({"action":"add", ...}) — 0s
# 下一次会话,无需重新说明,Agent 已知晓你的偏好
memory_write(add/update/remove)/ memory_search(FTS5 按需检索)工具;② 上下文压缩前由 LLM 自动提取持久事实(fail-open);③ 每 N 轮注入记忆提示 nudge(防御性副本,不持久化)use_count/last_used_at)、陈旧记忆识别在 skills/<name>/SKILL.md 下放置领域知识,Agent 按需加载,System Prompt 始终精简:
skills/
├── refactor-guide/SKILL.md # 重构规范
└── testing-standards/SKILL.md # 测试标准
Agent 执行工具时,内置规则引擎自动评估风险等级,只有真正需要人类判断的操作才会暂停并弹出审批对话框:
╭─────────────────────────────────────────────────╮
│ ⚠ 工具审批请求 [高风险] │
│ │
│ 工具:bash │
│ 原因:强制递归删除文件/目录 │
│ │
│ ▶ [1] 允许(仅本次) │
│ [2] 允许(本会话不再提示) │
│ [3] 总是允许(写入白名单) │
│ [4] 拒绝 │
│ [5] 拒绝并提供反馈... │
╰─────────────────────────────────────────────────╯
allow(放行)/ deny(拒绝)/ ask(弹框审批),通过洋葱模型 Hook 链依次评估DangerHook 自动识别 rm -rf、curl | bash、sudo、dd if= 等 19 条高危模式.harness9/settings.json 支持 glob 语法规则,选择「总是允许」后立即生效无需重启~/.ssh、~/.aws、~/.kube、~/.gnupg 等路径无论任何配置均拒绝访问ask 决策自动视为 Allow,不破坏无人值守流程通过 Shift+Tab 切换到 Plan Mode(状态栏显示 [PLAN],色调切换为琥珀黄)。
用户:帮我写一个 Go Web API
[PLAN] 分析请求 → read_file/bash 只读探索
→ todo_write 生成实现计划
→ 文字简述后停止
╭──────────────────────────────────────╮
│ Plan Mode 完成 — 选择下一步操作 │
│ [1] 批准并自动执行 │
│ [2] 批准并逐步确认编辑 │
│ [3] 继续修改计划 │
│ [4] 取消 │
╰──────────────────────────────────────╯
批准后 Agent 按清单逐项执行,todo 快照实时追加在对话流中:
✓ todo_write({...}) — 0s
☰ Tasks · 3/11 · 1 active
1. ✔ 创建目录结构
2. ✔ 初始化 go.mod
3. ▶ 实现 main.go
write_file、edit_file 被从工具列表移除,无论 prompt 如何,LLM 根本看不到写工具todo_write 校验状态转换,pending → completed 直跳被拒绝,LLM 必须经过 in_progress 才能完成条目EventDone 无进度后停止自动执行,提示手动干预详见 Planning 模块实现原理。
工具输出超过阈值(默认 10,000 字符)时,OffloadHook 自动将完整内容写入文件,context 中仅保留摘要引用和预览,防止单次输出爆炸 context 窗口:
✓ bash(grep -r "TODO" .) — 1.2s
[输出已保存至 ~/.harness9/tool_results/{sessionID}/{id}.txt,共 847 行 / 32416 字节。
预览(前 20 行):
...
LLM 可通过 read_file 的 offset/limit 参数分页检索完整内容:
read_file({"path": "~/.harness9/tool_results/.../id.txt", "offset": 4096, "limit": 4096})
FilePlanWriter 在每次 todo_write 写入后将执行计划同步输出为 markdown 文件:
# 执行计划
session: abc12345
updated: 2026-05-22T15:30:00+08:00
## 任务列表
- [x] 创建目录结构
- [>] 实现 main.go
- [ ] 编写测试
{workDir}/.harness9/plans/,纳入版本控制详见 文件系统能力技术方案。
主代理可通过 task 工具,把边界清晰的子任务委派给运行在独立上下文、受限工具集的专门子代理执行——子代理只回传最终结论,不让冗长的中间过程污染主上下文。
harness9 内置一个 general-purpose(通用)子代理,设计对标 Claude Code 与 DeepAgents 的同名能力:继承父代理可用的全部工具与模型,用于需要兼顾探索与修改、复杂推理或多步依赖的任务,是「没有更专门子代理时」的兜底委派目标。
@general-purpose 调查 internal/tools/bash.go 的超时处理逻辑并总结实现要点
[general-purpose] 子代理启动…
[general-purpose] ▸ read_file ✓
[general-purpose] ✓ 完成
Tools/Model 留空即继承父代理可用的全部工具与模型,开箱即用.harness9/agents/*.md 用 YAML frontmatter 定义专门子代理(白名单/黑名单工具、模型覆盖、预加载 Skills)/tasks 面板查看 + 下次对话注入@agent 直跑:输入 @<agent> <task> 绕过主 LLM 决策,直接前台调用(Tab 补全子代理名)task 工具)、权限只能更严不能扩权、上下文完全隔离详见 Sub-Agent 系统实现原理。
通过三条非侵入式接入路径,将 OTEL Span 和 Metrics 无缝嵌入 Agent 运行全链路——引擎层、LLM 调用层、工具执行层各自独立,不改动核心代码:
harness9.interaction ← 一次完整 Agent 运行(含 sessionID)
└── harness9.turn ← 单个 ReAct Turn
├── harness9.llm_request ← LLM API 调用(含 token 数)
└── harness9.tool ← 工具执行(含工具名、成功/失败)
# 接入 Jaeger(本地开发)
docker run -p 16686:16686 -p 4318:4318 jaegertracing/all-in-one
export OTEL_ENABLED=true OTEL_EXPORTER_TYPE=otlp
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
harness9
# 打开 http://localhost:16686 查看完整调用链
# 接入 Langfuse(生产)
export OTEL_ENABLED=true OTEL_EXPORTER_TYPE=otlp
export OTEL_EXPORTER_OTLP_ENDPOINT=https://cloud.langfuse.com/api/public/otel
harness9
OTEL_ENABLED 不设置时使用 noop 实现,无任何性能影响noop(默认)/ stdout(本地调试)/ otlp(生产接入 Langfuse、Grafana、Jaeger)详见 测试·评估·可观测体系。
内置评估框架,用 ScriptedProvider 把 LLM 行为脚本化,配合 Assertion 断言体系验证 Agent 的工具调用轨迹——无需真实 API,CI 中 hermetic 运行:
// 定义一个确定性 eval 用例
c := &evals.Case{
ID: "tool_calling/bash_basic",
Prompt: "运行 echo hello",
Provider: evals.NewScriptedProvider(
evals.ScriptedTurn{ToolCalls: []schema.ToolCall{
evals.MakeToolCall("tc1", "bash", `{"command":"echo hello"}`),
}},
evals.ScriptedTurn{Text: "命令已执行,输出 hello。"},
),
Assertions: []evals.Assertion{
&evals.ToolCalledAssertion{ToolName: "bash"},
&evals.NoErrorAssertion{},
&evals.MaxTurnsAssertion{Max: 3}, // soft:仅记警告
},
}
result := evals.RunCase(context.Background(), c)
# 运行黄金数据集(16 个内置用例,无需 API Key)
go test ./internal/evals/dataset/... -v
ScriptedProvider 按脚本序列返回回复,相同输入永远得到相同结果recordingHook 记录所有工具调用,Hard/Soft 双层断言覆盖正确性与效率SetupHermeticEnv 清除所有 API Key,防止 eval 意外调用付费服务.github/workflows/eval.yml),eval 失败则阻断合并详见 测试·评估·可观测体系。
harness9 默认在本地 Docker 容器内执行所有工具调用(需本地安装并运行 Docker)。Docker 不可用时自动降级为本地进程模式,Agent 行为不变。
[Sandbox] 3a2f (main) Running │ 7b1c (sub-1) Running
--cap-drop all)、防 fork bomb(--pids-limit 256)bash 命令通过 docker exec 进容器,文件工具通过 bind mount 共享 workDir——Agent 行为不变,无需修改 promptlabel=harness9=1 标记所有管理的容器,进程崩溃后下次启动自动清理残留容器# Sandbox 默认开启,直接启动即可
harness9
# 如需关闭 Sandbox(不使用 Docker 容器)
echo "SANDBOX_ENABLED=false" >> .env
详见 Sandbox 沙箱系统。
/autodev 自举开发)在 harness9 TUI 中输入 /autodev <功能描述>,主 Agent 通过对话澄清需求、生成 Feature Spec,用户确认后自动委派 dev sub-agent 完成编码、测试、PR 创建——全程无需人工写代码:
› /autodev 实现一个 token_count 工具
[主 Agent 澄清需求(2-3 轮对话)]
> 这个工具需要支持多语言吗?
用户: 是的,中文按字符计数,英文按 word 计数
[主 Agent 生成 Spec 并等待确认]
## Feature Spec: Add token_count Tool
...
用户: 确认
[Dev Sub-Agent 启动,在 git worktree + Docker Sandbox 中工作]
✓ go build ./...
✓ go test ./... — PASS(第 1 次)
✓ git push origin HEAD
✓ gh pr create
✓ PR 已创建:https://github.com/.../pull/42
前置条件:gh CLI 已登录;如需 Docker 隔离,在 .env 中设置 SANDBOX_IMAGE=golang:1.25-bookworm
详见 AutoDev 自举开发技术方案。
harness9 支持通过 MCP 协议连接外部工具服务器,将第三方工具无缝注入 Agent 工具注册表:
[MCP] context7 ● 2 tools
在项目根目录创建 .mcp.json 即可接入任意 MCP Server:
{
"mcpServers": {
"context7": {
"command": "npx",
"args": ["-y", "@upstash/context7-mcp"]
}
}
}
mcp__{server}__{tool} 格式注册到统一工具注册表,对 Engine 完全透明,自动获得并发执行、超时控制、错误回传等所有现有机制npx 类需要下载的 Server 也不会卡住启动流程stdio(子进程 stdin/stdout NDJSON,async reader goroutine + pending map ID 关联)+ http(无状态 POST)/mcp 命令:Tab 补全可用,打开模态工具面板查看所有已连接工具;按 e 用 $EDITOR 或系统关联程序打开 .mcp.json 编辑配置详见 MCP 工具集成技术方案。
web_search + web_fetch)Agent 可直接搜索互联网并抓取页面内容,无需任何 API Key 或外部账号:
› 搜索 Go 1.25 的新特性并总结
✓ web_search("Go 1.25 release notes") — 0.8s
[1] Go 1.25 Release Notes | URL: https://go.dev/doc/go1.25
[2] ...
✓ web_fetch("https://go.dev/doc/go1.25") — 1.2s
# Go 1.25 Release Notes
> 来源:https://go.dev/doc/go1.25
...
web_search:POST 到 DuckDuckGo HTML 端点(html.duckduckgo.com/html/),解析返回标题、URL、摘要列表web_fetch:HTTP GET + go-readability 提取主内容 + html-to-markdown 转换,输出对 LLM 友好的 Markdownftp://、userinfo、127.x.x.x、169.254.x.x(云 metadata)、RFC1918 私网等;DNS 解析后二次检查 IP,防御 DNS rebinding;重定向链每跳复检当前日期:YYYY-MM-DD,Agent 搜索时自然带入正确年份详见 网页搜索与抓取技术方案。
每个 Turn 执行一次 LLM 调用(携带完整工具列表),工具结果作为 Observation 注入上下文,驱动下一轮推理:
Turn N: LLM(messages, tools) → Action → 并发执行工具 → Observation → Turn N+1
自然终止:模型不再发起工具调用 → 输出最终回复
同一 Turn 内多个工具并发执行,每个工具独立超时控制。执行失败时,错误信息原样回传给 LLM 触发自动重试。
Run(阻塞)和 RunStream(流式)双模式,共享同一引擎实例:
stream, _ := eng.RunStream(ctx, prompt)
for evt := range stream {
switch evt.Type {
case engine.EventActionDelta:
fmt.Print(evt.Data.(string))
case engine.EventDone:
return
}
}

| 模块 | 说明 | 状态 |
|---|---|---|
| TUI | 全屏 TUI(Bubbletea):双 Phase、流式输出、Spinner + 精确耗时、Tab 补全、Token 用量实时展示、Shell 执行模式(! 前缀) |
✅ |
| Engine | 标准 ReAct 主循环,阻塞 + 流式双模式,EventTokenUpdate / EventCompaction / EventToolResult(精确耗时)事件 | ✅ |
| Hooks | 工具拦截器:HookRegistry(洋葱模型)+ OffloadHook(超大输出 offload)+ FilePlanWriter(计划持久化)+ DangerHook(高危命令拦截) | ✅ |
| Permission | Human-in-the-Loop 权限控制:PermissionHook(JSON 规则)+ 审批对话框(5 选项)+ 白名单动态更新 + 敏感路径硬保护 | ✅ |
| Sub-Agent | 子代理委派:内置 general-purpose 通用子代理、文件式定义(.harness9/agents/*.md)、task 工具(前台/后台)、@agent 直跑、TaskTracker、上下文隔离 + 防递归 + 权限不扩权 |
✅ |
| Planning | Plan Mode(先规划后执行)、TodoStore、todo_write 工具、PlanWriter 接口、工具层权限过滤、自动续跑 + 停滞检测 | ✅ |
| Memory | 短期记忆:SQLiteSession(WAL)、Manager(WithToolResultsDir + DeleteSession GC + DB() 访问器)、SummarizationCompactor(默认,LL |
$ claude mcp add harness9 \
-- python -m otcore.mcp_server <graph>