install
source · Clone the upstream repo
git clone https://github.com/Aradotso/trending-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Aradotso/trending-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/openharness-agent" ~/.claude/skills/aradotso-trending-skills-openharness-agent && rm -rf "$T"
manifest:
skills/openharness-agent/SKILL.mdsource content
--- name: openharness-agent description: Skill for using OpenHarness (oh) — the lightweight open-source Python agent harness with tool-use, skills, memory, and multi-agent coordination triggers: - set up OpenHarness agent - use oh command for AI agent - configure openharness tools - build agent with openharness - add skills to openharness - create multi-agent workflow with openharness - troubleshoot openharness tool execution - extend openharness with custom plugins --- # OpenHarness Agent Harness (`oh`) > Skill by [ara.so](https://ara.so) — Daily 2026 Skills collection. OpenHarness is a lightweight Python agent harness that wraps an LLM with tools, memory, permissions, and multi-agent coordination — 44x lighter than Claude Code at ~11,700 lines of Python. It exposes a single `oh` CLI and a composable Python API. --- ## Installation **Requirements:** Python 3.10+, [uv](https://docs.astral.sh/uv/), Node.js 18+ (optional, for React TUI) ```bash git clone https://github.com/HKUDS/OpenHarness.git cd OpenHarness uv sync --extra dev
Activate the environment or use
uv run prefix for all commands.
Environment Configuration
# Anthropic (default) export ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY # Moonshot / Kimi (Anthropic-compatible) export ANTHROPIC_BASE_URL=https://api.moonshot.cn/anthropic export ANTHROPIC_API_KEY=$MOONSHOT_API_KEY export ANTHROPIC_MODEL=kimi-k2.5 # Vertex-compatible gateway export ANTHROPIC_BASE_URL=https://your-vertex-gateway/anthropic export ANTHROPIC_API_KEY=$GCP_API_KEY # Bedrock-compatible gateway export ANTHROPIC_BASE_URL=https://your-bedrock-gateway export ANTHROPIC_API_KEY=$AWS_API_KEY
Provider detection is automatic based on
ANTHROPIC_BASE_URL content (moonshot, vertex, aiplatform, bedrock).
CLI Reference
Interactive Mode
oh # launch interactive TUI (React+Ink terminal UI) uv run oh # without activating venv
Non-Interactive / Scripted Mode
# Single prompt, plain text output oh -p "Explain this codebase" # JSON output (structured, for programmatic consumption) oh -p "List all functions in main.py" --output-format json # Streaming JSON events (real-time, line-delimited) oh -p "Fix the bug in src/api.py" --output-format stream-json # Pass a file as context oh -p "Review this file" --file src/main.py # Resume a previous session oh --resume <session-id> # Non-interactive with permission bypass (CI/automation) oh -p "Run tests and fix failures" --permission-mode auto
Key Slash Commands (inside interactive session)
/help — list all 54 commands /plan — show or set current task plan /commit — stage and commit changes via git /resume — resume a previous session /memory — view or edit persistent MEMORY.md /compact — manually trigger context compression /tools — list available tools /skills — list loaded skills /hooks — show registered hooks /agents — list spawned subagents /mcp — manage MCP server connections /config — view or edit current configuration /clear — clear conversation context /exit — quit the session
Architecture Overview
openharness/ engine/ # Agent loop: query → stream → tool-call → loop tools/ # 43 built-in tools (file, shell, search, web, MCP) skills/ # On-demand .md skill loading plugins/ # Commands, hooks, agents, MCP servers permissions/ # Multi-level safety: path rules, command deny hooks/ # PreToolUse / PostToolUse lifecycle events commands/ # 54 slash commands mcp/ # Model Context Protocol client memory/ # Persistent cross-session MEMORY.md tasks/ # Background task lifecycle coordinator/ # Subagent spawning + team coordination prompts/ # System prompt assembly, CLAUDE.md injection config/ # Multi-layer config with migrations ui/ # React+Ink TUI backend protocol
The Agent Loop (Python API)
from openharness.engine import AgentLoop from openharness.config import HarnessConfig config = HarnessConfig.load() # reads .openharness.json + env vars loop = AgentLoop(config=config) # Run a task programmatically result = await loop.run( prompt="Refactor src/utils.py to use dataclasses", permission_mode="auto", # bypass interactive approvals output_format="stream-json", # text | json | stream-json ) print(result.output) print(f"Tokens used: {result.token_count}, Cost: ${result.cost:.4f}")
Streaming Output
import asyncio from openharness.engine import AgentLoop from openharness.config import HarnessConfig async def stream_agent(): loop = AgentLoop(config=HarnessConfig.load()) async for event in loop.stream(prompt="Analyse main.py"): if event["type"] == "text": print(event["content"], end="", flush=True) elif event["type"] == "tool_use": print(f"\n[Tool] {event['name']}({event['input']})") elif event["type"] == "tool_result": print(f"[Result] {event['content'][:120]}") elif event["type"] == "done": print(f"\nDone. Cost: ${event['cost']:.4f}") asyncio.run(stream_agent())
Tools (43 Built-in)
Categories and representative tools:
| Category | Tools |
|---|---|
| File I/O | , , , , |
| Shell | , , |
| Search | , , |
| Web | , |
| MCP | Any MCP server tool via |
| Agent | , |
Calling a Tool Directly (Python)
from openharness.tools import ToolRegistry registry = ToolRegistry() # Read a file result = await registry.execute("read_file", {"path": "src/main.py"}) print(result.content) # Run a shell command result = await registry.execute("bash", {"command": "pytest tests/ -q"}) print(result.content) # Fetch a URL result = await registry.execute("web_fetch", {"url": "https://example.com"}) print(result.content[:500])
Skills (On-Demand Knowledge Loading)
Skills are
.md files injected into the system prompt on demand. Compatible with anthropics/skills and claude-code/plugins formats.
Installing a Skill
# Place skill file in the skills directory cp my-skill.md ~/.openharness/skills/my-skill.md # Or project-local cp my-skill.md .openharness/skills/my-skill.md
Writing a Custom Skill
--- name: my-project-patterns description: Coding patterns for MyProject triggers: - use myproject conventions - myproject coding style --- # MyProject Patterns Always use `MyProject.create()` factory, never direct constructors. Prefer async context managers for resource handling. ...
Loading Skills Programmatically
from openharness.skills import SkillLoader loader = SkillLoader(skill_dirs=["~/.openharness/skills", ".openharness/skills"]) # List available skills skills = loader.list_skills() for skill in skills: print(f"{skill.name}: {skill.description}") # Load specific skill into context content = loader.load("my-project-patterns") print(content)
Memory (Persistent Cross-Session)
OpenHarness reads and writes
MEMORY.md (and CLAUDE.md) for persistent context.
# View current memory /memory # The agent auto-updates MEMORY.md with important facts # You can also edit it directly vim ~/.openharness/MEMORY.md
from openharness.memory import MemoryStore memory = MemoryStore() # Read persisted memory content = memory.read() print(content) # Write a fact to persist across sessions memory.append("## Project Notes\n- Always run `make lint` before committing.")
CLAUDE.md is auto-discovered from the project root and injected as system context:
# Project-level context injection echo "# Project Rules\nUse Black for formatting. Python 3.11+." > CLAUDE.md
Permissions & Governance
Permission Modes
| Mode | Behaviour |
|---|---|
| Prompt user for every tool call (default interactive) |
| Execute all tools without prompting (CI/automation) |
| Only allow read operations; block write/shell |
oh -p "Run all tests" --permission-mode auto oh -p "Review code only" --permission-mode restricted
Configuration File (.openharness.json
)
.openharness.json{ "permission_mode": "ask", "allowed_paths": ["./src", "./tests"], "denied_commands": ["rm -rf", "curl | bash"], "denied_paths": ["/etc", "/usr"], "hooks": { "pre_tool_use": ["./hooks/log_tool.py"], "post_tool_use": ["./hooks/audit.py"] } }
Hooks (PreToolUse / PostToolUse)
# hooks/log_tool.py import sys, json event = json.load(sys.stdin) # {"tool": "bash", "input": {"command": "..."}} tool_name = event["tool"] if tool_name == "bash" and "rm" in event["input"].get("command", ""): # Block the tool call by exiting non-zero print(json.dumps({"block": True, "reason": "rm not allowed"})) sys.exit(1) # Allow by default print(json.dumps({"block": False})) sys.exit(0)
Register hooks in
.openharness.json or programmatically:
from openharness.hooks import HookRegistry registry = HookRegistry() @registry.pre_tool_use async def my_hook(event): print(f"About to call: {event.tool_name}") if event.tool_name == "bash": cmd = event.input.get("command", "") if "drop table" in cmd.lower(): return {"block": True, "reason": "DB drop blocked"} return {"block": False}
Multi-Agent Coordination
Spawning a Subagent
from openharness.coordinator import AgentCoordinator coordinator = AgentCoordinator() # Spawn and await a subagent result = await coordinator.spawn( prompt="Write unit tests for src/parser.py", tools=["read_file", "write_file", "bash"], permission_mode="auto", ) print(result.output)
Background Tasks
from openharness.tasks import TaskManager tasks = TaskManager() # Start a background task task_id = await tasks.start( name="run-ci", prompt="Run the full test suite and report failures", permission_mode="auto", ) # Poll status status = await tasks.status(task_id) print(status) # pending | running | done | failed # Await completion result = await tasks.wait(task_id) print(result.output)
MCP (Model Context Protocol) Integration
// .openharness.json — register an MCP server { "mcp_servers": { "filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"] }, "github": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-github"], "env": {"GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}"} } } }
from openharness.mcp import MCPClient client = MCPClient(server_name="filesystem") await client.connect() tools = await client.list_tools() result = await client.call_tool("read_file", {"path": "/workspace/README.md"}) print(result)
Context Compression (Auto-Compact)
When the context window fills, OpenHarness auto-compacts the conversation:
# Trigger manually inside session /compact # Configure threshold in .openharness.json
{ "context": { "auto_compact": true, "compact_threshold": 0.85 } }
Common Patterns
CI/CD Automation
#!/bin/bash # .github/scripts/agent-review.sh oh -p "Review the diff in this PR and list any bugs" \ --output-format json \ --permission-mode restricted \ | jq '.findings'
Project Bootstrap
oh -p "Inspect this repository and create a CLAUDE.md with project conventions"
Iterative Fix Loop
import asyncio from openharness.engine import AgentLoop from openharness.config import HarnessConfig async def fix_until_green(): loop = AgentLoop(config=HarnessConfig.load()) result = await loop.run( prompt="Run pytest. If any tests fail, fix the code and re-run until all pass.", permission_mode="auto", ) print(result.output) asyncio.run(fix_until_green())
Troubleshooting
oh: command not found
oh: command not found# Use uv run instead uv run oh -p "hello" # Or activate the venv first source .venv/bin/activate oh -p "hello"
API authentication errors
# Confirm env vars are exported (not just set) export ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY echo $ANTHROPIC_API_KEY # must be non-empty # For custom providers, verify base URL format export ANTHROPIC_BASE_URL=https://api.moonshot.cn/anthropic # no trailing slash
Tool execution blocked unexpectedly
# Check permission mode oh --permission-mode auto -p "your prompt" # Check denied_commands and denied_paths in .openharness.json cat .openharness.json | jq '.denied_commands, .denied_paths'
Context window full / slow responses
# Manually compact inside session /compact # Lower auto-compact threshold in config # "compact_threshold": 0.75
Subagent not returning
# Add a timeout result = await coordinator.spawn( prompt="...", timeout=120, # seconds )
Running Tests
# Unit tests (114 tests) uv run pytest tests/unit/ -q # E2E suites (6 suites) uv run pytest tests/e2e/ -q # Specific subsystem uv run pytest tests/unit/test_tools.py -v
Key Files to Know
| File | Purpose |
|---|---|
| Project-level context auto-injected into every session |
| Persistent cross-session memory |
| Project config (permissions, hooks, MCP servers) |
| Global user config |
| Provider detection logic |
| Core agent loop implementation |
| All 43 tool implementations |