Class: KairosMcp::InvocationContext
- Inherits:
-
Object
- Object
- KairosMcp::InvocationContext
- Defined in:
- lib/kairos_mcp/invocation_context.rb
Overview
Tracks invocation chain metadata for internal tool-to-tool calls. Carries depth, caller, mandate, and policy (whitelist/blacklist) through the entire invocation chain. Created by BaseTool#invoke_tool, threaded through ToolRegistry#call_tool.
Defined Under Namespace
Classes: DepthExceededError, PolicyDeniedError
Constant Summary collapse
- MAX_DEPTH =
10
Instance Attribute Summary collapse
-
#blacklist ⇒ Object
readonly
Returns the value of attribute blacklist.
-
#caller_tool ⇒ Object
readonly
Returns the value of attribute caller_tool.
-
#depth ⇒ Object
readonly
Returns the value of attribute depth.
-
#idem_key ⇒ Object
readonly
Returns the value of attribute idem_key.
-
#mandate_id ⇒ Object
readonly
Returns the value of attribute mandate_id.
-
#mode ⇒ Object
readonly
Returns the value of attribute mode.
-
#root_invocation_id ⇒ Object
readonly
Returns the value of attribute root_invocation_id.
-
#token_budget ⇒ Object
readonly
Returns the value of attribute token_budget.
-
#whitelist ⇒ Object
readonly
Returns the value of attribute whitelist.
Class Method Summary collapse
-
.from_h(hash) ⇒ Object
Reconstruct policy from a Hash (e.g., parsed from tool arguments).
- .from_json(json_string) ⇒ Object
Instance Method Summary collapse
-
#allowed?(tool_name) ⇒ Boolean
Check if a tool is allowed by whitelist/blacklist policy.
-
#child(caller_tool:) ⇒ Object
Create a child context for a nested invocation.
-
#derive(blacklist_remove: [], blacklist_add: []) ⇒ Object
Derive a new context with modified blacklist, preserving all other fields.
-
#derive_for_phase(whitelist: nil, blacklist_add: []) ⇒ Object
Derive a phase-specific context with an optional whitelist and additional blacklist.
-
#initialize(depth: 0, caller_tool: nil, mandate_id: nil, token_budget: nil, whitelist: nil, blacklist: nil, root_invocation_id: nil, mode: nil, idem_key: nil) ⇒ InvocationContext
constructor
A new instance of InvocationContext.
-
#to_h ⇒ Object
Serialize to a plain Hash for passing through tool arguments.
- #to_json(*args) ⇒ Object
Constructor Details
#initialize(depth: 0, caller_tool: nil, mandate_id: nil, token_budget: nil, whitelist: nil, blacklist: nil, root_invocation_id: nil, mode: nil, idem_key: nil) ⇒ InvocationContext
Returns a new instance of InvocationContext.
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# File 'lib/kairos_mcp/invocation_context.rb', line 17 def initialize(depth: 0, caller_tool: nil, mandate_id: nil, token_budget: nil, whitelist: nil, blacklist: nil, root_invocation_id: nil, mode: nil, idem_key: nil) @depth = depth @caller_tool = caller_tool @mandate_id = mandate_id @token_budget = token_budget @whitelist = whitelist @blacklist = blacklist @root_invocation_id = root_invocation_id || SecureRandom.hex(8) # mode: :direct (default), :daemon, :agent, ... — identifies the # top-level runner of the invocation chain. Kept as a Symbol in # Ruby space; serialized as a String in to_h/from_h. @mode = mode.nil? ? nil : mode.to_sym # idem_key: optional client-supplied key that lets the daemon # deduplicate retries of the same logical command. @idem_key = idem_key end |
Instance Attribute Details
#blacklist ⇒ Object (readonly)
Returns the value of attribute blacklist.
13 14 15 |
# File 'lib/kairos_mcp/invocation_context.rb', line 13 def blacklist @blacklist end |
#caller_tool ⇒ Object (readonly)
Returns the value of attribute caller_tool.
13 14 15 |
# File 'lib/kairos_mcp/invocation_context.rb', line 13 def caller_tool @caller_tool end |
#depth ⇒ Object (readonly)
Returns the value of attribute depth.
13 14 15 |
# File 'lib/kairos_mcp/invocation_context.rb', line 13 def depth @depth end |
#idem_key ⇒ Object (readonly)
Returns the value of attribute idem_key.
13 14 15 |
# File 'lib/kairos_mcp/invocation_context.rb', line 13 def idem_key @idem_key end |
#mandate_id ⇒ Object (readonly)
Returns the value of attribute mandate_id.
13 14 15 |
# File 'lib/kairos_mcp/invocation_context.rb', line 13 def mandate_id @mandate_id end |
#mode ⇒ Object (readonly)
Returns the value of attribute mode.
13 14 15 |
# File 'lib/kairos_mcp/invocation_context.rb', line 13 def mode @mode end |
#root_invocation_id ⇒ Object (readonly)
Returns the value of attribute root_invocation_id.
13 14 15 |
# File 'lib/kairos_mcp/invocation_context.rb', line 13 def root_invocation_id @root_invocation_id end |
#token_budget ⇒ Object (readonly)
Returns the value of attribute token_budget.
13 14 15 |
# File 'lib/kairos_mcp/invocation_context.rb', line 13 def token_budget @token_budget end |
#whitelist ⇒ Object (readonly)
Returns the value of attribute whitelist.
13 14 15 |
# File 'lib/kairos_mcp/invocation_context.rb', line 13 def whitelist @whitelist end |
Class Method Details
.from_h(hash) ⇒ Object
Reconstruct policy from a Hash (e.g., parsed from tool arguments). Only restores policy fields — depth and caller are not transferred.
117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/kairos_mcp/invocation_context.rb', line 117 def self.from_h(hash) return nil if hash.nil? new( whitelist: hash['whitelist'], blacklist: hash['blacklist'], mandate_id: hash['mandate_id'], token_budget: hash['token_budget'], mode: hash['mode'], idem_key: hash['idem_key'] ) end |
.from_json(json_string) ⇒ Object
130 131 132 133 |
# File 'lib/kairos_mcp/invocation_context.rb', line 130 def self.from_json(json_string) require 'json' from_h(JSON.parse(json_string)) end |
Instance Method Details
#allowed?(tool_name) ⇒ Boolean
Check if a tool is allowed by whitelist/blacklist policy. Blacklist is checked first (deny wins). Both use fnmatch patterns. For namespaced tools (e.g., “peer1/agent_start”), also checks the bare name (“agent_start”) to prevent blacklist bypass via remote proxy tool namespace prefix.
140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/kairos_mcp/invocation_context.rb', line 140 def allowed?(tool_name) names = [tool_name] names << tool_name.split('/').last if tool_name.include?('/') if @blacklist return false if names.any? { |n| @blacklist.any? { |pat| File.fnmatch(pat, n) } } end if @whitelist return names.any? { |n| @whitelist.any? { |pat| File.fnmatch(pat, n) } } end true end |
#child(caller_tool:) ⇒ Object
Create a child context for a nested invocation. Inherits all policy from the parent; increments depth.
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# File 'lib/kairos_mcp/invocation_context.rb', line 38 def child(caller_tool:) raise DepthExceededError, "Max invocation depth (#{MAX_DEPTH}) exceeded" if @depth >= MAX_DEPTH self.class.new( depth: @depth + 1, caller_tool: caller_tool, mandate_id: @mandate_id, token_budget: @token_budget, whitelist: @whitelist&.dup, blacklist: @blacklist&.dup, root_invocation_id: @root_invocation_id, mode: @mode, idem_key: @idem_key ) end |
#derive(blacklist_remove: [], blacklist_add: []) ⇒ Object
Derive a new context with modified blacklist, preserving all other fields. Used by agent ACT phase to selectively unblock autoexec tools. Does NOT increment depth — child() does that at invoke_tool time.
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/kairos_mcp/invocation_context.rb', line 79 def derive(blacklist_remove: [], blacklist_add: []) new_blacklist = Array(@blacklist).dup blacklist_remove.each { |pat| new_blacklist.delete(pat) } blacklist_add.each { |pat| new_blacklist << pat unless new_blacklist.include?(pat) } self.class.new( depth: @depth, caller_tool: @caller_tool, mandate_id: @mandate_id, token_budget: @token_budget, whitelist: @whitelist&.dup, blacklist: new_blacklist.empty? ? nil : new_blacklist, root_invocation_id: @root_invocation_id, mode: @mode, idem_key: @idem_key ) end |
#derive_for_phase(whitelist: nil, blacklist_add: []) ⇒ Object
Derive a phase-specific context with an optional whitelist and additional blacklist. Used by agent OODA phases to restrict tool access per phase (e.g., OBSERVE, ORIENT). Invariant: effective set = whitelist ∩ complement(parent_blacklist ∪ blacklist_add) Parent deny always takes precedence — a phase whitelist cannot override a parent blacklist. Does NOT increment depth — child() does that at invoke_tool time.
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/kairos_mcp/invocation_context.rb', line 59 def derive_for_phase(whitelist: nil, blacklist_add: []) new_blacklist = Array(@blacklist).dup blacklist_add.each { |pat| new_blacklist << pat unless new_blacklist.include?(pat) } self.class.new( depth: @depth, caller_tool: @caller_tool, mandate_id: @mandate_id, token_budget: @token_budget, whitelist: whitelist ? whitelist.dup : @whitelist&.dup, blacklist: new_blacklist.empty? ? nil : new_blacklist, root_invocation_id: @root_invocation_id, mode: @mode, idem_key: @idem_key ) end |
#to_h ⇒ Object
Serialize to a plain Hash for passing through tool arguments. Only includes policy-relevant fields (whitelist, blacklist, mandate_id, token_budget).
99 100 101 102 103 104 105 106 107 108 |
# File 'lib/kairos_mcp/invocation_context.rb', line 99 def to_h { 'whitelist' => @whitelist, 'blacklist' => @blacklist, 'mandate_id' => @mandate_id, 'token_budget' => @token_budget, 'mode' => @mode&.to_s, 'idem_key' => @idem_key } end |
#to_json(*args) ⇒ Object
110 111 112 113 |
# File 'lib/kairos_mcp/invocation_context.rb', line 110 def to_json(*args) require 'json' to_h.to_json(*args) end |