# Agent-harness – A minimal, composable Go library to build AI Agent Harnesses

> Source: <https://github.com/lox/agent-harness>
> Published: 2026-10-02 02:36:20+00:00

A minimal, composable Go library for building agentic tool-calling loops on top of LLM APIs.

- Go `1.26+`

```
result, err := harness.Run(ctx, provider,
    harness.WithSystem("You are a helpful assistant."),
    harness.WithMessages(thread.Messages...),
    harness.WithTools(tools...),
    harness.WithModel("claude-opus-4-6"),
    harness.WithMaxSteps(10),
)
```

Implements the core agent loop: call the LLM → execute tool calls → feed results back → repeat. Everything else (storage, prompts, routing) is your problem.

- Core harness loop, hooks, and thread state are implemented
- Unit tests are in place for core loop behaviour and pause/resume
- OpenAI Responses API adapter is implemented (`provider/openai` )
- Anthropic provider adapter is implemented (`provider/anthropic` )
- Optional file-backed memory, recall tools, capture, and promotion are implemented (`memory` )
- `examples/claw` provides a REPL harness for manual testing

- Force a specific LLM provider — use built-in adapters or implement your own `Chat()` provider
- Manage conversation storage — you serialise the `Thread` type however you want
- Construct system prompts — you pass a string
- Orchestrate multi-agent workflows — call `Run()` from a tool for sub-agents

- Single `Run()` function, not a framework
- `Provider` interface with one method
- Tools bundle schema + execution in one place
- Hooks for approval gates (`WithBeforeTool` ), streaming (`WithOnDelta` ), and observability (`WithEventHandler` )
- Progressive disclosure via `WithToolFilter`
- Pause/resume with explicit `PendingToolCalls` for approval workflows
- Composes naturally with [ACP](https://agentclientprotocol.com/) and[MCP](https://modelcontextprotocol.io/)

-  Extract the reusable harness core (`Run` , messages, tools, provider interface)
-  Add pause/resume support (`StopPaused` ,`PendingToolCalls` ,`Thread.ResolvePending` )
- Add lifecycle hooks and event emission
- Stabilise core loop semantics with unit tests
-  Add a runnable REPL example under `examples/claw`
-  Add CI for `go test` ,`go test -race` , and`go vet`
-  Implement `provider/openai` Responses adapter (stateful continuation + streaming)
-  Implement `provider/anthropic` adapter (non-streaming + streaming)
- Add provider integration tests using local HTTP test servers
- Add provider-neutral finish states, continuation, and cache-aware usage
- Add optional file-backed memory and recoverable tool transcripts

- [docs/architecture.md](https://github.com/lox/agent-harness/blob/main/docs/architecture.md) — API shape, loop lifecycle, and state model
- [docs/runner.md](https://github.com/lox/agent-harness/blob/main/docs/runner.md) — optional helper for starting/stopping active runs
- [docs/providers.md](https://github.com/lox/agent-harness/blob/main/docs/providers.md) — provider adapter contracts and type mappings
- [docs/memory.md](https://github.com/lox/agent-harness/blob/main/docs/memory.md) — optional file-backed memory package, recall tools, and promotion primitives
- [docs/research.md](https://github.com/lox/agent-harness/blob/main/docs/research.md) — research notes and design rationale

The `docs/` directory is the source of truth for design and implementation guidance.

```
thread := harness.NewThread()
thread.AddUser("Delete old preview deployments")

result, err := harness.Run(ctx, provider,
    harness.WithMessages(thread.Messages...),
    harness.WithTools(tools...),
    harness.WithBeforeTool(func(ctx context.Context, call harness.ToolCall) (harness.ToolAction, error) {
        if call.Name == "delete_deployment" {
            return harness.ToolActionPause, nil
        }
        return harness.ToolActionContinue, nil
    }),
)
if result != nil {
    thread.Append(result)
}
if err != nil {
    return err
}

if result.StopReason == harness.StopPaused {
    // approval flow happens outside the harness
    err = thread.ResolvePending(ctx, func(ctx context.Context, call harness.ToolCall) (*harness.ToolResult, error) {
        return executeApprovedTool(ctx, call)
    })
    if err != nil {
        return err
    }

    result, err = harness.Run(ctx, provider,
        harness.WithMessages(thread.Messages...),
        harness.WithTools(tools...),
    )
    if result != nil {
        thread.Append(result)
    }
    if err != nil {
        return err
    }
}
result, err := harness.Run(ctx, provider,
    harness.WithMessages(thread.Messages...),
    harness.WithTools(readTool, writeTool),
    harness.WithToolFilter(func(step int, _ []harness.Message) []harness.Tool {
        if step == 0 {
            return []harness.Tool{readTool}
        }
        return []harness.Tool{readTool, writeTool}
    }),
)
```

Use `runner.Runner` when you want to interrupt an in-flight run from external control input such as a user saying "stop".

```
r := runner.New()

done, err := r.Start(context.Background(), thread.ID, func(ctx context.Context) error {
    result, err := harness.Run(ctx, provider,
        harness.WithMessages(thread.Messages...),
        harness.WithTools(tools...),
    )
    if result != nil {
        thread.Append(result)
    }
    return err
})
if err != nil {
    return err
}

// elsewhere: control-plane stop command
if strings.EqualFold(strings.TrimSpace(userInput), "stop") {
    r.Stop(thread.ID)
}

runErr := <-done
_ = runErr
OPENAI_API_KEY=... go run ./examples/claw
```

Then type prompts or control commands (`/stop`, `/history`, `/tools`, `/memory`, `/remember <text>`, `/new`, `/quit`).
The example enables file-backed memory by default at `~/.agent-harness/claw`;
pass `--memory-dir ""` to disable it.

See [docs/architecture.md](https://github.com/lox/agent-harness/blob/main/docs/architecture.md) for the primary implementation guide.
