Module: KairosMcp::Daemon::Planner
- Defined in:
- lib/kairos_mcp/daemon/planner.rb
Overview
Planner — converts a Chronos FiredEvent into a plan for WAL commit.
Design (v0.2 P3.0):
One plan per cycle. Each OODA phase becomes a WAL step with a
content-addressed params_hash, plus pre/expected-post hashes that
let WAL recovery idempotency-check each step on restart.
Why 5 steps (observe/orient/decide/act/reflect):
The CognitiveLoop semantics map 1:1 onto these phases. Making each
phase a WAL step gives crash recovery the finest granularity the
loop currently distinguishes. If future CognitiveLoop variants add
or collapse phases, the Planner is where that change lives — the
WAL contract remains unchanged.
Constant Summary collapse
- OODA_PHASES =
%w[observe orient decide act reflect].freeze
Class Method Summary collapse
-
.build_step(phase:, idx:, cycle:, plan_id:, mandate_name:) ⇒ Object
—————————————————————- helpers.
- .event_name(fired_event) ⇒ Object
-
.generate_plan_id(fired_event) ⇒ Object
plan_id is deterministic on (name, fired_at) when both are present, otherwise falls back to a random suffix so distinct calls never collide.
- .phase_marker(phase, cycle, state) ⇒ Object
-
.plan_from_fired_event(fired_event, cycle: 1, plan_id: nil) ⇒ Hash
Build a plan for a single cycle of a fired event.
-
.step_id_for(phase, cycle) ⇒ Object
Canonical step id for a phase within a cycle.
Class Method Details
.build_step(phase:, idx:, cycle:, plan_id:, mandate_name:) ⇒ Object
—————————————————————- helpers
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/kairos_mcp/daemon/planner.rb', line 58 def build_step(phase:, idx:, cycle:, plan_id:, mandate_name:) params = { phase: phase, order: idx, cycle: cycle, plan_id: plan_id, mandate_name: mandate_name } { step_id: step_id_for(phase, cycle), tool: "ooda.#{phase}", params_hash: Canonical.sha256_json(params), pre_hash: Canonical.sha256_json(phase_marker(phase, cycle, 'pre')), expected_post_hash: Canonical.sha256_json(phase_marker(phase, cycle, 'post')) } end |
.event_name(fired_event) ⇒ Object
79 80 81 82 |
# File 'lib/kairos_mcp/daemon/planner.rb', line 79 def event_name(fired_event) return fired_event.name.to_s if fired_event.respond_to?(:name) && fired_event.name 'unnamed_event' end |
.generate_plan_id(fired_event) ⇒ Object
plan_id is deterministic on (name, fired_at) when both are present, otherwise falls back to a random suffix so distinct calls never collide. Determinism matters for recovery — the same event fired twice with the same timestamp should resolve to the same plan.
88 89 90 91 92 93 94 95 96 97 |
# File 'lib/kairos_mcp/daemon/planner.rb', line 88 def generate_plan_id(fired_event) name = event_name(fired_event) fired_at = fired_event.respond_to?(:fired_at) ? fired_event.fired_at : nil if fired_at && !fired_at.to_s.empty? digest = Digest::SHA256.hexdigest("#{name}|#{fired_at}") "plan_#{digest[0, 12]}" else "plan_#{name}_#{SecureRandom.hex(4)}" end end |
.phase_marker(phase, cycle, state) ⇒ Object
75 76 77 |
# File 'lib/kairos_mcp/daemon/planner.rb', line 75 def phase_marker(phase, cycle, state) { phase: phase, cycle: cycle, state: state } end |
.plan_from_fired_event(fired_event, cycle: 1, plan_id: nil) ⇒ Hash
Build a plan for a single cycle of a fired event.
36 37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/kairos_mcp/daemon/planner.rb', line 36 def plan_from_fired_event(fired_event, cycle: 1, plan_id: nil) cycle = Integer(cycle) raise ArgumentError, 'cycle must be positive' unless cycle.positive? pid = plan_id || generate_plan_id(fired_event) name = event_name(fired_event) steps = OODA_PHASES.each_with_index.map do |phase, idx| build_step(phase: phase, idx: idx, cycle: cycle, plan_id: pid, mandate_name: name) end { plan_id: pid, cycle: cycle, steps: steps } end |
.step_id_for(phase, cycle) ⇒ Object
Canonical step id for a phase within a cycle. Exposed as a helper so WalPhaseRecorder can derive the same ids without re-planning.
52 53 54 |
# File 'lib/kairos_mcp/daemon/planner.rb', line 52 def step_id_for(phase, cycle) format('%s_%03d', phase.to_s, Integer(cycle)) end |