Class: Protege::Orchestrator::Harness

Inherits:
Object
  • Object
show all
Defined in:
lib/protege/orchestrator/harness.rb

Overview

Multi-turn inference runner — the heart of the LOGI Orchestrator. Generates against the configured provider and dispatches tool calls until the model returns a tool-free response or max_tool_turns is reached.

This is the abstract base that owns the shared algorithm (event-wrapped tool loop, provider generation, tool dispatch, attachment review). What drives a run is left to subclasses, which each define one clean path: ReplyHarness for a reactive run (an inbound email) and ResponsibilityHarness for a proactive one (a scheduled Loop responsibility). The branch between them lives at the job boundary — the caller picks the subclass — so there are no run-kind conditionals in this loop. Subclasses fill the hooks: #resolvers, #user_content, #context, and (reply only) #message / #streaming?.

Protege::Current.correlation_id must be set before calling so that emitted events carry the id automatically. Collaborates with Inference (request building and generation), Tool (dispatch and registry), the persona's ResolverChain (context assembly), and the Protege::*Event classes (observability).

Lifecycle

  1. Emit inference_started event.
  2. Build the initial Provider::Request from the opening user turn, registered tools, and resolver-contributed context messages.
  3. Enter the tool loop — generate, dispatch any tool calls, extend the request with results, repeat until the model returns no tool calls or max_tool_turns is exhausted.
  4. Emit inference_completed event and return the final response.

Examples:

ReplyHarness.run(persona:, message:)
ResponsibilityHarness.run(persona:, responsibility:)

Direct Known Subclasses

ReplyHarness, ResponsibilityHarness

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(persona:) ⇒ void

Parameters:



50
51
52
# File 'lib/protege/orchestrator/harness.rb', line 50

def initialize(persona:)
  @persona = persona
end

Class Method Details

.runObject

Instantiate the concrete harness and run it in one call. Keyword arguments are forwarded to the subclass initializer (e.g. message: for ReplyHarness, responsibility: for ResponsibilityHarness).

Returns:

  • (Object)

    the final Provider::Response



43
44
45
# File 'lib/protege/orchestrator/harness.rb', line 43

def run(**)
  new(**).run
end

Instance Method Details

#runObject

Entry point. Emit lifecycle events around the tool loop.

Always emits a terminal event — inference_completed on success or inference_failed when the loop raises — so subscribers (and stateful hooks keyed by correlation id) are never left waiting on a completion that never comes. The error is re-raised so the job's retry/discard policy still applies.

Returns:

  • (Object)

    the final Provider::Response

Raises:

  • (StandardError)

    re-raised after inference_failed is emitted



63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/protege/orchestrator/harness.rb', line 63

def run
  InferenceStartedEvent.emit(persona:, message:)

  result = run_tool_loop

  InferenceCompletedEvent.emit(persona:, message:, result:)

  result
rescue StandardError => e
  InferenceFailedEvent.emit(persona:, message:, error: e)
  raise
end