harness

module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 14, 2026 License: MIT

README

Harness

Harness is a Go framework for multi-turn agents backed by OpenAI Chat Completions. Your application implements an agent that supplies messages; the runner owns the canonical transcript, streams model output, dispatches tools, and can persist a session for later resumption.

Install

go get github.com/nep-0/harness

This repository's module path is github.com/nep-0/harness.

Interactive CLI

Set OPENAI_API_KEY and start a chat:

go run ./cmd/harness -model gpt-4.1

Type a message and press Enter. Use /exit, /quit, or EOF to finish.

Resume a persisted session:

go run ./cmd/harness \
  -model gpt-4.1 \
  -session-dir .sessions \
  -session project-chat

Enable automatic context compaction with an approximate context budget:

go run ./cmd/harness -model gpt-4.1 -compact-tokens 6000

Alternatively, retain only a fixed number of recent messages in model context:

go run ./cmd/harness -model gpt-4.1 -window 20

-window and -compact-tokens cannot be combined. A sliding window discards old model context, while compaction retains it in a summary.

Run one turn

Construct a runner with an explicit API key and model, then submit messages:

runner, err := agent.NewRunner(
	agent.WithAPIKey(os.Getenv("OPENAI_API_KEY")),
	agent.WithModel("gpt-4.1"),
)
if err != nil {
	return err
}

snapshot, err := runner.RunTurn(ctx, agent.RunSnapshot{}, []agent.Message{{
	Role: agent.RoleUser,
	Content: "Say hello.",
})

Callers create system, developer, and user messages. The runner creates assistant and tool-result messages, so it can preserve valid tool-call sequences.

For autonomous workflows, implement agent.Agent.Decide and use agent.Drive. The runner itself never waits for external input.

Autonomous workflow

An autonomous agent decides what messages to submit next. Drive owns the loop; RunTurn still owns each model/tool interaction.

type ResearchAgent struct{}

func (ResearchAgent) Decide(
	ctx context.Context,
	snapshot agent.RunSnapshot,
) (agent.Action, error) {
	if len(snapshot.Transcript) == 0 {
		return agent.Action{Messages: []agent.Message{{
			Role:    agent.RoleUser,
			Content: "Find the current weather in Nanjing and summarize it.",
		}}}, nil
	}

	// The previous logical turn, including any tool calls, is complete.
	return agent.Action{Done: true}, nil
}

snapshot, err := agent.Drive(
	ctx,
	runner,
	ResearchAgent{},
	agent.RunSnapshot{},
)

Return Done: true to stop. Returning neither messages nor Done is an error, preventing a busy loop. For user-driven applications, do not use Drive; submit each incoming message directly with RunTurn.

Packages

  • agent: runner, messages, tools, events, run snapshots, and middleware API.
  • middleware: model-context policies, including SlidingWindow and AutoCompact.
  • session: JSON file-backed session persistence through the Store interface.
  • cli: an interactive terminal agent.
  • cmd/harness: the runnable interactive CLI.

Context middleware

Context middleware changes only the transcript sent to the model. The runner retains the complete canonical transcript and returns it in a RunSnapshot.

Every middleware has a stable ID and serializable state:

type ContextMiddleware interface {
	ID() string
	Context(context.Context, Transcript) (Transcript, error)
	MarshalState() (json.RawMessage, error)
	UnmarshalState(json.RawMessage) error
}

An API key is required for the default OpenAI endpoint. When WithBaseURL is set for a local OpenAI-compatible server, the key may be omitted.

AutoCompact stores its generated summary and the canonical-message boundary it represents. After compaction, later requests reuse that summary until newly added messages exceed the configured approximate token budget. The summary state is saved in a RunSnapshot, so resumed sessions do not immediately compact the same history again.

Sessions

Persist a run by passing a checkpoint and, later, resume with its snapshot:

store := session.FileStore{Dir: ".sessions"}
value := session.Session{ID: "project-chat"}

snapshot := agent.RunSnapshot{
	Transcript:      value.Transcript,
	MiddlewareState: value.MiddlewareState,
}
snapshot, err := runner.RunTurn(ctx, snapshot, messages,
	agent.WithCheckpoint(session.Checkpoint(store, &value)),
)

session.FileStore uses atomic file replacement and restrictive file permissions. Sessions persist transcript and middleware state only; API keys, HTTP clients, tool handlers, and Go agent implementations remain application configuration.

Verification

go test ./...
go vet ./...

Directories

Path Synopsis
Package agent provides an agent-controlled OpenAI Chat Completions runner.
Package agent provides an agent-controlled OpenAI Chat Completions runner.
Package cli provides an interactive terminal agent.
Package cli provides an interactive terminal agent.
cmd
harness command
Command harness runs an interactive Chat Completions agent.
Command harness runs an interactive Chat Completions agent.
Package middleware contains reusable agent wrappers.
Package middleware contains reusable agent wrappers.
Package session persists resumable agent conversations.
Package session persists resumable agent conversations.
tools
ip
Package ip provides an agent tool backed by api.ipapi.is.
Package ip provides an agent tool backed by api.ipapi.is.
weather
Package weather provides an agent tool backed by wttr.in.
Package weather provides an agent tool backed by wttr.in.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL