cd /news/ai-agents/agent-harness-a-minimal-composable-g… · home › topics › ai-agents › article
[ARTICLE · art-143606] src=github.com ↗ pub= topic=ai-agents verified=true sentiment=· neutral

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.

read3 min views1 publishedOct 2, 2026
Agent-harness – A minimal, composable Go library to build AI Agent Harnesses
Image: Michielbdejong (auto-discovered)

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 /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

  • /resume with explicit PendingToolCalls for approval workflows

  • Composes naturally with ACP andMCP

  • Extract the reusable harness core (Run , messages, tools, provider interface)

  • Add /resume support (Stopd ,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 , andgo 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 — API shape, loop lifecycle, and state model

  • docs/runner.md — optional helper for starting/stopping active runs

  • docs/providers.md — provider adapter contracts and type mappings

  • docs/memory.md — optional file-backed memory package, recall tools, and promotion primitives

  • 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.ToolAction, nil
        }
        return harness.ToolActionContinue, nil
    }),
)
if result != nil {
    thread.Append(result)
}
if err != nil {
    return err
}

if result.StopReason == harness.Stopd {
    // 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 for the primary implementation guide.

── more in #ai-agents 4 stories · sorted by recency
── more on @agent-harness 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/agent-harness-a-mini…] indexed:0 read:3min 2026-10-02 · —