An Agent Runtime Engine — a Go binary that runs a complete agentic loop: receives user prompts via WebSocket, calls Gemini, dispatches built-in tools (file I/O, search, shell), and streams structured events back to ADK clients.
Uses pipe-based handshake (stdin/stdout) for secure startup and WebSocket + Protobuf for wire protocol.
lhctl (Interactive TUI) / SDK ◄── WebSocket + Protobuf ──► LocalHarness Binary / Daemon (Go)
│
├── Dual Execution Modes
│ ├── Ephemeral Subprocess (Stdin/Stdout pipe handshake)
│ └── Persistent Background Daemon (Unix socket + TCP)
│
├── Web Remote Control (Cloudflare Quick Tunnels)
│ ├── Zero-login trycloudflare.com ephemeral tunnels
│ ├── ANSI terminal QR code generation for mobile pairing
│ ├── Dual-protocol engine (protojson for browsers, protobuf for CLI)
│ └── Mobile web app with streaming tokens & approval cards
│
├── Interactive TUI (lhctl)
│ ├── Real-time token streaming & markdown rendering
│ ├── Animated tool spinners & execution duration
│ ├── Interactive unified diff approvals & YOLO mode
│ ├── Subagent tree & transcript drill-down
│ └── @file autocompletion across workspaces
│
├── Agentic Engine
│ ├── LLM ↔ Tools ↔ Results loop
│ ├── Turn pause/resume (InterruptRequest / ResumeRequest)
│ └── Context compaction & reduction
│
├── LLM Providers
│ ├── OpenAI-compatible (GPT-4o, Claude, DeepSeek, etc.)
│ └── Resilient backoff retry with Retry-After header parsing
│
├── Multi-Workspace & Trust
│ ├── First-time trust prompts (~/.divmora/config/settings.json)
│ └── Dynamic /workspace add/remove/list
│
└── Built-in Tools
├── view_file, write_to_file, replace_file_content
├── list_dir, grep_search, find_file
├── run_command, manage_task, finish
├── invoke_subagent, define_subagent, manage_subagents
├── search_web, read_url_content, schedule
└── knowledge_write/replace/delete, browser (Playwright)
- Go 1.25 or higher (required for build and SDK usage)
The SDK auto-downloads the binary on first run (zero-install). For manual control:
# Option 1: go install (builds from source)
go install github.com/divmora/localharness/cmd/localharness@latest
# Option 2: Download prebuilt binary
curl -sSL https://github.com/divmora/localharness/releases/latest/download/localharness-linux-amd64.tar.gz | tar xz
sudo mv localharness /usr/local/bin/
# Option 3: Build from source
make buildIf you download the .dmg installer or the binary for macOS from GitHub Releases, Gatekeeper may flag the file as "damaged" because it is not officially code-signed with an Apple Developer Program certificate.
To bypass this error and run the app, you must remove the quarantine flag using the terminal:
# If using the Divmora.app from the .dmg
xattr -cr /Applications/Divmora.app
# If using the raw binary
xattr -cr localharnessSee docs/binary-distribution.md for the full resolution chain and cross-SDK strategy.
Build and run the interactive multi-agent chat interface:
make build-lhctl
# Launch a fresh interactive session (default)
./bin/lhctl
# Resume the most recent conversation (or specify full/partial ID with -c <id>)
./bin/lhctl -c
# Or run with explicit model and YOLO mode
./bin/lhctl run --model=gpt-4o --yolo
# Launch background task and detach
./bin/lhctl run --prompt="Audit project dependencies" --detach
# Attach to a running session
./bin/lhctl attach <session-id>
# View version and runtime information
./bin/lhctl versionIn
lhctl, sessions always start fresh by default. Use-cto resume the most recent conversation or-c <id>to resume by ID. On exit,lhctldisplays the exact command to resume your session. PressShift+Tabto cycle betweenDEFAULT(Safe Mode),ACCEPT-EDITS(Auto-Accept Edits), andPLAN(Plan-Before-Act) modes. Type/for instant command autocomplete or@for workspace file mentions. See docs/lhctl.md for full documentation.
Control LocalHarness sessions from your phone, tablet, or secondary browser over a zero-login Cloudflare Quick Tunnel:
# Launch session with remote control enabled
./bin/lhctl --tunnel
# Or start tunnel inside active TUI
/remote-controlPrints an instant scannable ANSI QR code and secure link (https://<subdomain>.trycloudflare.com/?key=...#<session-id>). See docs/remote-control.md for full details.
make build
# Binary created at bin/localharness# Run a prompt (requires Gemini / LiteLLM API key)
export LITELLM_API_KEY=your_key
go run ./cmd/testclient --prompt "List the files in the current directory"
# Enable shell commands
go run ./cmd/testclient --enable-commands --prompt "Run 'ls -la'"
# Use thinking model
go run ./cmd/testclient --thinking=medium --prompt "Explain the architecture"
# Auto-approve all tool calls (for CI)
go run ./cmd/testclient --auto-approve --prompt "Create hello.txt with 'Hello World'"The wire protocol uses a pipe-based handshake for secure startup, followed by WebSocket with Protocol Buffer binary frames for communication.
SDK Binary
│── Spawn + capture pipes ──────────►│
│── Write InputConfig (stdin) ──────►│ Binary binds :0, generates API key
│◄── Read OutputConfig (stdout) ────┤ Returns port + API key
│── WS connect with API key ────────►│
Client Harness
│ │
├── ClientMessage(InitRequest) ──►│ Configure session
│◄── ServerMessage(InitResponse) ─┤
│ │
├── ClientMessage(UserMessage) ──►│ Send prompt
│ │
│◄── ServerMessage(StepUpdate) ──┤ Tool call (ACTIVE)
│◄── ServerMessage(StepUpdate) ──┤ Tool result (DONE)
│◄── ServerMessage(StepUpdate) ──┤ Tool call (ACTIVE)
│◄── ServerMessage(StepUpdate) ──┤ Tool result (DONE)
│◄── ServerMessage(StepUpdate) ──┤ Final text response
│◄── ServerMessage(TrajState) ──┤ IDLE
│ │
See proto/localharness/v1/localharness.proto for the full schema.
Key messages:
ClientMessage— wrapsInitRequest,UserMessage,ToolResult,CancelRequest,PermissionResponse,QuestionResponseServerMessage— wrapsInitResponse,StepUpdate,TrajectoryState,ErrorEventStepUpdate— the core event with tool actions, text, thinking, state machineHarnessConfig— LLM provider, tools, workspaces, system instructions
The harness provides a rich set of built-in tools organized into categories:
| Category | Tools | Description |
|---|---|---|
| File I/O | view_file, write_to_file, replace_file_content, multi_replace_file_content, list_dir |
Read, create, edit files and list directories |
| Search | grep_search, find_file |
Ripgrep-powered content search and filename pattern matching |
| Execution | run_command, manage_task, schedule |
Shell commands (sync/background/persistent), task management, timers and cron |
| Web | search_web, read_url_content |
Web search and URL content fetching |
| Agent Orchestration | invoke_subagent, define_subagent, manage_subagents, send_message |
Spawn typed child agents, define types, manage instances |
| Knowledge | knowledge_write, knowledge_replace, knowledge_delete |
Persistent project-scoped knowledge items (engine-intercepted) |
| Code Graph | codegraph_search, codegraph_find_references, codegraph_call_hierarchy, codegraph_get_impact, codegraph_diff_branches |
AST-level symbol search, call hierarchy, and blast radius in DuckDB (docs) |
| Browser | browser_*, browser_subagent |
Browser automation via Playwright MCP with persistent profiles (--user-data-dir), vision bounding boxes, session recordings, and 2FA/CAPTCHA handoff (docs) |
| Desktop | desktop_*, desktop_subagent |
Native cross-platform desktop automation (macOS, Linux, Windows) with visual screenshots |
All file tools enforce workspace restrictions — operations outside configured workspace directories are rejected.
For the complete list of proto action fields, message types, and field numbers, see the StepUpdate Actions table in docs/architecture.md.
The project includes a Go ADK client under sdk/ that allows running the agent programmatically:
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/divmora/localharness/adk"
)
func main() {
cfg := adk.NewLocalAgentConfig()
cfg.LitellmAPIKey = os.Getenv("LITELLM_API_KEY")
agent, err := adk.NewAgent(cfg)
if err != nil {
log.Fatalf("failed to create agent: %v", err)
}
defer agent.Close()
ctx := context.Background()
if err := agent.Start(ctx); err != nil {
log.Fatalf("failed to start agent: %v", err)
}
resp, err := agent.Chat(ctx, "List the files in the current directory")
if err != nil {
log.Fatalf("chat failed: %v", err)
}
fmt.Println(resp.Text)
}See the Examples folder for advanced SDK usage patterns:
- Policy Enforcement: Declarative safety rules.
- Safe Defaults: Read-only access with write approval confirmation handlers.
- Logging & Token Limits: Configuring custom
slog.Logger, togglingVerbosedebug logs, and setting a session-levelMaxTotalTokenslimit.
Detailed architectural specifications can be found in docs/architecture.md.
The agents/ directory contains specialized AI agent submodules built on the LocalHarness ADK:
| Agent | Path | Focus |
|---|---|---|
| Jules | agents/jules |
Codebase analysis — performance (Bolt), design (Palette), security (Sentinel), maintainability (Sweeper) |
| Code Reviewer | agents/code-reviewer |
PR/diff review across GitLab, GitHub, Bitbucket — posts inline comments, labels, commit statuses |
# Build Jules (codebase analysis agents)
cd agents/jules && go build -o ../../bin/jules . && cd ../..
# Build Code Reviewer (PR review agent)
cd agents/code-reviewer && go build -o ../../bin/code-reviewer . && cd ../..# Jules — analyze a workspace for security issues
./bin/jules --agent sentinel --workspace /path/to/project --prompt "Audit for security vulnerabilities"
# Code Reviewer — review a GitHub PR and post comments
./bin/code-reviewer --url https://github.com/org/repo/pull/123 --token $GITHUB_TOKEN --post
# Code Reviewer — review local uncommitted changes
./bin/code-reviewer --workspace . --diffNote: Agent submodules are checked out automatically with
git clone --recursive. If you already cloned without submodules, rungit submodule update --init --recursive.
LocalHarness uses a structured error handling system with machine-readable error codes and contextual metadata. See:
- Error Handling Guide - SDK developer guide for handling structured errors
- Proto Schema Changes - Schema migration guide for SDK maintainers
- Architecture - Error Handling - System-level error handling architecture
The binary is managed by the SDK via pipe handshake. These flags are for the binary:
| Flag | Default | Description |
|---|---|---|
--workspace |
cwd |
Default workspace directory |
--data-dir |
~/.divmora/localharness/ |
Data directory for conversations |
--debug |
false |
Enable debug logging |
--version |
— | Print version and exit |
local-harness/
├── cmd/
│ ├── localharness/main.go # CLI entry point
│ └── testclient/main.go # CLI test client
├── proto/localharness/v1/ # Protobuf schema
│ └── localharness.proto
├── gen/go/localharness/v1/ # Generated Go code (gitignored)
├── internal/
│ ├── server/ # WebSocket server + session
│ ├── engine/ # Agentic loop orchestrator
│ ├── llm/ # LLM provider interface + Gemini
│ ├── tools/ # Built-in tool implementations
│ ├── workspace/ # Path validation
│ └── config/ # CLI config
├── Makefile
└── buf.yaml / buf.gen.yaml
# Build the harness binary
make build
# Build CLI debugger lhctl
make build-lhctl
# Run unit tests
make test
# Format source files
make fmt
# Static analysis and linting
make lint
# Regenerate protobuf code (requires buf)
make proto
# Full build: proto + binary + testclient + lhctl
make all- Product Roadmap: Track planned capabilities, optimizations, and technical debt in ROADMAP.md.
- AI Agents & Contributors: Read AGENTS.md for code conventions, architecture maps, and guidelines.
- Contributions: Read CONTRIBUTING.md to get started with pull requests and local setup.
- Code of Conduct: Please review our Code of Conduct (inherited from
.github). - Security Policy: To report vulnerabilities, refer to SECURITY.md.
This project is open-source software licensed under the Apache License, Version 2.0.
- Free & Unrestricted Use: Permitted for free use, reproduction, modification, distribution, and execution in any environment (including commercial, enterprise production, SaaS, private, and homelab environments) without requiring any commercial license (EULA) or payment.
- Patent Grant & Protection: Includes standard Apache 2.0 perpetual patent license grants and liability disclaimers.
See LICENSE for the full license text.