Metadata-Version: 2.4
Name: mechaharness
Version: 0.1.2
Summary: Agentic harness that abstracts inference backends and model-family harness architectures
Author-email: Richard Lee <rlee@tokyo3.com>
License: MIT
Project-URL: Homepage, https://rl337.org/mechaharness/
Project-URL: Documentation, https://rl337.org/mechaharness/
Project-URL: Repository, https://github.com/rl337/mechaharness
Project-URL: Issues, https://github.com/rl337/mechaharness/issues
Project-URL: Changelog, https://rl337.org/mechaharness/changelog.html
Keywords: agent,llm,harness,inference,cli
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pyiv>=0.3.0
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.7
Requires-Dist: pydantic-settings>=2.3
Requires-Dist: typer>=0.12
Requires-Dist: rich>=13.7
Requires-Dist: fastapi>=0.111
Requires-Dist: uvicorn[standard]>=0.30
Requires-Dist: eval_type_backport>=0.2; python_version < "3.10"
Provides-Extra: dev
Requires-Dist: pytest>=8.2; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
Requires-Dist: ruff>=0.5; extra == "dev"
Requires-Dist: mypy==1.19.1; extra == "dev"
Requires-Dist: build>=1.2; extra == "dev"
Requires-Dist: twine>=5.0; extra == "dev"
Provides-Extra: docs
Requires-Dist: sphinx>=5.0.0; extra == "docs"
Requires-Dist: sphinx-rtd-theme>=1.0.0; extra == "docs"
Requires-Dist: myst-parser>=2.0.0; extra == "docs"
Dynamic: license-file

# MechaHarness

![MechaHarness](docs/logo.png)

Python **agent harness** for hosts that compose inference safely — not a thin
chat-client wrapper. Founding principles:

1. **Modular via dependency injection** — pyiv `MechaHarnessConfig`; hosts
   subclass and compose (no forked enums / string registries)
2. **Multi-model / lanes native** — reason, judge, and media lanes;
   `Completion` / `Judgement` / `Generation` outcomes
3. **Cost in the object model** — `CostAccountant` on the harness path so runs
   cannot quietly go AWOL on spend
4. **EventLog telemetry built in** — queryable lifecycle, inference, tools,
   cost, and access events on every run

Also: deny-by-default grants / `CompoundPolicy`, and host-extendable open
identity. Docs: [https://rl337.org/mechaharness/](https://rl337.org/mechaharness/)

## Install

```bash
pip install mechaharness
```

Requires Python 3.9+. Unreleased `main`:

```bash
pip install git+https://github.com/rl337/mechaharness.git
```

## Quick start

```bash
# List backends / harness families
mechaharness backends
mechaharness families

# Local LM Studio (OpenAI-compatible)
mechaharness run "What is 2+2?" \
  --backend lmstudio \
  --family tool_loop \
  --model local-model

# OpenAI
export MECHA_API_KEY=sk-...
mechaharness run "Hello" --backend openai --model gpt-4o-mini

# HTTP API
mechaharness serve --port 8080
curl -s http://127.0.0.1:8080/health
```

Environment variables use the `MECHA_` prefix (`MECHA_API_KEY`, `MECHA_INFERENCE_BACKEND`, `MECHA_MODEL`, `MECHA_BASE_URL`, …).

## Library usage

Non-DI (OpenAPI-shaped):

```python
import asyncio
from mechaharness import RunRequest, run

async def main() -> None:
    result = await run(RunRequest(prompt="What is 19 + 23?", backend="lmstudio"))
    print(result.final_text)

asyncio.run(main())
```

DI (pyiv Config hooks):

```python
from pyiv import get_injector

from mechaharness.di import MechaHarnessConfig
from mechaharness.harness.base import AbstractHarness
from mechaharness.harness.tool_loop import ToolLoopHarness
from mechaharness.inference.openai_compat import OpenAICompatStrategy


class MyConfig(MechaHarnessConfig):
    def get_inference_class(self):
        return OpenAICompatStrategy

    def get_harness_class(self):
        return ToolLoopHarness


injector = get_injector(MyConfig)
harness = injector.inject(AbstractHarness)
```

See [Dependency injection](https://rl337.org/mechaharness/guides/dependency-injection.html).

## Extending

Subclass `InferenceStrategy` (constructor takes `Settings`) and return it from `get_inference_class()`, or merge it into `SettingsConfig.inference_classes()`. Subclass `AbstractHarness` and return it from `get_harness_class()`.

## Development

```bash
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev,docs]"
./run_checks.sh
```

`run_checks.sh` is what CI runs: ruff, mypy, pytest, Sphinx, and CLI smoke. EventLog + cost without a GPU:

```bash
mechaharness run "What is 2+2?" --backend mock --family pass_through --model mock --json
```

## Design notes

| Concern | Pattern | Why |
|--------|---------|-----|
| Wiring | pyiv Config hooks | DI-first; hosts inject MechaHarness types |
| Provider I/O | Strategy | Swap cloud/local without touching agent logic |
| Agent loop | Template method hierarchy | Share turn accounting; specialize stop/tool rules per model family |
| Frontends | OpenAPI `RunRequest` / `run()` | Same contract for CLI, HTTP, and non-DI Python |
| Bindings later | Pydantic + OpenAPI | Types stay serializable; API is the first non-Python client surface |
