Metadata-Version: 2.5
Name: localharness
Version: 0.17.1
Summary: Model-agnostic hierarchical agent harness for local LLMs
Project-URL: Homepage, https://localharness.dev
Project-URL: Documentation, https://localharness.dev
Project-URL: Repository, https://github.com/ahwurm/localharness
Project-URL: Issues, https://github.com/ahwurm/localharness/issues
Author: ahwurm
License: MIT
License-File: LICENSE
Keywords: agent-harness,agents,llm,local-llm,ollama,vllm
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.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Requires-Python: >=3.12
Requires-Dist: agent-client-protocol<0.13,>=0.12
Requires-Dist: aiosqlite<1,>=0.22
Requires-Dist: anyio<5,>=4.0
Requires-Dist: click<9,>=8.0
Requires-Dist: ddgs>=6.0
Requires-Dist: httpx>=0.27
Requires-Dist: huggingface-hub>=0.26
Requires-Dist: mcp<2,>=1.27.1
Requires-Dist: numpy<3,>=1.26
Requires-Dist: openai<2,>=1.0
Requires-Dist: packaging>=23.2
Requires-Dist: pluggy<2,>=1.5
Requires-Dist: prompt-toolkit<4,>=3.0
Requires-Dist: pydantic-yaml<2,>=1.4
Requires-Dist: pydantic<3,>=2.13
Requires-Dist: pyyaml<7,>=6.0
Requires-Dist: rich<16,>=15.0
Requires-Dist: scipy<2,>=1.17
Requires-Dist: structlog<26,>=25.5
Requires-Dist: tiktoken>=0.13.0
Requires-Dist: typer<1,>=0.25
Provides-Extra: dev
Requires-Dist: mypy>=1.13; extra == 'dev'
Requires-Dist: pillow<13,>=10; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.8; extra == 'dev'
Provides-Extra: dispatch
Requires-Dist: discord-py>=2.3; extra == 'dispatch'
Provides-Extra: embeddings
Requires-Dist: sentence-transformers<6,>=2.2; extra == 'embeddings'
Requires-Dist: torch<3,>=2.0; extra == 'embeddings'
Provides-Extra: exact-tokenizer
Requires-Dist: llama-cpp-python>=0.3; extra == 'exact-tokenizer'
Provides-Extra: json-repair
Requires-Dist: json-repair<1,>=0.30; extra == 'json-repair'
Provides-Extra: mobile
Requires-Dist: cryptography>=46; extra == 'mobile'
Requires-Dist: http-ece<2,>=1.2; extra == 'mobile'
Requires-Dist: py-vapid<2,>=1.9; extra == 'mobile'
Requires-Dist: segno<2,>=1.6; extra == 'mobile'
Requires-Dist: starlette<1,>=0.40; extra == 'mobile'
Requires-Dist: uvicorn<1,>=0.30; extra == 'mobile'
Provides-Extra: vision
Requires-Dist: pillow<13,>=10; extra == 'vision'
Description-Content-Type: text/markdown

<p align="center">
  <img src="https://raw.githubusercontent.com/ahwurm/localharness/main/docs/assets/logo.svg" alt="LocalHarness logo" width="96" height="96">
</p>

# LocalHarness

[![GitHub stars](https://img.shields.io/github/stars/ahwurm/localharness?style=social)](https://github.com/ahwurm/localharness/stargazers) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)

**Run AI agents on the models you already run locally.**

LocalHarness is an open-source agent harness for local LLMs. It does not serve models: it connects to one you already serve over an OpenAI-compatible API (vLLM, llama.cpp, Ollama or LM Studio) and gives it agents, defined in YAML, with tools, a permission gate in front of every tool call, and helper agents that each work in a fresh context with only the tools they are allowed. A small core runs the agent loop; memory, the phone app, Discord, image generation and the self-improvement loop are plugins that you turn on or off with one command.

![LocalHarness: init detects your local model, start drops you into a ready agent, and it researches a question live with web search and multi-step tool calls](assets/demo.gif)

## A small core, five plugins

- **Core** owns the agent loop, the built-in tools (read, write, edit, glob, grep, bash, python, web search and fetch, delegation to helper agents), MCP servers, config, the permission gate, the bench and the CLI. With a vision model it also takes pictures: drop a screenshot onto the terminal or `/image <path>`, paste one into Zed or the phone page, or attach one on Discord.
- **Plugins** add tools, commands, slash commands, `doctor` checks, settings and chat channels through one plugin API. The five that ship with LocalHarness use the same API as a plugin you write yourself. Turning one off removes what it adds; turning off memory or autoresearch leaves their files on disk (the memories, the experiment archive).

## Quick start

You need Python 3.12 or later, [uv](https://docs.astral.sh/uv/), and a model server running.

```bash
uv tool install 'localharness[embeddings,mobile]'  # memory's CPU model + the phone app
localharness init                    # finds your model server, writes your config
localharness start                   # an interactive session

localharness plugins list            # every plugin and whether it is on
localharness plugins disable autoresearch
localharness plugins enable image --set comfyui_url=http://127.0.0.1:8188
```

`plugins enable` and `plugins disable` write `overrides.yaml`, never your `config.yaml`. Add `--workspace` to change a bundled plugin for one project only. Add the `dispatch` extra for Discord.

`init` ends with every plugin and the command that sets it up. Inside a running session, `/plugins enable <name>` does the same and turns the plugin on right away: the first time, or while the plugin is not set up yet, it asks what the plugin needs and checks it, then it restarts the session with your conversation kept. A first `localharness start` with no config runs `init`'s setup and goes on into the session.

## Supported runtimes

Every runtime is reached through one OpenAI-compatible client; `init` can also attach to any endpoint that is already running. Live-validated means a real end-to-end run on the [DGX Spark](docs/reference-architectures/dgx-spark.md) reference machine; recorded bench runs exist for vLLM only.

| Runtime | Harness starts the server | Tool calling | Token counting | Live-validated |
|---|---|---|---|---|
| **vLLM** | docker or binary | native | exact | reference machine and bench |
| **llama.cpp** | spawns `llama-server` | XML / Hermes fallback | exact | reference machine |
| **Ollama** | spawns `ollama serve` | native | exact with the `exact-tokenizer` extra, else estimated | CPU round-trip |
| **LM Studio** | drives headless `lms` | native | exact with the `exact-tokenizer` extra, else estimated | CPU round-trip |

Setup: [llama.cpp](docs/runtimes/llamacpp.md), [Ollama](docs/runtimes/ollama.md), [LM Studio](docs/runtimes/lmstudio.md), vLLM ([reference architectures](docs/reference-architectures/README.md)). Details: [spec 13](docs/specs/13-provider-support.md).

## Security

An agent that reads untrusted content, such as a web page or an MCP tool's result, is never given tools that change your machine; this separation is on by default, checked when an agent's tools are resolved, and fails closed. Every tool call of every agent, helper agents included, passes one permission gate. A plugin you install has its tools' gate declarations clamped, except for a tool it registers directly on the tool registry instead of returning it from `tools()`, which skips that rule ([SECURITY.md](SECURITY.md#plugins)). Settings that say where the harness connects, which credential it uses or who may talk to it can be set only in your machine-level config, never by a repository you clone. A plugin you turn on is trusted code that runs with your privileges, and nothing contains a plugin that means harm: read [SECURITY.md](SECURITY.md) before you enable one.

## Plugins

| Name | What it does | Default | How to switch |
|------|--------------|---------|---------------|
| `image` | makes pictures with ComfyUI | off | `localharness plugins enable image --set comfyui_url=<url>` |
| `mobile` | Mobile: the phone page, served with its event API by `localharness mobile` | on; needs `localharness[mobile]` | `localharness plugins disable mobile` |
| `memory` | persistent memory: facts recalled into each turn, memory tools, background consolidation | on | `localharness plugins disable memory` |
| `dispatch` | chat: Discord | on; needs `localharness[dispatch]` | `localharness plugins disable dispatch` |
| `autoresearch` | experiment loop | on | `localharness plugins disable autoresearch` |

Each plugin has its own page:

- **image**: a `generate_image` tool and `localharness generate-image`, against a ComfyUI server you run yourself. [Page](docs/plugins/image.md) · [site](https://localharness.dev/plugins/image/) · [Docs](docs/reference-architectures/image-generation.md)
- **Mobile (the `mobile` plugin)**: drive a session from your phone on your own private network. [Page](docs/plugins/mobile.md) · [site](https://localharness.dev/plugins/mobile/) · [Docs](docs/mobile.md)
- **memory**: per-agent SQLite memory recalled into each turn, with idle consolidation. [Page](docs/plugins/memory.md) · [site](https://localharness.dev/plugins/memory/) · [Docs](docs/specs/05-memory.md)
- **dispatch**: `localharness start --channel discord` drives a session from allowlisted Discord messages. [Page](docs/plugins/dispatch.md) · [site](https://localharness.dev/plugins/dispatch/) · [Docs](docs/specs/11-channels.md#the-dispatch-plugin-chat-platforms-discord-today)
- **autoresearch**: propose one harness change, run it through a statistical gate, adopt or reject it. [Page](docs/plugins/autoresearch.md) · [site](https://localharness.dev/plugins/autoresearch/) · [Docs](docs/specs/09-hooks-plugins.md#the-bundled-plugins)

A plugin you install is found in two places, both on your machine and never in a project: a Python package with a `localharness.plugins` entry point in the same environment as LocalHarness, or a folder `~/.localharness/plugins/<name>/`. It stays off, with none of its code imported, until `localharness plugins enable <name>`, which only your machine-level config can do.

## Write a plugin

Copy [the example plugin](examples/plugin-template/README.md): one tool, one command, one slash command, one `doctor` check and two settings, exercised by the test suite and in CI. The API is [spec 09](docs/specs/09-hooks-plugins.md); its version is `"1"`, and changes since its first release have only added optional fields. Plugins written for the previous plugin API no longer load; [spec 09](docs/specs/09-hooks-plugins.md#plugins-written-for-015) says how to port one.

## Status

Early stage (v0.17.1, pre-1.0). Interfaces and config schema may change without notice. Each release's known limitations are listed in the [CHANGELOG](CHANGELOG.md).

## Links

- [SECURITY.md](SECURITY.md): trust boundaries, the permission gate, the prompt-injection threat model
- [docs/specs/](docs/specs/): component specs, starting with the [architecture overview](docs/specs/00-architecture-overview.md); the CLI is [spec 10](docs/specs/10-cli.md)
- [docs/zed.md](docs/zed.md): use LocalHarness from Zed's agent panel
- [docs/task-context.md](docs/task-context.md): how a normal session keeps a working record for substantive work (references, checks, delegation, resume), with a synthetic [example workflow](examples/workflows/research-note/)
- [docs/reference-architectures/](docs/reference-architectures/README.md): tested hardware and setup notes
- [docs/running-agents-locally.md](docs/running-agents-locally.md): where LocalHarness fits among local agent tools, and where it is behind
- [LocalShift](https://github.com/ahwurm/localshift): the companion project that moves a headless Claude Code job onto LocalHarness once a local model proves good enough
- [localharness.dev](https://localharness.dev)

## License

[MIT](LICENSE)
