Skip to documentation
Documentation navigation

Documentation navigation

Documentation / guides

Overview

Control a live Harness Session with validated, journaled commands.

developer

Harness commands are small, typed messages that move work or control between a session and its loop actors. The application-facing contracts live in pkg/session and pkg/loop. The sealed pkg/command union is the runtime’s transport and journal vocabulary. It is exported so the runtime, journal, and tests can share exact types, but pkg/session deliberately does not export a command constructor or a command sink. A normal application calls Session.Submit, Session.Interrupt, or a loop controller method; the trusted runtime stamps IDs, agency, timestamps, and routes the command.

Two command lanes

The durable lane contains intent-log records. UserInput, SubagentResult, CancelQueuedInput, CancelDelegateRequest, gate replies, Compact, Interrupt, Shutdown, and ProcessNotification all have concrete wire forms. MarshalCommand puts a type discriminator and schema version beside the payload; restore uses UnmarshalCommand, then validates the result before it can be re-enqueued.

The live control lane carries a buffered reply channel and is not encoded by the command codec. SetLoopMode, ChangeLoopInference, and ReplaceLoopExternalTools are represented durably by the enduring events they cause (LoopModeChanged, LoopInferenceChanged, and LoopExternalToolsetChanged). Their live acknowledgements report the values the actor committed for the next turn.

IntentCommand typeApplication-facing entry pointObservable result
Human inputUserInputSession.Submit, SubmitToLoopInputQueued, TurnStarted, TurnFoldedInto, TurnRejected, or InputCancelled on the event fan-in
Delegate hand-backSubagentResultmanaged delegation runtimeparent-loop resolution event; the child loop ID is carried in Header.Cause
Permission answerApproveToolCall, DenyToolCallSession.RespondGatedurable GateResolved, then gate command delivery
Ask-user answerProvideUserInputSession.RespondGatedurable GateResolved, then the parked tool resumes
Queue retractionCancelQueuedInputtrusted delegation/runtime pathInputCancelled{Reason: CancelClientRetracted} when the item is still queued; otherwise no-op
Managed request cancellationCancelDelegateRequesttrusted managed-delegation pathtransient DelegateCancelResult on its live ack
Manual compactionCompactSession.Compact, CompactToLoopcompaction events and a waiter reply correlated by command ID
Stop active workInterruptSession.Interrupt or a loop controllerTurnInterrupted for work that was running; a fully idle interrupt returns false
Runtime configurationSetLoopMode, ChangeLoopInferenceloop.Controller.SetMode, Changetyped live result plus an enduring next-turn configuration event
External tool slotReplaceLoopExternalToolsoptional loop.ExternalToolInstallertyped live result plus LoopExternalToolsetChanged
Process completionProcessNotificationtool.ProcessCompletionNotifier.NotifyProcessCompletionaccepted, duplicate, collision, or stopped disposition
TeardownShutdownSessionController.Shutdownall loops drain, then SessionStopped

The command ID is the correlation key. Submit methods return it immediately after the command is handed to the target loop. They do not return a turn result. Reply events carry the ID in Header.Cause.CommandID, so a subscriber can follow one input without assuming that it started immediately.

%%{init: {"theme":"dark"}}%%
sequenceDiagram
    participant App as Application
    participant S as Session contract
    participant J as Intent journal
    participant L as Loop actor
    participant H as Event fan-in

    App->>S: Submit(ctx, blocks)
    S->>J: append command intent (when applicable)
    S->>L: UserInput{Header.CommandID}
    S-->>App: command ID
    L->>H: InputQueued or TurnStarted
    L->>H: TurnFoldedInto, TurnDone, or TurnRejected
    H-->>App: events with Cause.CommandID

The public contracts

These are the exact consumer-facing methods. Construction and restoration stay in pkg/rig; an application receives a live implementation through that composition root.

type Session interface {
	SessionID() uuid.UUID
	ActiveLoop() loop.Handle
	Loop(uuid.UUID) (loop.Handle, bool)
	Submit(context.Context, []content.Block) (uuid.UUID, error)
	SubmitToLoop(context.Context, uuid.UUID, []content.Block) (uuid.UUID, error)
	Compact(context.Context) (uuid.UUID, error)
	CompactToLoop(context.Context, uuid.UUID) (uuid.UUID, error)
	SubscribeEvents(event.EventFilter) (event.Subscription, error)
	RespondGate(context.Context, gate.GateResponse) error
	Interrupt(context.Context) (bool, error)
}

type SessionController interface {
	Session
	SetActiveLoop(context.Context, uuid.UUID) error
	LoopController(uuid.UUID) (loop.Controller, bool)
	CheckpointWorkspace(context.Context) (workspacestore.Ref, error)
	RestoreWorkspace(context.Context, workspacestore.Ref) error
	Shutdown(context.Context) error
}

The SessionID spelling above is exactly the source method name, even though the code block omits imports for readability. A compile-realistic submit looks like this:

func submitQuestion(ctx context.Context, s session.Session) error {
	id, err := s.Submit(ctx, []content.Block{
		&content.TextBlock{Text: "Check the current working tree."},
	})
	if err != nil {
		return err // no command was handed to the loop; id is zero
	}
	// id is the correlation key. Read the subscription returned by
	// SubscribeEvents and match Reply events by EventHeader().Cause.CommandID.
	_ = id
	return nil
}

The proof is internal/sessionruntime/submit_test.go, which checks the returned ID and the command.UserInput delivered to the actor. The exhaustive codec proof is pkg/command/marshal_test.go, which round-trips every durable command type and rejects codec drift.

Choosing a page

Start with Command envelope and routing when you are writing a journal adapter or restore reader. Use Submit input for user or delegate payloads, Approve and deny and Provide requested user input for parked gates, and Shutdown when owning the whole session lifecycle. The remaining pages document the narrow control paths and their durable boundaries.

Source and proof

← back to documentation