Class: KairosMcp::Daemon::OodaCycleRunner
- Inherits:
-
Object
- Object
- KairosMcp::Daemon::OodaCycleRunner
- Defined in:
- lib/kairos_mcp/daemon/ooda_cycle_runner.rb
Overview
OodaCycleRunner — the single callable that Integration.wire! receives as its cycle_runner: parameter.
Design (P3.5 v0.1 §2):
Orchestrates OBSERVE→ORIENT→DECIDE→ACT→REFLECT using all P3.x components.
No global state — all collaborators injected at construction.
Returns the shape Integration expects:
{ status:, llm_calls:, input_tokens:, output_tokens: }
Constant Summary collapse
- PAUSED_STATUS =
'paused_awaiting_approval'
Instance Method Summary collapse
-
#call(mandate) ⇒ Hash
{ status:, llm_calls:, input_tokens:, output_tokens:, phases: }.
-
#initialize(workspace_root:, safety:, invoker:, active_observe:, orient_fn:, decide_fn:, reflect_fn:, code_gen_phase_handler:, chain_recorder:, shell:, wal_factory:, usage_accumulator: nil, logger: nil) ⇒ OodaCycleRunner
constructor
A new instance of OodaCycleRunner.
Constructor Details
#initialize(workspace_root:, safety:, invoker:, active_observe:, orient_fn:, decide_fn:, reflect_fn:, code_gen_phase_handler:, chain_recorder:, shell:, wal_factory:, usage_accumulator: nil, logger: nil) ⇒ OodaCycleRunner
Returns a new instance of OodaCycleRunner.
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 |
# File 'lib/kairos_mcp/daemon/ooda_cycle_runner.rb', line 21 def initialize( workspace_root:, safety:, invoker:, active_observe:, orient_fn:, decide_fn:, reflect_fn:, code_gen_phase_handler:, chain_recorder:, shell:, wal_factory:, usage_accumulator: nil, logger: nil ) @ws = workspace_root @safety = safety @invoker = invoker @observe = active_observe @orient = orient_fn @decide = decide_fn @reflect = reflect_fn @cg_handler = code_gen_phase_handler @chain = chain_recorder @shell = shell @wal_factory = wal_factory @logger = logger # Usage accumulator: if orient_fn/decide_fn/reflect_fn use # LlmPhaseFunctions with a shared UsageAccumulator, inject it here # to enable per-cycle budget tracking. Otherwise falls back to zeros. @usage_accumulator = usage_accumulator end |
Instance Method Details
#call(mandate) ⇒ Hash
Returns { status:, llm_calls:, input_tokens:, output_tokens:, phases: }.
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/kairos_mcp/daemon/ooda_cycle_runner.rb', line 56 def call(mandate) @usage_accumulator&.reset! if @usage_accumulator.respond_to?(:reset!) # Flush any pending chain records (always, including resume paths) @chain.retry_pending # Step 0: Check for pending proposal resume resolved = @cg_handler.resume_if_pending case resolved when nil # No pending proposal — proceed with full cycle when :still_pending return result_hash('paused', phases: []) when Hash # F1 fix: Open WAL for resume path mandate_id = mandate[:id] || mandate['id'] || 'unknown' wal = @wal_factory.call(mandate_id) cycle = (mandate[:cycles_completed] || mandate['cycles_completed'] || 0) + 1 recorder = WalPhaseRecorder.new(wal: wal, cycle: cycle) begin if resolved[:status] == 'applied' maybe_run_post_commit(mandate, resolved) # F3 fix: chain recording already done by CodeGenAct — don't duplicate run_reflect(resolved, mandate, recorder) return result_hash('ok', phases: [:resume, :reflect]) else run_reflect(resolved, mandate, recorder) return result_hash(resolved[:status], phases: [:resume, :reflect]) end ensure wal.close rescue nil if wal.respond_to?(:close) end end # Open WAL mandate_id = mandate[:id] || mandate['id'] || 'unknown' wal = @wal_factory.call(mandate_id) cycle = (mandate[:cycles_completed] || mandate['cycles_completed'] || 0) + 1 recorder = WalPhaseRecorder.new(wal: wal, cycle: cycle) begin # Step 1: OBSERVE observation = run_observe(mandate, recorder) # Step 2: ORIENT orient_output = run_orient(observation, mandate, recorder) # Step 3: DECIDE decision = run_decide(orient_output, mandate, recorder) # Step 4: ACT act_result = run_act(decision, mandate, recorder) if act_result[:status] == PAUSED_STATUS return result_hash('paused', phases: [:observe, :orient, :decide, :act], proposal_id: act_result[:proposal_id]) end # Post-commit shell (git add/commit) maybe_run_post_commit(mandate, act_result, decision: decision) # Chain recording handled by CodeGenAct internally (no duplication) # Step 5: REFLECT run_reflect(act_result, mandate, recorder) result_hash('ok', phases: [:observe, :orient, :decide, :act, :reflect]) ensure wal.close rescue nil if wal.respond_to?(:close) end end |