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

- Providerinterface 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 PendingToolCallsfor approval workflows

- Composes naturally with ACP and MCP

- 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, andgo vet

- Implement provider/openaiResponses adapter (stateful continuation + streaming)

- Implement provider/anthropicadapter (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.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

_ = runErrOPENAI_API_KEY=... go run ./examples/clawThen 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.