flywhl-harness 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flywhl_harness-0.2.0/.gitignore +21 -0
- flywhl_harness-0.2.0/CHANGELOG.md +81 -0
- flywhl_harness-0.2.0/LICENSE +21 -0
- flywhl_harness-0.2.0/PKG-INFO +198 -0
- flywhl_harness-0.2.0/README.en.md +157 -0
- flywhl_harness-0.2.0/README.md +238 -0
- flywhl_harness-0.2.0/conftest.py +152 -0
- flywhl_harness-0.2.0/examples/01_minimal_loop.py +56 -0
- flywhl_harness-0.2.0/examples/02_crash_resume.py +75 -0
- flywhl_harness-0.2.0/examples/03_cancel_semantics.py +52 -0
- flywhl_harness-0.2.0/examples/04_context_compaction.py +42 -0
- flywhl_harness-0.2.0/examples/05_hooks_permissions.py +100 -0
- flywhl_harness-0.2.0/examples/06_subagent.py +76 -0
- flywhl_harness-0.2.0/examples/07_swarm_dag.py +59 -0
- flywhl_harness-0.2.0/examples/08_replay_eval.py +85 -0
- flywhl_harness-0.2.0/examples/09_edge_cloud.py +53 -0
- flywhl_harness-0.2.0/examples/10_long_running.py +156 -0
- flywhl_harness-0.2.0/examples/11_kill9_cli_recover.py +162 -0
- flywhl_harness-0.2.0/examples/_common.py +42 -0
- flywhl_harness-0.2.0/examples/mcp_echo_server.py +66 -0
- flywhl_harness-0.2.0/pyproject.toml +98 -0
- flywhl_harness-0.2.0/scripts/safe_pytest.py +255 -0
- flywhl_harness-0.2.0/src/flywhl_harness/__init__.py +45 -0
- flywhl_harness-0.2.0/src/flywhl_harness/_utils.py +161 -0
- flywhl_harness-0.2.0/src/flywhl_harness/agent/__init__.py +0 -0
- flywhl_harness-0.2.0/src/flywhl_harness/agent/budget.py +204 -0
- flywhl_harness-0.2.0/src/flywhl_harness/agent/compaction.py +196 -0
- flywhl_harness-0.2.0/src/flywhl_harness/agent/context.py +186 -0
- flywhl_harness-0.2.0/src/flywhl_harness/agent/executor.py +458 -0
- flywhl_harness-0.2.0/src/flywhl_harness/agent/inbox.py +50 -0
- flywhl_harness-0.2.0/src/flywhl_harness/agent/loop.py +500 -0
- flywhl_harness-0.2.0/src/flywhl_harness/agent/run_context.py +89 -0
- flywhl_harness-0.2.0/src/flywhl_harness/agent/subagent.py +110 -0
- flywhl_harness-0.2.0/src/flywhl_harness/cancel.py +131 -0
- flywhl_harness-0.2.0/src/flywhl_harness/cli.py +553 -0
- flywhl_harness-0.2.0/src/flywhl_harness/config.py +595 -0
- flywhl_harness-0.2.0/src/flywhl_harness/events.py +73 -0
- flywhl_harness-0.2.0/src/flywhl_harness/llm/__init__.py +64 -0
- flywhl_harness-0.2.0/src/flywhl_harness/llm/anthropic.py +232 -0
- flywhl_harness-0.2.0/src/flywhl_harness/llm/base.py +272 -0
- flywhl_harness-0.2.0/src/flywhl_harness/llm/doctor.py +180 -0
- flywhl_harness-0.2.0/src/flywhl_harness/llm/mock.py +340 -0
- flywhl_harness-0.2.0/src/flywhl_harness/llm/models.py +136 -0
- flywhl_harness-0.2.0/src/flywhl_harness/llm/openai_compat.py +278 -0
- flywhl_harness-0.2.0/src/flywhl_harness/llm/profiles.py +162 -0
- flywhl_harness-0.2.0/src/flywhl_harness/llm/replay.py +79 -0
- flywhl_harness-0.2.0/src/flywhl_harness/llm/resilient.py +103 -0
- flywhl_harness-0.2.0/src/flywhl_harness/observe/__init__.py +0 -0
- flywhl_harness-0.2.0/src/flywhl_harness/observe/evals.py +130 -0
- flywhl_harness-0.2.0/src/flywhl_harness/observe/export.py +90 -0
- flywhl_harness-0.2.0/src/flywhl_harness/observe/replay.py +247 -0
- flywhl_harness-0.2.0/src/flywhl_harness/observe/trace.py +132 -0
- flywhl_harness-0.2.0/src/flywhl_harness/policy/__init__.py +0 -0
- flywhl_harness-0.2.0/src/flywhl_harness/policy/approvals.py +107 -0
- flywhl_harness-0.2.0/src/flywhl_harness/policy/bash_analyzer.py +2050 -0
- flywhl_harness-0.2.0/src/flywhl_harness/policy/fatigue.py +97 -0
- flywhl_harness-0.2.0/src/flywhl_harness/policy/hooks.py +283 -0
- flywhl_harness-0.2.0/src/flywhl_harness/policy/permissions.py +362 -0
- flywhl_harness-0.2.0/src/flywhl_harness/policy/sensitive.py +84 -0
- flywhl_harness-0.2.0/src/flywhl_harness/policy/supply_chain.py +517 -0
- flywhl_harness-0.2.0/src/flywhl_harness/prompts.py +156 -0
- flywhl_harness-0.2.0/src/flywhl_harness/runtime/__init__.py +0 -0
- flywhl_harness-0.2.0/src/flywhl_harness/runtime/dispatch.py +224 -0
- flywhl_harness-0.2.0/src/flywhl_harness/runtime/manager.py +845 -0
- flywhl_harness-0.2.0/src/flywhl_harness/runtime/retry.py +167 -0
- flywhl_harness-0.2.0/src/flywhl_harness/runtime/scheduler.py +206 -0
- flywhl_harness-0.2.0/src/flywhl_harness/runtime/session.py +124 -0
- flywhl_harness-0.2.0/src/flywhl_harness/runtime/state.py +115 -0
- flywhl_harness-0.2.0/src/flywhl_harness/runtime/store.py +619 -0
- flywhl_harness-0.2.0/src/flywhl_harness/runtime/workspace.py +249 -0
- flywhl_harness-0.2.0/src/flywhl_harness/server/__init__.py +0 -0
- flywhl_harness-0.2.0/src/flywhl_harness/server/app.py +608 -0
- flywhl_harness-0.2.0/src/flywhl_harness/tools/__init__.py +37 -0
- flywhl_harness-0.2.0/src/flywhl_harness/tools/base.py +254 -0
- flywhl_harness-0.2.0/src/flywhl_harness/tools/confine.py +198 -0
- flywhl_harness-0.2.0/src/flywhl_harness/tools/demo.py +142 -0
- flywhl_harness-0.2.0/src/flywhl_harness/tools/egress.py +278 -0
- flywhl_harness-0.2.0/src/flywhl_harness/tools/extra.py +147 -0
- flywhl_harness-0.2.0/src/flywhl_harness/tools/fs.py +401 -0
- flywhl_harness-0.2.0/src/flywhl_harness/tools/mcp.py +245 -0
- flywhl_harness-0.2.0/src/flywhl_harness/tools/regex_search.py +127 -0
- flywhl_harness-0.2.0/src/flywhl_harness/tools/registry.py +57 -0
- flywhl_harness-0.2.0/src/flywhl_harness/tools/sandbox.py +513 -0
- flywhl_harness-0.2.0/src/flywhl_harness/tools/schema.py +233 -0
- flywhl_harness-0.2.0/src/flywhl_harness/tools/shell.py +264 -0
- flywhl_harness-0.2.0/src/flywhl_harness/types.py +243 -0
- flywhl_harness-0.2.0/src/flywhl_harness/web/app.js +315 -0
- flywhl_harness-0.2.0/src/flywhl_harness/web/boot.js +2 -0
- flywhl_harness-0.2.0/src/flywhl_harness/web/index.html +62 -0
- flywhl_harness-0.2.0/src/flywhl_harness/web/lib/api.js +52 -0
- flywhl_harness-0.2.0/src/flywhl_harness/web/lib/dom.js +97 -0
- flywhl_harness-0.2.0/src/flywhl_harness/web/lib/icons.js +85 -0
- flywhl_harness-0.2.0/src/flywhl_harness/web/lib/meta.js +133 -0
- flywhl_harness-0.2.0/src/flywhl_harness/web/lib/ui.js +300 -0
- flywhl_harness-0.2.0/src/flywhl_harness/web/logo.svg +1 -0
- flywhl_harness-0.2.0/src/flywhl_harness/web/md.js +134 -0
- flywhl_harness-0.2.0/src/flywhl_harness/web/style.css +877 -0
- flywhl_harness-0.2.0/src/flywhl_harness/web/views/inspector.js +371 -0
- flywhl_harness-0.2.0/src/flywhl_harness/web/views/pages.js +215 -0
- flywhl_harness-0.2.0/src/flywhl_harness/web/views/session.js +544 -0
- flywhl_harness-0.2.0/src/flywhl_harness/web/views/settings.js +158 -0
- flywhl_harness-0.2.0/src/flywhl_harness/web/views/timeline.js +343 -0
- flywhl_harness-0.2.0/tests/__init__.py +0 -0
- flywhl_harness-0.2.0/tests/conftest.py +47 -0
- flywhl_harness-0.2.0/tests/fixtures/sse/dashscope_final.sse +30 -0
- flywhl_harness-0.2.0/tests/fixtures/sse/dashscope_tool_calls.sse +52 -0
- flywhl_harness-0.2.0/tests/fixtures/sse/deepseek_final.sse +34 -0
- flywhl_harness-0.2.0/tests/fixtures/sse/deepseek_tool_calls.sse +100 -0
- flywhl_harness-0.2.0/tests/fixtures/sse/errors.json +30 -0
- flywhl_harness-0.2.0/tests/fixtures/sse/kimi_final.sse +92 -0
- flywhl_harness-0.2.0/tests/fixtures/sse/kimi_tool_calls.sse +146 -0
- flywhl_harness-0.2.0/tests/fixtures/sse/minimax_final.sse +42 -0
- flywhl_harness-0.2.0/tests/fixtures/sse/minimax_tool_calls.sse +38 -0
- flywhl_harness-0.2.0/tests/fixtures/sse/openrouter_final.sse +42 -0
- flywhl_harness-0.2.0/tests/fixtures/sse/openrouter_tool_calls.sse +30 -0
- flywhl_harness-0.2.0/tests/live/README.md +121 -0
- flywhl_harness-0.2.0/tests/live/__init__.py +0 -0
- flywhl_harness-0.2.0/tests/live/conftest.py +175 -0
- flywhl_harness-0.2.0/tests/live/record_sse.py +84 -0
- flywhl_harness-0.2.0/tests/live/test_live_agent.py +212 -0
- flywhl_harness-0.2.0/tests/live/test_live_provider.py +174 -0
- flywhl_harness-0.2.0/tests/live/test_live_runtime.py +188 -0
- flywhl_harness-0.2.0/tests/test_backlog.py +1306 -0
- flywhl_harness-0.2.0/tests/test_bench.py +347 -0
- flywhl_harness-0.2.0/tests/test_cancel.py +202 -0
- flywhl_harness-0.2.0/tests/test_context.py +179 -0
- flywhl_harness-0.2.0/tests/test_hardening.py +705 -0
- flywhl_harness-0.2.0/tests/test_lifecycle.py +168 -0
- flywhl_harness-0.2.0/tests/test_loop.py +262 -0
- flywhl_harness-0.2.0/tests/test_policy.py +293 -0
- flywhl_harness-0.2.0/tests/test_providers.py +399 -0
- flywhl_harness-0.2.0/tests/test_recovery.py +138 -0
- flywhl_harness-0.2.0/tests/test_replay.py +170 -0
- flywhl_harness-0.2.0/tests/test_runtime.py +583 -0
- flywhl_harness-0.2.0/tests/test_safety_net.py +147 -0
- flywhl_harness-0.2.0/tests/test_sandbox_default.py +1047 -0
- flywhl_harness-0.2.0/tests/test_security.py +831 -0
- flywhl_harness-0.2.0/uv.lock +768 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
.venv/
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.pyc
|
|
4
|
+
.pytest_cache/
|
|
5
|
+
.coverage
|
|
6
|
+
.coverage.*
|
|
7
|
+
coverage.xml
|
|
8
|
+
htmlcov/
|
|
9
|
+
.flywhl/
|
|
10
|
+
playground/.flywhl/
|
|
11
|
+
playground/hello.py
|
|
12
|
+
playground/demo_big.txt
|
|
13
|
+
dist/
|
|
14
|
+
*.egg-info/
|
|
15
|
+
docs/node_modules/
|
|
16
|
+
docs/.vitepress/cache/
|
|
17
|
+
docs/.vitepress/dist/
|
|
18
|
+
.DS_Store
|
|
19
|
+
.env
|
|
20
|
+
.claude/
|
|
21
|
+
.idea/
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# 更新记录
|
|
2
|
+
|
|
3
|
+
版本号遵循[语义化版本](https://semver.org/lang/zh-CN/)。1.0 之前,次版本号的升级可能带破坏性变化,这类变化都写在“行为变化”一节。
|
|
4
|
+
|
|
5
|
+
## [0.2.0] - 2026-11-05
|
|
6
|
+
|
|
7
|
+
第一个发布到 PyPI 的版本。v0.1.0 之后的主线是安全边界:系统沙箱从可选原型变成默认开启,命令分析、凭证保护、供应链检查都重做了一轮。
|
|
8
|
+
|
|
9
|
+
### 行为变化
|
|
10
|
+
|
|
11
|
+
**bash、命令 hook、stdio MCP 服务器、`verify_command` 默认都在系统沙箱里运行。** macOS 用 `sandbox-exec`,Linux 用 bubblewrap。
|
|
12
|
+
命令读不到凭证位置,只能写工作区和临时目录,网络默认是空白名单。
|
|
13
|
+
|
|
14
|
+
| 你会看到 | 原因 | 怎么处理 |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| Linux 上 bash 拒绝执行,提示没有可用的沙箱 | 没装 bubblewrap,或 user namespace 被限制。这时不会退回沙箱外 | 装 bubblewrap;Ubuntu 24.04 可能还要 `sudo sysctl kernel.apparmor_restrict_unprivileged_userns=0`。确实不要沙箱时,在 `~/.flywhl/config.toml` 写 `sandbox = "off"`,或启动时加 `--sandbox off` |
|
|
17
|
+
| 配置了 MCP 服务器时 harness 启动失败 | 同上,MCP 服务器不会在沙箱外启动 | 同上 |
|
|
18
|
+
| `pip install`、`npm install`、`git clone` 连不上网 | 默认白名单为空,报错里写着被拦的主机 | 启动时加 `--net-allow pypi.org`,或写进 `permissions.network_allow` |
|
|
19
|
+
| `git push`(SSH)、`gh`、`kubectl`、`docker` 失败 | 沙箱里没有凭证和本机套接字 | 让模型带 `outside_sandbox: true` 请求,由人确认后在沙箱外执行;或者自己在终端里跑 |
|
|
20
|
+
| `git config`、`git remote add`、`git push -u` 失败 | `.git/hooks`、`.git/config` 在沙箱里只读。`git add`、`git commit` 不受影响 | 同上 |
|
|
21
|
+
| 项目 `flywhl.toml` 里的 `sandbox = "off"` 不生效 | 关闭沙箱只有三个入口:用户配置、`-c` 指定的文件、`--sandbox off`。信任项目配置也不行 | 改用这三个入口之一 |
|
|
22
|
+
| 无人值守时 `write`、`edit` 改 `.git/config`、`.git/hooks`、`flywhl.toml` 被拒绝,`--yes` 也不行;有人看时每次都要确认 | 这些位置在沙箱里只读,文件工具不在沙箱里,放开就能绕过沙箱。没开沙箱时不受影响 | 由人确认,或者自己在终端里改 |
|
|
23
|
+
| 写了 `sandbox = "macos"` 或 `"linux"` 的配置连不上网 | 没写 `sandbox_network` 时从 `allow` 变成 `allowlist` | 显式写 `sandbox_network = "allow"`,或把主机加进白名单 |
|
|
24
|
+
|
|
25
|
+
`outside_sandbox` 是一道独立的人工门:任何模式下都要人确认,`auto_approve`、规则、会话授权都放行不了,无人值守时直接拒绝。
|
|
26
|
+
`write`、`edit` 写沙箱里只读的位置走同一道门。
|
|
27
|
+
|
|
28
|
+
**English summary.** bash, command hooks, stdio MCP servers and `verify_command` now run inside an OS sandbox by default
|
|
29
|
+
(`sandbox-exec` on macOS, bubblewrap on Linux). Credentials are unreadable, writes are limited to the workspace and temp directories,
|
|
30
|
+
and the network starts as an empty allowlist. If no sandbox is available the harness refuses to run instead of falling back:
|
|
31
|
+
install bubblewrap, or opt out with `sandbox = "off"` in `~/.flywhl/config.toml` or `--sandbox off`; project config cannot opt out.
|
|
32
|
+
Allow hosts with `--net-allow` or `permissions.network_allow`. Commands that need your credentials (`git push` over SSH, `gh`, `kubectl`)
|
|
33
|
+
either go through the human-only `outside_sandbox` gate or run in your own terminal. `.git/hooks` and `.git/config` are read-only inside the
|
|
34
|
+
sandbox, so `git config`, `git remote add` and `git push -u` fail there. `write` and `edit` on those paths and on `flywhl.toml` take the same
|
|
35
|
+
human-only gate, so unattended runs refuse them, including `--yes`. Configs that set `macos` or `linux` without `sandbox_network`
|
|
36
|
+
move from `allow` to `allowlist`.
|
|
37
|
+
|
|
38
|
+
### 新增
|
|
39
|
+
|
|
40
|
+
- 系统沙箱:`sandbox = "auto"` 启动时真跑一次探测;Linux bubblewrap 档位;两个平台都有出站白名单,宿主代理按解析结果放行、重写 Host,命令结束时回收后台进程;单条 hook、MCP 服务器可以单独设 `sandbox`、`network_allow`,MCP 还可以设 `sandbox_network`。
|
|
41
|
+
- `outside_sandbox`:在沙箱外执行一条命令的人工门。
|
|
42
|
+
- 无人值守自动批准:`auto_approve` 配置和 `--approve`。
|
|
43
|
+
- 审批疲劳提示:秒批成串、同类操作反复批准时提醒,审批页给出疲劳指标。
|
|
44
|
+
- hook 与 MCP 的供应链检查:引用的脚本按内容绑定,经 `npx`、`uvx`、`docker run` 临时拉取的包可以要求锁到确切版本(`require_pinned`)。
|
|
45
|
+
- 「环境与安全」页、`flywhl doctor`、启动信息显示实际生效的沙箱档位和网络策略。
|
|
46
|
+
- 精简手册、能力清单与质量审计记录。
|
|
47
|
+
|
|
48
|
+
### 安全
|
|
49
|
+
|
|
50
|
+
- 命令分析:按 bash 的命令边界切分,堵住换行、注释、保留字绕过;跟踪变量取值与当前目录;展开函数和别名;预算或组合数超限时拒绝,不再放行。
|
|
51
|
+
- 凭证保护:匹配不区分大小写;bash 命中凭证位置时任何模式都拒绝;文件工具和 bash 共用一份清单。
|
|
52
|
+
- 本机 IPC:钥匙串、剪贴板、`open`、launchd、Unix 套接字不再绕过沙箱。
|
|
53
|
+
- 关掉沙箱外的两条通道:影子 git 快照不再替沙箱执行工作区里的配置,回放不再绕过人工门。
|
|
54
|
+
- 命令 hook 子进程剥离密钥类环境变量,需要的用 `env`、`pass_env` 显式传入。MCP 服务器自报的注解默认不采信。
|
|
55
|
+
- 项目配置不能替人打开自动批准;放行规则按文件实际落点匹配;MCP 服务器与 hook 的放宽类设置按类型严格校验。
|
|
56
|
+
- Web 服务:写请求的 Origin 必须与 Host 同主机同端口,查询串里的令牌只在 `/` 兑换。会话库与数据目录只给属主访问。
|
|
57
|
+
|
|
58
|
+
### 修复
|
|
59
|
+
|
|
60
|
+
- 演示模型名只走内置演示供应商,不会访问网络。
|
|
61
|
+
- 命令行的 `--provider` 优先于 `.env` 和环境变量里自带供应商的模型。
|
|
62
|
+
- 窄屏下检查器覆盖层避开页头,可以点遮罩收起。
|
|
63
|
+
- 内置提示词不再要求模型用中文回答,改为跟随用户使用的语言。
|
|
64
|
+
- 审批时写的拒绝理由改走提醒通道。放在工具结果里时,模型会按“工具结果是数据”的规则不照做,停下来反问。
|
|
65
|
+
- 压缩时模型没给出可用的摘要,之前保留的事实不再丢失。调用失败或空回复时退回的确定性摘要不带上一次的内容;模型接着对话往下说时,那句话会被当成摘要存下来。
|
|
66
|
+
- OpenRouter 的 `not a valid model ID` 识别为模型不存在,降级链能接手。
|
|
67
|
+
- 经 OpenRouter 调 Claude 时打开缓存。之前缓存命中一直是 0。
|
|
68
|
+
- `.flywhl/` 里自动放一份 `.gitignore`,忽略会话库、审批记录和快照,`config.toml` 和 `skills/` 仍然可以提交。之前这些文件一直显示为未跟踪,`git add -A` 会把它们带进仓库。
|
|
69
|
+
- 命令的环境变量过滤不再删掉 `GIT_AUTHOR_NAME`、`GIT_AUTHOR_EMAIL`、`GIT_AUTHOR_DATE`。它们撞上了 `AUTH` 规则,靠环境变量配身份时提交会报身份未知。
|
|
70
|
+
- 确定性回放用录制的压缩摘要、插话、恢复说明和验收结果,同一会话的后续 Run 也能回放。之前真实模型的压缩轨迹必定报分叉,验收失败重试的轨迹会少走几步而查不出来。新增 `replay_trajectory()`,只靠保存的轨迹就能回放。
|
|
71
|
+
|
|
72
|
+
### 工程
|
|
73
|
+
|
|
74
|
+
- 新增 `bench/`:公开基准的流水线、30 个任务和方法说明。真实模型只在一次性容器里运行,第一次结果见[基准页](https://flywhlway.github.io/flywhl-harness/benchmark/)。live 夹具可以用 `FLYWHL_LIVE_ARTIFACTS` 导出结果和轨迹。
|
|
75
|
+
- 包:`pip install flywhl-harness`、`pipx install flywhl-harness` 可用;sdist 改成白名单;版本号只在 `__init__.py` 写一处。
|
|
76
|
+
- 测试统一在系统沙箱里运行,子进程启动前有执行绊线,起因见[事故复盘](https://github.com/flywhlway/flywhl-harness/blob/main/docs/quality-audit/incident-2026-10-31.md)。
|
|
77
|
+
- CI:Linux 和 macOS 真跑沙箱用例,沙箱起不来时失败;新增干净安装验收、覆盖率和依赖漏洞报告;推送 `v*` 标签走发布流水线。
|
|
78
|
+
|
|
79
|
+
## [0.1.0] - 2026-10-16
|
|
80
|
+
|
|
81
|
+
首个标签。Agent Loop、工具与 MCP、上下文压缩、四层状态模型、检查点与崩溃恢复、取消语义、权限与 Hooks、多 Agent、轨迹回放与评测、Web 控制台和 30 章教程。
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 flywhl
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: flywhl-harness
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: A hand-written Agent Harness in Python: agent loop, tools, context compaction, crash recovery, permissions, a default-on OS sandbox, replay and a web console.
|
|
5
|
+
Project-URL: Homepage, https://github.com/flywhlway/flywhl-harness
|
|
6
|
+
Project-URL: Documentation, https://flywhlway.github.io/flywhl-harness/en/
|
|
7
|
+
Project-URL: Source, https://github.com/flywhlway/flywhl-harness
|
|
8
|
+
Project-URL: Issues, https://github.com/flywhlway/flywhl-harness/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/flywhlway/flywhl-harness/blob/main/CHANGELOG.md
|
|
10
|
+
Author: flywhl
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: agent,harness,llm,mcp,runtime,sandbox,tool-calling
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Environment :: Console
|
|
16
|
+
Classifier: Environment :: Web Environment
|
|
17
|
+
Classifier: Framework :: AsyncIO
|
|
18
|
+
Classifier: Intended Audience :: Developers
|
|
19
|
+
Classifier: Natural Language :: Chinese (Simplified)
|
|
20
|
+
Classifier: Natural Language :: English
|
|
21
|
+
Classifier: Operating System :: MacOS
|
|
22
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
23
|
+
Classifier: Programming Language :: Python :: 3
|
|
24
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
27
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
28
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
29
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
30
|
+
Requires-Python: >=3.11
|
|
31
|
+
Requires-Dist: fastapi>=0.110
|
|
32
|
+
Requires-Dist: httpx>=0.27
|
|
33
|
+
Requires-Dist: uvicorn>=0.29
|
|
34
|
+
Provides-Extra: anthropic
|
|
35
|
+
Requires-Dist: anthropic>=1.0; extra == 'anthropic'
|
|
36
|
+
Provides-Extra: dev
|
|
37
|
+
Requires-Dist: coverage>=7.10; extra == 'dev'
|
|
38
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
39
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
40
|
+
Description-Content-Type: text/markdown
|
|
41
|
+
|
|
42
|
+
# flywhl-harness
|
|
43
|
+
|
|
44
|
+
[](https://github.com/flywhlway/flywhl-harness/actions/workflows/tests.yml)
|
|
45
|
+
[](https://pypi.org/project/flywhl-harness/)
|
|
46
|
+
|
|
47
|
+
English | [简体中文](https://github.com/flywhlway/flywhl-harness/blob/main/README.md)
|
|
48
|
+
|
|
49
|
+
> **Agent = Model + Harness.** The model does the thinking; the harness does everything else.
|
|
50
|
+
|
|
51
|
+
flywhl-harness is an Agent Harness written by hand in Python, with no agent framework underneath: about 15,000 lines that implement the
|
|
52
|
+
mechanisms a production harness needs as code you can run, test and replace. It comes with a web console and a 30-chapter tutorial.
|
|
53
|
+
|
|
54
|
+
It is meant for two kinds of reader: engineers who want to see how a harness works all the way down, and developers who want a local
|
|
55
|
+
coding agent that can open an untrusted repository without being handed the keys to the machine.
|
|
56
|
+
|
|
57
|
+
**The console, CLI messages, scripted demo and code comments are in Chinese.** The model replies in the language you write in. The
|
|
58
|
+
[English docs](https://flywhlway.github.io/flywhl-harness/en/) summarize every chapter and link to the Chinese full text.
|
|
59
|
+
|
|
60
|
+
## What it implements
|
|
61
|
+
|
|
62
|
+
| Area | What you get |
|
|
63
|
+
|---|---|
|
|
64
|
+
| Loop | Async-generator main loop with explicit phases; steering and queued follow-ups while a run is active |
|
|
65
|
+
| Models | Unified messages and stream events; provider profiles for DeepSeek, Alibaba Bailian, Kimi, Zhipu, MiniMax, OpenRouter, Ollama and Anthropic; error classification, jittered retries, circuit breaking, cross-provider fallback |
|
|
66
|
+
| Tools | Three side-effect levels (read / write / external); concurrent reads, serialized writes, results returned in call order; oversized results moved behind handles; MCP client |
|
|
67
|
+
| Context | Context usage estimates, clearing of stale tool results, summary compaction that never splits a tool call from its result and always keeps the original task |
|
|
68
|
+
| State | Session / Entry / Run / Step / Event model on SQLite WAL; branching session tree; append-only history |
|
|
69
|
+
| Lifecycle | 14-state run machine with compare-and-swap transitions; cooperative cancellation; pause and resume; heartbeat leases and crash takeover, so a `kill -9` still gives exactly-once external side effects |
|
|
70
|
+
| Policy | Four permission modes; deny > ask > allow rules; bash risk analysis; credential locations denied in every mode; taint tracking; hooks compatible with the Claude Code protocol; durable human approval with diff previews |
|
|
71
|
+
| Isolation | Default-on OS sandbox with an egress allowlist; path confinement; shadow-git snapshots and rollback; secrets stripped from child processes |
|
|
72
|
+
| Multi-agent | Subagents with isolated context and a structured return contract; DAG scheduling with concurrency quotas |
|
|
73
|
+
| Measurement | Trajectory export with OTel GenAI-style spans; deterministic replay and counterfactual forks; evaluation with Wilson intervals and McNemar tests |
|
|
74
|
+
|
|
75
|
+
## Quick start
|
|
76
|
+
|
|
77
|
+
Requires Python 3.11+ on macOS or Linux. Windows is not supported. On Linux, install
|
|
78
|
+
[bubblewrap](https://github.com/containers/bubblewrap) first (`sudo apt install bubblewrap`): bash runs in an OS sandbox by default and
|
|
79
|
+
refuses to run if there isn't one.
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pipx install flywhl-harness # or: uv tool install flywhl-harness
|
|
83
|
+
|
|
84
|
+
mkdir agent-demo
|
|
85
|
+
flywhl serve -w agent-demo # open the tokenized link it prints
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
With no API key set, the console uses the built-in `flywhl-demo` model, a scripted model that needs no network. The demo cards on the start
|
|
89
|
+
page walk through approvals, cancellation, compaction, subagents and loop detection.
|
|
90
|
+
|
|
91
|
+
To use a real model (the default is `deepseek:deepseek-flash`):
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
cd agent-demo
|
|
95
|
+
export DEEPSEEK_API_KEY=sk-...
|
|
96
|
+
flywhl doctor # streaming and tool round trip for each configured provider
|
|
97
|
+
flywhl serve -w .
|
|
98
|
+
flywhl chat --model kimi:kimi-k2.6 # also dashscope:qwen3.8-flash, minimax:MiniMax-M2.7, ollama:qwen3.5:9b-mlx
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
For Anthropic's official SDK, install the extra with `pipx install 'flywhl-harness[anthropic]'`, then use
|
|
102
|
+
`--model anthropic:claude-opus-5-5`.
|
|
103
|
+
|
|
104
|
+
The access token is on by default, even on loopback. It is stored in `~/.flywhl/server_token` and survives restarts.
|
|
105
|
+
|
|
106
|
+
## Default security posture
|
|
107
|
+
|
|
108
|
+
The goal is that opening an untrusted repository is safe with no configuration. bash, command hooks, stdio MCP servers and
|
|
109
|
+
`verify_command` all run in an OS sandbox: `sandbox-exec` on macOS, bubblewrap on Linux.
|
|
110
|
+
|
|
111
|
+
**What it stops**
|
|
112
|
+
|
|
113
|
+
- Reading credential locations such as `~/.ssh` and cloud config, enforced by the kernel on real paths.
|
|
114
|
+
- Writing outside the workspace and temp directories.
|
|
115
|
+
- Reaching the network, unless the host is on the allowlist. The list starts empty, so this is effectively offline until you add a host, and a
|
|
116
|
+
blocked call names the host and how to allow it.
|
|
117
|
+
- Local IPC that would bypass the sandbox: keychain, clipboard, `open`, launchd, Unix sockets.
|
|
118
|
+
- A repository's own config turning any of this off. Only your user config, a file passed with `-c`, or `--sandbox off` can do that.
|
|
119
|
+
|
|
120
|
+
If no sandbox is available, the harness refuses to run commands. It does not quietly fall back.
|
|
121
|
+
|
|
122
|
+
**What it does not stop**
|
|
123
|
+
|
|
124
|
+
- Things written into the workspace that you later run outside the sandbox: a `Makefile`, `.envrc` or `package.json` script.
|
|
125
|
+
- Data leaving through a host you allowed. The workspace is readable, so allowing a host anyone can upload to allows exfiltration.
|
|
126
|
+
- Kernel exploits. The sandbox shares the host kernel. It contains mistakes and injection-driven overreach, and it is not a boundary for
|
|
127
|
+
multi-tenant use.
|
|
128
|
+
|
|
129
|
+
**Everyday consequences**
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
flywhl chat --net-allow pypi.org --net-allow files.pythonhosted.org # let the model install packages
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Commands that need your credentials (`git push` over SSH, `gh`, `kubectl`) fail in the sandbox. Run them in your own terminal, or let the
|
|
136
|
+
model request `outside_sandbox: true`, a separate human gate that always needs a person: `auto_approve`, rules and session grants cannot
|
|
137
|
+
pass it, and unattended runs are refused. `.git/hooks` and `.git/config` are read-only in the sandbox, so `git add` and `git commit` work
|
|
138
|
+
while `git config`, `git remote add` and `git push -u` do not.
|
|
139
|
+
|
|
140
|
+
Details: [workspace and sandbox](https://flywhlway.github.io/flywhl-harness/en/governance/workspace-sandbox) ·
|
|
141
|
+
[behavior changes in 0.2.0](https://github.com/flywhlway/flywhl-harness/blob/main/CHANGELOG.md)
|
|
142
|
+
|
|
143
|
+
## Use as a library
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
import asyncio
|
|
147
|
+
from typing import Annotated
|
|
148
|
+
from flywhl_harness import Harness, HarnessConfig, HookEvent, HookOutput, Effect, tool
|
|
149
|
+
|
|
150
|
+
@tool(effect=Effect.READ)
|
|
151
|
+
async def weather(city: Annotated[str, "City name"]) -> str:
|
|
152
|
+
"""Look up the weather for a city."""
|
|
153
|
+
return f"{city}: sunny, 23°C"
|
|
154
|
+
|
|
155
|
+
async def main():
|
|
156
|
+
async with Harness(HarnessConfig.load(workspace=".")) as h:
|
|
157
|
+
h.tools.register(weather)
|
|
158
|
+
|
|
159
|
+
@h.hooks.on(HookEvent.PRE_TOOL_USE, matcher="bash")
|
|
160
|
+
async def no_rm(inp):
|
|
161
|
+
if "rm " in inp.tool_input["command"]:
|
|
162
|
+
return HookOutput(decision="deny", reason="Deleting files is not allowed")
|
|
163
|
+
|
|
164
|
+
result = await h.run("What's the weather in Shanghai?", on_event=print)
|
|
165
|
+
print(result.status, result.text)
|
|
166
|
+
|
|
167
|
+
asyncio.run(main())
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Project status
|
|
171
|
+
|
|
172
|
+
Beta. The supported use is a local, single-user developer tool. It is not built for multi-user or multi-tenant service, and the console
|
|
173
|
+
should not be exposed beyond loopback.
|
|
174
|
+
|
|
175
|
+
- CI runs the offline suite on Linux (Python 3.11–3.13) and macOS, runs the sandbox tests for real on both, and installs the wheel into a
|
|
176
|
+
clean environment to verify the sandbox boundaries end to end.
|
|
177
|
+
- 33 live scenarios against real models live in `tests/live`. They cost money and need keys, so they run outside CI, in a disposable container.
|
|
178
|
+
- Open security gaps are listed rather than hidden. Workspace files are not yet a taint source, and project instruction files still reach
|
|
179
|
+
the system prompt. See the [security chapter](https://flywhlway.github.io/flywhl-harness/en/governance/security) and the
|
|
180
|
+
[readiness review](https://github.com/flywhlway/flywhl-harness/blob/main/docs/quality-audit/release-readiness.md).
|
|
181
|
+
- The [first benchmark](https://flywhlway.github.io/flywhl-harness/en/benchmark/) covers 5 models and 30 small tasks at one commit. It is evidence for the harness mechanisms, not a claim about task success rates in real work.
|
|
182
|
+
|
|
183
|
+
## Documentation
|
|
184
|
+
|
|
185
|
+
- [English docs](https://flywhlway.github.io/flywhl-harness/en/): quick start, architecture, and a summary of all 30 chapters
|
|
186
|
+
- [Chinese tutorial](https://flywhlway.github.io/flywhl-harness/): the full text
|
|
187
|
+
- [Changelog](https://github.com/flywhlway/flywhl-harness/blob/main/CHANGELOG.md)
|
|
188
|
+
|
|
189
|
+
To read the code or run the examples and tests, clone the repository. The examples are not in the wheel.
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
git clone https://github.com/flywhlway/flywhl-harness && cd flywhl-harness
|
|
193
|
+
uv sync --all-extras
|
|
194
|
+
.venv/bin/python examples/11_kill9_cli_recover.py # kill -9 a run, take over from another process, verify 7 invariants
|
|
195
|
+
.venv/bin/python -m pytest -q # runs inside an OS sandbox, in two stages
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
MIT License
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# flywhl-harness
|
|
2
|
+
|
|
3
|
+
[](https://github.com/flywhlway/flywhl-harness/actions/workflows/tests.yml)
|
|
4
|
+
[](https://pypi.org/project/flywhl-harness/)
|
|
5
|
+
|
|
6
|
+
English | [简体中文](https://github.com/flywhlway/flywhl-harness/blob/main/README.md)
|
|
7
|
+
|
|
8
|
+
> **Agent = Model + Harness.** The model does the thinking; the harness does everything else.
|
|
9
|
+
|
|
10
|
+
flywhl-harness is an Agent Harness written by hand in Python, with no agent framework underneath: about 15,000 lines that implement the
|
|
11
|
+
mechanisms a production harness needs as code you can run, test and replace. It comes with a web console and a 30-chapter tutorial.
|
|
12
|
+
|
|
13
|
+
It is meant for two kinds of reader: engineers who want to see how a harness works all the way down, and developers who want a local
|
|
14
|
+
coding agent that can open an untrusted repository without being handed the keys to the machine.
|
|
15
|
+
|
|
16
|
+
**The console, CLI messages, scripted demo and code comments are in Chinese.** The model replies in the language you write in. The
|
|
17
|
+
[English docs](https://flywhlway.github.io/flywhl-harness/en/) summarize every chapter and link to the Chinese full text.
|
|
18
|
+
|
|
19
|
+
## What it implements
|
|
20
|
+
|
|
21
|
+
| Area | What you get |
|
|
22
|
+
|---|---|
|
|
23
|
+
| Loop | Async-generator main loop with explicit phases; steering and queued follow-ups while a run is active |
|
|
24
|
+
| Models | Unified messages and stream events; provider profiles for DeepSeek, Alibaba Bailian, Kimi, Zhipu, MiniMax, OpenRouter, Ollama and Anthropic; error classification, jittered retries, circuit breaking, cross-provider fallback |
|
|
25
|
+
| Tools | Three side-effect levels (read / write / external); concurrent reads, serialized writes, results returned in call order; oversized results moved behind handles; MCP client |
|
|
26
|
+
| Context | Context usage estimates, clearing of stale tool results, summary compaction that never splits a tool call from its result and always keeps the original task |
|
|
27
|
+
| State | Session / Entry / Run / Step / Event model on SQLite WAL; branching session tree; append-only history |
|
|
28
|
+
| Lifecycle | 14-state run machine with compare-and-swap transitions; cooperative cancellation; pause and resume; heartbeat leases and crash takeover, so a `kill -9` still gives exactly-once external side effects |
|
|
29
|
+
| Policy | Four permission modes; deny > ask > allow rules; bash risk analysis; credential locations denied in every mode; taint tracking; hooks compatible with the Claude Code protocol; durable human approval with diff previews |
|
|
30
|
+
| Isolation | Default-on OS sandbox with an egress allowlist; path confinement; shadow-git snapshots and rollback; secrets stripped from child processes |
|
|
31
|
+
| Multi-agent | Subagents with isolated context and a structured return contract; DAG scheduling with concurrency quotas |
|
|
32
|
+
| Measurement | Trajectory export with OTel GenAI-style spans; deterministic replay and counterfactual forks; evaluation with Wilson intervals and McNemar tests |
|
|
33
|
+
|
|
34
|
+
## Quick start
|
|
35
|
+
|
|
36
|
+
Requires Python 3.11+ on macOS or Linux. Windows is not supported. On Linux, install
|
|
37
|
+
[bubblewrap](https://github.com/containers/bubblewrap) first (`sudo apt install bubblewrap`): bash runs in an OS sandbox by default and
|
|
38
|
+
refuses to run if there isn't one.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pipx install flywhl-harness # or: uv tool install flywhl-harness
|
|
42
|
+
|
|
43
|
+
mkdir agent-demo
|
|
44
|
+
flywhl serve -w agent-demo # open the tokenized link it prints
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
With no API key set, the console uses the built-in `flywhl-demo` model, a scripted model that needs no network. The demo cards on the start
|
|
48
|
+
page walk through approvals, cancellation, compaction, subagents and loop detection.
|
|
49
|
+
|
|
50
|
+
To use a real model (the default is `deepseek:deepseek-flash`):
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
cd agent-demo
|
|
54
|
+
export DEEPSEEK_API_KEY=sk-...
|
|
55
|
+
flywhl doctor # streaming and tool round trip for each configured provider
|
|
56
|
+
flywhl serve -w .
|
|
57
|
+
flywhl chat --model kimi:kimi-k2.6 # also dashscope:qwen3.8-flash, minimax:MiniMax-M2.7, ollama:qwen3.5:9b-mlx
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
For Anthropic's official SDK, install the extra with `pipx install 'flywhl-harness[anthropic]'`, then use
|
|
61
|
+
`--model anthropic:claude-opus-5-5`.
|
|
62
|
+
|
|
63
|
+
The access token is on by default, even on loopback. It is stored in `~/.flywhl/server_token` and survives restarts.
|
|
64
|
+
|
|
65
|
+
## Default security posture
|
|
66
|
+
|
|
67
|
+
The goal is that opening an untrusted repository is safe with no configuration. bash, command hooks, stdio MCP servers and
|
|
68
|
+
`verify_command` all run in an OS sandbox: `sandbox-exec` on macOS, bubblewrap on Linux.
|
|
69
|
+
|
|
70
|
+
**What it stops**
|
|
71
|
+
|
|
72
|
+
- Reading credential locations such as `~/.ssh` and cloud config, enforced by the kernel on real paths.
|
|
73
|
+
- Writing outside the workspace and temp directories.
|
|
74
|
+
- Reaching the network, unless the host is on the allowlist. The list starts empty, so this is effectively offline until you add a host, and a
|
|
75
|
+
blocked call names the host and how to allow it.
|
|
76
|
+
- Local IPC that would bypass the sandbox: keychain, clipboard, `open`, launchd, Unix sockets.
|
|
77
|
+
- A repository's own config turning any of this off. Only your user config, a file passed with `-c`, or `--sandbox off` can do that.
|
|
78
|
+
|
|
79
|
+
If no sandbox is available, the harness refuses to run commands. It does not quietly fall back.
|
|
80
|
+
|
|
81
|
+
**What it does not stop**
|
|
82
|
+
|
|
83
|
+
- Things written into the workspace that you later run outside the sandbox: a `Makefile`, `.envrc` or `package.json` script.
|
|
84
|
+
- Data leaving through a host you allowed. The workspace is readable, so allowing a host anyone can upload to allows exfiltration.
|
|
85
|
+
- Kernel exploits. The sandbox shares the host kernel. It contains mistakes and injection-driven overreach, and it is not a boundary for
|
|
86
|
+
multi-tenant use.
|
|
87
|
+
|
|
88
|
+
**Everyday consequences**
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
flywhl chat --net-allow pypi.org --net-allow files.pythonhosted.org # let the model install packages
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Commands that need your credentials (`git push` over SSH, `gh`, `kubectl`) fail in the sandbox. Run them in your own terminal, or let the
|
|
95
|
+
model request `outside_sandbox: true`, a separate human gate that always needs a person: `auto_approve`, rules and session grants cannot
|
|
96
|
+
pass it, and unattended runs are refused. `.git/hooks` and `.git/config` are read-only in the sandbox, so `git add` and `git commit` work
|
|
97
|
+
while `git config`, `git remote add` and `git push -u` do not.
|
|
98
|
+
|
|
99
|
+
Details: [workspace and sandbox](https://flywhlway.github.io/flywhl-harness/en/governance/workspace-sandbox) ·
|
|
100
|
+
[behavior changes in 0.2.0](https://github.com/flywhlway/flywhl-harness/blob/main/CHANGELOG.md)
|
|
101
|
+
|
|
102
|
+
## Use as a library
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
import asyncio
|
|
106
|
+
from typing import Annotated
|
|
107
|
+
from flywhl_harness import Harness, HarnessConfig, HookEvent, HookOutput, Effect, tool
|
|
108
|
+
|
|
109
|
+
@tool(effect=Effect.READ)
|
|
110
|
+
async def weather(city: Annotated[str, "City name"]) -> str:
|
|
111
|
+
"""Look up the weather for a city."""
|
|
112
|
+
return f"{city}: sunny, 23°C"
|
|
113
|
+
|
|
114
|
+
async def main():
|
|
115
|
+
async with Harness(HarnessConfig.load(workspace=".")) as h:
|
|
116
|
+
h.tools.register(weather)
|
|
117
|
+
|
|
118
|
+
@h.hooks.on(HookEvent.PRE_TOOL_USE, matcher="bash")
|
|
119
|
+
async def no_rm(inp):
|
|
120
|
+
if "rm " in inp.tool_input["command"]:
|
|
121
|
+
return HookOutput(decision="deny", reason="Deleting files is not allowed")
|
|
122
|
+
|
|
123
|
+
result = await h.run("What's the weather in Shanghai?", on_event=print)
|
|
124
|
+
print(result.status, result.text)
|
|
125
|
+
|
|
126
|
+
asyncio.run(main())
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Project status
|
|
130
|
+
|
|
131
|
+
Beta. The supported use is a local, single-user developer tool. It is not built for multi-user or multi-tenant service, and the console
|
|
132
|
+
should not be exposed beyond loopback.
|
|
133
|
+
|
|
134
|
+
- CI runs the offline suite on Linux (Python 3.11–3.13) and macOS, runs the sandbox tests for real on both, and installs the wheel into a
|
|
135
|
+
clean environment to verify the sandbox boundaries end to end.
|
|
136
|
+
- 33 live scenarios against real models live in `tests/live`. They cost money and need keys, so they run outside CI, in a disposable container.
|
|
137
|
+
- Open security gaps are listed rather than hidden. Workspace files are not yet a taint source, and project instruction files still reach
|
|
138
|
+
the system prompt. See the [security chapter](https://flywhlway.github.io/flywhl-harness/en/governance/security) and the
|
|
139
|
+
[readiness review](https://github.com/flywhlway/flywhl-harness/blob/main/docs/quality-audit/release-readiness.md).
|
|
140
|
+
- The [first benchmark](https://flywhlway.github.io/flywhl-harness/en/benchmark/) covers 5 models and 30 small tasks at one commit. It is evidence for the harness mechanisms, not a claim about task success rates in real work.
|
|
141
|
+
|
|
142
|
+
## Documentation
|
|
143
|
+
|
|
144
|
+
- [English docs](https://flywhlway.github.io/flywhl-harness/en/): quick start, architecture, and a summary of all 30 chapters
|
|
145
|
+
- [Chinese tutorial](https://flywhlway.github.io/flywhl-harness/): the full text
|
|
146
|
+
- [Changelog](https://github.com/flywhlway/flywhl-harness/blob/main/CHANGELOG.md)
|
|
147
|
+
|
|
148
|
+
To read the code or run the examples and tests, clone the repository. The examples are not in the wheel.
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
git clone https://github.com/flywhlway/flywhl-harness && cd flywhl-harness
|
|
152
|
+
uv sync --all-extras
|
|
153
|
+
.venv/bin/python examples/11_kill9_cli_recover.py # kill -9 a run, take over from another process, verify 7 invariants
|
|
154
|
+
.venv/bin/python -m pytest -q # runs inside an OS sandbox, in two stages
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
MIT License
|