Class: ActiveHarness::Engine

Inherits:
Object
  • Object
show all
Defined in:
lib/active_harness/pipeline/engine.rb

Overview

Orchestrates the full agent execution pipeline:

validate_context → check_rate_limits → [guard chain] → build_prompt → run_with_fallback → parse_output → result

Instance Method Summary collapse

Constructor Details

#initialize(agent_config) ⇒ Engine

Returns a new instance of Engine.



5
6
7
# File 'lib/active_harness/pipeline/engine.rb', line 5

def initialize(agent_config)
  @agent_config = agent_config
end

Instance Method Details

#call(input:, context: {}, constraints: {}, language: nil, translate: nil) ⇒ Result

Parameters:

  • input (String)
  • context (Hash) (defaults to: {})
  • constraints (Hash) (defaults to: {})
  • language (Symbol, String, nil) (defaults to: nil)

    language hint forwarded to guards

  • translate (#call, nil) (defaults to: nil)

    translation callable

Returns:



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/active_harness/pipeline/engine.rb', line 15

def call(input:, context: {}, constraints: {}, language: nil, translate: nil)
  debug_data = {}

  validate_context!(context)
  check_rate_limits!(context[:user_id])

  # Build the unified payload and run the agent's setup hook (if any).
  payload = Payload.new(input: input, context: context, language: language, translate: translate)
  if (setup_block = @agent_config[:setup])
    payload = setup_block.call(payload)
  end

  # Constraint validation — runs after setup so stripped/normalized input is measured.
  merged_constraints = (@agent_config[:constraints] || {}).merge(constraints)
  validate_constraints!(payload.input, merged_constraints)

  # Guard phase — callbacks receive (payload, current_value) → new_current_value
  payload.input = run_callbacks(:before_guards, payload, payload.input, debug_data)
  guard_result  = run_guards(payload, debug_data)
  guard_result  = run_callbacks(:after_guards,  payload, guard_result,  debug_data)

  if blocked_by_guard?(guard_result)
    record_risky!(context[:user_id])
    answer = @agent_config[:default_error_answer]
    answer = answer.call(payload) if answer.respond_to?(:call)
    return Result.blocked(
      input:  guard_result,
      output: answer,
      debug:  build_debug(debug_data)
    )
  end

  # Request phase
  prompt   = build_prompt_hash(guard_result, payload.context, constraints, debug_data, language: payload.language)
  prompt   = run_callbacks(:before_request, payload, prompt,   debug_data)
  runner   = run_primary(prompt)
  response = runner[:response]
  response = run_callbacks(:after_request,  payload, response, debug_data)
  attempts = runner[:attempts]

  output   = parse_output(response.content)

  Result.success(
    input:        guard_result,
    output:       output,
    raw_response: response.content,
    provider:     response.provider,
    model:        response.model,
    usage:        response.usage,
    attempts:     attempts,
    debug:        build_debug(debug_data)
  )
rescue Errors::ContextValidationError => e
  Result.failed(error: e, debug: build_debug(debug_data))
rescue Errors::ConstraintViolationError => e
  Result.failed(error: e, debug: build_debug(debug_data))
rescue Errors::ThrottleError => e
  Result.failed(error: e, debug: build_debug(debug_data))
rescue Errors::ProviderError, Errors::SchemaValidationError => e
  Result.failed(error: e, debug: build_debug(debug_data))
end