Class: KairosMcp::Daemon::ActiveObserve
- Inherits:
-
Object
- Object
- KairosMcp::Daemon::ActiveObserve
- Defined in:
- lib/kairos_mcp/daemon/active_observe.rb
Overview
ActiveObserve — policy-driven OBSERVE phase.
Design (v0.2 §2, P3.1):
The passive OBSERVE phase inspects mandate state in memory. The
active variant additionally invokes a whitelisted set of READ-ONLY
tools named in the mandate's `observe_policies`, collects their
results, and runs a cheap triage step to highlight what looks
relevant. In P3.1 the triage is a keyword filter stub; a future
revision will slot in a cheap LLM call with the same interface.
Safety:
Only tools listed in READ_ONLY_ALLOWLIST (or the caller-supplied
allowlist) are ever invoked. A mandate cannot widen its own
observation surface — policies must be a subset of the allowlist.
Constant Summary collapse
- READ_ONLY_ALLOWLIST =
A deliberately conservative default. Additional read-only tools may be allowed by passing an explicit ‘allowlist:` into #initialize.
%w[ chain_history chain_status chain_verify knowledge_get knowledge_list skills_list skills_get skills_dsl_list skills_dsl_get resource_list resource_read introspection_health introspection_check state_status state_history document_status meeting_browse meeting_check_freshness skillset_browse ].freeze
Instance Method Summary collapse
-
#initialize(allowlist: READ_ONLY_ALLOWLIST, keywords: nil, logger: nil) ⇒ ActiveObserve
constructor
A new instance of ActiveObserve.
-
#observe(mandate_hash, tool_invoker:) ⇒ Hash
Execute the active OBSERVE step.
-
#select_relevant(results, mandate_hash) ⇒ Hash
Triage stub.
Constructor Details
#initialize(allowlist: READ_ONLY_ALLOWLIST, keywords: nil, logger: nil) ⇒ ActiveObserve
Returns a new instance of ActiveObserve.
44 45 46 47 48 |
# File 'lib/kairos_mcp/daemon/active_observe.rb', line 44 def initialize(allowlist: READ_ONLY_ALLOWLIST, keywords: nil, logger: nil) @allowlist = allowlist.map(&:to_s).freeze @keywords = keywords @logger = logger end |
Instance Method Details
#observe(mandate_hash, tool_invoker:) ⇒ Hash
Execute the active OBSERVE step.
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 |
# File 'lib/kairos_mcp/daemon/active_observe.rb', line 61 def observe(mandate_hash, tool_invoker:) raise ArgumentError, 'mandate_hash required' unless mandate_hash.is_a?(Hash) raise ArgumentError, 'tool_invoker must respond to call' unless tool_invoker.respond_to?(:call) policies = Array(mandate_hash[:observe_policies] || mandate_hash['observe_policies']) # Deduplicate by normalized tool name — first-wins for args if dupes exist. policies = policies.uniq { |e| normalize_policy(e).first } invoked = [] skipped = [] results = {} errors = {} policies.each do |entry| tool_name, args = normalize_policy(entry) unless allowed?(tool_name) skipped << tool_name log(:warn, :active_observe_skip, tool: tool_name, reason: 'not_in_allowlist') next end begin results[tool_name] = tool_invoker.call(tool_name, args) invoked << tool_name rescue StandardError => e errors[tool_name] = "#{e.class}: #{e.}" log(:error, :active_observe_error, tool: tool_name, error: errors[tool_name]) end end relevant = select_relevant(results, mandate_hash) { policies_invoked: invoked, policies_skipped: skipped, results: results, relevant: relevant, errors: errors } end |
#select_relevant(results, mandate_hash) ⇒ Hash
Triage stub. In P3.1 we score results by simple keyword membership (mandate goal tokens). Returning the full result under a :match entry keeps the interface stable for the eventual LLM-backed implementation, which will add confidence scores without changing the key layout.
108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/kairos_mcp/daemon/active_observe.rb', line 108 def select_relevant(results, mandate_hash) keywords = effective_keywords(mandate_hash) return {} if results.empty? results.each_with_object({}) do |(tool, payload), acc| matched = match_keywords(payload, keywords) acc[tool] = { match: !matched.empty? || keywords.empty?, score: keywords.empty? ? 1.0 : matched.size.to_f / keywords.size, matched_keywords: matched } end end |