Class: KairosMcp::SafeEvolver

Inherits:
Object
  • Object
show all
Defined in:
lib/kairos_mcp/safe_evolver.rb

Defined Under Namespace

Classes: EvolutionError, LayerViolationError

Constant Summary collapse

@@evolution_count =

Session counter for evolution limits

0

Class Method Summary collapse

Class Method Details

.add_skill(skill_id:, definition:, approved: false) ⇒ Object



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/kairos_mcp/safe_evolver.rb', line 159

def self.add_skill(skill_id:, definition:, approved: false)
  config = SkillsConfig.load
  
  if config['require_human_approval'] && !approved
    return { success: false, error: "Human approval required.", pending: true }
  end
  
  unless SkillsConfig.evolution_enabled?
    return { success: false, error: "Evolution is disabled." }
  end

  # Layer validation: Only Kairos meta-skills can be added to L0 (skills/kairos.rb)
  layer_check = validate_layer_constraint(skill_id)
  return layer_check unless layer_check[:success]
  
  if Kairos.skill(skill_id)
    return { success: false, error: "Skill '#{skill_id}' already exists. Use 'propose' to modify." }
  end
  
  full_definition = "skill :#{skill_id} do\n#{definition}\nend"
  validation = validate_in_sandbox(full_definition)
  return validation unless validation[:success]
  
  snapshot = VersionManager.create_snapshot(reason: "before adding #{skill_id}")
  prev_content = File.read(KairosMcp.dsl_path)
  
  begin
    File.open(KairosMcp.dsl_path, 'a') do |f|
      f.puts "\n#{full_definition}"
    end
    
    new_content = File.read(KairosMcp.dsl_path)
    record_transition(skill_id, prev_content, new_content, snapshot)
    
    @@evolution_count += 1
    Kairos.reload!
    
    ActionLog.record(
      action: 'skill_added',
      skill_id: skill_id,
      details: { snapshot: snapshot }
    )
    
    # Track pending change for state commit
    track_pending_change(layer: 'L0', action: 'create', skill_id: skill_id, reason: "Skill added")
    
    { success: true, message: "Skill '#{skill_id}' added successfully and recorded on KairosChain." }
  rescue => e
    VersionManager.rollback(snapshot)
    Kairos.reload!
    { success: false, error: "Failed to add skill: #{e.message}" }
  end
end

.allowed_l0_skillsObject

Get allowed L0 skills from l0_governance (or fallback)



238
239
240
# File 'lib/kairos_mcp/safe_evolver.rb', line 238

def self.allowed_l0_skills
  l0_governance_config[:allowed_skills] || []
end

.apply(skill_id:, new_definition:, approved: false) ⇒ Object



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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/kairos_mcp/safe_evolver.rb', line 94

def self.apply(skill_id:, new_definition:, approved: false)
  config = SkillsConfig.load
  
  if config['require_human_approval'] && !approved
    return { 
      success: false, 
      error: "Human approval required. Set approved=true to confirm.",
      pending: true 
    }
  end
  
  unless SkillsConfig.evolution_enabled?
    return { success: false, error: "Evolution is disabled." }
  end

  # Layer validation: Only Kairos meta-skills can be in L0 (skills/kairos.rb)
  layer_check = validate_layer_constraint(skill_id)
  return layer_check unless layer_check[:success]
  
  skill = Kairos.skill(skill_id)
  if skill && skill.evolution_rules
    rules = skill.evolution_rules
    if rules.denied.include?(:all)
      return { success: false, error: "Skill '#{skill_id}' denies all evolution." }
    end
  end
  
  validation = validate_in_sandbox(new_definition)
  return validation unless validation[:success]
  
  snapshot = VersionManager.create_snapshot(reason: "before evolving #{skill_id}")
  prev_content = File.read(KairosMcp.dsl_path)
  
  begin
    # Apply the change
    new_content = apply_change_to_content(prev_content, skill_id, new_definition)
    File.write(KairosMcp.dsl_path, new_content)
    
    # Record to Blockchain
    record_transition(skill_id, prev_content, new_content, snapshot)
    
    @@evolution_count += 1
    Kairos.reload!
    
    ActionLog.record(
      action: 'skill_evolved',
      skill_id: skill_id,
      details: { 
        new_definition: new_definition[0, 500],
        snapshot: snapshot,
        evolution_count: @@evolution_count
      }
    )
    
    # Track pending change for state commit
    track_pending_change(layer: 'L0', action: 'update', skill_id: skill_id, reason: "Skill evolved")
    
    { success: true, message: "Skill '#{skill_id}' evolved successfully and recorded on KairosChain. Snapshot: #{snapshot}" }
  rescue => e
    VersionManager.rollback(snapshot)
    Kairos.reload!
    { success: false, error: "Evolution failed and rolled back: #{e.message}" }
  end
end

.apply_change_to_content(content, skill_id, new_definition) ⇒ Object



295
296
297
298
299
300
301
302
# File 'lib/kairos_mcp/safe_evolver.rb', line 295

def self.apply_change_to_content(content, skill_id, new_definition)
  pattern = /skill\s+:#{skill_id}\s+do.*?^end/m
  if content.match?(pattern)
    content.gsub(pattern, new_definition)
  else
    content + "\n#{new_definition}"
  end
end

.check_knowledge_compatibility(definition) ⇒ Object

Check compatibility with L1 knowledge references (if any)



276
277
278
279
280
# File 'lib/kairos_mcp/safe_evolver.rb', line 276

def self.check_knowledge_compatibility(definition)
  # Future: Parse definition to find knowledge references and validate
  # For now, just return success
  { success: true }
end

.evolution_countObject



23
24
25
# File 'lib/kairos_mcp/safe_evolver.rb', line 23

def self.evolution_count
  @@evolution_count
end

.immutable_l0_skillsObject

Get immutable skills from l0_governance (or fallback)



243
244
245
# File 'lib/kairos_mcp/safe_evolver.rb', line 243

def self.immutable_l0_skills
  l0_governance_config[:immutable_skills] || [:core_safety]
end

.l0_allowed_skill?(skill_id) ⇒ Boolean

Check if a skill is allowed in L0

Returns:

  • (Boolean)


248
249
250
# File 'lib/kairos_mcp/safe_evolver.rb', line 248

def self.l0_allowed_skill?(skill_id)
  allowed_l0_skills.include?(skill_id.to_sym)
end

.l0_governance_configObject

Get L0 governance configuration from the l0_governance skill itself This implements the Pure Agent Skill principle: L0 rules are in L0 Falls back to config.yml only during bootstrapping



218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/kairos_mcp/safe_evolver.rb', line 218

def self.l0_governance_config
  # Try to get from l0_governance skill first (canonical source)
  governance_skill = Kairos.skill(:l0_governance)
  if governance_skill&.behavior
    begin
      return governance_skill.behavior.call
    rescue StandardError => e
      warn "[SafeEvolver] Failed to evaluate l0_governance behavior: #{e.message}"
    end
  end
  
  # Fallback to config.yml (for bootstrapping or if skill not loaded)
  {
    allowed_skills: SkillsConfig.kairos_meta_skills.map(&:to_sym),
    immutable_skills: (SkillsConfig.load['immutable_skills'] || ['core_safety']).map(&:to_sym),
    require_human_approval: SkillsConfig.load['require_human_approval']
  }
end

.l0_immutable_skill?(skill_id) ⇒ Boolean

Check if a skill is immutable

Returns:

  • (Boolean)


253
254
255
# File 'lib/kairos_mcp/safe_evolver.rb', line 253

def self.l0_immutable_skill?(skill_id)
  immutable_l0_skills.include?(skill_id.to_sym)
end

.propose(skill_id:, new_definition:, reason: nil) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/kairos_mcp/safe_evolver.rb', line 27

def self.propose(skill_id:, new_definition:, reason: nil)
  # Run L0 Auto-Check (defined in approval_workflow skill for Pure Agent Skill compliance)
  auto_check_result = run_l0_auto_check(skill_id: skill_id, definition: new_definition, reason: reason)
  
  # If mechanical checks failed, return immediately with check results
  unless auto_check_result[:mechanical_passed]
    return { 
      success: false, 
      error: auto_check_result[:summary],
      auto_check: auto_check_result
    }
  end
  
  # Additional sandbox validation
  validation = validate_in_sandbox(new_definition)
  return validation unless validation[:success]
  
  { 
    success: true, 
    preview: new_definition,
    message: "Proposal validated. Use 'apply' command with approved=true to apply.",
    auto_check: auto_check_result
  }
end

.record_transition(skill_id, prev_content, new_content, snapshot) ⇒ Object



304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
# File 'lib/kairos_mcp/safe_evolver.rb', line 304

def self.record_transition(skill_id, prev_content, new_content, snapshot)
  prev_hash = Digest::SHA256.hexdigest(prev_content)
  next_hash = Digest::SHA256.hexdigest(new_content)
  diff_hash = Digest::SHA256.hexdigest(prev_content + new_content) # Simplified diff hash
  
  transition = KairosChain::SkillTransition.new(
    skill_id: skill_id,
    prev_ast_hash: prev_hash,
    next_ast_hash: next_hash,
    diff_hash: diff_hash,
    reason_ref: snapshot
  )
  
  chain = KairosChain::Chain.new
  chain.add_block([transition.to_json])
end

.reset_session!Object



19
20
21
# File 'lib/kairos_mcp/safe_evolver.rb', line 19

def self.reset_session!
  @@evolution_count = 0
end

.run_l0_auto_check(skill_id:, definition:, reason: nil) ⇒ Object

Run the auto-check logic defined in L0 (approval_workflow skill) This keeps the check criteria within L0 for Pure Agent Skill compliance



54
55
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
# File 'lib/kairos_mcp/safe_evolver.rb', line 54

def self.run_l0_auto_check(skill_id:, definition:, reason: nil)
  approval_workflow = Kairos.skill(:approval_workflow)
  
  unless approval_workflow&.behavior
    # Fallback if approval_workflow skill not loaded
    return {
      passed: true,
      mechanical_passed: true,
      human_review_needed: 0,
      checks: [],
      summary: "Warning: approval_workflow skill not loaded. Skipping auto-check."
    }
  end
  
  begin
    workflow_data = approval_workflow.behavior.call
    auto_check = workflow_data[:auto_check]
    
    if auto_check
      auto_check.call(skill_id: skill_id, definition: definition, reason: reason)
    else
      {
        passed: true,
        mechanical_passed: true,
        human_review_needed: 0,
        checks: [],
        summary: "Warning: auto_check not available in approval_workflow."
      }
    end
  rescue StandardError => e
    {
      passed: false,
      mechanical_passed: false,
      human_review_needed: 0,
      checks: [],
      summary: "Auto-check error: #{e.message}"
    }
  end
end

.track_pending_change(layer:, action:, skill_id:, reason: nil) ⇒ Object

Track pending change for state commit auto-commit



322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
# File 'lib/kairos_mcp/safe_evolver.rb', line 322

def self.track_pending_change(layer:, action:, skill_id:, reason: nil)
  return unless SkillsConfig.state_commit_enabled?

  require_relative 'state_commit/pending_changes'
  require_relative 'state_commit/commit_service'

  StateCommit::PendingChanges.add(
    layer: layer,
    action: action,
    skill_id: skill_id,
    reason: reason
  )

  # Check if auto-commit should be triggered
  if SkillsConfig.state_commit_auto_enabled?
    service = StateCommit::CommitService.new(user_context: Thread.current[:kairos_user_context])
    service.check_and_auto_commit
  end
rescue StandardError => e
  # Log but don't fail if state commit tracking fails
  warn "[SafeEvolver] Failed to track pending change: #{e.message}"
end

.validate_in_sandbox(definition) ⇒ Object



282
283
284
285
286
287
288
289
290
291
292
293
# File 'lib/kairos_mcp/safe_evolver.rb', line 282

def self.validate_in_sandbox(definition)
  begin
    RubyVM::AbstractSyntaxTree.parse(definition)
    test_dsl = SkillsDsl.new
    test_dsl.instance_eval(definition)
    { success: true }
  rescue SyntaxError => e
    { success: false, error: "Syntax error: #{e.message}" }
  rescue StandardError => e
    { success: false, error: "Validation error: #{e.message}" }
  end
end

.validate_layer_constraint(skill_id) ⇒ Object

Validate that a skill can be placed in L0 (skills/kairos.rb) Only Kairos meta-skills are allowed in L0 Now reads from l0_governance skill itself (Pure Agent Skill compliance)



260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/kairos_mcp/safe_evolver.rb', line 260

def self.validate_layer_constraint(skill_id)
  unless l0_allowed_skill?(skill_id)
    allowed = allowed_l0_skills.join(', ')
    return {
      success: false,
      error: "Skill '#{skill_id}' is not allowed in L0. " \
             "Allowed L0 skills (from l0_governance): #{allowed}. " \
             "To add a new skill type to L0, first evolve the l0_governance skill. " \
             "For project-specific knowledge, use the L1 knowledge layer (knowledge_update tool)."
    }
  end

  { success: true }
end