{"slug": "agent-harness-a-minimal-composable-go-library-to-build-ai-agent-harnesses", "title": "Agent-harness – A minimal, composable Go library to build AI Agent Harnesses", "summary": "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.", "body_md": "A minimal, composable Go library for building agentic tool-calling loops on top of LLM APIs.\n\n- Go `1.26+`\n\n```\nresult, err := harness.Run(ctx, provider,\n    harness.WithSystem(\"You are a helpful assistant.\"),\n    harness.WithMessages(thread.Messages...),\n    harness.WithTools(tools...),\n    harness.WithModel(\"claude-opus-4-6\"),\n    harness.WithMaxSteps(10),\n)\n```\n\nImplements the core agent loop: call the LLM → execute tool calls → feed results back → repeat. Everything else (storage, prompts, routing) is your problem.\n\n- Core harness loop, hooks, and thread state are implemented\n- Unit tests are in place for core loop behaviour and pause/resume\n- OpenAI Responses API adapter is implemented (`provider/openai` )\n- Anthropic provider adapter is implemented (`provider/anthropic` )\n- Optional file-backed memory, recall tools, capture, and promotion are implemented (`memory` )\n- `examples/claw` provides a REPL harness for manual testing\n\n- Force a specific LLM provider — use built-in adapters or implement your own `Chat()` provider\n- Manage conversation storage — you serialise the `Thread` type however you want\n- Construct system prompts — you pass a string\n- Orchestrate multi-agent workflows — call `Run()` from a tool for sub-agents\n\n- Single `Run()` function, not a framework\n- `Provider` interface with one method\n- Tools bundle schema + execution in one place\n- Hooks for approval gates (`WithBeforeTool` ), streaming (`WithOnDelta` ), and observability (`WithEventHandler` )\n- Progressive disclosure via `WithToolFilter`\n- Pause/resume with explicit `PendingToolCalls` for approval workflows\n- Composes naturally with [ACP](https://agentclientprotocol.com/) and[MCP](https://modelcontextprotocol.io/)\n\n-  Extract the reusable harness core (`Run` , messages, tools, provider interface)\n-  Add pause/resume support (`StopPaused` ,`PendingToolCalls` ,`Thread.ResolvePending` )\n- Add lifecycle hooks and event emission\n- Stabilise core loop semantics with unit tests\n-  Add a runnable REPL example under `examples/claw`\n-  Add CI for `go test` ,`go test -race` , and`go vet`\n-  Implement `provider/openai` Responses adapter (stateful continuation + streaming)\n-  Implement `provider/anthropic` adapter (non-streaming + streaming)\n- Add provider integration tests using local HTTP test servers\n- Add provider-neutral finish states, continuation, and cache-aware usage\n- Add optional file-backed memory and recoverable tool transcripts\n\n- [docs/architecture.md](https://github.com/lox/agent-harness/blob/main/docs/architecture.md) — API shape, loop lifecycle, and state model\n- [docs/runner.md](https://github.com/lox/agent-harness/blob/main/docs/runner.md) — optional helper for starting/stopping active runs\n- [docs/providers.md](https://github.com/lox/agent-harness/blob/main/docs/providers.md) — provider adapter contracts and type mappings\n- [docs/memory.md](https://github.com/lox/agent-harness/blob/main/docs/memory.md) — optional file-backed memory package, recall tools, and promotion primitives\n- [docs/research.md](https://github.com/lox/agent-harness/blob/main/docs/research.md) — research notes and design rationale\n\nThe `docs/` directory is the source of truth for design and implementation guidance.\n\n```\nthread := harness.NewThread()\nthread.AddUser(\"Delete old preview deployments\")\n\nresult, err := harness.Run(ctx, provider,\n    harness.WithMessages(thread.Messages...),\n    harness.WithTools(tools...),\n    harness.WithBeforeTool(func(ctx context.Context, call harness.ToolCall) (harness.ToolAction, error) {\n        if call.Name == \"delete_deployment\" {\n            return harness.ToolActionPause, nil\n        }\n        return harness.ToolActionContinue, nil\n    }),\n)\nif result != nil {\n    thread.Append(result)\n}\nif err != nil {\n    return err\n}\n\nif result.StopReason == harness.StopPaused {\n    // approval flow happens outside the harness\n    err = thread.ResolvePending(ctx, func(ctx context.Context, call harness.ToolCall) (*harness.ToolResult, error) {\n        return executeApprovedTool(ctx, call)\n    })\n    if err != nil {\n        return err\n    }\n\n    result, err = harness.Run(ctx, provider,\n        harness.WithMessages(thread.Messages...),\n        harness.WithTools(tools...),\n    )\n    if result != nil {\n        thread.Append(result)\n    }\n    if err != nil {\n        return err\n    }\n}\nresult, err := harness.Run(ctx, provider,\n    harness.WithMessages(thread.Messages...),\n    harness.WithTools(readTool, writeTool),\n    harness.WithToolFilter(func(step int, _ []harness.Message) []harness.Tool {\n        if step == 0 {\n            return []harness.Tool{readTool}\n        }\n        return []harness.Tool{readTool, writeTool}\n    }),\n)\n```\n\nUse `runner.Runner` when you want to interrupt an in-flight run from external control input such as a user saying \"stop\".\n\n```\nr := runner.New()\n\ndone, err := r.Start(context.Background(), thread.ID, func(ctx context.Context) error {\n    result, err := harness.Run(ctx, provider,\n        harness.WithMessages(thread.Messages...),\n        harness.WithTools(tools...),\n    )\n    if result != nil {\n        thread.Append(result)\n    }\n    return err\n})\nif err != nil {\n    return err\n}\n\n// elsewhere: control-plane stop command\nif strings.EqualFold(strings.TrimSpace(userInput), \"stop\") {\n    r.Stop(thread.ID)\n}\n\nrunErr := <-done\n_ = runErr\nOPENAI_API_KEY=... go run ./examples/claw\n```\n\nThen type prompts or control commands (`/stop`, `/history`, `/tools`, `/memory`, `/remember <text>`, `/new`, `/quit`).\nThe example enables file-backed memory by default at `~/.agent-harness/claw`;\npass `--memory-dir \"\"` to disable it.\n\nSee [docs/architecture.md](https://github.com/lox/agent-harness/blob/main/docs/architecture.md) for the primary implementation guide.", "url": "https://wpnews.pro/news/agent-harness-a-minimal-composable-go-library-to-build-ai-agent-harnesses", "canonical_source": "https://github.com/lox/agent-harness", "published_at": "2026-10-02 02:36:20+00:00", "updated_at": "2026-10-02 03:15:59.657586+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "large-language-models", "agent-protocols"], "entities": ["agent-harness", "lox", "Go", "OpenAI Responses API", "Anthropic", "Model Context Protocol", "Agent Client Protocol"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/agent-harness-a-minimal-composable-go-library-to-build-ai-agent-harnesses", "markdown": "https://wpnews.pro/news/agent-harness-a-minimal-composable-go-library-to-build-ai-agent-harnesses.md", "text": "https://wpnews.pro/news/agent-harness-a-minimal-composable-go-library-to-build-ai-agent-harnesses.txt", "jsonld": "https://wpnews.pro/news/agent-harness-a-minimal-composable-go-library-to-build-ai-agent-harnesses.jsonld"}}