Recipe: test an agent
The public harbortest package (github.com/hurtener/Harbor/harbortest) is Harbor's flow-level test kit. It is importable from outside the module, so end-users test their agents with the same surface Harbor uses internally. The kit's parameter vocabulary (Deps.Bus / Deps.Redactor / Deps.Identity, AssertSequence's event types, NewFaultInjector's catalog) is satisfied externally through the sdk/ aliases — sdk/events, sdk/audit, sdk/identity, sdk/tools (RFC §3.6 item 5); the harbortest godoc carries the worked external shapes.
The full runnable version of this recipe is examples/agents/echo/.
The surface
| Function | Purpose |
|---|---|
RunOnce | Drive an Agent once under a deterministic identity quadruple; returns output + captured EventLog. |
AssertSequence | Pin the observed event-type sequence. |
AssertNoLeaks | The cross-session-isolation gate — fails on any event under a foreign (tenant, user, session) triple. |
SimulateFailure | Inject N tool failures of a given error class via a FaultInjector. |
RecordedEvents | Pull the events recorded for a specific run ID. |
Steps
Implement the
Agentinterface on your code path:goimport "github.com/hurtener/Harbor/harbortest" type EchoAgent struct{} // Compile-time assertion — turns interface drift into a build error. var _ harbortest.Agent = (*EchoAgent)(nil) func (a *EchoAgent) Run(ctx context.Context, input any) (any, error) { if err := ctx.Err(); err != nil { return nil, fmt.Errorf("echo agent: %w", err) } return input, nil }If your code path is a plain function rather than a method, adapt it with
harbortest.AgentFunc.Drive it with
RunOnce:gofunc TestEchoAgent_RoundTrips(t *testing.T) { out, log, err := harbortest.RunOnce(context.Background(), &EchoAgent{}, "hello") if err != nil { t.Fatalf("RunOnce: %v", err) } if out != "hello" { t.Errorf("output = %v, want %q", out, "hello") } harbortest.AssertNoLeaks(t, log) }RunOnceassembles the identity quadruple, opens an in-memory event bus, subscribes, runs the agent, and closes the subscription before returning — no goroutine leaks past the call. It is safe to call from N concurrent goroutines (D-025).Always assert
AssertNoLeaks. Even for an event-free agent it stays green — and turns red the instant a real agent emits an event under a foreign triple. This is the multi-isolation gate.Test failure modes too. A cancelled context, a forced tool failure (
SimulateFailure) — not just the happy path (CLAUDE.md §17.3).
Notes
- Run tests under the race detector:
go test -race ./.... CI gates on it. - For a fresh project,
harbor scaffoldgenerates a workedharbortest-driven test out of the box — see Scaffold a new agent project. - The
harbortestgodoc documents the full surface;harbortest/agent_test.gois the in-tree worked example.