Skip to documentation
Documentation navigation

Documentation navigation

Documentation / start

Run the agent with Harness

Turn the coding assistant model client into a Loop, Rig, and live Session, then submit input, consume terminal events, and shut down cleanly.

developer

Harness turns the model boundary into a stateful agent runtime. A Loop freezes one agent definition. A Rig combines loops with runtime services. A Session owns live turns, events, resources, and shutdown.

Define the Loop

Create agent.go and pass in the client and model created on the previous page:

func defineAssistant(client inference.Client, selected model.Model) (loop.Definition, error) {
	return loop.Define(
		loop.WithName("coding-assistant"),
		loop.WithSystem("Inspect evidence before answering. Never claim a file was read unless a tool returned it."),
		// Harness receives the provider-neutral client and its matching model.
		loop.WithInference(client, selected),
	)
}

The Loop is configuration, not a live goroutine. System instructions, tools, modes, gates, context policy, delegates, and model selection belong here because they define the agent that a session will run.

Assemble the Rig

Start with an in-memory session store while learning the lifecycle:

func defineRuntime(assistant loop.Definition) (*rig.Rig, error) {
	store, err := sessionstore.Open(memstore.New())
	if err != nil {
		return nil, err
	}
	return rig.Define(
		rig.WithLoops(assistant),
		// Primers are Loops that a new session may start with.
		rig.WithPrimers("coding-assistant"),
		rig.WithSessionStore(store),
	)
}

The Rig owns immutable topology and shared services. The next pages replace memory storage and add a workspace without changing the Session-facing code.

Run a Session

Subscribe before submitting so a fast terminal event cannot pass before the CLI is listening:

func runTurn(ctx context.Context, runtime *rig.Rig, question string) error {
	live, err := runtime.NewSession(ctx)
	if err != nil {
		return err
	}
	defer live.Shutdown(context.Background())

	events, err := live.SubscribeEvents(event.EventFilter{
		Enduring: event.LoopScope{All: true},
	})
	if err != nil {
		return err
	}
	defer events.Close()

	_, err = live.Submit(ctx, []content.Block{
		&content.TextBlock{Text: question},
	})
	if err != nil {
		return err
	}

	for delivery := range events.Events() {
		if done, ok := delivery.Event.(event.TurnDone); ok {
			fmt.Println(responseText(&inference.Response{Message: done.Message}))
			return nil
		}
	}
	return events.Err()
}

Submit returns a correlation ID. Output arrives as events because one turn can stream tokens, request tools, pause at a gate, accept queued input, or terminate with an error.

Runtime sequence

%%{init: {"theme":"dark"}}%%
sequenceDiagram
  participant CLI
  participant Session
  participant L as Loop
  participant Model
  CLI->>Session: SubscribeEvents
  CLI->>Session: Submit(text blocks)
  Session->>L: start Turn
  L->>Model: inference.Request
  Model-->>L: streamed assistant blocks
  L-->>Session: TurnDone
  Session-->>CLI: enduring event delivery
  CLI->>Session: Shutdown

Read Harness commands, events, Step, Turn, Loop, and Rig for the full lifecycle.

Runnable checkpoint

Run or copy the complete commented Harness quickstart. Its exact-output test proves the session reaches TurnDone and shuts down. The fixture uses a deterministic client; your coding assistant supplies the production client from model.go.

Continue to add read-only tools and gates.

← back to documentation