python-agent-harness 1.5.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.
- python_agent_harness-1.5.0/LICENSE +21 -0
- python_agent_harness-1.5.0/PKG-INFO +251 -0
- python_agent_harness-1.5.0/README.md +230 -0
- python_agent_harness-1.5.0/pyproject.toml +80 -0
- python_agent_harness-1.5.0/python_agent_harness/__init__.py +20 -0
- python_agent_harness-1.5.0/python_agent_harness/__main__.py +5 -0
- python_agent_harness-1.5.0/python_agent_harness/agent.py +703 -0
- python_agent_harness-1.5.0/python_agent_harness/cli.py +273 -0
- python_agent_harness-1.5.0/python_agent_harness/client.py +832 -0
- python_agent_harness-1.5.0/python_agent_harness/commands.py +181 -0
- python_agent_harness-1.5.0/python_agent_harness/config.py +464 -0
- python_agent_harness-1.5.0/python_agent_harness/context_manager.py +100 -0
- python_agent_harness-1.5.0/python_agent_harness/diffrender.py +84 -0
- python_agent_harness-1.5.0/python_agent_harness/mcp/__init__.py +21 -0
- python_agent_harness-1.5.0/python_agent_harness/mcp/client.py +161 -0
- python_agent_harness-1.5.0/python_agent_harness/mcp/config.py +130 -0
- python_agent_harness-1.5.0/python_agent_harness/mcp/manager.py +290 -0
- python_agent_harness-1.5.0/python_agent_harness/models.py +149 -0
- python_agent_harness-1.5.0/python_agent_harness/persistence.py +297 -0
- python_agent_harness-1.5.0/python_agent_harness/planmode.py +112 -0
- python_agent_harness-1.5.0/python_agent_harness/prompts/agent.md +362 -0
- python_agent_harness-1.5.0/python_agent_harness/prompts/build-switch.md +5 -0
- python_agent_harness-1.5.0/python_agent_harness/prompts/commands/explain.md +13 -0
- python_agent_harness-1.5.0/python_agent_harness/prompts/compact.md +33 -0
- python_agent_harness-1.5.0/python_agent_harness/prompts/initialize.md +66 -0
- python_agent_harness-1.5.0/python_agent_harness/prompts/plan-mode.md +70 -0
- python_agent_harness-1.5.0/python_agent_harness/prompts/plan.md +26 -0
- python_agent_harness-1.5.0/python_agent_harness/prompts/review.md +100 -0
- python_agent_harness-1.5.0/python_agent_harness/prompts/subagent.md +208 -0
- python_agent_harness-1.5.0/python_agent_harness/prompts/summary.md +11 -0
- python_agent_harness-1.5.0/python_agent_harness/prompts/task-completion-rules.md +50 -0
- python_agent_harness-1.5.0/python_agent_harness/prompts/title.md +44 -0
- python_agent_harness-1.5.0/python_agent_harness/prompts.py +498 -0
- python_agent_harness-1.5.0/python_agent_harness/session.py +781 -0
- python_agent_harness-1.5.0/python_agent_harness/subagent.py +61 -0
- python_agent_harness-1.5.0/python_agent_harness/token_estimator.py +125 -0
- python_agent_harness-1.5.0/python_agent_harness/tool_runner.py +247 -0
- python_agent_harness-1.5.0/python_agent_harness/tools/__init__.py +56 -0
- python_agent_harness-1.5.0/python_agent_harness/tools/agent_tool.py +75 -0
- python_agent_harness-1.5.0/python_agent_harness/tools/base.py +147 -0
- python_agent_harness-1.5.0/python_agent_harness/tools/bash.py +298 -0
- python_agent_harness-1.5.0/python_agent_harness/tools/edit.py +272 -0
- python_agent_harness-1.5.0/python_agent_harness/tools/filesystem.py +180 -0
- python_agent_harness-1.5.0/python_agent_harness/tools/glob.py +161 -0
- python_agent_harness-1.5.0/python_agent_harness/tools/grep.py +149 -0
- python_agent_harness-1.5.0/python_agent_harness/tools/insert.py +61 -0
- python_agent_harness-1.5.0/python_agent_harness/tools/mcp.py +203 -0
- python_agent_harness-1.5.0/python_agent_harness/tools/mkdir.py +30 -0
- python_agent_harness-1.5.0/python_agent_harness/tools/planexit.py +45 -0
- python_agent_harness-1.5.0/python_agent_harness/tools/question.py +70 -0
- python_agent_harness-1.5.0/python_agent_harness/tools/read.py +104 -0
- python_agent_harness-1.5.0/python_agent_harness/tools/skill.py +32 -0
- python_agent_harness-1.5.0/python_agent_harness/tools/todo.py +60 -0
- python_agent_harness-1.5.0/python_agent_harness/tools/write.py +56 -0
- python_agent_harness-1.5.0/python_agent_harness/tui/__init__.py +68 -0
- python_agent_harness-1.5.0/python_agent_harness/tui/commands.py +652 -0
- python_agent_harness-1.5.0/python_agent_harness/tui/core.py +385 -0
- python_agent_harness-1.5.0/python_agent_harness/tui/input.py +412 -0
- python_agent_harness-1.5.0/python_agent_harness/tui/render.py +535 -0
- python_agent_harness-1.5.0/python_agent_harness.egg-info/PKG-INFO +251 -0
- python_agent_harness-1.5.0/python_agent_harness.egg-info/SOURCES.txt +86 -0
- python_agent_harness-1.5.0/python_agent_harness.egg-info/dependency_links.txt +1 -0
- python_agent_harness-1.5.0/python_agent_harness.egg-info/entry_points.txt +2 -0
- python_agent_harness-1.5.0/python_agent_harness.egg-info/requires.txt +14 -0
- python_agent_harness-1.5.0/python_agent_harness.egg-info/top_level.txt +1 -0
- python_agent_harness-1.5.0/setup.cfg +4 -0
- python_agent_harness-1.5.0/tests/test_agents_md.py +260 -0
- python_agent_harness-1.5.0/tests/test_cli.py +463 -0
- python_agent_harness-1.5.0/tests/test_client.py +1694 -0
- python_agent_harness-1.5.0/tests/test_commands.py +119 -0
- python_agent_harness-1.5.0/tests/test_concurrent_subagents.py +378 -0
- python_agent_harness-1.5.0/tests/test_config.py +468 -0
- python_agent_harness-1.5.0/tests/test_context_rules.py +133 -0
- python_agent_harness-1.5.0/tests/test_diffrender.py +53 -0
- python_agent_harness-1.5.0/tests/test_filesystem.py +1216 -0
- python_agent_harness-1.5.0/tests/test_invariants.py +1271 -0
- python_agent_harness-1.5.0/tests/test_mcp.py +825 -0
- python_agent_harness-1.5.0/tests/test_models.py +130 -0
- python_agent_harness-1.5.0/tests/test_persistence.py +347 -0
- python_agent_harness-1.5.0/tests/test_planmode.py +238 -0
- python_agent_harness-1.5.0/tests/test_prompts.py +444 -0
- python_agent_harness-1.5.0/tests/test_scenarios.py +681 -0
- python_agent_harness-1.5.0/tests/test_session.py +1048 -0
- python_agent_harness-1.5.0/tests/test_subagent.py +169 -0
- python_agent_harness-1.5.0/tests/test_subagent_isolation.py +402 -0
- python_agent_harness-1.5.0/tests/test_todos_scope.py +111 -0
- python_agent_harness-1.5.0/tests/test_token_estimator.py +104 -0
- python_agent_harness-1.5.0/tests/test_tools_misc.py +509 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 陈虎明
|
|
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,251 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: python-agent-harness
|
|
3
|
+
Version: 1.5.0
|
|
4
|
+
Summary: Python agent execution harness: agent loop, tools, plan/build modes, sessions
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Dist: rich>=13.0
|
|
9
|
+
Requires-Dist: httpx>=0.27
|
|
10
|
+
Requires-Dist: prompt_toolkit>=3.0
|
|
11
|
+
Provides-Extra: mcp
|
|
12
|
+
Requires-Dist: mcp<3,>=2.0; extra == "mcp"
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
15
|
+
Requires-Dist: pyright>=1.1.380; extra == "dev"
|
|
16
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
17
|
+
Requires-Dist: coverage[toml]>=7.0; extra == "dev"
|
|
18
|
+
Requires-Dist: pip-audit>=2.7; extra == "dev"
|
|
19
|
+
Requires-Dist: mcp<3,>=2.0; extra == "dev"
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
<div align="center">
|
|
23
|
+
|
|
24
|
+
# python-agent-harness
|
|
25
|
+
|
|
26
|
+
**A lightweight Python coding-agent harness for reliable autonomous coding.**
|
|
27
|
+
FSM-driven execution · OpenAI-compatible · built for daily use and easy customization
|
|
28
|
+
|
|
29
|
+
[](https://github.com/beacoder/python-agent-harness/actions/workflows/ci.yml)
|
|
30
|
+
[](https://www.python.org/downloads/)
|
|
31
|
+
[](LICENSE)
|
|
32
|
+
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
A terminal coding agent that reads your codebase, plans changes, edits files, runs commands, and verifies its work.
|
|
36
|
+
|
|
37
|
+
`python-agent-harness` is inspired by [gptel-agent-harness](https://github.com/beacoder/gptel-agent-harness) and [opencode](https://github.com/anomalyco/opencode). It brings opencode's prompts and core behaviors—such as `AGENTS.md` discovery, plan/build modes, skills, sub-agents, and todo tracking—into a lightweight Python implementation with only **three runtime dependencies**:
|
|
38
|
+
|
|
39
|
+
- `rich`
|
|
40
|
+
- `httpx`
|
|
41
|
+
- `prompt_toolkit`
|
|
42
|
+
|
|
43
|
+
It works with any **OpenAI-compatible API** and is designed to be easy to inspect, customize, and use for everyday software development.
|
|
44
|
+
|
|
45
|
+
## Demo
|
|
46
|
+
|
|
47
|
+

|
|
48
|
+
|
|
49
|
+
## Quick start
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
git clone git@github.com:beacoder/python-agent-harness.git
|
|
53
|
+
cd python-agent-harness
|
|
54
|
+
|
|
55
|
+
make install
|
|
56
|
+
. venv/bin/activate
|
|
57
|
+
|
|
58
|
+
python-agent-harness config --init
|
|
59
|
+
python-agent-harness run
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Edit `~/.config/python-agent-harness/config.json` and set your `base_url`, `api_key`, and `model`.
|
|
63
|
+
|
|
64
|
+
Optional extras:
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
pip install -e ".[mcp]" # MCP server integration
|
|
68
|
+
pip install -e ".[dev]" # development tools
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Features
|
|
72
|
+
|
|
73
|
+
- **FSM-driven execution** — explicit `WAIT` / `TOOL` / `TRET` / `SUPERVISE` / `DONE` / `ERRS` / `ABRT` states. Completion supervision nudges the model when it stops early, while failed tool calls are sanitized so they never strand the agent. Transient API failures (`429` / `5xx`) retry with exponential backoff and jitter.
|
|
74
|
+
- **Context management** — CJK-aware token estimation, per-model context windows, and automatic compaction at 70% usage.
|
|
75
|
+
- **Coding tools** — `Agent`, `TodoWrite`, `Glob`, `Grep`, `Read`, `Insert`, `Edit` (including unified diffs), `Write`, `Mkdir`, `Bash`, `Skill`, `Question`, and `PlanExit`. Synchronous tools execute sequentially; asynchronous tools such as `Bash` and `Agent` can run concurrently while preserving emitted order.
|
|
76
|
+
- **Plan / Build modes** — plan mode is read-only except for the per-session plan file.
|
|
77
|
+
- **Persistent sessions** — sessions are automatically saved after every response to `~/.local/share/python-agent-harness/sessions/`, with LLM-generated titles and support for `/restore --latest` and `/sessions`.
|
|
78
|
+
- **Focused TUI** — a Rich-based interface with a pinned status bar, Todos panel, inline red/green diff rendering for `Edit` and `Write`, and a `prompt_toolkit` editor with history and completion. `Esc+Enter` submits, `Ctrl-D` quits, and `Ctrl-C` cancels without leaving the application.
|
|
79
|
+
- **MCP support** — optional MCP integration through the `[mcp]` extra. MCP tools become ordinary agent tools such as `mcp__<server>__<tool>`. Supports `stdio`, `streamable-http`, and `sse` transports.
|
|
80
|
+
- **Slash commands** — built-in `/init`, `/review`, `/explain`, and other commands, plus custom commands loaded from `prompts/commands/*.md`.
|
|
81
|
+
|
|
82
|
+
## Inspired by opencode
|
|
83
|
+
|
|
84
|
+
Most of [opencode](https://github.com/anomalyco/opencode)'s prompts and core behaviors have been ported to this project. The goal is to retain its practical coding-agent workflow while keeping the implementation small, dependency-light, and easy to customize.
|
|
85
|
+
|
|
86
|
+
### Prompt and behavior mapping
|
|
87
|
+
|
|
88
|
+
The following opencode prompts have corresponding implementations in `python-agent-harness`:
|
|
89
|
+
|
|
90
|
+
| opencode | python-agent-harness |
|
|
91
|
+
|---|---|
|
|
92
|
+
| `default.txt` (main agent) | `agent.md` |
|
|
93
|
+
| `plan.txt` / `plan-mode.txt` / `build-switch.txt` | `plan.md` / `plan-mode.md` / `build-switch.md` |
|
|
94
|
+
| `task.txt` (sub-agent) | `subagent.md` + `Agent` tool |
|
|
95
|
+
| `todowrite.txt` / `question.txt` / `skill.txt` | `TodoWrite` / `Question` / `Skill` tools |
|
|
96
|
+
| `read.txt` / `write.txt` / `edit.txt` / `grep.txt` / `glob.txt` | `Read` / `Write` / `Edit` / `Grep` / `Glob` tools |
|
|
97
|
+
| `shell.txt` | `Bash` tool + `agent.md` Git/GitHub guidance |
|
|
98
|
+
| `plan-enter.txt` / `plan-exit.txt` | `PlanExit` tool |
|
|
99
|
+
| `initialize.txt` / `review.txt` / `explain` | `initialize.md` / `review.md` / `commands/explain.md` |
|
|
100
|
+
| compaction / summary / title | `compact.md` / `summary.md` / `title.md` |
|
|
101
|
+
| `AGENTS.md` handling | `prompts.py` (`find_agents_md_files`, `load_context_files`, per-file resolution) |
|
|
102
|
+
|
|
103
|
+
## Configuration
|
|
104
|
+
|
|
105
|
+
All LLM settings live in a single JSON configuration file. Environment variables are optional.
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"llm": {
|
|
110
|
+
"base_url": "https://api.openai.com/v1",
|
|
111
|
+
"api_key": "sk-...",
|
|
112
|
+
"model": "gpt-5-mini",
|
|
113
|
+
"reasoning_effort": null,
|
|
114
|
+
"stream": true
|
|
115
|
+
},
|
|
116
|
+
"models": {
|
|
117
|
+
"_comment": "Named LLM profiles for /model switching. Partial settings; unset keys inherit the main llm.",
|
|
118
|
+
"deepseek": {
|
|
119
|
+
"base_url": "https://api.deepseek.com/v1",
|
|
120
|
+
"model": "deepseek-chat"
|
|
121
|
+
},
|
|
122
|
+
"qwen": {
|
|
123
|
+
"base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
124
|
+
"model": "qwen3.5-coder"
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
"subagent_llm": {
|
|
128
|
+
"profile": null,
|
|
129
|
+
"base_url": null,
|
|
130
|
+
"api_key": null,
|
|
131
|
+
"model": null,
|
|
132
|
+
"temperature": null,
|
|
133
|
+
"max_tokens": null,
|
|
134
|
+
"timeout": null,
|
|
135
|
+
"reasoning_effort": null,
|
|
136
|
+
"stream": null
|
|
137
|
+
},
|
|
138
|
+
"paths": {
|
|
139
|
+
"context_path": null,
|
|
140
|
+
"skill_path": null
|
|
141
|
+
},
|
|
142
|
+
"mcp": {
|
|
143
|
+
"servers": {
|
|
144
|
+
"example": {
|
|
145
|
+
"transport": "stdio",
|
|
146
|
+
"command": "npx",
|
|
147
|
+
"args": [
|
|
148
|
+
"-y",
|
|
149
|
+
"@modelcontextprotocol/server-filesystem",
|
|
150
|
+
"/tmp"
|
|
151
|
+
],
|
|
152
|
+
"env": [],
|
|
153
|
+
"parallel": false,
|
|
154
|
+
"timeout": null,
|
|
155
|
+
"enabled": false
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Configuration options
|
|
163
|
+
|
|
164
|
+
- **`llm`** — main LLM configuration. Optional keys include `backend`, `temperature`, `max_tokens`, `timeout`, `reasoning_effort`, and `stream`. Values such as `reasoning_effort` are passed to the API as-is when set. `run --no-stream` overrides `stream`.
|
|
165
|
+
- **`models`** — named LLM profiles for runtime switching with `/model`. A profile is a partial settings dictionary; unset keys inherit from the main `llm`. `default` restores the main LLM configuration.
|
|
166
|
+
- **`subagent_llm`** — LLM configuration for `Agent` tool requests. Unset values inherit from the main `llm`. Set `profile` to reuse a profile from `models`. Precedence is: profile settings > explicit `subagent_llm` settings > main `llm` > environment variables.
|
|
167
|
+
- **`paths.context_path` / `paths.skill_path`** — locations from which to load context files and skills. When unset, the project-local `<project>/contexts` and `<project>/skills` directories are used.
|
|
168
|
+
- **`mcp.servers`** — MCP server configuration. Requires the `[mcp]` extra. Each server supports `transport`, `command`, `args`, `env`, `url`, `headers`, `parallel`, `timeout`, and `enabled`.
|
|
169
|
+
- **Configuration precedence** — code defaults < config file < `OPENAI_*` environment variables. Sub-agent settings also support `OPENAI_SUBAGENT_*` (`_BASE_URL`, `_API_KEY`, `_MODEL`, `_BACKEND`).
|
|
170
|
+
- **Custom config** — use `--config PATH` or `PYTHON_AGENT_HARNESS_CONFIG`.
|
|
171
|
+
- **LLM logging** — request and response bodies are logged as JSON to `/tmp/python-agent-harness-<date>-<id>.json`. Set `LLM_LOG_DIR` to change the directory. The log path is printed at startup.
|
|
172
|
+
|
|
173
|
+
## Usage
|
|
174
|
+
|
|
175
|
+
```sh
|
|
176
|
+
python-agent-harness run [project-dir]
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Launches the interactive TUI agent. If `project-dir` is omitted, the current directory is used.
|
|
180
|
+
|
|
181
|
+
### Slash commands
|
|
182
|
+
|
|
183
|
+
| Command | Description |
|
|
184
|
+
|---|---|
|
|
185
|
+
| `/plan` / `/build` | Switch between read-only plan mode and build mode |
|
|
186
|
+
| `/init` | Create or update `AGENTS.md` |
|
|
187
|
+
| `/review` | Review uncommitted changes, commits, branches, or pull requests |
|
|
188
|
+
| `/explain [project] [target]` | Explain code |
|
|
189
|
+
| `/compact` | Compact the conversation |
|
|
190
|
+
| `/summary` | Append a conversation summary |
|
|
191
|
+
| `/save` | Save the current session |
|
|
192
|
+
| `/sessions` | List saved sessions |
|
|
193
|
+
| `/restore [path\|title\|--latest\|latest]` | Restore a session; title matching uses substring search |
|
|
194
|
+
| `/clear` | Start a fresh conversation |
|
|
195
|
+
| `/model [name]` | Switch LLM profiles; `default` restores the session's original model |
|
|
196
|
+
| `/exit` | Quit |
|
|
197
|
+
|
|
198
|
+
Custom commands from `prompts/commands/*.md` are registered as slash commands as well (TUI only).
|
|
199
|
+
|
|
200
|
+
## Project layout
|
|
201
|
+
|
|
202
|
+
```text
|
|
203
|
+
python_agent_harness/
|
|
204
|
+
├── agent.py # Agent FSM core: states, transitions, supervision
|
|
205
|
+
├── tool_runner.py # Tool-call execution/delivery + history salvage
|
|
206
|
+
├── context_manager.py # Context-ratio tracking + compaction
|
|
207
|
+
├── client.py # OpenAI-compatible streaming client (httpx)
|
|
208
|
+
├── models.py # Message / ToolCall / ToolSpec data classes
|
|
209
|
+
├── token_estimator.py # CJK-aware token estimation + calibration
|
|
210
|
+
├── planmode.py # Plan/build modes + plan-file lifecycle
|
|
211
|
+
├── prompts.py # Prompt loading + system-prompt assembly
|
|
212
|
+
├── persistence.py # Session persistence + titles
|
|
213
|
+
├── session.py # Session wiring hub + MCP lifecycle
|
|
214
|
+
├── subagent.py # Sub-agent runner + error containment
|
|
215
|
+
├── commands.py # Init/review/custom command definitions
|
|
216
|
+
├── cli.py # CLI entry points
|
|
217
|
+
├── tui/ # Rich + prompt_toolkit TUI (package)
|
|
218
|
+
├── diffrender.py # Unified diff generation + Rich rendering
|
|
219
|
+
├── mcp/ # Optional MCP client
|
|
220
|
+
└── tools/ # Tool implementations + registry
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Development
|
|
224
|
+
|
|
225
|
+
Requires Python ≥ 3.11. CI runs against Python 3.11, 3.12, and 3.13.
|
|
226
|
+
|
|
227
|
+
```sh
|
|
228
|
+
make test # unit tests
|
|
229
|
+
venv/bin/pip install -e ".[dev]" # development tools
|
|
230
|
+
venv/bin/ruff check . # lint
|
|
231
|
+
venv/bin/pyright # type checking
|
|
232
|
+
venv/bin/python -m build # build sdist + wheel
|
|
233
|
+
venv/bin/pip-audit # dependency audit
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
CI blocks on Ruff and Pyright failures.
|
|
237
|
+
|
|
238
|
+
## Design philosophy
|
|
239
|
+
|
|
240
|
+
**Keep it intact, not bloated.**
|
|
241
|
+
|
|
242
|
+
The project aims to provide a capable coding-agent within a lightweight framework.
|
|
243
|
+
|
|
244
|
+
## Related projects
|
|
245
|
+
|
|
246
|
+
- [gptel-agent-harness](https://github.com/beacoder/gptel-agent-harness) — the Emacs-based implementation that inspired this project.
|
|
247
|
+
- [opencode](https://github.com/anomalyco/opencode) — the primary source of many prompts and coding-agent behaviors.
|
|
248
|
+
|
|
249
|
+
## License
|
|
250
|
+
|
|
251
|
+
MIT
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# python-agent-harness
|
|
4
|
+
|
|
5
|
+
**A lightweight Python coding-agent harness for reliable autonomous coding.**
|
|
6
|
+
FSM-driven execution · OpenAI-compatible · built for daily use and easy customization
|
|
7
|
+
|
|
8
|
+
[](https://github.com/beacoder/python-agent-harness/actions/workflows/ci.yml)
|
|
9
|
+
[](https://www.python.org/downloads/)
|
|
10
|
+
[](LICENSE)
|
|
11
|
+
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
A terminal coding agent that reads your codebase, plans changes, edits files, runs commands, and verifies its work.
|
|
15
|
+
|
|
16
|
+
`python-agent-harness` is inspired by [gptel-agent-harness](https://github.com/beacoder/gptel-agent-harness) and [opencode](https://github.com/anomalyco/opencode). It brings opencode's prompts and core behaviors—such as `AGENTS.md` discovery, plan/build modes, skills, sub-agents, and todo tracking—into a lightweight Python implementation with only **three runtime dependencies**:
|
|
17
|
+
|
|
18
|
+
- `rich`
|
|
19
|
+
- `httpx`
|
|
20
|
+
- `prompt_toolkit`
|
|
21
|
+
|
|
22
|
+
It works with any **OpenAI-compatible API** and is designed to be easy to inspect, customize, and use for everyday software development.
|
|
23
|
+
|
|
24
|
+
## Demo
|
|
25
|
+
|
|
26
|
+

|
|
27
|
+
|
|
28
|
+
## Quick start
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
git clone git@github.com:beacoder/python-agent-harness.git
|
|
32
|
+
cd python-agent-harness
|
|
33
|
+
|
|
34
|
+
make install
|
|
35
|
+
. venv/bin/activate
|
|
36
|
+
|
|
37
|
+
python-agent-harness config --init
|
|
38
|
+
python-agent-harness run
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Edit `~/.config/python-agent-harness/config.json` and set your `base_url`, `api_key`, and `model`.
|
|
42
|
+
|
|
43
|
+
Optional extras:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
pip install -e ".[mcp]" # MCP server integration
|
|
47
|
+
pip install -e ".[dev]" # development tools
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Features
|
|
51
|
+
|
|
52
|
+
- **FSM-driven execution** — explicit `WAIT` / `TOOL` / `TRET` / `SUPERVISE` / `DONE` / `ERRS` / `ABRT` states. Completion supervision nudges the model when it stops early, while failed tool calls are sanitized so they never strand the agent. Transient API failures (`429` / `5xx`) retry with exponential backoff and jitter.
|
|
53
|
+
- **Context management** — CJK-aware token estimation, per-model context windows, and automatic compaction at 70% usage.
|
|
54
|
+
- **Coding tools** — `Agent`, `TodoWrite`, `Glob`, `Grep`, `Read`, `Insert`, `Edit` (including unified diffs), `Write`, `Mkdir`, `Bash`, `Skill`, `Question`, and `PlanExit`. Synchronous tools execute sequentially; asynchronous tools such as `Bash` and `Agent` can run concurrently while preserving emitted order.
|
|
55
|
+
- **Plan / Build modes** — plan mode is read-only except for the per-session plan file.
|
|
56
|
+
- **Persistent sessions** — sessions are automatically saved after every response to `~/.local/share/python-agent-harness/sessions/`, with LLM-generated titles and support for `/restore --latest` and `/sessions`.
|
|
57
|
+
- **Focused TUI** — a Rich-based interface with a pinned status bar, Todos panel, inline red/green diff rendering for `Edit` and `Write`, and a `prompt_toolkit` editor with history and completion. `Esc+Enter` submits, `Ctrl-D` quits, and `Ctrl-C` cancels without leaving the application.
|
|
58
|
+
- **MCP support** — optional MCP integration through the `[mcp]` extra. MCP tools become ordinary agent tools such as `mcp__<server>__<tool>`. Supports `stdio`, `streamable-http`, and `sse` transports.
|
|
59
|
+
- **Slash commands** — built-in `/init`, `/review`, `/explain`, and other commands, plus custom commands loaded from `prompts/commands/*.md`.
|
|
60
|
+
|
|
61
|
+
## Inspired by opencode
|
|
62
|
+
|
|
63
|
+
Most of [opencode](https://github.com/anomalyco/opencode)'s prompts and core behaviors have been ported to this project. The goal is to retain its practical coding-agent workflow while keeping the implementation small, dependency-light, and easy to customize.
|
|
64
|
+
|
|
65
|
+
### Prompt and behavior mapping
|
|
66
|
+
|
|
67
|
+
The following opencode prompts have corresponding implementations in `python-agent-harness`:
|
|
68
|
+
|
|
69
|
+
| opencode | python-agent-harness |
|
|
70
|
+
|---|---|
|
|
71
|
+
| `default.txt` (main agent) | `agent.md` |
|
|
72
|
+
| `plan.txt` / `plan-mode.txt` / `build-switch.txt` | `plan.md` / `plan-mode.md` / `build-switch.md` |
|
|
73
|
+
| `task.txt` (sub-agent) | `subagent.md` + `Agent` tool |
|
|
74
|
+
| `todowrite.txt` / `question.txt` / `skill.txt` | `TodoWrite` / `Question` / `Skill` tools |
|
|
75
|
+
| `read.txt` / `write.txt` / `edit.txt` / `grep.txt` / `glob.txt` | `Read` / `Write` / `Edit` / `Grep` / `Glob` tools |
|
|
76
|
+
| `shell.txt` | `Bash` tool + `agent.md` Git/GitHub guidance |
|
|
77
|
+
| `plan-enter.txt` / `plan-exit.txt` | `PlanExit` tool |
|
|
78
|
+
| `initialize.txt` / `review.txt` / `explain` | `initialize.md` / `review.md` / `commands/explain.md` |
|
|
79
|
+
| compaction / summary / title | `compact.md` / `summary.md` / `title.md` |
|
|
80
|
+
| `AGENTS.md` handling | `prompts.py` (`find_agents_md_files`, `load_context_files`, per-file resolution) |
|
|
81
|
+
|
|
82
|
+
## Configuration
|
|
83
|
+
|
|
84
|
+
All LLM settings live in a single JSON configuration file. Environment variables are optional.
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"llm": {
|
|
89
|
+
"base_url": "https://api.openai.com/v1",
|
|
90
|
+
"api_key": "sk-...",
|
|
91
|
+
"model": "gpt-5-mini",
|
|
92
|
+
"reasoning_effort": null,
|
|
93
|
+
"stream": true
|
|
94
|
+
},
|
|
95
|
+
"models": {
|
|
96
|
+
"_comment": "Named LLM profiles for /model switching. Partial settings; unset keys inherit the main llm.",
|
|
97
|
+
"deepseek": {
|
|
98
|
+
"base_url": "https://api.deepseek.com/v1",
|
|
99
|
+
"model": "deepseek-chat"
|
|
100
|
+
},
|
|
101
|
+
"qwen": {
|
|
102
|
+
"base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
103
|
+
"model": "qwen3.5-coder"
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
"subagent_llm": {
|
|
107
|
+
"profile": null,
|
|
108
|
+
"base_url": null,
|
|
109
|
+
"api_key": null,
|
|
110
|
+
"model": null,
|
|
111
|
+
"temperature": null,
|
|
112
|
+
"max_tokens": null,
|
|
113
|
+
"timeout": null,
|
|
114
|
+
"reasoning_effort": null,
|
|
115
|
+
"stream": null
|
|
116
|
+
},
|
|
117
|
+
"paths": {
|
|
118
|
+
"context_path": null,
|
|
119
|
+
"skill_path": null
|
|
120
|
+
},
|
|
121
|
+
"mcp": {
|
|
122
|
+
"servers": {
|
|
123
|
+
"example": {
|
|
124
|
+
"transport": "stdio",
|
|
125
|
+
"command": "npx",
|
|
126
|
+
"args": [
|
|
127
|
+
"-y",
|
|
128
|
+
"@modelcontextprotocol/server-filesystem",
|
|
129
|
+
"/tmp"
|
|
130
|
+
],
|
|
131
|
+
"env": [],
|
|
132
|
+
"parallel": false,
|
|
133
|
+
"timeout": null,
|
|
134
|
+
"enabled": false
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Configuration options
|
|
142
|
+
|
|
143
|
+
- **`llm`** — main LLM configuration. Optional keys include `backend`, `temperature`, `max_tokens`, `timeout`, `reasoning_effort`, and `stream`. Values such as `reasoning_effort` are passed to the API as-is when set. `run --no-stream` overrides `stream`.
|
|
144
|
+
- **`models`** — named LLM profiles for runtime switching with `/model`. A profile is a partial settings dictionary; unset keys inherit from the main `llm`. `default` restores the main LLM configuration.
|
|
145
|
+
- **`subagent_llm`** — LLM configuration for `Agent` tool requests. Unset values inherit from the main `llm`. Set `profile` to reuse a profile from `models`. Precedence is: profile settings > explicit `subagent_llm` settings > main `llm` > environment variables.
|
|
146
|
+
- **`paths.context_path` / `paths.skill_path`** — locations from which to load context files and skills. When unset, the project-local `<project>/contexts` and `<project>/skills` directories are used.
|
|
147
|
+
- **`mcp.servers`** — MCP server configuration. Requires the `[mcp]` extra. Each server supports `transport`, `command`, `args`, `env`, `url`, `headers`, `parallel`, `timeout`, and `enabled`.
|
|
148
|
+
- **Configuration precedence** — code defaults < config file < `OPENAI_*` environment variables. Sub-agent settings also support `OPENAI_SUBAGENT_*` (`_BASE_URL`, `_API_KEY`, `_MODEL`, `_BACKEND`).
|
|
149
|
+
- **Custom config** — use `--config PATH` or `PYTHON_AGENT_HARNESS_CONFIG`.
|
|
150
|
+
- **LLM logging** — request and response bodies are logged as JSON to `/tmp/python-agent-harness-<date>-<id>.json`. Set `LLM_LOG_DIR` to change the directory. The log path is printed at startup.
|
|
151
|
+
|
|
152
|
+
## Usage
|
|
153
|
+
|
|
154
|
+
```sh
|
|
155
|
+
python-agent-harness run [project-dir]
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Launches the interactive TUI agent. If `project-dir` is omitted, the current directory is used.
|
|
159
|
+
|
|
160
|
+
### Slash commands
|
|
161
|
+
|
|
162
|
+
| Command | Description |
|
|
163
|
+
|---|---|
|
|
164
|
+
| `/plan` / `/build` | Switch between read-only plan mode and build mode |
|
|
165
|
+
| `/init` | Create or update `AGENTS.md` |
|
|
166
|
+
| `/review` | Review uncommitted changes, commits, branches, or pull requests |
|
|
167
|
+
| `/explain [project] [target]` | Explain code |
|
|
168
|
+
| `/compact` | Compact the conversation |
|
|
169
|
+
| `/summary` | Append a conversation summary |
|
|
170
|
+
| `/save` | Save the current session |
|
|
171
|
+
| `/sessions` | List saved sessions |
|
|
172
|
+
| `/restore [path\|title\|--latest\|latest]` | Restore a session; title matching uses substring search |
|
|
173
|
+
| `/clear` | Start a fresh conversation |
|
|
174
|
+
| `/model [name]` | Switch LLM profiles; `default` restores the session's original model |
|
|
175
|
+
| `/exit` | Quit |
|
|
176
|
+
|
|
177
|
+
Custom commands from `prompts/commands/*.md` are registered as slash commands as well (TUI only).
|
|
178
|
+
|
|
179
|
+
## Project layout
|
|
180
|
+
|
|
181
|
+
```text
|
|
182
|
+
python_agent_harness/
|
|
183
|
+
├── agent.py # Agent FSM core: states, transitions, supervision
|
|
184
|
+
├── tool_runner.py # Tool-call execution/delivery + history salvage
|
|
185
|
+
├── context_manager.py # Context-ratio tracking + compaction
|
|
186
|
+
├── client.py # OpenAI-compatible streaming client (httpx)
|
|
187
|
+
├── models.py # Message / ToolCall / ToolSpec data classes
|
|
188
|
+
├── token_estimator.py # CJK-aware token estimation + calibration
|
|
189
|
+
├── planmode.py # Plan/build modes + plan-file lifecycle
|
|
190
|
+
├── prompts.py # Prompt loading + system-prompt assembly
|
|
191
|
+
├── persistence.py # Session persistence + titles
|
|
192
|
+
├── session.py # Session wiring hub + MCP lifecycle
|
|
193
|
+
├── subagent.py # Sub-agent runner + error containment
|
|
194
|
+
├── commands.py # Init/review/custom command definitions
|
|
195
|
+
├── cli.py # CLI entry points
|
|
196
|
+
├── tui/ # Rich + prompt_toolkit TUI (package)
|
|
197
|
+
├── diffrender.py # Unified diff generation + Rich rendering
|
|
198
|
+
├── mcp/ # Optional MCP client
|
|
199
|
+
└── tools/ # Tool implementations + registry
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Development
|
|
203
|
+
|
|
204
|
+
Requires Python ≥ 3.11. CI runs against Python 3.11, 3.12, and 3.13.
|
|
205
|
+
|
|
206
|
+
```sh
|
|
207
|
+
make test # unit tests
|
|
208
|
+
venv/bin/pip install -e ".[dev]" # development tools
|
|
209
|
+
venv/bin/ruff check . # lint
|
|
210
|
+
venv/bin/pyright # type checking
|
|
211
|
+
venv/bin/python -m build # build sdist + wheel
|
|
212
|
+
venv/bin/pip-audit # dependency audit
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
CI blocks on Ruff and Pyright failures.
|
|
216
|
+
|
|
217
|
+
## Design philosophy
|
|
218
|
+
|
|
219
|
+
**Keep it intact, not bloated.**
|
|
220
|
+
|
|
221
|
+
The project aims to provide a capable coding-agent within a lightweight framework.
|
|
222
|
+
|
|
223
|
+
## Related projects
|
|
224
|
+
|
|
225
|
+
- [gptel-agent-harness](https://github.com/beacoder/gptel-agent-harness) — the Emacs-based implementation that inspired this project.
|
|
226
|
+
- [opencode](https://github.com/anomalyco/opencode) — the primary source of many prompts and coding-agent behaviors.
|
|
227
|
+
|
|
228
|
+
## License
|
|
229
|
+
|
|
230
|
+
MIT
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "python-agent-harness"
|
|
7
|
+
version = "1.5.0"
|
|
8
|
+
description = "Python agent execution harness: agent loop, tools, plan/build modes, sessions"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"rich>=13.0",
|
|
13
|
+
"httpx>=0.27",
|
|
14
|
+
"prompt_toolkit>=3.0",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.optional-dependencies]
|
|
18
|
+
# MCP (Model Context Protocol) client support: exposes MCP server tools to
|
|
19
|
+
# the agent as namespaced mcp__<server>__<tool> tools. Install with:
|
|
20
|
+
# pip install -e ".[mcp]"
|
|
21
|
+
# The SDK is NOT a core dependency: the harness works without it, and the
|
|
22
|
+
# mcp/ modules degrade gracefully (MCPUnavailableError) when it is absent.
|
|
23
|
+
mcp = [
|
|
24
|
+
"mcp>=2.0,<3",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
# Tooling used by CI (lint + type check + wheel build + coverage + audit).
|
|
28
|
+
# Install with: pip install -e ".[dev]"
|
|
29
|
+
# The mcp extra is included so CI's type-check/lint jobs can resolve the
|
|
30
|
+
# optional SDK imports in python_agent_harness/mcp/.
|
|
31
|
+
dev = [
|
|
32
|
+
"ruff>=0.6",
|
|
33
|
+
"pyright>=1.1.380",
|
|
34
|
+
"build>=1.2",
|
|
35
|
+
"coverage[toml]>=7.0",
|
|
36
|
+
"pip-audit>=2.7",
|
|
37
|
+
"mcp>=2.0,<3",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[project.scripts]
|
|
41
|
+
python-agent-harness = "python_agent_harness.cli:main"
|
|
42
|
+
|
|
43
|
+
[tool.setuptools.packages.find]
|
|
44
|
+
include = ["python_agent_harness*"]
|
|
45
|
+
|
|
46
|
+
[tool.setuptools.package-data]
|
|
47
|
+
python_agent_harness = ["prompts/*.md", "prompts/commands/*.md"]
|
|
48
|
+
|
|
49
|
+
[tool.ruff]
|
|
50
|
+
line-length = 100
|
|
51
|
+
target-version = "py311"
|
|
52
|
+
|
|
53
|
+
[tool.ruff.lint]
|
|
54
|
+
# Core pyflakes (F) + pycodestyle error (E), plus import sorting (I),
|
|
55
|
+
# bugbears (B), pyupgrade (UP) and simplify (SIM). The tree is clean
|
|
56
|
+
# under this set (CI blocks on it).
|
|
57
|
+
select = ["E", "F", "I", "B", "UP", "SIM"]
|
|
58
|
+
# E501 (line-too-long) is left to the formatter.
|
|
59
|
+
ignore = ["E501"]
|
|
60
|
+
|
|
61
|
+
[tool.pyright]
|
|
62
|
+
include = ["python_agent_harness"]
|
|
63
|
+
pythonVersion = "3.11"
|
|
64
|
+
# "basic" surfaces the real Optional-access issues without the full strict
|
|
65
|
+
# firehose. The tree is clean under this mode and the CI type-check job
|
|
66
|
+
# blocks on it.
|
|
67
|
+
typeCheckingMode = "basic"
|
|
68
|
+
|
|
69
|
+
[tool.coverage.run]
|
|
70
|
+
source = ["python_agent_harness"]
|
|
71
|
+
branch = true
|
|
72
|
+
# The console/TUI entry points aren't exercised by unittest; omit the ones
|
|
73
|
+
# that would otherwise drag the number down without adding signal.
|
|
74
|
+
omit = ["python_agent_harness/__main__.py"]
|
|
75
|
+
|
|
76
|
+
[tool.coverage.report]
|
|
77
|
+
show_missing = true
|
|
78
|
+
# Current line + branch coverage is ~98%. Gate at 90 to leave headroom for
|
|
79
|
+
# small changes while still catching real regressions. Raise as coverage grows.
|
|
80
|
+
fail_under = 90
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""python-agent-harness: a Python port of the gptel-agent-harness."""
|
|
2
|
+
|
|
3
|
+
from .mcp.config import MCPConfig, MCPServerConfig
|
|
4
|
+
from .mcp.manager import MCPManager
|
|
5
|
+
from .models import AgentMode, Message, ToolCall, ToolSpec
|
|
6
|
+
from .session import Session
|
|
7
|
+
|
|
8
|
+
__version__ = "1.5.0"
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"Session",
|
|
12
|
+
"AgentMode",
|
|
13
|
+
"MCPConfig",
|
|
14
|
+
"MCPManager",
|
|
15
|
+
"MCPServerConfig",
|
|
16
|
+
"Message",
|
|
17
|
+
"ToolCall",
|
|
18
|
+
"ToolSpec",
|
|
19
|
+
"__version__",
|
|
20
|
+
]
|