Class: KairosMcp::ContextManager

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

Overview

ContextManager: Manages L2 (context layer) skills in Anthropic format

L2 characteristics:

  • Temporary context and hypotheses

  • No blockchain recording (free modification)

  • Session-based organization

Instance Method Summary collapse

Constructor Details

#initialize(context_dir = nil, user_context: nil) ⇒ ContextManager

Returns a new instance of ContextManager.



18
19
20
21
22
23
# File 'lib/kairos_mcp/context_manager.rb', line 18

def initialize(context_dir = nil, user_context: nil)
  context_dir ||= KairosMcp.context_dir(user_context: user_context)
  @context_dir = context_dir
  @user_context = user_context
  FileUtils.mkdir_p(@context_dir)
end

Instance Method Details

#create_subdir(session_id, name, subdir) ⇒ Hash

Create a subdirectory (scripts, assets, or references)

Parameters:

  • session_id (String)

    Session ID

  • name (String)

    Context name

  • subdir (String)

    Subdirectory name (‘scripts’, ‘assets’, or ‘references’)

Returns:

  • (Hash)

    Result with success status and path



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/kairos_mcp/context_manager.rb', line 174

def create_subdir(session_id, name, subdir)
  valid_subdirs = %w[scripts assets references]
  unless valid_subdirs.include?(subdir)
    return { success: false, error: "Invalid subdir. Must be one of: #{valid_subdirs.join(', ')}" }
  end

  context_dir = File.join(@context_dir, session_id, name)
  unless File.directory?(context_dir)
    return { success: false, error: "Context '#{name}' not found in session '#{session_id}'" }
  end

  subdir_path = File.join(context_dir, subdir)
  FileUtils.mkdir_p(subdir_path)
  { success: true, path: subdir_path }
end

#delete_context(session_id, name) ⇒ Hash

Delete a context

Parameters:

  • session_id (String)

    Session ID

  • name (String)

    Context name

Returns:

  • (Hash)

    Result with success status



141
142
143
144
145
146
147
148
149
150
# File 'lib/kairos_mcp/context_manager.rb', line 141

def delete_context(session_id, name)
  context_dir = File.join(@context_dir, session_id, name)
  
  unless File.directory?(context_dir)
    return { success: false, error: "Context '#{name}' not found in session '#{session_id}'" }
  end

  FileUtils.rm_rf(context_dir)
  { success: true, deleted: name }
end

#delete_session(session_id) ⇒ Hash

Delete an entire session

Parameters:

  • session_id (String)

    Session ID

Returns:

  • (Hash)

    Result with success status



156
157
158
159
160
161
162
163
164
165
166
# File 'lib/kairos_mcp/context_manager.rb', line 156

def delete_session(session_id)
  session_dir = File.join(@context_dir, session_id)
  
  unless File.directory?(session_dir)
    return { success: false, error: "Session '#{session_id}' not found" }
  end

  contexts_count = context_dirs(session_dir).size
  FileUtils.rm_rf(session_dir)
  { success: true, deleted: session_id, contexts_deleted: contexts_count }
end

#generate_session_id(prefix: 'session') ⇒ String

Generate a unique session ID

Parameters:

  • prefix (String) (defaults to: 'session')

    Optional prefix for the session ID

Returns:

  • (String)

    Generated session ID



194
195
196
197
198
# File 'lib/kairos_mcp/context_manager.rb', line 194

def generate_session_id(prefix: 'session')
  timestamp = Time.now.strftime('%Y%m%d_%H%M%S')
  random = SecureRandom.hex(4)
  "#{prefix}_#{timestamp}_#{random}"
end

#get_context(session_id, name) ⇒ AnthropicSkillParser::SkillEntry?

Get a specific context

Parameters:

  • session_id (String)

    Session ID

  • name (String)

    Context name

Returns:



68
69
70
71
72
73
# File 'lib/kairos_mcp/context_manager.rb', line 68

def get_context(session_id, name)
  context_dir = File.join(@context_dir, session_id, name)
  return nil unless File.directory?(context_dir)

  AnthropicSkillParser.parse(context_dir)
end

#list_assets(session_id, name) ⇒ Array<Hash>

List assets in a context

Parameters:

  • session_id (String)

    Session ID

  • name (String)

    Context name

Returns:

  • (Array<Hash>)

    List of asset info



217
218
219
220
221
222
# File 'lib/kairos_mcp/context_manager.rb', line 217

def list_assets(session_id, name)
  context = get_context(session_id, name)
  return [] unless context

  AnthropicSkillParser.list_assets(context)
end

#list_contexts_in_session(session_id) ⇒ Array<Hash>

List all contexts in a session

Parameters:

  • session_id (String)

    Session ID

Returns:

  • (Array<Hash>)

    List of context summaries



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/kairos_mcp/context_manager.rb', line 45

def list_contexts_in_session(session_id)
  session_dir = File.join(@context_dir, session_id)
  return [] unless File.directory?(session_dir)

  context_dirs(session_dir).map do |dir|
    skill = AnthropicSkillParser.parse(dir)
    next unless skill

    {
      name: skill.name,
      description: skill.description,
      has_scripts: skill.has_scripts?,
      has_assets: skill.has_assets?,
      has_references: skill.has_references?
    }
  end.compact
end

#list_scripts(session_id, name) ⇒ Array<Hash>

List scripts in a context

Parameters:

  • session_id (String)

    Session ID

  • name (String)

    Context name

Returns:

  • (Array<Hash>)

    List of script info



205
206
207
208
209
210
# File 'lib/kairos_mcp/context_manager.rb', line 205

def list_scripts(session_id, name)
  context = get_context(session_id, name)
  return [] unless context

  AnthropicSkillParser.list_scripts(context)
end

#list_sessionsArray<Hash>

List all active sessions

Returns:

  • (Array<Hash>)

    List of session info



28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/kairos_mcp/context_manager.rb', line 28

def list_sessions
  session_dirs.map do |dir|
    session_id = File.basename(dir)
    contexts = list_contexts_in_session(session_id)
    {
      session_id: session_id,
      context_count: contexts.size,
      created_at: File.ctime(dir),
      modified_at: File.mtime(dir)
    }
  end.sort_by { |s| s[:modified_at] }.reverse
end

#save_context(session_id, name, content, create_subdirs: false) ⇒ Hash

Save a context (create or update)

Parameters:

  • session_id (String)

    Session ID

  • name (String)

    Context name

  • content (String)

    Full content including YAML frontmatter

  • create_subdirs (Boolean) (defaults to: false)

    Whether to create scripts/assets/references

Returns:

  • (Hash)

    Result with success status



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/kairos_mcp/context_manager.rb', line 82

def save_context(session_id, name, content, create_subdirs: false)
  validate_relations_in_content!(content)

  session_dir = File.join(@context_dir, session_id)
  FileUtils.mkdir_p(session_dir)

  context_dir = File.join(session_dir, name)

  if File.directory?(context_dir)
    # Update existing
    skill = AnthropicSkillParser.update(context_dir, content)
    { success: true, action: 'updated', context: skill.to_h }
  else
    # Create new
    skill = AnthropicSkillParser.create(session_dir, name, content, create_subdirs: create_subdirs)
    { success: true, action: 'created', context: skill.to_h }
  end
rescue ContextGraph::Error => e
  { success: false, error: "#{e.class.name.split('::').last}: #{e.message}" }
rescue StandardError => e
  { success: false, error: e.message }
end

#validate_relations_in_content!(content) ⇒ Object

Parse the incoming content’s frontmatter, and if it carries relations[] (Context Graph Phase 1), enforce the v2.1 §1.1 schema rules and path-containment guard. Pure validation — does not mutate content.

Raises:

  • ContextGraph::* on any violation



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
# File 'lib/kairos_mcp/context_manager.rb', line 110

def validate_relations_in_content!(content)
  return unless content.is_a?(String)

  m = content.match(/\A---\r?\n(.+?)\r?\n---\r?\n/m)
  return unless m

  begin
    front = YAML.safe_load(m[1], permitted_classes: [Symbol, Date, Time]) || {}
  rescue StandardError => e
    raise ContextGraph::InvalidFrontmatterError, "frontmatter parse failed: #{e.message}"
  end

  relations = front['relations'] || front[:relations]
  return if relations.nil?

  ContextGraph.validate_relations!(relations)

  # For each target, run the path-containment guard. PathEscape and
  # SymlinkRejected are hard fails on the write path. ENOENT (dangling)
  # is allowed (forward references are part of L2-evidential ontology).
  relations.each do |item|
    target = item['target'] || item[:target]
    ContextGraph.resolve_target(target, @context_dir)
  end
end