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.
Files changed (88) hide show
  1. python_agent_harness-1.5.0/LICENSE +21 -0
  2. python_agent_harness-1.5.0/PKG-INFO +251 -0
  3. python_agent_harness-1.5.0/README.md +230 -0
  4. python_agent_harness-1.5.0/pyproject.toml +80 -0
  5. python_agent_harness-1.5.0/python_agent_harness/__init__.py +20 -0
  6. python_agent_harness-1.5.0/python_agent_harness/__main__.py +5 -0
  7. python_agent_harness-1.5.0/python_agent_harness/agent.py +703 -0
  8. python_agent_harness-1.5.0/python_agent_harness/cli.py +273 -0
  9. python_agent_harness-1.5.0/python_agent_harness/client.py +832 -0
  10. python_agent_harness-1.5.0/python_agent_harness/commands.py +181 -0
  11. python_agent_harness-1.5.0/python_agent_harness/config.py +464 -0
  12. python_agent_harness-1.5.0/python_agent_harness/context_manager.py +100 -0
  13. python_agent_harness-1.5.0/python_agent_harness/diffrender.py +84 -0
  14. python_agent_harness-1.5.0/python_agent_harness/mcp/__init__.py +21 -0
  15. python_agent_harness-1.5.0/python_agent_harness/mcp/client.py +161 -0
  16. python_agent_harness-1.5.0/python_agent_harness/mcp/config.py +130 -0
  17. python_agent_harness-1.5.0/python_agent_harness/mcp/manager.py +290 -0
  18. python_agent_harness-1.5.0/python_agent_harness/models.py +149 -0
  19. python_agent_harness-1.5.0/python_agent_harness/persistence.py +297 -0
  20. python_agent_harness-1.5.0/python_agent_harness/planmode.py +112 -0
  21. python_agent_harness-1.5.0/python_agent_harness/prompts/agent.md +362 -0
  22. python_agent_harness-1.5.0/python_agent_harness/prompts/build-switch.md +5 -0
  23. python_agent_harness-1.5.0/python_agent_harness/prompts/commands/explain.md +13 -0
  24. python_agent_harness-1.5.0/python_agent_harness/prompts/compact.md +33 -0
  25. python_agent_harness-1.5.0/python_agent_harness/prompts/initialize.md +66 -0
  26. python_agent_harness-1.5.0/python_agent_harness/prompts/plan-mode.md +70 -0
  27. python_agent_harness-1.5.0/python_agent_harness/prompts/plan.md +26 -0
  28. python_agent_harness-1.5.0/python_agent_harness/prompts/review.md +100 -0
  29. python_agent_harness-1.5.0/python_agent_harness/prompts/subagent.md +208 -0
  30. python_agent_harness-1.5.0/python_agent_harness/prompts/summary.md +11 -0
  31. python_agent_harness-1.5.0/python_agent_harness/prompts/task-completion-rules.md +50 -0
  32. python_agent_harness-1.5.0/python_agent_harness/prompts/title.md +44 -0
  33. python_agent_harness-1.5.0/python_agent_harness/prompts.py +498 -0
  34. python_agent_harness-1.5.0/python_agent_harness/session.py +781 -0
  35. python_agent_harness-1.5.0/python_agent_harness/subagent.py +61 -0
  36. python_agent_harness-1.5.0/python_agent_harness/token_estimator.py +125 -0
  37. python_agent_harness-1.5.0/python_agent_harness/tool_runner.py +247 -0
  38. python_agent_harness-1.5.0/python_agent_harness/tools/__init__.py +56 -0
  39. python_agent_harness-1.5.0/python_agent_harness/tools/agent_tool.py +75 -0
  40. python_agent_harness-1.5.0/python_agent_harness/tools/base.py +147 -0
  41. python_agent_harness-1.5.0/python_agent_harness/tools/bash.py +298 -0
  42. python_agent_harness-1.5.0/python_agent_harness/tools/edit.py +272 -0
  43. python_agent_harness-1.5.0/python_agent_harness/tools/filesystem.py +180 -0
  44. python_agent_harness-1.5.0/python_agent_harness/tools/glob.py +161 -0
  45. python_agent_harness-1.5.0/python_agent_harness/tools/grep.py +149 -0
  46. python_agent_harness-1.5.0/python_agent_harness/tools/insert.py +61 -0
  47. python_agent_harness-1.5.0/python_agent_harness/tools/mcp.py +203 -0
  48. python_agent_harness-1.5.0/python_agent_harness/tools/mkdir.py +30 -0
  49. python_agent_harness-1.5.0/python_agent_harness/tools/planexit.py +45 -0
  50. python_agent_harness-1.5.0/python_agent_harness/tools/question.py +70 -0
  51. python_agent_harness-1.5.0/python_agent_harness/tools/read.py +104 -0
  52. python_agent_harness-1.5.0/python_agent_harness/tools/skill.py +32 -0
  53. python_agent_harness-1.5.0/python_agent_harness/tools/todo.py +60 -0
  54. python_agent_harness-1.5.0/python_agent_harness/tools/write.py +56 -0
  55. python_agent_harness-1.5.0/python_agent_harness/tui/__init__.py +68 -0
  56. python_agent_harness-1.5.0/python_agent_harness/tui/commands.py +652 -0
  57. python_agent_harness-1.5.0/python_agent_harness/tui/core.py +385 -0
  58. python_agent_harness-1.5.0/python_agent_harness/tui/input.py +412 -0
  59. python_agent_harness-1.5.0/python_agent_harness/tui/render.py +535 -0
  60. python_agent_harness-1.5.0/python_agent_harness.egg-info/PKG-INFO +251 -0
  61. python_agent_harness-1.5.0/python_agent_harness.egg-info/SOURCES.txt +86 -0
  62. python_agent_harness-1.5.0/python_agent_harness.egg-info/dependency_links.txt +1 -0
  63. python_agent_harness-1.5.0/python_agent_harness.egg-info/entry_points.txt +2 -0
  64. python_agent_harness-1.5.0/python_agent_harness.egg-info/requires.txt +14 -0
  65. python_agent_harness-1.5.0/python_agent_harness.egg-info/top_level.txt +1 -0
  66. python_agent_harness-1.5.0/setup.cfg +4 -0
  67. python_agent_harness-1.5.0/tests/test_agents_md.py +260 -0
  68. python_agent_harness-1.5.0/tests/test_cli.py +463 -0
  69. python_agent_harness-1.5.0/tests/test_client.py +1694 -0
  70. python_agent_harness-1.5.0/tests/test_commands.py +119 -0
  71. python_agent_harness-1.5.0/tests/test_concurrent_subagents.py +378 -0
  72. python_agent_harness-1.5.0/tests/test_config.py +468 -0
  73. python_agent_harness-1.5.0/tests/test_context_rules.py +133 -0
  74. python_agent_harness-1.5.0/tests/test_diffrender.py +53 -0
  75. python_agent_harness-1.5.0/tests/test_filesystem.py +1216 -0
  76. python_agent_harness-1.5.0/tests/test_invariants.py +1271 -0
  77. python_agent_harness-1.5.0/tests/test_mcp.py +825 -0
  78. python_agent_harness-1.5.0/tests/test_models.py +130 -0
  79. python_agent_harness-1.5.0/tests/test_persistence.py +347 -0
  80. python_agent_harness-1.5.0/tests/test_planmode.py +238 -0
  81. python_agent_harness-1.5.0/tests/test_prompts.py +444 -0
  82. python_agent_harness-1.5.0/tests/test_scenarios.py +681 -0
  83. python_agent_harness-1.5.0/tests/test_session.py +1048 -0
  84. python_agent_harness-1.5.0/tests/test_subagent.py +169 -0
  85. python_agent_harness-1.5.0/tests/test_subagent_isolation.py +402 -0
  86. python_agent_harness-1.5.0/tests/test_todos_scope.py +111 -0
  87. python_agent_harness-1.5.0/tests/test_token_estimator.py +104 -0
  88. 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
+ [![CI](https://github.com/beacoder/python-agent-harness/actions/workflows/ci.yml/badge.svg)](https://github.com/beacoder/python-agent-harness/actions/workflows/ci.yml)
30
+ [![Python](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org/downloads/)
31
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](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
+ ![python-agent-harness demo](demo.png)
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
+ [![CI](https://github.com/beacoder/python-agent-harness/actions/workflows/ci.yml/badge.svg)](https://github.com/beacoder/python-agent-harness/actions/workflows/ci.yml)
9
+ [![Python](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org/downloads/)
10
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](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
+ ![python-agent-harness demo](demo.png)
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
+ ]
@@ -0,0 +1,5 @@
1
+ import sys
2
+
3
+ from .cli import main
4
+
5
+ sys.exit(main())