kairos-chain 3.9.4 → 3.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +52 -0
  3. data/lib/kairos_mcp/safety.rb +6 -0
  4. data/lib/kairos_mcp/tools/skills_promote.rb +30 -4
  5. data/lib/kairos_mcp/tools/system_upgrade.rb +33 -0
  6. data/lib/kairos_mcp/version.rb +1 -1
  7. data/templates/knowledge/multi_llm_review_workflow/multi_llm_review_workflow.md +60 -1
  8. data/templates/skillsets/agent/tools/agent_start.rb +3 -1
  9. data/templates/skillsets/dream/lib/dream/scanner.rb +102 -7
  10. data/templates/skillsets/dream/tools/dream_scan.rb +9 -2
  11. data/templates/skillsets/introspection/config/introspection.yml +4 -0
  12. data/templates/skillsets/introspection/knowledge/introspection_guide/introspection_guide.md +90 -0
  13. data/templates/skillsets/introspection/lib/introspection/health_scorer.rb +136 -0
  14. data/templates/skillsets/introspection/lib/introspection/safety_inspector.rb +85 -0
  15. data/templates/skillsets/introspection/lib/introspection.rb +7 -0
  16. data/templates/skillsets/introspection/skillset.json +17 -0
  17. data/templates/skillsets/introspection/tools/introspection_check.rb +207 -0
  18. data/templates/skillsets/introspection/tools/introspection_health.rb +80 -0
  19. data/templates/skillsets/introspection/tools/introspection_safety.rb +46 -0
  20. data/templates/skillsets/llm_client/lib/llm_client/adapter.rb +1 -1
  21. data/templates/skillsets/llm_client/lib/llm_client/anthropic_adapter.rb +7 -1
  22. data/templates/skillsets/llm_client/lib/llm_client/bedrock_adapter.rb +8 -1
  23. data/templates/skillsets/llm_client/lib/llm_client/claude_code_adapter.rb +10 -3
  24. data/templates/skillsets/llm_client/lib/llm_client/openai_adapter.rb +11 -1
  25. data/templates/skillsets/llm_client/lib/llm_client/schema_converter.rb +12 -1
  26. data/templates/skillsets/llm_client/tools/llm_call.rb +9 -1
  27. metadata +12 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fa87c57bf88c7f0f225793d049592120e2872deccda39c3eba3a3af059663dac
4
- data.tar.gz: bf793a2f05c4e41bc43aa1f1d5a948985eeac39cee0c9c4c1c744d577e0c32ef
3
+ metadata.gz: 0acca30b533051c884363e6c9215394d308f501985a6dc647f92c513e875ce6b
4
+ data.tar.gz: 862c366ebcb1f88a66eb5c30b48086e454145609d57f1fbb8ba6aa62aeb89652
5
5
  SHA512:
6
- metadata.gz: 27ea12fe7352161972a0e81754a8012ba03da6e03c4f1db6fd4f51bb60d599f0ebc1777c54a8d8169b138e308b035628666d80dcccdf95ad1b82f78fb047295c
7
- data.tar.gz: 811bd5bf6f2339086196f1ea8edfe22371b3374bb1290ebbece3f8696c3a7c514baafbd24a1bf4cc7f7df0f8e29f0c825f1b36f2b22122345d3fcf704dab8141
6
+ metadata.gz: 9066154fcf88d0e39457fca4c4f4ebac74e7104c9d5d2bf047783e2f058ddfc9b03e38be965f492ac914b81eb611da50cff7e63264e6a16caadf0e23d8d707ef
7
+ data.tar.gz: b7324da5f140698a74d11e94dedee3a3bdc2d778a9f0f2ca12417861bb5a5b23ad96eb6f4ad94077c1b6f72cbc2f2e0c3651c62d08242452451d7f0ada75d330
data/CHANGELOG.md CHANGED
@@ -4,6 +4,58 @@ All notable changes to the `kairos-chain` gem will be documented in this file.
4
4
 
5
5
  This project follows [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [3.11.0] - 2026-04-01
8
+
9
+ ### Added
10
+
11
+ - **`llm_call` `output_schema` parameter** — Enables structured JSON output from LLMs.
12
+ When provided, the LLM is constrained to return JSON matching the given JSON Schema.
13
+ - Anthropic/Bedrock: system prompt injection with tools-aware qualifier
14
+ ("When you are NOT using a tool, respond with ONLY valid JSON...")
15
+ - OpenAI: native `response_format: { type: "json_schema", strict: true }`
16
+ - Claude Code CLI: prompt text injection
17
+ - Backward compatible: `nil` default preserves all existing behavior
18
+ - **`SchemaConverter.normalize_for_openai` `required` auto-population** — When an
19
+ object schema has no `required` key, all property names are auto-populated into
20
+ `required` to satisfy OpenAI strict mode constraints. Existing `required` arrays
21
+ are preserved unchanged.
22
+ - **Multi-LLM review XML block prompts** (`multi_llm_review_workflow` v3.2) — Five
23
+ XML blocks for structured review prompts:
24
+ `<task>`, `<structured_output_contract>`, `<grounding_rules>`,
25
+ `<verification_loop>`, `<default_follow_through_policy>`.
26
+ LLM-agnostic; works with Claude, GPT, and Composer models.
27
+
28
+ ### Design Process
29
+
30
+ - Inspired by codex-plugin-cc (Claude Code Codex plugin) analysis — 4-agent team review
31
+ - Implementation reviewed: 2 rounds x 3 LLMs (Claude Opus 4.6, Codex GPT-5.4, Cursor Composer-2)
32
+ - R1: 3/3 APPROVE WITH CHANGES — 2 must-fix findings (tools+schema conflict, OpenAI required)
33
+ - R2: 2/3 APPROVE, 1/3 AWC, 0 FAIL/HIGH — converged
34
+ - Tests: 32 → 47 (all PASS)
35
+
36
+ ## [3.10.0] - 2026-03-31
37
+
38
+ ### Added
39
+
40
+ - **introspection SkillSet** (v0.1.0) — New self-inspection SkillSet with 3 tools:
41
+ - `introspection_check`: Full inspection (L1 health + blockchain integrity + safety mechanisms + recommendations)
42
+ - `introspection_health`: L1 knowledge health scores using Synoptis TrustScorer (optional, falls back to staleness-only)
43
+ - `introspection_safety`: 4-layer safety mechanism visibility (L0 approval workflow, RBAC policies, agent safety gates, blockchain recording)
44
+ - **Dream SkillSet L1 dedup + confidence scoring** (v0.2.1) — `dream_scan` now checks promotion candidates against existing L1 knowledge (name similarity + tag Jaccard overlap) and scores candidates with 3-dimension confidence (recurrence, tag consistency, session diversity). New `include_l1_dedup` parameter.
45
+ - **`skills_promote` attestation integration** — Successful L2→L1 promotions now automatically issue Synoptis attestations (`claim: "promoted_from_l2"`, `actor_role: "automated"`). Graceful degradation when Synoptis is not loaded.
46
+ - **`Safety.registered_policy_names`** — New thread-safe public API for introspecting registered RBAC policies. Replaces `instance_variable_get(:@policies)` pattern.
47
+
48
+ ### Fixed
49
+
50
+ - **`system_upgrade` SkillSet-only install** — When specific SkillSet names are provided via `names` parameter but L0 templates are already up-to-date, `system_upgrade apply` now correctly installs/upgrades the requested SkillSets instead of returning "No upgrade needed". Previously, the L0 upgrade check (`UpgradeAnalyzer.upgrade_needed?`) gated all operations including SkillSet installs.
51
+
52
+ ### Design Process
53
+
54
+ - Design reviewed: 2 rounds x 3 LLMs (Claude Opus 4.6, Codex GPT-5.4, Cursor Composer-2)
55
+ - Implementation reviewed: 1 round x 3 LLMs per phase
56
+ - 8 P0/P1 bugs found and fixed during design review (before any code was written)
57
+ - Inspired by oh-my-claudecode analysis; independently designed with KairosChain philosophy
58
+
7
59
  ## [3.9.4] - 2026-03-30
8
60
 
9
61
  ### Added
@@ -25,6 +25,12 @@ module KairosMcp
25
25
  @policy_mutex.synchronize { @policies[name.to_sym] }
26
26
  end
27
27
 
28
+ # Thread-safe list of registered policy names.
29
+ # Used by introspection SkillSet for safety visibility.
30
+ def self.registered_policy_names
31
+ @policy_mutex.synchronize { @policies.keys.map(&:to_s) }
32
+ end
33
+
28
34
  # For testing only
29
35
  def self.clear_policies!
30
36
  @policy_mutex.synchronize { @policies = {} }
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'json'
3
4
  require_relative 'base_tool'
4
5
  require_relative '../knowledge_provider'
5
6
  require_relative '../context_manager'
@@ -30,7 +31,8 @@ module KairosMcp
30
31
 
31
32
  def description
32
33
  'Promote knowledge between layers (L2→L1, L1→L0) with optional Persona Assembly for decision support. ' \
33
- 'Assembly generates a structured discussion from multiple perspectives before human decision.'
34
+ 'Assembly generates a structured discussion from multiple perspectives before human decision. ' \
35
+ 'For pattern detection and auto-scan, use dream_scan instead.'
34
36
  end
35
37
 
36
38
  def category
@@ -63,7 +65,7 @@ module KairosMcp
63
65
  end
64
66
 
65
67
  def related_tools
66
- %w[context_save knowledge_update skills_evolve skills_audit]
68
+ %w[context_save knowledge_update skills_evolve skills_audit attestation_issue]
67
69
  end
68
70
 
69
71
  def input_schema
@@ -72,7 +74,7 @@ module KairosMcp
72
74
  properties: {
73
75
  command: {
74
76
  type: 'string',
75
- description: 'Command: "analyze" (with assembly), "promote" (direct promotion), "status" (check requirements), or "suggest" (LLM suggests optimal personas for content)',
77
+ description: 'Command: "analyze" (with assembly), "promote" (direct promotion), "status" (check requirements), or "suggest" (LLM suggests optimal personas)',
76
78
  enum: %w[analyze promote status suggest]
77
79
  },
78
80
  source_name: {
@@ -122,7 +124,7 @@ module KairosMcp
122
124
  consensus_threshold: {
123
125
  type: 'number',
124
126
  description: 'Consensus threshold for early termination in discussion mode (default: 0.6 = 60%)'
125
- }
127
+ },
126
128
  },
127
129
  required: %w[command]
128
130
  }
@@ -576,6 +578,9 @@ module KairosMcp
576
578
  # Track promotion event for state commit (this triggers auto-commit on promotion)
577
579
  track_promotion_change(from_layer: 'L2', to_layer: 'L1', skill_id: target_name, reason: reason)
578
580
 
581
+ # Issue attestation AFTER L1 exists
582
+ issue_promotion_attestation(target_name, reason)
583
+
579
584
  action = existing ? 'updated' : 'created'
580
585
  output = "## Promotion Successful\n\n"
581
586
  output += "**Target**: #{target_name} (L1)\n"
@@ -681,6 +686,27 @@ module KairosMcp
681
686
  end
682
687
  end
683
688
 
689
+ # Issue attestation after successful promotion
690
+ def issue_promotion_attestation(target_name, reason)
691
+ return unless synoptis_available?
692
+
693
+ invoke_tool('attestation_issue', {
694
+ 'subject_ref' => "knowledge://#{target_name}",
695
+ 'claim' => 'promoted_from_l2',
696
+ 'evidence' => reason.to_s,
697
+ 'actor_role' => 'automated'
698
+ })
699
+ rescue StandardError => e
700
+ # Synoptis may not be loaded — silently skip
701
+ warn "[SkillsPromote] Attestation skipped: #{e.message}"
702
+ end
703
+
704
+ def synoptis_available?
705
+ @registry && true
706
+ rescue StandardError
707
+ false
708
+ end
709
+
684
710
  # Track promotion event for state commit auto-commit
685
711
  def track_promotion_change(from_layer:, to_layer:, skill_id:, reason: nil)
686
712
  return unless SkillsConfig.state_commit_enabled?
@@ -5,6 +5,7 @@ require 'fileutils'
5
5
  require_relative 'base_tool'
6
6
  require_relative '../upgrade_analyzer'
7
7
  require_relative '../config_merger'
8
+ require_relative '../skillset_manager'
8
9
 
9
10
  module KairosMcp
10
11
  module Tools
@@ -240,6 +241,12 @@ module KairosMcp
240
241
  analyzer = UpgradeAnalyzer.new
241
242
  analyzer.analyze
242
243
 
244
+ # When specific SkillSet names are requested, skip L0 upgrade check
245
+ # and go directly to SkillSet install/upgrade
246
+ if names && !names.empty? && !analyzer.upgrade_needed?
247
+ return handle_skillset_only_install(names)
248
+ end
249
+
243
250
  unless analyzer.upgrade_needed?
244
251
  return text_content("No upgrade needed. Data directory is already up to date.")
245
252
  end
@@ -437,6 +444,32 @@ module KairosMcp
437
444
  # =====================================================================
438
445
  # skillsets — List all available SkillSets with install status
439
446
  # =====================================================================
447
+ # Install/upgrade specific SkillSets without requiring a full L0 upgrade.
448
+ # Called when names are specified but L0 templates are already up to date.
449
+ def handle_skillset_only_install(names)
450
+ ss_mgr = KairosMcp::SkillSetManager.new
451
+ ss_results = ss_mgr.upgrade_apply(names: names)
452
+
453
+ if ss_results.empty?
454
+ return text_content("No SkillSet changes needed for: #{names.join(', ')}")
455
+ end
456
+
457
+ output = "# SkillSet Install/Upgrade\n\n"
458
+ ss_results.each do |r|
459
+ if r[:action] == 'installed'
460
+ output += " [INSTALLED] #{r[:name]} v#{r[:to]}\n"
461
+ else
462
+ output += " [UPGRADED] #{r[:name]} v#{r[:from]} → v#{r[:to]} (#{r[:files_updated]} files)\n"
463
+ end
464
+ end
465
+ output += "\nNo L0 template changes needed.\n"
466
+ output += "Restart the MCP server to load new SkillSet tools.\n"
467
+
468
+ text_content(output)
469
+ rescue StandardError => e
470
+ text_content("SkillSet install failed: #{e.message}")
471
+ end
472
+
440
473
  def handle_skillsets
441
474
  ss_mgr = KairosMcp::SkillSetManager.new
442
475
  available = ss_mgr.available_skillsets
@@ -1,4 +1,4 @@
1
1
  module KairosMcp
2
- VERSION = "3.9.4"
2
+ VERSION = "3.11.0"
3
3
  CHANGELOG_URL = "https://github.com/masaomi/KairosChain_2026/blob/main/CHANGELOG.md"
4
4
  end
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: multi_llm_review_workflow
3
3
  description: "Multi-LLM review methodology and execution — workflow pattern, CLI tooling, consensus analysis, Persona Assembly. Applicable to design, implementation, documentation, or any artifact."
4
- version: "3.1"
4
+ version: "3.2"
5
5
  tags:
6
6
  - workflow
7
7
  - review
@@ -243,6 +243,65 @@ Every review prompt MUST include these 7 items:
243
243
 
244
244
  All prompt content MUST be in **English** for consistent parsing across LLM tools.
245
245
 
246
+ ### XML Block Structure for Review Prompts
247
+
248
+ Review prompts SHOULD use XML blocks to give LLMs explicit structural contracts.
249
+ This reduces hallucination, enforces grounded findings, and standardizes output.
250
+
251
+ ```xml
252
+ <task>
253
+ Review the provided artifact for [review type: design correctness / implementation bugs / ...].
254
+ Target: [artifact name and version]
255
+ Scope: [what changed since last round, or "initial review"]
256
+ </task>
257
+
258
+ <structured_output_contract>
259
+ Output a Markdown file with this structure:
260
+ - **Reviewer**: [tool_name]
261
+ - **Model**: [model_id]
262
+ - **Date**: [ISO date]
263
+ - **Overall Verdict**: APPROVE / APPROVE WITH CHANGES / REJECT
264
+
265
+ For each finding:
266
+ - **Severity**: FAIL / HIGH / MEDIUM / LOW
267
+ - **Confidence**: 0.0-1.0 (how certain you are this is a real issue)
268
+ - **Location**: file:line or section reference
269
+ - **What can go wrong**: concrete failure scenario
270
+ - **Why this is vulnerable**: code path or design gap
271
+ - **Likely impact**: data loss, security breach, silent corruption, etc.
272
+ - **Recommended fix**: specific change (not "consider improving")
273
+ </structured_output_contract>
274
+
275
+ <grounding_rules>
276
+ Ground every finding in the provided artifact text or referenced source files.
277
+ If a claim is an inference (not directly visible in the artifact), label it:
278
+ "[INFERRED] Based on X, this likely means Y."
279
+ Do not invent files, methods, or runtime behavior not shown in the artifact.
280
+ Keep confidence scores honest — 0.5 if uncertain, 0.9+ only if directly evidenced.
281
+ </grounding_rules>
282
+
283
+ <verification_loop>
284
+ Before finalizing your review:
285
+ 1. Re-read each FAIL/HIGH finding. Is the failure scenario concrete and reproducible?
286
+ 2. Check for second-order failures: empty-state, retry, stale state, rollback risk.
287
+ 3. Verify file paths and line numbers are accurate.
288
+ 4. If you found zero issues, state that explicitly — do not manufacture findings.
289
+ </verification_loop>
290
+
291
+ <default_follow_through_policy>
292
+ Complete the full review in one pass. Do not ask clarifying questions.
293
+ If context is missing, note it as a finding with severity LOW and confidence 0.3.
294
+ </default_follow_through_policy>
295
+ ```
296
+
297
+ **Usage**: Include these XML blocks in the prompt body (rule #3 "Review instructions").
298
+ They replace or supplement free-form review instructions. The blocks are
299
+ LLM-agnostic and work with Claude, GPT, and Composer models.
300
+
301
+ **When to use full XML blocks vs. lightweight**:
302
+ - **Full** (all 5 blocks): Design review, implementation review, security-critical
303
+ - **Lightweight** (`<task>` + `<structured_output_contract>` only): Fix plan review, document review
304
+
246
305
  ### Output Directive in Prompt Body
247
306
 
248
307
  The prompt body itself (what the LLM sees) must contain:
@@ -39,7 +39,9 @@ module KairosMcp
39
39
  properties: {
40
40
  goal_name: {
41
41
  type: 'string',
42
- description: 'Name of the goal (must exist as L1 knowledge or L2 context)'
42
+ description: 'Name of the goal. Create it with context_save (L2) first. ' \
43
+ 'L1 knowledge is only for reusable goal templates. ' \
44
+ 'The agent searches L2 contexts first, then falls back to L1.'
43
45
  },
44
46
  max_cycles: {
45
47
  type: 'integer',
@@ -25,8 +25,9 @@ module KairosMcp
25
25
  # @param scope [String] 'l2', 'l1', or 'all'
26
26
  # @param since_session [String, nil] Only scan sessions after this ID
27
27
  # @param include_archive_candidates [Boolean] Whether to detect stale L2
28
+ # @param include_l1_dedup [Boolean] Check promotion candidates against existing L1
28
29
  # @return [Hash] Structured scan result
29
- def scan(scope: 'l2', since_session: nil, include_archive_candidates: true)
30
+ def scan(scope: 'l2', since_session: nil, include_archive_candidates: true, include_l1_dedup: true)
30
31
  result = {
31
32
  scope: scope,
32
33
  scanned_at: Time.now.iso8601,
@@ -38,7 +39,8 @@ module KairosMcp
38
39
 
39
40
  if %w[l2 all].include?(scope)
40
41
  scan_l2(result, since_session: since_session,
41
- include_archive_candidates: include_archive_candidates)
42
+ include_archive_candidates: include_archive_candidates,
43
+ include_l1_dedup: include_l1_dedup)
42
44
  end
43
45
 
44
46
  if %w[l1 all].include?(scope)
@@ -54,7 +56,7 @@ module KairosMcp
54
56
  # L2 scanning
55
57
  # ---------------------------------------------------------------
56
58
 
57
- def scan_l2(result, since_session: nil, include_archive_candidates: true)
59
+ def scan_l2(result, since_session: nil, include_archive_candidates: true, include_l1_dedup: true)
58
60
  all_contexts = load_all_l2_contexts(since_session: since_session)
59
61
 
60
62
  # Partition: live contexts vs archived stubs
@@ -62,7 +64,20 @@ module KairosMcp
62
64
 
63
65
  # Promotion candidates: tag co-occurrence across sessions
64
66
  sessions_tags = build_sessions_tags(live)
65
- result[:promotion_candidates] = detect_promotion_candidates(sessions_tags)
67
+ candidates = detect_promotion_candidates(sessions_tags, all_sessions_tags: sessions_tags)
68
+
69
+ # L1 dedup: mark candidates that already exist as L1 knowledge
70
+ if include_l1_dedup
71
+ kp = knowledge_provider
72
+ existing_l1 = kp ? kp.list : []
73
+ candidates.each do |candidate|
74
+ match = find_l1_match(candidate[:tag], Array(candidate[:tag]), existing_l1)
75
+ candidate[:already_in_l1] = !match.nil?
76
+ candidate[:l1_match] = match if match
77
+ end
78
+ end
79
+
80
+ result[:promotion_candidates] = candidates
66
81
 
67
82
  # Consolidation candidates: name token overlap
68
83
  result[:consolidation_candidates] = detect_consolidation_candidates(live)
@@ -132,8 +147,11 @@ module KairosMcp
132
147
  # Detect tags that recur across min_recurrence+ distinct sessions.
133
148
  #
134
149
  # @param sessions_tags [Hash] { session_id => { context_name => [tags] } }
135
- # @return [Array<Hash>] promotion candidate hashes
136
- def detect_promotion_candidates(sessions_tags)
150
+ # @param all_sessions_tags [Hash] same as sessions_tags (for scoring)
151
+ # @return [Array<Hash>] promotion candidate hashes with confidence scoring
152
+ def detect_promotion_candidates(sessions_tags, all_sessions_tags: nil)
153
+ all_sessions_tags ||= sessions_tags
154
+
137
155
  tag_sessions = Hash.new { |h, k| h[k] = Set.new }
138
156
 
139
157
  sessions_tags.each do |session_id, contexts|
@@ -148,12 +166,15 @@ module KairosMcp
148
166
  .sort_by { |_tag, sids| -sids.size }
149
167
  .first(@max_candidates)
150
168
  .map do |tag, sids|
169
+ confidence = score_promotion_candidate(tag, sids, all_sessions_tags)
151
170
  {
152
171
  tag: tag,
153
172
  session_count: sids.size,
154
173
  sessions: sids.to_a,
155
174
  signal: 'tag_recurrence',
156
- strength: sids.size.to_f / @min_recurrence
175
+ strength: sids.size.to_f / @min_recurrence,
176
+ confidence: confidence,
177
+ already_in_l1: false
157
178
  }
158
179
  end
159
180
 
@@ -274,6 +295,80 @@ module KairosMcp
274
295
  tags
275
296
  end
276
297
 
298
+ # ---------------------------------------------------------------
299
+ # L1 dedup and confidence scoring (migrated from skills_promote auto_scan)
300
+ # ---------------------------------------------------------------
301
+
302
+ # Find an existing L1 entry that matches a candidate by name or tag overlap.
303
+ #
304
+ # @param candidate_name [String] suggested/tag name of the candidate
305
+ # @param candidate_tags [Array<String>] tags for the candidate
306
+ # @param existing_l1 [Array<Hash>] list from KnowledgeProvider#list
307
+ # @return [String, nil] matching L1 entry name or nil
308
+ def find_l1_match(candidate_name, candidate_tags, existing_l1)
309
+ existing_l1.each do |entry|
310
+ # Name match (normalized substring ratio)
311
+ name_sim = name_similarity(candidate_name, entry[:name])
312
+ return entry[:name] if name_sim > 0.8
313
+
314
+ # Tag overlap (Jaccard on name tokens vs entry tags)
315
+ entry_tags = Array(entry[:tags]).to_set
316
+ candidate_set = candidate_tags.to_set
317
+ sim = candidate_set.empty? && entry_tags.empty? ? 0.0 : (candidate_set & entry_tags).size.to_f / (candidate_set | entry_tags).size.to_f
318
+ return entry[:name] if sim > 0.8
319
+ end
320
+ nil
321
+ end
322
+
323
+ # Compute name similarity using token-level Jaccard.
324
+ #
325
+ # @param a [String]
326
+ # @param b [String]
327
+ # @return [Float] 0.0..1.0
328
+ def name_similarity(a, b)
329
+ a_parts = a.to_s.split('_').to_set
330
+ b_parts = b.to_s.split('_').to_set
331
+ return 0.0 if a_parts.empty? && b_parts.empty?
332
+ (a_parts & b_parts).size.to_f / [a_parts.size, b_parts.size].max
333
+ end
334
+
335
+ # Score a promotion candidate across 3 dimensions:
336
+ # - recurrence: how many sessions the tag appears in (max 40)
337
+ # - tag_consistency: co-occurrence consistency across sessions (max 30)
338
+ # - session_diversity: distinct sessions (max 30)
339
+ #
340
+ # @param tag [String] the recurring tag
341
+ # @param sessions [Set<String>] session IDs where this tag appears
342
+ # @param all_sessions_tags [Hash] { session_id => { context_name => [tags] } }
343
+ # @return [Integer] 0..100
344
+ def score_promotion_candidate(tag, sessions, all_sessions_tags)
345
+ # Recurrence: 3=10, 4=20, 5=30, 6+=40 (max 40 points)
346
+ recurrence_score = [[sessions.size - 2, 4].min * 10, 40].min
347
+ recurrence_score = [recurrence_score, 0].max
348
+
349
+ # Tag consistency: what fraction of sessions containing this tag
350
+ # also share the same co-occurring tags?
351
+ co_tags_per_session = sessions.map do |sid|
352
+ contexts = all_sessions_tags[sid] || {}
353
+ contexts.values.flatten.uniq.reject { |t| t == tag }
354
+ end.reject(&:empty?)
355
+
356
+ if co_tags_per_session.size >= 2
357
+ co_sets = co_tags_per_session.map(&:to_set)
358
+ intersection = co_sets.reduce(:&)
359
+ union = co_sets.reduce(:|)
360
+ tag_consistency = union.empty? ? 0.0 : intersection.size.to_f / union.size
361
+ else
362
+ tag_consistency = 0.0
363
+ end
364
+ tag_score = (tag_consistency * 30).round
365
+
366
+ # Session diversity: distinct sessions (max 30 points)
367
+ session_score = [[sessions.size - 1, 3].min * 10, 30].min
368
+
369
+ [recurrence_score + tag_score + session_score, 100].min
370
+ end
371
+
277
372
  # ---------------------------------------------------------------
278
373
  # Helpers
279
374
  # ---------------------------------------------------------------
@@ -42,6 +42,10 @@ module KairosMcp
42
42
  include_archive_candidates: {
43
43
  type: 'boolean',
44
44
  description: 'Whether to detect stale L2 contexts for archival. Default: true'
45
+ },
46
+ include_l1_dedup: {
47
+ type: 'boolean',
48
+ description: 'Check promotion candidates against existing L1 knowledge to mark duplicates. Default: true'
45
49
  }
46
50
  },
47
51
  required: []
@@ -55,7 +59,8 @@ module KairosMcp
55
59
  scan_result = scanner.scan(
56
60
  scope: arguments['scope'] || config.dig('scan', 'default_scope') || 'l2',
57
61
  since_session: arguments['since_session'],
58
- include_archive_candidates: arguments.fetch('include_archive_candidates', true)
62
+ include_archive_candidates: arguments.fetch('include_archive_candidates', true),
63
+ include_l1_dedup: arguments.fetch('include_l1_dedup', true)
59
64
  )
60
65
 
61
66
  # Record findings on blockchain if non-empty
@@ -131,7 +136,9 @@ module KairosMcp
131
136
  lines << "_No recurring patterns detected._"
132
137
  else
133
138
  promo.each do |c|
134
- lines << "- **#{c[:tag]}**: #{c[:session_count]} sessions (strength: #{c[:strength].round(2)})"
139
+ dedup_marker = c[:already_in_l1] ? " [already in L1: #{c[:l1_match]}]" : ''
140
+ confidence_str = c[:confidence] ? " (confidence: #{c[:confidence]})" : ''
141
+ lines << "- **#{c[:tag]}**: #{c[:session_count]} sessions (strength: #{c[:strength].round(2)})#{confidence_str}#{dedup_marker}"
135
142
  end
136
143
  end
137
144
  lines << ""
@@ -0,0 +1,4 @@
1
+ introspection:
2
+ health:
3
+ staleness_days: 180
4
+ report_format: "markdown"
@@ -0,0 +1,90 @@
1
+ ---
2
+ title: Introspection Guide
3
+ description: Usage guide for the introspection SkillSet — self-inspection, health scoring, and safety visibility
4
+ version: "0.1.0"
5
+ tags:
6
+ - introspection
7
+ - health
8
+ - safety
9
+ - blockchain
10
+ - maintenance
11
+ ---
12
+
13
+ # Introspection Guide
14
+
15
+ ## Overview
16
+
17
+ The introspection SkillSet provides self-inspection capabilities for KairosChain.
18
+ It examines knowledge health, blockchain integrity, and safety mechanisms to produce
19
+ actionable reports and recommendations.
20
+
21
+ ## Tools
22
+
23
+ ### introspection_check
24
+
25
+ Full self-inspection combining all domains. Returns a consolidated report with
26
+ recommendations.
27
+
28
+ ```
29
+ introspection_check() # All domains, markdown format
30
+ introspection_check(format: "json") # JSON output
31
+ introspection_check(domains: ["health"]) # Health only
32
+ introspection_check(domains: ["blockchain"]) # Blockchain only
33
+ ```
34
+
35
+ ### introspection_health
36
+
37
+ Focused L1 knowledge health scoring.
38
+
39
+ ```
40
+ introspection_health() # All entries
41
+ introspection_health(name: "my_knowledge") # Single entry
42
+ introspection_health(below_threshold: 0.5) # Only unhealthy entries
43
+ introspection_health(sort_by: "name") # Sort alphabetically
44
+ ```
45
+
46
+ ### introspection_safety
47
+
48
+ Safety mechanism visibility across all layers.
49
+
50
+ ```
51
+ introspection_safety() # Returns 4-layer safety report
52
+ ```
53
+
54
+ ## Health Scoring
55
+
56
+ Health scores range from 0.0 (unhealthy) to 1.0 (healthy).
57
+
58
+ When Synoptis TrustScorer is available:
59
+ - **Trust score** (70% weight): Based on attestation count and quality
60
+ - **Staleness score** (30% weight): Based on file modification time
61
+
62
+ When TrustScorer is not available:
63
+ - **Staleness score only** (100%): Linear decay over configurable threshold
64
+
65
+ ### Staleness Threshold
66
+
67
+ Default: 180 days. Configure in `config/introspection.yml`:
68
+
69
+ ```yaml
70
+ introspection:
71
+ health:
72
+ staleness_days: 90 # More aggressive freshness requirement
73
+ ```
74
+
75
+ ## Safety Layers
76
+
77
+ The safety report covers four layers:
78
+
79
+ 1. **L0 Approval Workflow**: Whether Kairos DSL approval_workflow skill is loaded
80
+ 2. **Runtime RBAC**: Registered Safety policies (can_modify_l0, etc.)
81
+ 3. **Agent Safety Gates**: Autonomous mode limits from agent.yml
82
+ 4. **Blockchain Recording**: Chain integrity and block count
83
+
84
+ ## Recommendations
85
+
86
+ The full check generates prioritized recommendations:
87
+
88
+ - **CRITICAL**: Blockchain integrity failure
89
+ - **HIGH**: Missing L0 approval workflow
90
+ - **MEDIUM**: Low health scores on individual knowledge entries