Metadata-Version: 2.3
Name: singleyunn-harness
Version: 0.1.0
Summary: A safe, observable, extensible Python agent harness under staged development.
Author: singleyunn
License: MIT License
         
         Copyright (c) 2026 singleyunn
         
         Permission is hereby granted, free of charge, to any person obtaining a copy
         of this software and associated documentation files (the "Software"), to deal
         in the Software without restriction, including without limitation the rights
         to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         copies of the Software, and to permit persons to whom the Software is
         furnished to do so, subject to the following conditions:
         
         The above copyright notice and this permission notice shall be included in all
         copies or substantial portions of the Software.
         
         THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
         SOFTWARE.
Requires-Dist: jsonschema>=4.21
Requires-Python: >=3.12
Project-URL: Homepage, https://github.com/xizhiyun1995-netizen/singleyunn-harness
Project-URL: Repository, https://github.com/xizhiyun1995-netizen/singleyunn-harness
Project-URL: Issues, https://github.com/xizhiyun1995-netizen/singleyunn-harness/issues
Project-URL: Documentation, https://github.com/xizhiyun1995-netizen/singleyunn-harness#readme
Project-URL: Security, https://github.com/xizhiyun1995-netizen/singleyunn-harness/security/advisories
Description-Content-Type: text/markdown

# SingleYunn Harness

> 状态：探索期 · 里程碑 4（公共 API）✅ 已完成并验收通过（2026-08-10） · GitHub 测试版准备中 · 详见 [PROJECT_STATE.md](PROJECT_STATE.md)

项目地址：[GitHub](https://github.com/xizhiyun1995-netizen/singleyunn-harness) · [Issues](https://github.com/xizhiyun1995-netizen/singleyunn-harness/issues) · [Security](https://github.com/xizhiyun1995-netizen/singleyunn-harness/security/advisories)

[![Python](https://img.shields.io/badge/python-3.12%2B-blue)](https://www.python.org/)

这是一个实验性项目，当前版本已完成公共 API 与开源准备，但仍属于 GitHub 测试版候选，不应视为生产级稳定发行版。

本地 Python Agent Harness 的隔离学习与开发子项目。目标顺序：

1. 先做出一个自己能实际使用的**安全、可观察**的最小 Harness；
2. 根据真实使用反馈持续优化；
3. 稳定后提炼公共核心，整理为可开源、可安装使用的项目。

## 里程碑 2 已实现（2026-08-10）

- 独立策略引擎：读=允许；新建/修改=确认；删除/工作区外=拒绝；配置只能收紧权限
- 5 个工具：里程碑 1 的 `list_files` / `read_file` / `search_text`，新增 `write_file` / `delete_file`（删除硬拒绝且底层不可执行）
- CLI 确认：`y` 单次批准、`a` 将同一操作+解析后路径加入本次运行白名单；非交互模式安全拒绝
- `--approval prompt|deny|allow`；显式 `allow` 也不能覆盖删除和工作区外拒绝
- 写入使用同目录临时文件 + 原子替换；确认期间目标状态变化会使批准失效
- 路径防护覆盖逃逸、符号链接、硬链接、Windows 设备名 / ADS 与结构化参数注入
- JSONL Trace 记录策略、确认、批准失效，并用 `tool_call_id` 关联调用
- 保留里程碑 1 的 8 Turn、连续 3 次错误停止、固定 Evals 与 OpenAI 兼容模型接入
- 用户在 Windows PowerShell 中实际选择 `y` 批准新建，`note.txt` 内容与 Trace 均核验通过

设计见 [docs/design-brief.md](docs/design-brief.md)，验收见 [MILESTONES.md](MILESTONES.md)。

## 里程碑 3 已实现（2026-08-10，历史记录）

- 会话持久化显式开启；未传 `--session` 的现有运行保持临时会话
- v1 独立追加式 JSONL 保存完整状态检查点，连续序号、SHA-256 链、单记录/总量限制与 `fsync` 防止静默损坏
- sidecar 路径锁 + 会话文件身份锁阻止同一路径、硬链接和 Windows 8.3 别名并发恢复
- 恢复命令再次提供仓库路径，并核对目录身份、模型、系统提示、策略、工具 Schema 和停止上限
- 中断或结果未知的工具不自动重放；旧批准与本次进程白名单不恢复
- 最后一个撕裂尾部先隔离再恢复；内部损坏、未知版本、序号/校验链异常硬失败
- 精确恢复上下文、累计 Turn 与连续错误；v1 固定未压缩 generation 0，不实现跨会话长期记忆
- 合成 v0 → v1 以新文件迁移并复读校验；现有 Trace 不视为旧会话
- Trace 延迟到首个事件才截断，并拒绝会话别名、符号/硬链接、Windows ADS；读工具拒绝常见凭据文件
- 146 个自动测试通过（5 skipped），`deepseek-v4-flash-free` 固定 Evals 10/10 通过，最终独立 Reviewer 无剩余阻塞项

详细取舍见 [ADR-0004](docs/decisions/0004-session-storage.md)。

## 公共 Python API

嵌入应用应从 `harness_learning` 导入以下稳定入口，而不是依赖内部模块：

- `ModelProvider` / `ModelResponse` / `ToolCall`：模型适配协议与响应类型；
- `Harness` / `HarnessConfig` / `AgentResult`：一次任务运行入口、限制和结果；结果区分 `files_read`（由 `read_file` 返回内容，可能被截断）与 `files_with_search_matches`（展示的搜索匹配涉及）；
- `ToolSpec` / `ToolRegistry`：声明和注册自定义工具；`ToolOperation`、`PolicyRequest`、`Sandbox`、`ToolPolicy`、`ApprovalHandler` 等策略与边界类型也从顶层导出；
- `OpenAICompatibleProvider`：OpenAI Chat Completions 兼容端点；`FakeModel`：离线测试。

```python
import os

from harness_learning import Harness, HarnessConfig, OpenAICompatibleProvider

harness = Harness(
    model=OpenAICompatibleProvider(
        base_url=os.environ["HARNESS_BASE_URL"],
        api_key=os.environ["HARNESS_API_KEY"],
        model=os.environ.get("HARNESS_MODEL", "deepseek-v4-flash-free"),
    ),
    repo=".",
    config=HarnessConfig(max_turns=8),
)
result = harness.run("概述这个项目，并引用实际读取的文件。")
print(result.final_answer)
```

`Harness` 默认把 Trace 写到当前目录的 `trace-<时间戳>.jsonl`；该模式已被 `.gitignore` 忽略，但 Trace 可能包含任务、源码和工具结果，仍不要提交。完整的离线示例见 [`examples/basic.py`](examples/basic.py)，自定义只读工具见 [`examples/custom_tool.py`](examples/custom_tool.py)。写工具仍必须经过策略和确认边界；`Harness` 不提供绕过权限的快捷入口。

## 快速开始

```bash
uv sync --managed-python          # 安装依赖（含 jsonschema）

# 配置模型（Key 只写 .env，已 gitignore）：
#   HARNESS_BASE_URL=https://opencode.ai/zen/v1
#   HARNESS_API_KEY=<你的 opencode Key>
#   HARNESS_MODEL=deepseek-v4-flash-free

uv run harness run evals/sample "这个仓库是做什么的？"
uv run harness run <仓库> "创建 note.txt"          # 默认交互确认：y / a / N
uv run harness run <仓库> "创建 note.txt" --approval deny   # 所有写确认均拒绝
uv run harness run <仓库> "创建 note.txt" --approval allow  # 预批准新建/修改；删/越界仍拒绝

# 显式持久会话：SESSION 必须位于被分析仓库之外，且不得已存在
uv run harness run evals/sample "分析项目" --session ./demo.session.jsonl
# resume 只恢复 SESSION 中已有的中断任务；终态会话只重放结果，不接受后续问题
uv run harness resume evals/sample ./demo.session.jsonl --approval prompt
uv run harness migrate-session ./legacy-v0.jsonl ./migrated.session.jsonl

uv run harness eval               # 10 个固定任务，真实模型
uv run harness eval --fake        # FakeModel 冒烟（不验收）
uv run python -m unittest discover -s tests -v   # 测试
```

> 会话文件明文保存任务、模型消息和工具结果，可能包含源码或用户主动提供的秘密；请放在受保护的本地路径，按需留存。确认白名单、批准结果和 Harness 自身配置的 API Key 不会恢复或作为会话元数据保存。

## 目录结构

```text
harness-learning/
├── AGENTS.override.md    # 本子项目稳定规则
├── README.md             # 定位、结构、文档导航
├── MILESTONES.md         # 里程碑节点与完成门槛（会话切换依据）
├── PROJECT_STATE.md      # 当前状态：目标/决策/进度/下一步
├── pyproject.toml / uv.lock / .python-version
├── .env                  # 本地模型配置（gitignore，不入库）
├── examples/             # 最小 Python API 示例
├── LICENSE               # MIT 许可证
├── SECURITY.md           # 安全边界与漏洞披露说明
├── src/harness_learning/ # 源码包
│   ├── cli.py            # CLI：run / resume / migrate-session / eval
│   ├── agent_loop.py     # 主循环、恢复与副作用检查点
│   ├── tools.py          # 工具声明/校验/底层执行
│   ├── policy.py         # 允许/确认/拒绝策略与确认协议
│   ├── sandbox.py        # 路径沙箱
│   ├── trace.py          # 非权威 JSONL Trace
│   ├── session_storage.py # v1 会话状态、锁、恢复与 v0 迁移
│   ├── model_provider.py # OpenAI 兼容客户端 + FakeModel
│   └── eval_runner.py    # 固定评估运行器
├── tests/                # 自动化测试（维护迭代 5 后更新，当前 5 个 Windows 能力相关用例跳过）
├── docs/
│   ├── design-brief.md   # 一页式设计（已确认）
│   └── decisions/        # ADR-0001～0005（范围、模型、策略、会话、公共 API）
└── evals/
    ├── sample/           # 自建固定样本库 samplelib
    ├── tasks.json        # 10 个固定任务 + 通过标准
    └── README.md         # 评估说明
```

## CLI 稳定契约

当前 CLI 命令为 `run`、`resume`、`migrate-session` 和 `eval`。成功返回 0，配置、路径或模型错误返回 2；`eval` 在有任务失败时返回 1。`run` 默认临时会话，只有显式 `--session` 才落盘。真实模型配置从 `HARNESS_BASE_URL`、`HARNESS_MODEL`、`HARNESS_API_KEY` 或 `.env` 读取；出于安全原因不支持 `--api-key`。写入确认使用 `--approval prompt|deny|allow`，非交互的 `prompt` 会安全拒绝。

`resume` 只恢复会话文件中已有的任务，不接受新任务或后续问题；已结束的会话会重放保存的结果。不存在的会话路径会在创建锁文件前失败。`run` 和 `resume` 完成后分别显示由 `read_file` 返回内容的去重文件列表，以及 `search_text` 返回的展示匹配所涉及的文件；两者都没有时显示非阻断提示。公共 API 的稳定导入路径是 `harness_learning` 顶层导出；`agent_loop`、`policy`、`session_storage` 和 `trace` 是高级/内部实现模块，可能随后续版本调整。当前版本承诺 Python 3.12+ 的源码兼容，不承诺会话格式跨未来主版本兼容。

## 安全说明

- 默认沙箱限制在传入仓库内；越界、删除和未分类工具请求会被拒绝。
- 读取会跳过二进制文件和常见凭据文件，但不能识别用户主动粘贴或非常规格式中的所有秘密。
- 写入默认需要确认，使用同目录临时文件和原子替换；不要对不受信任的模型使用 `--approval allow`。
- Trace 是诊断日志；显式会话是更敏感的明文 JSONL，可能包含任务、源码和工具结果。请将它们放在受保护路径并按需清理。
- API Key 只能通过环境变量或 `.env` 提供；CLI 不接受 `--api-key`，不要提交密钥。网络模型调用可能把上下文发送到配置的服务商。
- `--fake` 仅使用确定性的离线 FakeModel 做流程冒烟，不代表真实模型的对话或分析能力；它会明确声明自己是测试模型。真实模型的配置标识会作为未验证的配置元数据提供给模型，Harness 不猜测底层身份。
- 本项目不执行 shell 命令，不支持目录创建、补丁合并或持久化批准白名单。

发现安全问题请不要公开发布利用细节，先按 [SECURITY.md](SECURITY.md) 提交最小复现和影响范围。

## 环境要求

- [uv](https://docs.astral.sh/uv/)（本机 v0.11.19）
- Python 3.12（由 uv 管理，见 `.python-version`）
- 真实模型需 opencode API Key（免费档 `deepseek-v4-flash-free` 亦可）

## 许可证与发布状态

代码以 [MIT License](LICENSE) 提供。当前已完成 GitHub 测试版所需的本地材料和构建检查，尚未外部发布；发布动作需要单独授权。

## 文档导航

- 里程碑节点与完成门槛：[MILESTONES.md](MILESTONES.md)
- 当前状态：[PROJECT_STATE.md](PROJECT_STATE.md)
- 架构决策记录：[docs/decisions/](docs/decisions/)（ADR-0001～0005）
- 根工作区定义与规则：`D:\singleyunn\README.md` 与 `D:\singleyunn\AGENTS.md`

## 决策记录

| ADR | 决策 | 状态 |
|---|---|---|
| [0001](docs/decisions/0001-stage1-scope.md) | 阶段 1 范围与默认起点 | 已确认 |
| [0002](docs/decisions/0002-model-provider.md) | OpenCode Zen 的 OpenAI 兼容模型接入 | 已确认 |
| [0003](docs/decisions/0003-tool-policy.md) | 工具策略、单调权限配置与 CLI 确认边界 | 已确认 |
| [0004](docs/decisions/0004-session-storage.md) | 显式会话存储、恢复绑定、崩溃边界与迁移 | 已确认 |
| [0005](docs/decisions/0005-public-api.md) | 里程碑 4 公共 API 与开源准备 | 已确认 |

## 当前阶段非目标

- 不做多 Agent、MCP、长期记忆
- 不提前搭建多模型委员会
- 不接入付费 API 或外部发布
