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.
go get github.com/rumpl/harness
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)
}
})
}// 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).
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 |
| 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 |
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"MIT