Open-source Python agent harness (~3M chars) reimplementing Claude Code's architecture with multi-provider support (Anthropic/OpenAI/Copilot/Gemini/Ollama), 43 tools, Pydantic-based tool system, plugin/skill/hook extensibility, multi-agent swarm coordination, and an opinionated personal agent (ohmo) with Telegram/Slack/Discord/Feishu integration.
| Field | Value |
|---|---|
| Repo | |
| Primary language | Python (2.97M bytes), TypeScript (140K bytes) |
| Total chars | ~3.1M across Python + TypeScript + Shell |
| License | MIT |
| Stars | 13,112 |
| Version analyzed | v0.1.9 |
| Maintainer / Org | HKUDS (HKU Data Science Lab) |
| Created | 2026-04-01 |
| Date read | 2026-05-26 |
| CLI entry points | oh / openharness / openh (harness), ohmo (personal agent) |
| Package | openharness-ai (PyPI) |
| Build system | hatchling |
One-line pitch: Open-source Python reimplementation of Claude Code's agent harness with first-class multi-provider support, enabling researchers and builders to run the same agentic loop against any LLM backend.
Claude Code ships as a compiled TypeScript binary tied to Anthropic's API. Three pain points motivate a reimplementation: (1) Closed source — the decompiled TypeScript is research-only; no one can legally extend or redistribute it. (2) Single-provider lock-in — Claude Code works only with Anthropic's Claude; users with OpenAI, DeepSeek, Gemini, or local Ollama deployments are excluded. (3) No personal agent story — Claude Code is a session-scoped CLI tool; it has no mechanism for persistent, always-on agent presence across chat platforms. A community-maintainable Python reimplementation with provider abstraction and a personal agent layer addresses all three gaps.
OpenHarness factors the agent into three layers:
SupportsStreamingMessages works.BaseTool subclass with a Pydantic input_model for validation, execute() for logic, and is_read_only() for permission classification. The ToolRegistry maps names → implementations and generates JSON Schema for the API.soul/, identity/), multi-channel gateway (Telegram, Slack, Discord, Feishu), and workspace at ~/.ohmo/. Runs on existing Claude Code or Codex subscriptions.The multi-provider abstraction lives in src/openharness/api/ with separate clients for Anthropic-compatible, OpenAI-compatible, and GitHub Copilot APIs. Provider profiles are configured via oh setup or oh provider edit.
The provider-abstracted streaming tool-use loop built on Python's AsyncIterator[StreamEvent]. The QueryEngine.submit_message() method returns an AsyncIterator that yields StreamEvent objects as they arrive from any backend (Anthropic, OpenAI, Copilot, Gemini, Ollama). This single abstraction makes every downstream consumer — the CLI, the React TUI, the ohmo gateway, and the SDK — work identically regardless of which LLM provider backs the session. The hard part is not the abstraction itself but maintaining streaming fidelity across providers with incompatible SSE formats: Anthropic uses content_block_delta events with explicit tool_use blocks, OpenAI uses function_call chunks within choices[0].delta, and Copilot requires device-flow OAuth before any streaming begins. Normalizing these into a single StreamEvent type while preserving backpressure, cancellation, and partial-result semantics is the core engineering challenge.
13K+ stars in under 2 months (2026-04-01 → 2026-05-26). 114 unit/integration tests passing, 6 real-model E2E tests, 9 harness feature E2E tests, 12 skill/plugin compatibility tests. Supports 10+ provider backends out of the box. The ohmo personal agent adds a novel product surface absent from all comparable agent CLIs.
Top modules by centrality:
| Module | Purpose |
|---|---|
src/openharness/engine/ | Agent loop core: streaming tool-call cycle, API retry, token counting, cost tracking |
src/openharness/tools/base.py | BaseTool ABC + ToolRegistry + ToolResult — the tool contract |
src/openharness/cli.py (91KB) | Primary CLI entry point — typer app with all flags, subcommands, session mgmt |
src/openharness/api/ | Multi-provider API clients: Anthropic-compat, OpenAI-compat, Copilot |
src/openharness/permissions/ | 3-mode permission system: default (ask), auto (allow), plan (block writes) |
src/openharness/hooks/ | PreToolUse / PostToolUse lifecycle event hooks |
src/openharness/commands/ | 54 slash commands |
src/openharness/coordinator/ | Subagent spawning, team registry, task delegation |
src/openharness/memory/ | Persistent cross-session memory: MEMORY.md, session memory, durable extraction |
src/openharness/mcp/ | MCP client with HTTP transport and auto-reconnect |
src/openharness/plugins/ | Plugin ecosystem: commands, hooks, agents, MCP server plugins |
ohmo/ | Personal agent: gateway, workspace, soul/identity, multi-channel chat |
| Entry | Path | Role |
|---|---|---|
| CLI (harness) | src/openharness/cli.py | oh / openharness / openh — primary user-facing CLI |
| CLI (ohmo) | ohmo/cli.py | ohmo — personal agent CLI (init, config, gateway) |
| React TUI | frontend/terminal/ | TypeScript/Ink terminal UI |
| Python API | from openharness | Programmatic SDK access |
| Flag | Purpose |
|---|---|
-c / --continue | Resume last session |
-r / --resume | Resume specific session by ID |
-n / --name | Name a session |
-m / --model | Select model |
--effort | Set model effort level |
--max-turns | Limit agent loop iterations |
-p / --print | Non-interactive print mode |
--output-format | text / json / stream-json |
--permission-mode | default / auto / plan |
--dangerously-skip-permissions | Bypass all permission checks |
oh setup (provider config), oh provider (manage backends), oh auth (credential mgmt), oh mcp (MCP server config), oh plugin (plugin mgmt).
BaseTool, define name, description, input_model, implement execute() — register via ToolRegistry.register()BaseTool subclasses in plugin tools/ directories — auto-discovered at runtime.md files in bundled/user/project/plugin skill directories — loaded on demand via Skill toolSupportsStreamingMessages protocol| Profile | Backends |
|---|---|
| Anthropic-compatible | Claude official, Kimi, GLM, MiniMax |
| Claude subscription | ~/.claude/.credentials.json (local credential reuse) |
| OpenAI-compatible | OpenAI, OpenRouter, DashScope, DeepSeek, SiliconFlow, Groq, Ollama, GitHub Models, NVIDIA NIM, Gemini |
| Codex subscription | ~/.codex/auth.json |
| GitHub Copilot | Device-flow OAuth login |
BaseTool — Tool Contract (src/openharness/tools/base.py) #name: str, description: str, input_model: type[BaseModel]. Three methods: execute() (async, the tool logic), is_read_only() (permission hint), to_api_schema() (JSON Schema for API).ToolRegistry. Stateless — all mutable state flows through ToolExecutionContext.execute() override.ToolResult — Execution Output (src/openharness/tools/base.py) #output: str, is_error: bool, metadata: dict[str, Any].tool_result message.ToolExecutionContext — Invocation Environment (src/openharness/tools/base.py) #cwd: Path, metadata: dict[str, Any], hook_executor: HookExecutor | None.QueryEngine's current state.ToolRegistry — Tool Name → Implementation Map (src/openharness/tools/base.py) #_tools: dict[str, BaseTool].QueryEngine — Conversation + Loop Owner (src/openharness/engine/query_engine.py) #api_client (any provider), tool_registry, permission_checker, messages[] (conversation history), model config, context window limits, auto-compact thresholds, hooks, settings.| Hop | Dominant cost |
|---|---|
| Message sanitization + goal tracking | ~1-5 ms (CPU) |
| Hook execution (USER_PROMPT_SUBMIT) | ~1-50 ms (shell script execution) |
| Provider API prefill | ~100 ms–10 s (network + GPU, proportional to context) |
| Provider API decode | ~50 ms–60 s (token generation, model dependent) |
| Permission check (per tool) | ~0 ms (auto) to unbounded (interactive prompt) |
| Tool execution (Bash) | ~50 ms–300 s (command dependent) |
| Tool execution (Read/Edit) | ~1-10 ms (disk I/O) |
| Tool execution (WebFetch/WebSearch) | ~100 ms–10 s (network) |
| Memory pipeline (post-turn) | ~10-500 ms (session memory update + optional LLM extraction) |
The provider API call dominates wall-clock time. The permission pipeline in default mode is the critical human-in-the-loop bottleneck. The memory pipeline (session memory update, durable extraction, auto-dream scheduling) runs as post-turn cleanup and adds latency only when durable extraction triggers an LLM call.
The README's agent loop pseudocode shows a 6-line while-true pattern. The actual submit_message() implementation adds 50+ lines of orchestration: message sanitization, user goal tracking, session memory preparation, hook execution, coordinator context injection, cost accumulation, session memory update, durable memory extraction, and auto-dream scheduling. The simple loop framing correctly captures the control flow but understates the lifecycle management by an order of magnitude.
無形式化作者証明 — 仅实証。
OpenHarness is an open-source tool, not a research artifact. No formal proofs exist. Correctness assurances come from:
BaseModel schema before execution — malformed arguments are rejected with structured error messages.dataclass, ABC).@dataclass(frozen=True) ensures tool outputs are immutable after creation, preventing mutation bugs in the conversation history pipeline.The key liveness property — that the agent loop terminates — is enforced by max_turns (configurable, default 8). Without this guard, the loop runs until the model emits a non-tool_use stop reason or the user interrupts.
| Mechanism | Where | Why |
|---|---|---|
| Single-threaded asyncio event loop | Python main process | All core logic is async/await; I/O is non-blocking |
| AsyncIterator streaming | QueryEngine.submit_message() → consumer | Pull-based streaming with natural backpressure |
| Subprocess agents | Coordinator subagent spawning | Isolated execution contexts for multi-agent tasks |
| Background services | autodream, session memory, memory extraction | Post-turn async processing |
| Docker sandbox | sandbox.backend = "docker" | Full process isolation with resource limits |
messages[] grows monotonically within a session. No GC of old messages — this drives the need for auto-compaction.auto_compact_threshold_tokens, the engine compresses conversation history via a dedicated API call.MEMORY.md) surviving across sessions.execute()) block the event loop. Mitigated by delegating heavy work to subprocesses (Bash tool, subprocess agents).ToolExecutionContext.metadata is a plain dict shared across hook execution and tool execution within a single invocation. No locking — safe only because the asyncio model is single-threaded.No formal benchmarks published. The test suite validates correctness, not performance.
| Metric | Estimate |
|---|---|
Cold start (oh) | ~1-3 s (Python startup + typer + module import + config load + provider discovery) |
Warm start (--continue) | +session replay time (proportional to conversation JSONL size) |
ToolSearch tool defers loading of rarely-used tools to manage context budget.max_turns per agent bounds each level.| Step | Claim | Evidence | Depends on |
|---|---|---|---|
| 1 | A production coding agent needs more than a bare tool-use loop — it needs permissions, memory, extensibility, and multi-provider support | Claude Code's closed-source TypeScript harness proves the concept but locks users to one provider and one language | — |
| 2 | Provider abstraction via SupportsStreamingMessages protocol decouples the agent loop from any specific LLM backend | src/openharness/api/ contains separate Anthropic-compat, OpenAI-compat, and Copilot clients, all consumed by the same QueryEngine | Step 1 |
| 3 | Pydantic-based BaseTool with ToolRegistry makes the 43-tool inventory maintainable and extensible | Each tool is ~30-50 lines of meaningful logic (input model + execute); schema generation is automatic via model_json_schema() | Step 1 |
| 4 | Multi-level permission system (default/auto/plan) + hook system provides safety without sacrificing power-user velocity | Default mode asks; auto mode in sandboxes skips prompts; plan mode blocks writes entirely. Hooks intercept at the tool-call boundary. | Steps 1, 3 |
| 5 | Persistent memory (MEMORY.md + session memory + durable extraction + auto-dream) enables long-horizon agent effectiveness | Memory pipeline runs post-turn: session memory persists across conversation, durable extraction uses LLM to identify key learnings, auto-dream schedules background consolidation | Steps 1-4 |
| 6 | The ohmo personal agent layer proves the harness is general enough to support a fundamentally different product surface (persistent multi-channel agent) beyond CLI sessions | ohmo uses the same QueryEngine and tool system but adds gateway architecture, soul/identity, and Telegram/Slack/Discord/Feishu channels | Steps 1-5 |
| Suite | Count | Type |
|---|---|---|
| Unit + Integration | 114 | Pytest |
| CLI Flags E2E | 6 | Real model calls |
| Harness Features E2E | 9 | Retry, skills, parallel, permissions |
| React TUI E2E | 3 | Welcome, conversation, status |
| TUI Interactions E2E | 4 | Commands, permissions, shortcuts |
| Skill/Plugin Compat | 12 | anthropics/skills + claude-code/plugins |
| Issue | Severity | Evidence |
|---|---|---|
cli.py at 91KB | High | Single file containing the entire typer CLI app — likely 2000+ lines. Refactoring risk for any CLI flag change. |
| ~3M chars of Python | Medium | Described as "lightweight" in the README, but the codebase is a full-featured production system. Newcomer onboarding friction. |
| Rapid version churn (9 releases in ~5 weeks) | Medium | API surface may not be stable. Breaking changes between minor versions likely. |
| Claude Code compatibility surface | Medium | Deliberate compatibility with anthropics/skills and claude-code/plugins formats creates coupling to Anthropic's undocumented conventions. If Anthropic changes these formats, OpenHarness must follow. |
| Dual product in one repo | Low-Medium | ohmo (personal agent) and OpenHarness (harness library) share a repo. Separate concerns but coupled releases. |
| Dependency | Role | Risk |
|---|---|---|
anthropic>=0.40.0 | Anthropic API client | Actively maintained, primary provider |
openai>=1.0.0 | OpenAI-compat client | Actively maintained |
pydantic>=2.0.0 | Tool input validation | Stable, widely adopted |
rich>=13.0.0 | Terminal formatting | Actively maintained |
typer>=0.12.0 | CLI framework | Actively maintained |
mcp>=1.0.0 | MCP protocol client | Early-stage, spec still evolving |
python-telegram-bot>=21.0.0 | Telegram channel | Actively maintained |
slack-sdk>=3.0.0 | Slack channel | Actively maintained |
discord.py>=2.0.0 | Discord channel | Maintenance varies (community-driven) |
lark-oapi>=1.5.0 | Feishu channel | Regional dependency (ByteDance) |
GitHub Actions CI workflow added in v0.1.8. Details of the CI matrix not specified in the L1.
| Metric | Value |
|---|---|
| Stars | 13,112 (in ~8 weeks) |
| License | MIT |
| Maintainer org | HKUDS (HKU Data Science Lab) |
| Key contributors | novix-science, HKUDS team |
| Growth rate | ~1,600 stars/week (viral) |
| Release cadence | 9 releases in 5 weeks (2026-04 → 2026-05-07) |
| Governance | University lab project, open to community contributions |
| CONTRIBUTING.md | Present |
The 13K stars in 8 weeks signals strong community demand for an open-source Claude Code alternative. The university-lab origin (HKUDS) provides research credibility but raises questions about long-term maintenance if the lab's focus shifts. The MIT license maximizes adoption potential.
| Dimension | OpenHarness | Claude Code | Aider | Continue.dev |
|---|---|---|---|---|
| Language | Python | TypeScript | Python | TypeScript |
| License | MIT (open) | Proprietary | Apache-2.0 | Apache-2.0 |
| Provider support | 10+ backends (Anthropic, OpenAI, Copilot, Gemini, DeepSeek, Ollama, etc.) | Anthropic only | Multi-model (GPT, Claude, etc.) | Multi-model |
| Tool count | 43 built-in + MCP + plugins | 40+ built-in + MCP + plugins | Shell + file edit | IDE-integrated |
| Permission system | 3-mode (default/auto/plan) + hooks | 4-stage pipeline + hooks | Yes/no per edit | IDE-native |
| Multi-agent | Subagent spawning + swarm coordination | 5 spawn modes + team protocols + coordinator | None | None |
| Context management | Auto-compact + session memory | 3 strategies (auto/snip/collapse) | Repo map + chat history | IDE context |
| MCP support | Full (HTTP transport, auto-reconnect) | Full (stdio/sse/http/ws/sdk, OAuth) | None | MCP client |
| Memory persistence | MEMORY.md + durable extraction + auto-dream | CLAUDE.md + session JSONL | Git-based | IDE history |
| Personal agent | ohmo (Telegram/Slack/Discord/Feishu) | None | None | None |
| Skill/plugin compat | anthropics/skills + claude-code/plugins | Native | Limited | Extensions |
| Docker sandbox | Yes | No (native sandbox) | No | No |
| Stars | 13K | N/A (proprietary) | ~30K | ~20K |
| Codebase size | ~3M chars Python | ~512K LOC TypeScript | ~30K LOC Python | ~50K LOC TypeScript |
Winner by scenario:
| Component | File | Role |
|---|---|---|
| Tool contract | src/openharness/tools/base.py | BaseTool, ToolRegistry, ToolResult, ToolExecutionContext |
| Agent loop | src/openharness/engine/query_engine.py | QueryEngine.submit_message() → AsyncIterator[StreamEvent] |
| CLI entry | src/openharness/cli.py (91KB) | typer app: flags, subcommands, session management |
| API clients | src/openharness/api/ | Anthropic-compat, OpenAI-compat, Copilot clients |
| Permissions | src/openharness/permissions/ | 3-mode permission system |
| Hooks | src/openharness/hooks/ | PreToolUse / PostToolUse lifecycle |
| Memory | src/openharness/memory/ | MEMORY.md, session memory, durable extraction |
| Skills | src/openharness/skills/ | On-demand .md loading from 4 locations |
| Plugins | src/openharness/plugins/ | commands + hooks + agents + MCP plugins |
| Commands | src/openharness/commands/ | 54 slash commands |
| MCP client | src/openharness/mcp/ | HTTP transport, auto-reconnect |
| Coordinator | src/openharness/coordinator/ | Subagent spawning, team registry |
| Swarm | src/openharness/swarm/ | Multi-agent swarm coordination |
| Sandbox | src/openharness/sandbox/ | Native + Docker backend |
| Config | src/openharness/config/ | Multi-layer config, provider profiles, migrations |
| Prompts | src/openharness/prompts/ | System prompt assembly, CLAUDE.md injection |
| Services | src/openharness/services/ | autodream, session memory, memory extraction |
| ohmo CLI | ohmo/cli.py | Personal agent entry point |
| Channels | src/openharness/channels/ | Telegram, Slack, Discord, Feishu |
| TUI frontend | frontend/terminal/ | React/Ink TypeScript TUI |
| Package config | pyproject.toml | hatchling build, entry points, dependencies |
input_model as the tool contract boundary: Each tool declares a BaseModel subclass as its input_model. The to_api_schema() method calls model_json_schema() to generate the JSON Schema sent to the LLM. This means the same Pydantic model serves three purposes: (a) documentation for the LLM (via JSON Schema description fields), (b) input validation at runtime (Pydantic parsing rejects malformed arguments), and (c) type-safe access in execute() (the arguments parameter is the parsed model instance). This triple-duty design eliminates schema-code drift — if a tool's input changes, the schema, validation, and code all update from a single source of truth.ToolResult as frozen dataclass, not dict: By making ToolResult a frozen dataclass rather than a plain dict, the codebase prevents a subtle mutation bug: if a tool's output were stored in a mutable dict, downstream code (e.g., conversation history serialization, memory extraction) could accidentally mutate it, causing non-reproducible conversation state. The frozen=True constraint eliminates this class of bugs at the Python level.anthropics/skills and claude-code/plugins formats.BaseTool + Pydantic pattern is straightforward and well-documented.cli.py monolith is a maintenance risk — if you plan to fork and heavily customize the CLI surface, expect refactoring work.cli.py: Factor the 91KB monolith into separate modules per concern (session management, provider config, output formatting, flag parsing).content_block_delta vs. OpenAI choices[0].delta) is the highest-risk code path — fuzz it with malformed/partial SSE events.