Agent-harness – A minimal, composable Go library to build AI Agent Harnesses Developer lox released agent-harness, a minimal, composable Go library (requiring Go 1.26+) for building agentic tool-calling loops on top of LLM APIs. The library implements the core agent loop — call the LLM, execute tool calls, feed results back, repeat — through a single Run() function, with a one-method Provider interface, built-in OpenAI Responses and Anthropic adapters, pause/resume via StopPaused and PendingToolCalls, lifecycle hooks for approval gates, streaming and observability, and an optional file-backed memory package. The project ships unit tests for core loop behavior and pause/resume, a REPL example under examples/claw, and documentation in docs/ as the source of truth for design and implementation guidance. 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