python-agent-harness
A lightweight, hackable mini-OpenCode written in Python.
FSM-driven execution · OpenAI-compatible · built for daily use and easy customization
A terminal coding agent that reads your codebase, plans changes, edits files, runs commands, and verifies its work.
python-agent-harness is inspired by gptel-agent-harness and 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:
richhttpxprompt_toolkit
It works with any OpenAI-compatible API and is designed to be easy to inspect, customize, and use for everyday software development.
Demo
Quick start
Install from PyPI
pip install python-agent-harness
python-agent-harness config --init
python-agent-harness run
Optional extras:
pip install "python-agent-harness[mcp]" # MCP server integration
pip install "python-agent-harness[dev]" # development tools
Install from source (GitHub)
git clone git@github.com:beacoder/python-agent-harness.git
cd python-agent-harness
make install
. venv/bin/activate
python-agent-harness config --init
python-agent-harness run
Optional extras:
pip install -e ".[mcp]" # MCP server integration
pip install -e ".[dev]" # development tools
Edit ~/.config/python-agent-harness/config.json and set your base_url, api_key, and model.
Features
- FSM-driven execution — explicit
WAIT/TOOL/TRET/SUPERVISE/DONE/ERRS/ABRTstates. 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. Auth-expired status codes (configurable viaAUTH_REFRESH_STATUS_CODESinconfig.py; defaults to[401, 502]) trigger automatic API key re-read from config/env — some API gateways return502instead of401when the backend auth token has expired. Note: codes in this list are treated as auth-expired exclusively and will not be retried with backoff, so only include codes that are unambiguously auth-related in your environment. - Context management — CJK-aware token estimation, per-model context windows, and automatic compaction at 70% usage.
- Coding tools —
Agent,TodoWrite,Glob,Grep,Read,Insert,Edit(including unified diffs),Write,Mkdir,Bash,Skill,Question,LSP, andPlanExit. Synchronous tools execute sequentially, but a round made up entirely of read-only tools (Read,Glob,Grep,Skill,LSP) is dispatched concurrently via a bounded thread pool; asynchronous tools such asBashandAgentcan run concurrently as well. Results are always delivered in the model's emitted order. - Plan / Build modes — plan mode is read-only except for the per-session plan file.
- Persistent sessions — sessions are automatically saved after every response to
~/.local/share/python-agent-harness/sessions/, with LLM-generated titles and support for/restore --latestand/sessions. - Focused TUI — a Rich-based interface with a pinned status bar, Todos panel, inline red/green diff rendering for
EditandWrite, and aprompt_toolkiteditor with history and completion.Esc+Entersubmits,Ctrl-Dquits, andCtrl-Ccancels without leaving the application. - MCP support — optional MCP integration through the
[mcp]extra. MCP tools become ordinary agent tools such asmcp__<server>__<tool>. Supportsstdio,streamable-http, andssetransports. - Slash commands — built-in
/init,/review,/explain, and other commands, plus custom commands loaded fromprompts/commands/*.md. - Custom agents — switch the main agent's system prompt at runtime with
/agent. Agent prompt files live inprompts/agents/*.md. Usedefault_agentin the config file to start sessions with a specific agent.
Inspired by opencode
Most of 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.
Prompt and behavior mapping
The following opencode prompts have corresponding implementations in python-agent-harness:
| opencode | python-agent-harness |
|---|---|
default.txt (main agent) |
agent.md |
plan.txt / plan-mode.txt / build-switch.txt |
plan.md / plan-mode.md / build-switch.md |
task.txt (sub-agent) |
subagent.md + Agent tool |
todowrite.txt / question.txt / skill.txt |
TodoWrite / Question / Skill tools |
read.txt / write.txt / edit.txt / grep.txt / glob.txt |
Read / Write / Edit / Grep / Glob tools |
shell.txt |
Bash tool + agent.md Git/GitHub guidance |
plan-enter.txt / plan-exit.txt |
PlanExit tool |
initialize.txt / review.txt / explain |
initialize.md / review.md / commands/explain.md |
| compaction / summary / title | compact.md / summary.md / title.md |
AGENTS.md handling |
prompts.py (find_agents_md_files, load_context_files, per-file resolution) |
Configuration
All LLM settings live in a single JSON configuration file. Environment variables are optional.
{
"llm": {
"base_url": "https://api.openai.com/v1",
"api_key": "sk-...",
"model": "gpt-5-mini",
"reasoning_effort": null,
"stream": true
},
"models": {
"_comment": "Named LLM profiles for /model switching. Partial settings; unset keys inherit the main llm.",
"deepseek": {
"base_url": "https://api.deepseek.com/v1",
"model": "deepseek-chat"
},
"qwen": {
"base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
"model": "qwen3.5-coder"
}
},
"context_windows": {
"_comment": "Optional per-model context-window overrides (tokens). Keys are model names or substrings (e.g. deepseek-v4 = 1000000); matched in file order, first match wins. Overrides the built-in CONTEXT_WINDOWS table in config.py.",
"deepseek-v4": 1000000
},
"subagent_llm": {
"profile": null,
"base_url": null,
"api_key": null,
"model": null,
"temperature": null,
"max_tokens": null,
"timeout": null,
"reasoning_effort": null,
"stream": null
},
"default_agent": null,
"paths": {
"context_path": null,
"skill_path": null
},
"lsp": {
"servers": {
".c": { "command": ["clangd", "--compile-commands-dir=$REPO/build/Linux_x86_64", "--background-index"], "language_id": "c" },
".h": { "command": ["clangd", "--compile-commands-dir=$REPO/build/Linux_x86_64", "--background-index"], "language_id": "c" },
".cpp": { "command": ["clangd", "--compile-commands-dir=$REPO/build/Linux_x86_64", "--background-index"], "language_id": "cpp" },
".cc": { "command": ["clangd", "--compile-commands-dir=$REPO/build/Linux_x86_64", "--background-index"], "language_id": "cpp" },
".hpp": { "command": ["clangd", "--compile-commands-dir=$REPO/build/Linux_x86_64", "--background-index"], "language_id": "cpp" },
".cxx": { "command": ["clangd", "--compile-commands-dir=$REPO/build/Linux_x86_64", "--background-index"], "language_id": "cpp" }
}
},
"mcp": {
"servers": {
"example": {
"transport": "stdio",
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"/tmp"
],
"env": [],
"parallel": false,
"timeout": null,
"enabled": false
}
}
}
}
Configuration options
llm— main LLM configuration. Optional keys includetemperature,max_tokens,timeout,reasoning_effort, andstream. Values such asreasoning_effortare passed to the API as-is when set.run --no-streamoverridesstream.models— named LLM profiles for runtime switching with/model. A profile is a partial settings dictionary; unset keys inherit from the mainllm.defaultrestores the main LLM configuration.context_windows— optional per-model context-window overrides (tokens). Keys are model names or substrings (e.g.,deepseek-v4); matched in file order, first match wins. Overrides the built-inCONTEXT_WINDOWStable inconfig.py.subagent_llm— LLM configuration forAgenttool requests. Unset values inherit from the mainllm. Setprofileto reuse a profile frommodels. Precedence is: profile settings > explicitsubagent_llmsettings > mainllm> environment variables.default_agent— name of the agent to use at session start (instead of the built-inagent.md). The agent must exist as a.mdfile in theprompts/agents/directory. When unset ornull, the built-in default agent is used. Use/agent defaultin the TUI to switch back to the built-in at any time.paths.context_path/paths.skill_path— locations from which to load context files and skills. When unset, the project-local<project>/contextsand<project>/skillsdirectories are used.lsp.servers— optional per-extension LSP server overrides for theLSPcode-intelligence tool. Keys are file extensions (e.g..py,.cpp); each value has acommand(the server argv) and an optionallanguage_id(defaults to the extension without its dot). These layer on top of the built-inDEFAULT_SERVERStable inlsp/manager.py; an entry for an existing extension replaces its default. The server binary must be onPATH.mcp.servers— MCP server configuration. Requires the[mcp]extra. Each server supportstransport,command,args,env,url,headers,parallel,timeout, andenabled.- Configuration precedence — code defaults < config file <
OPENAI_*environment variables. Sub-agent settings also supportOPENAI_SUBAGENT_*(_BASE_URL,_API_KEY,_MODEL). - Custom config — use
--config PATHorPYTHON_AGENT_HARNESS_CONFIG. - LLM logging — request and response bodies are logged as JSON to
/tmp/python-agent-harness-<date>-<id>.json. SetLLM_LOG_DIRto change the directory. The log path is printed at startup.
Usage
python-agent-harness run [project-dir]
Launches the interactive TUI agent. If project-dir is omitted, the current directory is used.
Slash commands
| Command | Description |
|---|---|
/plan / /build |
Switch between read-only plan mode and build mode |
/init |
Create or update AGENTS.md |
/review |
Review uncommitted changes, commits, branches, or pull requests |
/explain [project] [target] |
Explain code |
/compact |
Compact the conversation |
/summary |
Append a conversation summary |
/save |
Save the current session |
/sessions |
List saved sessions |
/restore [path|title|--latest|latest] |
Restore a session; title matching uses substring search |
/clear |
Start a fresh conversation |
/model [name] |
Switch LLM profiles; default restores the session's original model |
/agent [name] |
Switch agent system prompt; default restores the built-in agent.md |
/exit |
Quit |
Custom commands from prompts/commands/*.md are registered as slash commands as well (TUI only).
Custom agents
Agent prompt files are markdown files (.md) placed in the prompts/agents/ directory. Each file becomes a switchable agent profile available via the /agent TUI command.
An agent file may carry YAML frontmatter with two optional keys:
name:— override the agent name (defaults to the file stem)exclude_tools:— a list of tool names the agent must not see. An entry matches a tool by exact name, glob pattern (mcp__git__*), or__-delimited prefix (mcp__githidesmcp__git__list_reposbutWritedoes NOT hideTodoWrite). The built-indefaultagent always sees all tools.
---
name: assistant
exclude_tools:
- Bash
- Edit
- Write
- mcp__git__*
---
# Role and Behavior
You are a personal assistant. You do NOT modify files or run shell commands.
Commands vs Agents
Commands (/review) |
Agents (/agent reviewer) |
|
|---|---|---|
| Scope | One-shot (prompt resets after run) | Persistent (stays until next /agent) |
| Kickoff | Hardcoded kickoff message | User types their own prompt |
| Tools | Can restrict (allow_planexit=False) |
All tools, minus the agent's exclude_tools |
| Ctrl-C | Restores to default prompt | Stays on the custom agent |
Project layout
python_agent_harness/
├── agent.py # Agent FSM core: states, transitions, supervision
├── tool_runner.py # Tool-call execution/delivery + history salvage
├── context_manager.py # Context-ratio tracking + compaction
├── client.py # OpenAI-compatible streaming client (httpx)
├── models.py # Message / ToolCall / ToolSpec data classes
├── token_estimator.py # CJK-aware token estimation + calibration
├── planmode.py # Plan/build modes + plan-file lifecycle
├── prompts.py # Prompt loading + system-prompt assembly
├── persistence.py # Session persistence + titles
├── session.py # Session wiring hub + MCP lifecycle
├── subagent.py # Sub-agent runner + error containment
├── commands.py # Init/review/custom command definitions
├── cli.py # CLI entry points
├── tui/ # Rich + prompt_toolkit TUI (package)
├── diffrender.py # Unified diff generation + Rich rendering
├── lsp/ # Built-in LSP client
├── mcp/ # Optional MCP client
└── tools/ # Tool implementations + registry
Development
Requires Python ≥ 3.11. CI runs against Python 3.11, 3.12, and 3.13 on Linux, MacOS and Windows.
make test # unit tests
venv/bin/pip install -e ".[dev]" # development tools
venv/bin/ruff check . # lint
venv/bin/pyright # type checking
venv/bin/python -m build # build sdist + wheel
venv/bin/pip-audit # dependency audit
CI blocks on Ruff and Pyright failures.
Design philosophy
Keep it intact, not bloated.
The project aims to provide a capable coding-agent within a lightweight framework.
Related projects
- gptel-agent-harness — the Emacs-based implementation that inspired this project.
- opencode — the primary source of many prompts and coding-agent behaviors.
License
MIT
Metadata
Release files for python-agent-harness 1.5.5.8
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| python_agent_harness-1.5.5.8.tar.gz | 296.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| python_agent_harness-1.5.5.8-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 506.8 kB
Release files / python_agent_harness-1.5.5.8.tar.gz
| Download URL | python_agent_harness-1.5.5.8.tar.gz |
|---|---|
| Size | 296.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a85b82c7d118bce7dddc546749c38dc31dad6fe7ccc882f94d8870310164795c
|
|
BLAKE2b-256 checksum How to use checksums |
181852e1a2d3a5205ba6d8f71bedb63d442567d6dcd7dc177cb18015d8a9a5c5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 13, 2026.
Transparency logRelease files / python_agent_harness-1.5.5.8-py3-none-any.whl
| Download URL | python_agent_harness-1.5.5.8-py3-none-any.whl |
|---|---|
| Size | 210.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
11e8c61c951e88fc337f963430362dc9c0757b74d74e5e659322cc0bcee6733c
|
|
BLAKE2b-256 checksum How to use checksums |
d32b916d0ec9f44703c3b92d6b377fc529842e7ce576878f9ed5674ea9b0860b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 13, 2026.
Transparency log