Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 14 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@

**Minimal AI agent harness — wrap any LLM with tools and a streaming CLI.**

> 中文一句话:极小的 AI Agent 执行环境(harness),给任意 LLM 配上工具、对话循环、流式事件与 CLI 会话。约 1,100 行、零魔法、一个依赖,是 1998x-stack harness 家族的最小基线。
> 中文一句话:极小的 AI Agent 执行环境(harness),给 LLM 配上工具、对话循环、流式事件与 CLI 会话。强调可读性、显式状态和可测试的执行契约,是 1998x-stack harness 家族的最小基线。

[![Python](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org/)
[![Python tests](https://github.com/1998x-stack/tiny-harness/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/1998x-stack/tiny-harness/actions/workflows/ci.yml)
[![Lines](https://img.shields.io/badge/code-1%2C350%20lines-lightgrey)](.)
[![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)

`tiny-harness` is a ~1,100-line Python package that turns an LLM into an AI agent — giving it tools, a conversation loop, streaming events, and a CLI session. One dependency. One import.
`tiny-harness` is a small Python agent runtime with tools, a conversation loop, streaming events, and CLI sessions. It uses explicit contracts for tool validation, filesystem paths, and success/failure status.

```bash
pip install tiny-harness
Expand Down Expand Up @@ -49,7 +48,7 @@ tiny-harness "Create hello.py"
- **Rich TUI** — optional `--tui` mode with panels, markdown rendering, color-coded messages (`pip install tiny-harness[tui]`)
- **Persistence** — JSONL session history with `/save` and `/history` commands
- **Filesystem guard** — workspace boundary enforcement, path traversal protection
- **1,125 lines** — readable top-to-bottom, CleanRL-inspired code style
- **Inspectable contracts** — explicit tool risk classification, workspace path declarations, and JSON Schema Draft 2020-12 validation

## Quick Start

Expand All @@ -68,13 +67,13 @@ pip install tiny-harness
export ANTHROPIC_API_KEY="sk-ant-..."

# Run a single prompt
tiny-harness "Create a hello.py file that prints 'Hello, world!'"
tiny-harness --provider anthropic "Create a hello.py file that prints 'Hello, world!'"
```

### Session Mode

```bash
tiny-harness --model claude-sonnet-4-20250514 --skills files
tiny-harness --provider anthropic --model claude-sonnet-4-20250514 --skills files
```

```
Expand All @@ -96,7 +95,7 @@ Session commands: `/exit`, `/help`, `/tools`, `/clear`.

```bash
pip install tiny-harness[tui]
tiny-harness --tui --model claude-sonnet-4-20250514 --skills files
tiny-harness --tui --provider anthropic --model claude-sonnet-4-20250514 --skills files
```

```
Expand Down Expand Up @@ -207,7 +206,7 @@ agent.tools.register_from_def(
description="Get current weather for a city.",
parameters={"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]},
),
handler=lambda city: f"Sunny, 22°C in {city}",
handler=lambda args: "Sunny, 22°C in " + args["city"],
)

# Load built-in skills
Expand Down Expand Up @@ -236,12 +235,14 @@ agent.on("error", lambda e: print(f"Error: {e.message}"))
| `max_consecutive_errors` | `3` | Consecutive error budget |
| `timeout_ms` | `30_000` | Tool execution timeout |
| `max_tool_result_chars` | `50_000` | Truncate large results |
| `context_window` | `200_000` | Input + reserved output window (heuristic estimator) |
| `max_tokens` | `16_384` | Reserve for model output; must be less than context_window |

## Providers

### Anthropic (default)
### Anthropic (explicit)
```python
config = Config(model="claude-sonnet-4-20250514", api_key="...", workspace=".")
config = Config(model="claude-sonnet-4-20250514", api_key="...", workspace=".", provider="anthropic")
```

### OpenAI
Expand Down Expand Up @@ -290,6 +291,8 @@ Full design documentation in [`docs/`](docs/):
- [cli.md](docs/cli.md) — CLI design, streaming format
- [skills.md](docs/skills.md) — skill system architecture
- [code-style.md](docs/code-style.md) — coding conventions
- [TOOL_FACTORY.md](docs/TOOL_FACTORY.md) — JSON Schema and filesystem path contract for custom tools
- [TOOL_OUTCOME.md](docs/TOOL_OUTCOME.md) — typed tool execution success/failure semantics
- [adr/](docs/adr/) — architectural decision records

## Development
Expand All @@ -314,7 +317,7 @@ The built-in Shell skill requires human approval by default. In `--no-hitl` mode

## Security

The built-in Shell skill is **not** an OS sandbox. Read [security boundaries](docs/security.md) and the [phased optimization status](docs/OPTIMIZATION_STATUS.md) before deploying to untrusted environments.
Tool arguments are checked with JSON Schema Draft 2020-12 and declared filesystem paths are normalized before tool execution. **These checks are not an OS sandbox**. Read [security boundaries](docs/security.md) and the [phased optimization status](docs/OPTIMIZATION_STATUS.md) before deploying to untrusted environments.

## License

Expand Down
4 changes: 3 additions & 1 deletion docs/OPTIMIZATION_STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
| Phase 1 | [#16](https://github.com/1998x-stack/tiny-harness/pull/16) | Anthropic SSE 截断检测、message_stop 校验、usage/finish、可配置网关 | PR CI 通过;真实 API 冒烟仍待执行 |
| Phase 2 | [#18](https://github.com/1998x-stack/tiny-harness/pull/18) | ToolOutcome 显式成功/失败契约,文件、搜索、Shell 异常传给 ErrorBudget | PR CI 通过;低层直接调用保留字符串兼容 |
| Phase 1 | [#19](https://github.com/1998x-stack/tiny-harness/pull/19) | OpenAI/Anthropic 流式请求在首次输出前重试 429/5xx/部分网络错误,Retry-After 上限;输出后禁重试 | PR CI 通过;真实 API 冒烟仍待执行 |
| Phase 4 | [#21](https://github.com/1998x-stack/tiny-harness/pull/21) | ToolDef 显式 path_args、所有声明路径规范化、第三方兼容字段全部检查 | PR CI 通过;仍非 OS 沙箱 |
| Phase 4 | [#22](https://github.com/1998x-stack/tiny-harness/pull/22) | Draft 2020-12 JSON Schema(嵌套/组合/数组),注册时检查 Schema,拒绝远程引用 | PR CI 通过;引入 jsonschema 运行时依赖 |

## 待完成事项(不应标记为已修复)

Expand All @@ -38,7 +40,7 @@
### P2:持续质量提升

- 文件工具:有界读取、二进制识别、原子写、拒绝默认覆盖与 edit_file。
- ToolRegistry:完整 JSON Schema 与 path 参数契约;消除静态白名单。
- ToolRegistry:JSON Schema 与显式 path 参数契约已落地;进一步清理过渡期遗留工具名称兼容映射,完善工具能力策略与插件迁移。
- EventBus:事件成功状态、耗时与稳定的 finish/usage 契约。
- 错误预算与循环检测:区分失败重复与成功的合法重复,避免错误的长期封禁。
- Session:旧格式兼容迁移、增量写入、CLI `/resume`、大结果 sidecar 与索引。
Expand Down
69 changes: 69 additions & 0 deletions docs/TOOL_FACTORY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Tool Factory Contract / 工具定义与执行契约

工具不是可信任的函数调用。模型输出、工具参数、Skill 文本以及外部内容都应被视作不可信输入。

## 1. 工具注册

以下示例展示如何为插件定义类型、文件路径和失败语义:

```python
from tiny_harness import ToolDef
from tiny_harness._tool_outcome import ToolOutcome

def inspect_manifest(args: dict) -> ToolOutcome:
# args["manifest_path"] 已由 ToolExecutor 规范化为 workspace 内绝对路径。
with open(args["manifest_path"], encoding="utf-8") as f:
return ToolOutcome.ok(f.read())

agent.tools.register_from_def(
ToolDef(
name="inspect_manifest",
description="Read a manifest from this agent's workspace",
risk_level="read_only",
path_args=("manifest_path",),
parameters={
"type": "object",
"additionalProperties": False,
"properties": {
"manifest_path": {"type": "string", "minLength": 1},
},
"required": ["manifest_path"],
},
),
inspect_manifest,
)
```

`path_args` 用于声明路径语义,不限制字段名称为 `path`。多个位置必须全部列出,例如 `("source", "destination")`。每个声明字段必须是参数 Schema 中的字符串属性。旧插件的 `path/source/destination/cwd` 习惯字段仍进行兼容性检查;新代码不要依赖工具名称白名单。

## 2. JSON Schema(Draft 2020-12)

- ToolDef 创建时验证 Schema 是否合法,并要求根 Schema 表示对象。
- ToolExecutor 执行前使用标准 JSON Schema 校验:嵌套对象、数组元素、`required`、`additionalProperties`、`minLength`、范围、枚举、`oneOf` 等均生效。
- 用户审批处理函数若返回修改后的参数,会重新执行 Schema 与路径守卫。
- 支持同一 Schema 内的 `#/$defs/...` 引用;拒绝外部 URL 引用,避免执行过程中隐式获取不可信远程 Schema。
- 不会自动类型转换:`"42"` 不等于整数 `42`,布尔值不当作整数。

## 3. 风险分类与结果

| 风险等级 | 默认行为 |
|---|---|
| `safe`、`read_only` | 通常无需 HITL,但路径守卫仍生效 |
| `mutation` | 需要 HITL |
| `destructive`、`dangerous` | 每次都需要 HITL,不保留会话级批准 |
| 未指定 | 默认 `mutation` |
| 非法/未知等级 | 注册即失败 |

返回 `ToolOutcome.ok(text)` / `ToolOutcome.error(text)`,让错误预算可靠区分成功与失败。现有直接返回字符串的 handler 依旧兼容,但 **字符串内容不用于推断执行成功与否**。详见 [TOOL_OUTCOME.md](TOOL_OUTCOME.md)。

## 4. 不提供的安全保证

`FilesystemGuard` 通过规范化路径阻止显式的工作区外路径;**这不是 OS 沙箱**:不能阻止工具直接访问其他系统资源、TOCTOU 符号链接竞态,也不能限制任意 Shell 的网络/文件系统访问。部署于多租户或不可信环境时,必须另配进程、文件系统和网络隔离。

## 5. Regression checklist

- 参数缺少必需字段、数组/嵌套类型不符,handler 完全不执行。
- 受保护路径的相对值按 `AgentConfig.workspace` 解释,不能按调用 Python 进程的当前工作目录解释。
- 任意多路径中只要有一个越界,整个工具调用即失败。
- HITL 修改过的参数重新校验,不允许绕过风险和路径守卫。
- 任何新工具注册时应具备明确风险分类、Schema、`path_args`(如涉及路径)及成功/失败结果契约。
Loading