leharness
v0.4.0
Published
Experimental agent harness CLI.
Readme
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 --helpRun without installing:
npx leharness@latest --helpUpdate an existing global install:
npm install -g leharness@latestStart an interactive session:
lhRun one prompt and exit:
lh "summarize this repo"Use OpenAI:
export OPENAI_API_KEY=...
lh --provider openaiUse DeepSeek:
export DEEPSEEK_API_KEY=...
lh --provider deepseek
lh --provider deepseek --model deepseek-v4-proUse Ollama:
ollama pull qwen3.6:27b-coding-nvfp4
lh --provider ollamaIn the TUI, /model opens a client-side model picker and /effort opens a
client-side reasoning effort picker for supported models:
/model
/effortSee 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:verifyWhy
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 loopSimple parent loopOne 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 wrappersThe 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 sessionsThe 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 eventsThe 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 featureThe 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", }) // inline: // { status: "completed", output: "..." } // background: // { status: "started", task_id: "task_42" } onTaskCompleted(task) { appendEvent({ type: "task.completed", task_id: task.id, session_id: task.sessionId, }) markSessionRunnable(task.sessionId) }Isolated subagentsChild 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", }) // later: // waitTask(child.task_id) // or react when completion is projected back into the parent sessionFilesystem-backed artifactsBig 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:
- OpenAI Codex for strong tool/runtime layering and task-oriented execution. Notes: research/codex-architecture.md
- Claude Code leak coverage because I ended up studying a research copy for prompt caching, long-running task behavior, and subagent patterns. Notes: research/claude-code-architecture.md
- OpenCode for durable session state, artifacts, and a TypeScript codebase that is easier to read than most. Notes: research/opencode-architecture.md
- OpenDev for the cleanest staged loop and the best "this code was built to be explained" architecture of the bunch. Notes: research/opendev-architecture.md
- OpenClaw mostly as an adjacent reference for what starts growing around a harness once it turns into more of an agent product. Notes: research/openclaw-architecture.md
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:
