https://github.com/rumpl/harness
Go library for calling different AI harnesses with a unified interface
https://github.com/rumpl/harness
Last synced: about 1 month ago
JSON representation
Go library for calling different AI harnesses with a unified interface
- Host: GitHub
- URL: https://github.com/rumpl/harness
- Owner: rumpl
- License: mit
- Created: 2026-04-10T18:03:52.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2026-05-14T18:06:54.000Z (about 2 months ago)
- Last Synced: 2026-05-14T20:12:52.313Z (about 2 months ago)
- Language: Go
- Size: 26.4 KB
- Stars: 1
- Watchers: 0
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# harness
A Go library for calling different AI coding agent CLIs (Docker Agent, Claude Code, Pi, Codex, opencode) through a unified interface. Switch between agents by changing a single line of code.
## Install
```
go get github.com/rumpl/harness
```
## Quick start
```go
package main
import (
"context"
"fmt"
"github.com/rumpl/harness"
"github.com/rumpl/harness/dockeragent"
)
func main() {
// Create a provider — swap this line to switch agents.
p := dockeragent.New("coder")
// Run the agent and handle streaming events.
harness.Run(context.Background(), p, "Explain goroutines", func(ev harness.Event) {
switch ev.Type {
case harness.EventText:
fmt.Print(ev.Text)
case harness.EventToolCallStart:
fmt.Printf("[tool: %s]\n", ev.ToolName)
case harness.EventToolCallDelta:
fmt.Print(ev.ToolArgs)
case harness.EventToolCall:
fmt.Printf("[tool: %s] %s\n", ev.ToolName, ev.ToolArgs)
case harness.EventToolResult:
fmt.Printf("[tool result: %s] %s\n", ev.ToolName, ev.ToolOutput)
case harness.EventResult:
fmt.Printf("\nResult: %s\n", ev.Result)
}
})
}
```
### Switching providers
```go
// Docker Agent
p := dockeragent.New("coder")
// Claude Code
p := claudecode.New("claude-sonnet-4-6", claudecode.WithEffort(claudecode.EffortHigh))
// Pi
p := pi.New("claude-sonnet-4-6")
// Codex
p := codex.New("gpt-5.4-mini")
// opencode
p := opencode.New("anthropic/claude-sonnet-4-6")
```
The rest of your code stays exactly the same — all providers implement `harness.Provider`.
For providers whose CLIs have their own default model, pass an empty model string to omit the model flag entirely (for example, `codex.New("")` emits `codex exec ...` without `-m`).
## The `Provider` interface
```go
type Provider interface {
Name() string
PrintCommand(prompt string) string
InteractiveArgs(prompt string) []string
ParseStreamLine(line string) []Event
}
```
| Method | Purpose |
| ------------------ | ----------------------------------------------------------------- |
| `Name()` | Human-readable identifier (`"docker-agent"`, `"claude-code"`, etc.) |
| `PrintCommand()` | Shell command for non-interactive mode (`sh -c` safe) |
| `InteractiveArgs()`| Arg list for interactive mode (first element = binary) |
| `ParseStreamLine()`| Parse one NDJSON line into `[]Event` |
## Event types
| Type | Fields set |
| -------------- | ----------------------- |
| `EventText` | `Text` |
| `EventResult` | `Result`, `Usage` (opt) |
| `EventToolCallStart` | `ToolID` (opt), `ToolName` |
| `EventToolCallDelta` | `ToolID` (opt), `ToolName` (opt), `ToolArgs` raw delta |
| `EventToolCall`| `ToolID` (opt), `ToolName`, `ToolArgs` |
| `EventToolResult` | `ToolID` (opt), `ToolName` (opt), `ToolOutput`, `ToolError` |
## Example CLI
```bash
go run ./cmd/harness-example --provider docker-agent --model coder "Hello world"
go run ./cmd/harness-example --provider claude-code --model claude-sonnet-4-6 "Hello world"
go run ./cmd/harness-example --provider pi --model claude-sonnet-4-6 "Hello world"
go run ./cmd/harness-example --provider codex --model gpt-5.4-mini "Hello world"
go run ./cmd/harness-example --provider opencode --model anthropic/claude-sonnet-4-6 "Hello world"
# Just print the command without executing:
go run ./cmd/harness-example --print-cmd --provider docker-agent --model coder "test"
```
## License
MIT