leharness
leharness is me trying to understand harness engineering by building one myself.
I mostly want to understand the parts that actually matter underneath modern harnesses:
- the loop
- the event log
- the tool runtime
- the task model
- background vs blocking execution
- subagents
- compaction
- and the products that can grow on top of that core
Also, the name exists because I dreamt up the project while I was in Paris.
CLI
The CLI is experimental and published as leharness on npm. It installs one
command: lh.
Install globally:
npm install -g leharness
lh --help
Run without installing:
npx leharness@latest --help
Update an existing global install:
npm install -g leharness@latest
Start an interactive session:
lh
Run one prompt and exit:
lh "summarize this repo"
Use OpenAI:
export OPENAI_API_KEY=...
lh --provider openai
Use DeepSeek:
export DEEPSEEK_API_KEY=...
lh --provider deepseek
lh --provider deepseek --model deepseek-v4-pro
Use Ollama:
ollama pull qwen3.6:27b-coding-nvfp4
lh --provider ollama
In the TUI, /model opens a client-side model picker and /effort opens a
client-side reasoning effort picker for supported models:
/model
/effort
See lh --help for the full set of options and environment variables.
The CLI also reads a repo-local .env file before provider setup.
Sessions are saved under .leharness/sessions in the current working directory
unless LEHARNESS_HOME is set. Workspace-owned skills can live under
.leharness/skills/<name>/SKILL.md; inherited workspace skill locations
.agents/skills and .claude/skills are also discovered.
For local package development:
pnpm install
pnpm package:verify
Why
Most of the interesting agent repos mix together:
- a harness kernel
- product surfaces
- UI/TUI
- routing and integrations
- a lot of operational scar tissue
That is useful if you want the whole product, but it makes it harder to study the lower-level harness decisions cleanly.
So this repo is basically me taking notes, doing comparative research, and hopefully ending up with a core I can improve one feature at a time without having to keep rewriting the foundation.
High-Level Goals
- Build a small, explicit agent loop that stays easy to reason about.
- Use append-only event logs as the canonical session state.
- Persist important state and large outputs to the filesystem whenever possible.
- Treat long-running work as a first-class concept instead of a shell-only hack.
- Support isolated subagents and background work without turning the core into spaghetti.
- Keep the harness channel-agnostic so CLI, web, TUI, bots, or VM runners can all sit on top of the same engine.
- Make the system easy to revisit and improve in bursts instead of requiring rewrites every time a new feature appears.
Core Bets
These are the main architectural bets I want the base layer to rest on:
At the boundary, the flow should look like:
ingress -> invocation -> append invocation events -> run session loop
-
Simple parent loop
One clear control loop that stays small, readable, and focused on orchestration.
while (true) {
const events = loadEvents(sessionId)
const session = projectSession(events)
if (shouldCompact(session)) {
compact(session)
continue
}
const prompt = buildPrompt(session)
const modelOutput = await callModel(prompt)
const toolResults = await executeToolCalls(session, modelOutput.toolCalls)
if (!shouldContinue(session, modelOutput, toolResults)) break
}
-
Generic core, thin wrappers
The harness core should stay generic. CLI, coding-agent defaults, web/TUI, bots, and future products should sit on top instead of leaking into the loop.
-
Event-sourced sessions
The event log should be the source of truth for what happened in a session.
{"type":"invocation.received","kind":"message","text":"fix the failing test"}
{"type":"step.started","step_id":"step_1"}
{"type":"model.completed","tool_calls":[{"tool":"bash","execution":"auto"}]}
{"type":"task.started","task_id":"task_42","kind":"bash"}
{"type":"task.completed","task_id":"task_42","summary":"2 tests still failing"}
-
Session derived from events
The session should be rebuilt from events, and everything else should be derived from that session.
const session = projectSession(events)
const prompt = buildPrompt(session)
const notifications = projectTaskNotifications(session)
const artifacts = projectArtifacts(session)
-
Background as a first-class runtime feature
The agent should be able to send work off, keep moving, and react when completions come back. That means task-like operations should be able to finish inline or return a durable handle when they need to keep running.
const testRun = await bash({
command: "npm test",
execution: "auto",
})
onTaskCompleted(task) {
appendEvent({
type: "task.completed",
task_id: task.id,
session_id: task.sessionId,
})
markSessionRunnable(task.sessionId)
}
-
Isolated subagents
Child runs should have bounded scope, inspectable state, and a clear handoff back to the parent.
const child = await spawnSubagent({
session_id: session.id,
prompt: "investigate the lint failures",
execution: "background",
})
-
Filesystem-backed artifacts
Big outputs should live on disk with stable references so they can be revisited later without bloating active context.
const artifact = await persistArtifact({
kind: "tool_output",
content: stdout,
})
appendEvent({
type: "artifact.created",
artifact_id: artifact.id,
path: artifact.path,
})
Status
The kernel — loop, event log, tool runtime, background tasks, subagents, artifacts, compaction — is built. Per-feature design docs live in plans/.
What's Next
- web inspector
- coding-agent wrapper
- MCP integration
- tool-agnostic kernel: the bigger built-in features (tasks, subagents, artifacts, skills) currently ship their model-facing tools inside the kernel. Pull those into a modular "default tools" layer over the kernel services, so the core loop carries no opinions about which tools exist
- branchable session history
- VM runners
- Telegram or other bot adapters
- more opinionated agent products built on top of the same harness
Direct Inspiration
These are the repos I've been reading against while trying to figure out what I actually want the core of leharness to be:
North Star
I want a harness core that is:
- simple enough to explain
- modular enough to evolve
- durable enough to resume
- and strong enough that future work feels like adding a feature, not rebuilding the foundation
AI Tools Used
These were the main AI tools I used while doing the research and writing in this repo:
Links