Skip to content
Conduit

mcp server

mini-harness

A small, interview-ready coding agent harness

godjupit/mini-harnesscommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

Before you start

  • uv (`curl -LsSf https://astral.sh/uv/install.sh | sh`)
~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "mini-harness": {
      "command": "uvx",
      "args": [
        "mini-openharness"
      ]
    }
  }
}

same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12

Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs

README

synced Oct 26, 2026

Mini Harness

English | 简体中文

A compact, safety-aware runtime for building and studying coding agents.

Agent loop · permission engine · auto-review · subagents · hooks · MCP · skills · context compaction · trace/replay · bwrap sandbox

Mini Harness is a small but complete coding-agent runtime. It focuses on the control plane that is usually hidden behind a product UI: model/tool orchestration, permission decisions, safe execution boundaries, lifecycle hooks, context management, observability, resumable sessions, and delegated subagents.

Status: active development. Suitable for learning, experimentation, interviews, and local prototyping — not a hardened multi-tenant platform.

Why Mini Harness?

A useful coding agent is more than a loop that repeatedly calls an LLM. Once tools can read files, modify a workspace, call remote services, or run shell commands, the runtime must answer:

  • Which actions are allowed, denied, or require review?
  • Can independent tool calls run concurrently without racing on the same files?
  • What happens when a tool times out or the model repeats the same action forever?
  • How do large tool outputs and long conversations fit inside the context window?
  • How can a run be inspected later without replaying its side effects?

Mini Harness implements these concerns as explicit runtime components rather than hiding them inside one monolithic agent function.

Architecture

                 ┌──────────────────────┐
                 │    Model Provider    │
                 │ Responses / Chat API │
                 └──────────┬───────────┘
                            │  text / tool calls
                            ▼
┌──────────────┐   ┌────────┴────────┐
│  User / CLI  ├──►│    AgentLoop    │
└──────────────┘   └────────┬────────┘
                            │
          ┌─────────────────┼──────────────────┐
          ▼                 ▼                  ▼
   Context/Artifacts   Sessions          Trace Sink
                            │             (JSONL audit
                            │              + safe replay)
          pre-tool hook     ▼
          ─────────────► resource resolution + RW locks
                            │
                            ▼
          ┌──────────────────────────┐
          │      Tool Registry       │◄── Skills / MCP
          │  schema validate / authz  │
          └────────────┬─────────────┘
                       ▼
          ┌──────────────────────────┐
          │    PermissionEngine      │
          │  safety → deny/ask/allow │
          └────────────┬─────────────┘
                       │ ALLOW / ASK
                       ▼
          human approval  or  auto-review agent
                       │
                       ▼
                 actual tool run
                       │
                       ▼
                 post-tool hook ──► observation ──► AgentLoop

The built-in agent tool can delegate read-only investigation or planning to specialized subagent AgentLoops with a restricted tool subset.

Core capabilities

AreaWhat the runtime provides
Agent loopModel → tools → observations → model, with typed events, cancellation, max-step limits, and strict tool-call/result pairing
Permission engineOrdered safety → deny → ask → allow → default evaluation with workspace containment and conservative shell checks
Auto-reviewASK decisions can go to an independent tool-less reviewer model; failures and ambiguous responses fail closed
SubagentsBuilt-in Explore and Plan agents run their own AgentLoop with restricted tools; custom definitions can be registered
Tool systemJSON Schema validation, immutable security metadata, structured failures, timeouts, source/effect attribution
ConcurrencyEffect-aware resource locking: independent work runs concurrently, conflicting file access is serialized
Hooksuser_prompt_submit, pre_tool_use, post_tool_use, stop lifecycle hooks with priority, matching, and fail-open/fail-closed behavior
Verification gateA stop hook can block completion and return test/lint feedback so the model repairs before finishing
ContextAtomic tool-turn compaction, handoff summaries with deterministic fallback, reactive context recovery, artifact offloading
TracingAppend-only JSONL traces with secret redaction, timing, permission/tool/provider events, cost, pruning, side-effect-free replay
SessionsPersistent conversations with interruption detection and resume
MCPstdio and Streamable HTTP transports, schema validation, conservative annotation trust, OAuth
SandboxBubblewrap host shell: real host environment, read-only filesystem, writable workspace, fresh /tmp

Requirements

  • Python 3.10+
  • An OpenAI-compatible API key for real model runs
  • bwrap (bubblewrap) on Linux if sandbox_shell is enabled

Installation

git clone <your-repository-url>
cd mini-harness

python -m venv .venv
source .venv/bin/activate
pip install -e '.[dev]'

On Windows PowerShell:

.\.venv\Scripts\Activate.ps1

Create a local environment file and fill in your provider configuration:

cp .env.example .env
OPENAI_API_KEY=your_key_here
OPENAI_MODEL=gpt-4o-mini
OPENAI_BASE_URL=https://api.openai.com/v1
OPENAI_API_MODE=responses

The CLI loads .env from the current directory without overriding variables already present in the shell.

Quick start

Offline demo (no API key, exercises the real runtime path):

wqb --demo --workspace . "Inspect this project and summarize its architecture."

Real model run:

wqb --workspace . "Inspect the repository and explain the agent runtime."

The default provider path uses the Responses API. For a Chat Completions-compatible endpoint:

wqb \
  --api-mode chat \
  --base-url https://compatible.example/v1 \
  --workspace . \
  "Analyze the codebase."

Permission model

Every tool execution is converted into a PermissionRequest carrying the tool's declared source, effect, destructive flag, path, and/or shell command. The decision order is:

  1. Safety boundary
  2. Explicit DENY rules
  3. Explicit ASK rules
  4. Explicit ALLOW rules
  5. Runtime default

Runtime defaults by tool effect:

EffectDefault
read / computeALLOW
write / remote / unknownASK

Safety checks run before configurable rules:

  • Workspace path escape → hard DENY
  • Multi-line shell input → hard DENY
  • Shell syntax that cannot be statically verified → ASK

Human approval (DEFAULT mode)

ASK decisions ask for approval in an interactive terminal. Without an approval callback, ASK fails closed.

wqb --workspace . "Create docs/design.md"

Auto-review (AUTO_REVIEW mode)

--auto-review sends ASK decisions to an independent reviewer model call instead of a human:

wqb --auto-review --workspace . "Refactor the parser and update the tests."

The reviewer receives the tool, effect, path/command, workspace, arguments, and the permission reason, and can only return approve/reject for that specific ASK. Invariants:

  • An explicit DENY is never sent to the reviewer and cannot be overridden
  • Safety DENY remains final
  • Reviewer errors, timeouts, or invalid responses reject the action
  • The reviewer is invoked without tools, so it cannot execute the operation itself

Rules file

{
  "rules": [
    { "tool": "write_file", "path": "secrets/*", "action": "deny" },
    { "tool": "write_file", "path": "docs/*", "action": "allow" },
    { "tool": "sandbox_shell", "command": "npm publish*", "action": "ask" }
  ]
}

Save the JSON as a configuration file owned by the App or deployment that uses it, then pass that file with --permission-config.

Rule patterns use fnmatch-style matching. Unmatched requests still fall through to the runtime default and non-bypassable safety checks.

Agent Kernel / Profile / App architecture

The repository separates reusable mechanics from product-specific behavior:

Agent Kernel
  AgentLoop / Provider / MCP / Session / Trace / Permission
                         │
                         ▼
Agent Profile
  name / system prompt / tool factory / MCP / permissions / steps / output
  skills directory / memory directory
                         │
                         ▼
Agent App
  apps/coding_agent.py / apps/homestay_agent.py

AgentRuntimeBuilder assembles the kernel services around a profile; AgentApp binds that profile to the shared CLI/session lifecycle. A domain App therefore does not inherit Coding Agent tools or behavior unless its profile explicitly selects them.

from mini_openharness import (
    MARKDOWN_OUTPUT,
    AgentApp,
    AgentProfile,
    PermissionPolicy,
)
from mini_openharness.tools import ToolRegistry, ToolSearchTool

def domain_tools() -> ToolRegistry:
    registry = ToolRegistry()
    registry.register(ToolSearchTool(registry))
    return registry

profile = AgentProfile(
    name="domain",
    system_prompt="You are a domain assistant. Use MCP results as facts.",
    tool_factory=domain_tools,
    prompt_mode="replace",
    mcp_config="agent/config/mcp.json",
    permission_policy=PermissionPolicy.HUMAN_APPROVAL,
    max_steps=12,
    output_protocol=MARKDOWN_OUTPUT,
    enable_skills=True,
    enable_memory_prompt=True,
    skills_dir="agent/assets/skills",
    memory_dir="agent/assets/memory",
)

raise SystemExit(AgentApp(profile).run())

Use prompt_mode="append" only when a role intentionally extends the Coding Agent instructions. Tool factories must return a fresh registry for each runtime, which keeps mutable MCP registration and tool visibility isolated. OutputProtocol can additionally declare application/json plus a JSON Schema; its contract is injected into the model system context.

Each App can own an isolated Skill catalog and long-term memory store. Skills use the <skills_dir>/<name>/SKILL.md layout. Memory uses <memory_dir>/MEMORY.md as its index and stores individual topic files beside it. enable_memory_prompt=True both injects the index and exposes memory_read/memory_write, even when the App's tool factory does not include the Coding Agent's default tools. --skills-dir and --memory-dir override the profile paths for one invocation; profiles without either path keep the workspace defaults (skills/ and memdir/).

Run the concrete applications directly:

python apps/coding_agent.py --workspace ../my-project "修复登录错误"
python apps/homestay_agent.py "帮我找杭州西湖附近的民宿"

Each concrete app declares one AgentProfile and passes it to AgentApp; no copy of AgentLoop or provider/MCP/session setup is needed. Coding and Homestay use the same apps/<name>/{config,skills,memory,data} layout. Their entrypoints explicitly keep Session, Trace, and Artifact files in the matching App's data/ directory instead of the generic .mini-oh default. The Homestay MCP business workspace still defaults to the sibling gin-looklook repository; set HOMESTAY_WORKSPACE to override it.

Subagent delegation

Mini Harness exposes an agent tool to the main model. A subagent gets its own AgentLoop, a specialized system prompt, a turn limit, and only the tools declared by its definition.

Two agents are registered by default:

AgentPurposeDefault tools
explore_agentSearch and understand the codebaseread_file, list_dir, find_files
plan_agentProduce an implementation plan from gathered contextread_file, list_dir, find_files

Default subagents only receive read tools, so investigation and planning are separated from mutation by construction. Add a custom agent through AgentRegistry:

from mini_openharness.multiagent import AgentDefinition, default_agents

agents = default_agents()
agents.register(
    AgentDefinition(
        type="reviewer",
        description="reviews implementation changes",
        system_prompt="You are a focused code review agent.",
        max_turns=20,
        tools=("read_file", "list_dir", "find_files"),
    )
)

Delegation is explicit and inspectable — no implicit shared memory, autonomous swarm, or background scheduler.

Tool execution and concurrency

Built-in local tools:

ToolPurpose
read_fileRead a UTF-8 text file inside the workspace
list_dirList the entries inside a directory (one level, / marks directories)
find_filesRecursively search for files by name pattern (cli.py, *.py)
grepSearch file contents with a regex and return file:line: text matches
write_fileWrite a UTF-8 text file
edit_fileSnapshot-checked in-place edit
memory_writeSave long-term memory into topic files under memdir/ and keep memdir/MEMORY.md as the index
memory_readLoad one topic memory file from memdir/ on demand (Just-in-Time Memory)

Runtime-registered tools:

ToolWhen
agentSubagent delegation
load_skillWhen a skill catalog is configured
sandbox_shellWhen the bwrap sandbox shell is enabled
mcp__...Tools discovered from configured MCP servers

Each tool declares a ToolDescriptor used by permission evaluation, resource scheduling, tracing, and attribution:

ToolDescriptor(
    source="extension",
    effect="write",
    destructive=True,
    path_argument="path",
)

Resource-aware scheduling

A model may return several tool calls in one response. Each call resolves to logical ResourceAccess entries and is scheduled with async read/write locks:

Access patternBehavior
Same resource: read + readConcurrent
Same resource: read + writeSerialized
Same resource: write + writeSerialized
Different filesMay run concurrently
Unknown mutationGlobal write lock

Tool observations return in original call order even when execution finishes out of order:

wqb --max-concurrent-tools 4 --workspace . "Inspect several files."

Safe editing

edit_file uses optimistic concurrency: read_file records a SHA-256 snapshot for the run, edit_file requires that snapshot or an explicit expected_sha256, a stale file is rejected instead of overwritten, ambiguous matches are rejected by default, and the final replacement uses a same-directory temporary file with os.replace.

Hooks and verification gates

Hooks are trusted runtime extensions the model cannot opt out of.

EventRuns whenTypical use
user_prompt_submitBefore the prompt enters historyNormalization, policy checks
pre_tool_useBefore resource resolution and executionArgument rewriting, blocking
post_tool_useAfter a tool finishesAuditing, output filtering
stopBefore a final answer becomes doneTests, lint, security verification

A stop hook can act as a verification gate: if it blocks completion, its output returns to the model as feedback so the agent can fix the problem and try again.

Save the hook configuration with the App that owns the verification command, then pass it with --hooks-config.

Command hooks execute with argv directly (no shell), use the workspace as the working directory, and support explicit timeout and fail-open/fail-closed behavior.

Context compaction and artifacts

  • Before model calls, the runtime estimates context size — real token counts when tiktoken is installed (pip install -e '.[tiktoken]'), otherwise a character-based heuristic; with --context-window set, compaction starts at 70% of the window, otherwise --context-threshold is used
  • Tool turns are treated atomically so compaction never creates dangling protocol state
  • Recent messages are retained by token budget: scanning backwards from the newest unit until roughly 12000 tokens (--keep-recent-tokens), with at least one complete tool turn kept
  • The normal path requests a no-tools summary from the configured model; on failure a deterministic summary is used
  • The compaction result is persisted to the session as a compaction record, so resume restores the compacted context instead of replaying all history
  • Large tool outputs are offloaded to .mini-oh/artifacts/<run-id>/, keeping only a head/tail preview inline
wqb \
  --context-threshold 12000 \
  --context-window 200000 \
  --keep-recent 1 \
  --keep-recent-tokens 12000 \
  --max-inline-output 8000 \
  "Work through a long repository task."

If the provider reports a context-window error, the runtime can force one compaction and retry the same logical model step.

Trace and replay

Each run writes an append-only JSONL trace under .mini-oh/traces/<run-id>.jsonl, covering model requests/responses, streaming deltas, tool lifecycle, permission decisions, resource waits, hooks, compaction, MCP attribution, usage, cost, and final run state. Sensitive fields are redacted by default.

wqb trace list
wqb trace show <run-id>
wqb trace replay <run-id>
wqb trace prune --older-than 30          # dry-run
wqb trace prune --max-runs 100 --apply

trace replay only renders the recorded timeline — it does not call the model or execute tools again.

Sessions and resume

wqb sessions
wqb resume <session-id>
wqb resume --latest

continue is accepted as an alias for resume.

MCP and Skills

MCP

The Coding App includes an opt-in GitHub MCP integration. Set GITHUB_PERSONAL_ACCESS_TOKEN and use Docker, then run:

python apps/coding_agent.py \
  --mcp-config apps/coding/config/github-mcp.json \
  --workspace ../my-project \
  "Inspect the open issues."

HTTP OAuth MCP configuration is application-specific: keep its callback URL, token path, scopes, and credentials with the App that owns the integration.

Supports stdio and Streamable HTTP transports, input/output schema validation, structured content preservation, conservative readOnlyHint handling, and OAuth 2.1 authorization-code flows with PKCE. For OAuth-enabled HTTP servers, the MCP SDK discovers RFC 9728 Protected Resource Metadata from the server's 401 challenge, discovers the Authorization Server, requests a resource-bound access token, refreshes it when needed, and persists credentials in an owner-readable token file. The harness does not mint an MCP access token itself.

Skills

skills/
└── my-skill/
    └── SKILL.md

Only lightweight metadata is exposed initially; the model loads the full skill body through load_skill when needed.

wqb --skills-dir ./skills --workspace . "Use the available project skills."

Sandbox shell (bubblewrap)

sandbox_shell runs host bash inside a bubblewrap sandbox. The host environment (python, pytest, git, node, .venv, PATH) is available directly; only the workspace is writable, the rest of the filesystem is read-only, and /tmp is a fresh temporary directory. The working directory persists across calls.

wqb --workspace . "Run the project checks."

There is no unrestricted fallback: if bwrap is not installed, the shell tool is unavailable instead of running on the host without a sandbox.

This is a local development safety boundary, not a malicious multi-tenant sandbox.

Reliability controls

  • Per-tool wall-clock timeout
  • Configurable model retry policy
  • Repeated identical tool-batch circuit breaker
  • Maximum model-step and concurrent-tool limits
  • Fail-closed permission and reviewer failures
  • Typed provider errors for truncation and context overflow
  • Cancellation propagation; one active run per AgentLoop
  • Structured ToolFailure(code, stage, retryable) metadata
wqb \
  --tool-timeout 20 \
  --max-repeated-tool-batches 2 \
  --max-concurrent-tools 4 \
  --max-steps 16 \
  "Diagnose and repair the project."

Project layout

mini-harness/
├── apps/
│   ├── coding_agent.py       # Coding product entry + explicit profile
│   ├── homestay_agent.py     # Homestay product entry + explicit profile
│   ├── coding/               # config, Skills, Memory, and local runtime data
│   └── homestay/             # same App-owned layout
├── src/mini_openharness/
│   ├── agent_app.py          # profile-to-application binding
│   ├── agent_profile.py      # role, tools, permissions, output contract
│   ├── runtime.py            # shared runtime builder and lifecycle owner
│   ├── engine.py              # agent state machine and tool orchestration
│   ├── provider.py            # Responses + Chat-compatible providers
│   ├── tools/                 # registry, descriptors, locks, file tools
│   ├── permissions/           # safety, rules, engine, approval handlers
│   ├── multiagent.py          # subagent definitions, registry, delegation tool
│   ├── hooks.py               # lifecycle hook registry and executor
│   ├── compaction.py          # summaries and artifact offloading
│   ├── trace.py               # JSONL tracing, replay, pruning
│   ├── session.py             # persistent sessions and resume logic
│   ├── skills.py              # progressive skill loading
│   ├── mcp/                   # MCP integration and HTTP OAuth support
│   ├── sandbox.py             # bwrap-sandboxed host shell
│   ├── models.py              # messages and tool-call data structures
│   └── cli.py                 # `wqb` command-line interface
├── tests/                     # runtime and protocol tests
├── pyproject.toml
└── LICENSE

Development

pip install -e '.[dev]'
pytest -q
ruff check .

Tests cover the agent loop, tools, permissions, auto-review, hooks, providers, compaction, tracing, sessions, skills, MCP, sandboxing, and subagent delegation.

Design principles

  • Side effects need a control plane: validation, permissions, hooks, and resource scheduling happen before execution
  • Unknown behavior fails conservatively
  • The model receives recoverable failures: tool errors are observations whenever possible
  • Tool protocol state stays valid: calls and results remain paired through execution, compaction, interruption, and resume
  • Observability must not replay side effects
  • Security claims match the implementation: bwrap isolation, permission policy, and OAuth boundaries are documented with their limits
  • Complexity earns its place: small, inspectable mechanisms over hidden framework behavior

Limitations

  • Resource locking is process-local
  • The host filesystem read-only bind remains part of the trusted computing base
  • Local OAuth token files are permission-protected but not OS-keychain encrypted
  • Hooks are trusted extensions, not sandboxed plugins
  • Default subagents are lightweight delegated loops, not autonomous distributed workers
  • No TUI, plugin marketplace, or remote multi-user execution service
  • Permission rules are intentionally simple glob-based rules, not a full policy language

License

Mini Harness is released under the MIT License.


Small enough to understand. Complete enough to expose the real problems in agent runtimes.

↑↓ navigate openesc close