kairos-chain 3.6.2 → 3.8.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +65 -0
- data/lib/kairos_mcp/version.rb +1 -1
- data/templates/skillsets/agent/config/agent.yml +7 -0
- data/templates/skillsets/agent/lib/agent/cognitive_loop.rb +69 -9
- data/templates/skillsets/agent/lib/agent/mandate_adapter.rb +1 -1
- data/templates/skillsets/agent/lib/agent/session.rb +11 -4
- data/templates/skillsets/agent/tools/agent_start.rb +83 -11
- data/templates/skillsets/agent/tools/agent_step.rb +418 -68
- data/templates/skillsets/agent/tools/agent_stop.rb +1 -1
- data/templates/skillsets/autonomos/lib/autonomos/mandate.rb +36 -7
- data/templates/skillsets/dream/config/dream.yml +26 -0
- data/templates/skillsets/dream/knowledge/dream_trigger_policy/dream_trigger_policy.md +96 -0
- data/templates/skillsets/dream/lib/dream/archiver.rb +417 -0
- data/templates/skillsets/dream/lib/dream/proposer.rb +93 -0
- data/templates/skillsets/dream/lib/dream/scanner.rb +334 -0
- data/templates/skillsets/dream/lib/dream.rb +78 -0
- data/templates/skillsets/dream/skillset.json +18 -0
- data/templates/skillsets/dream/tools/dream_archive.rb +243 -0
- data/templates/skillsets/dream/tools/dream_propose.rb +203 -0
- data/templates/skillsets/dream/tools/dream_recall.rb +214 -0
- data/templates/skillsets/dream/tools/dream_scan.rb +177 -0
- metadata +14 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b7d045865d5f35b2ceba8abf858cc33219f88690feaf5591183b20154cda36e4
|
|
4
|
+
data.tar.gz: f333e43070e2268da45be99949d3c85217bfcf2a5d7d691e707fbe26f5b96796
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4951cb6216a2f85212c12882c02337fc86e2a8abd906bcfd1322ca7d23760226692608a92626ea807c7a67b44c3f94b6eadd96ef88a6125a551dcca30a951954
|
|
7
|
+
data.tar.gz: ccf714bf15d9b54219373f9a83560943fcff476cb700a459cf5d62b5c02c02ce87c0986bf81d8b5b3d7c9708b1eb58c94c5e1ecb96c5c5909ec9bf6f562c1d78
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,71 @@ 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.8.0] - 2026-03-30
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **Agent Autonomous Mode** — Multi-cycle OODA loop execution
|
|
12
|
+
- `agent_start(autonomous: true)`: Enable autonomous mode. Session starts at
|
|
13
|
+
`[observed]` as before; autonomous loop begins on `agent_step(approve)`.
|
|
14
|
+
- 8 safety gates: mandate termination, goal drift detection, wall-clock timeout
|
|
15
|
+
(300s), aggregate LLM budget (60 calls), risk budget, post-ACT termination,
|
|
16
|
+
confidence-based early exit, checkpoint pause.
|
|
17
|
+
- New session states: `autonomous_cycling`, `paused_risk`, `paused_error`
|
|
18
|
+
- Resume handlers: `approve` at `paused_risk` re-checks risk and resumes ACT;
|
|
19
|
+
`approve`/`skip` at `paused_error` skips failed cycle and continues.
|
|
20
|
+
- `agent.yml` autonomous config: `max_total_llm_calls`, `max_duration_seconds`,
|
|
21
|
+
`min_cycles_before_exit`, `confidence_exit_threshold`.
|
|
22
|
+
- Design: 2 rounds x 3 LLMs. Implementation: 1 round x 3 LLMs. All HIGH fixed.
|
|
23
|
+
|
|
24
|
+
- **Mandate locking** — `Mandate.with_lock` for single-writer batch execution
|
|
25
|
+
- File-based exclusive lock (`flock`), non-blocking with `LockError`
|
|
26
|
+
- Atomic save via tmp+rename pattern
|
|
27
|
+
- `Mandate.reload` helper for in-lock refresh
|
|
28
|
+
|
|
29
|
+
- **CognitiveLoop call tracking** — `total_calls` attribute for aggregate
|
|
30
|
+
LLM budget enforcement across autonomous cycles
|
|
31
|
+
|
|
32
|
+
- **Goal drift detection** — Content-based hash (not name-only) at mandate
|
|
33
|
+
creation; per-cycle drift check with fail-open semantics
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- `run_orient_decide` / `run_act_reflect` refactored into `_internal` (Hash return)
|
|
38
|
+
+ wrapper (text_content) pattern. Manual mode behavior unchanged.
|
|
39
|
+
- Manual risk pause now sets session to `paused_risk` (was `terminated`),
|
|
40
|
+
enabling resume via `agent_step(approve)`.
|
|
41
|
+
- `MandateAdapter.to_mandate_proposal` uses `dig` for nil safety.
|
|
42
|
+
|
|
43
|
+
## [3.7.0] - 2026-03-29
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
|
|
47
|
+
- **Dream SkillSet** — L2 memory consolidation and lifecycle management
|
|
48
|
+
- `dream_scan`: Pattern detection across L2 sessions — tag co-occurrence,
|
|
49
|
+
L2/L1 staleness (mtime-based), name overlap (Jaccard), archive candidate detection.
|
|
50
|
+
Filters soft-archived stubs from promotion candidates.
|
|
51
|
+
- `dream_archive`: L2 soft-archive — gzip compress .md, move full context directory
|
|
52
|
+
to archive, leave searchable stub (tags + summary). SHA256 verified inline.
|
|
53
|
+
Per-context flock. `dry_run: true` by default.
|
|
54
|
+
- `dream_recall`: Restore archived contexts with SHA256 integrity check.
|
|
55
|
+
Preview and verify-only modes (read-only, no permission required).
|
|
56
|
+
- `dream_propose`: Package L1 promotion proposals with ready-to-execute
|
|
57
|
+
`knowledge_update` commands. Optional Persona Assembly templates.
|
|
58
|
+
- L2 lifecycle model: Active → Candidate → Soft-Archived → Recalled
|
|
59
|
+
- `dream_trigger_policy` L1 knowledge for Kairotic trigger heuristics
|
|
60
|
+
- 119 tests across 27 test sections
|
|
61
|
+
|
|
62
|
+
- **Agent SkillSet — permission advisory**
|
|
63
|
+
- `agent_start` now includes `permission_advisory` in response,
|
|
64
|
+
recommending users configure permission mode (Normal / Auto-allow / Auto-accept)
|
|
65
|
+
for smoother autonomous operation
|
|
66
|
+
|
|
67
|
+
### Fixed
|
|
68
|
+
|
|
69
|
+
- **L1 staleness detection** — use tag overlap and name token matching instead of
|
|
70
|
+
exact L1 name-in-L2-tags check. Reduces false positives from 48/48 to 7/48.
|
|
71
|
+
|
|
7
72
|
## [3.6.0] - 2026-03-28
|
|
8
73
|
|
|
9
74
|
### Added
|
data/lib/kairos_mcp/version.rb
CHANGED
|
@@ -45,5 +45,12 @@ tool_blacklist:
|
|
|
45
45
|
orient_tools_extra: []
|
|
46
46
|
# - document_status # uncomment to enable draft checking during ORIENT
|
|
47
47
|
|
|
48
|
+
# Autonomous mode limits
|
|
49
|
+
autonomous:
|
|
50
|
+
max_total_llm_calls: 60 # across all cycles in one batch
|
|
51
|
+
max_duration_seconds: 300 # wall-clock timeout per batch (5 min)
|
|
52
|
+
min_cycles_before_exit: 2 # confidence exit disabled for first N cycles
|
|
53
|
+
confidence_exit_threshold: 0.9 # minimum confidence for early exit
|
|
54
|
+
|
|
48
55
|
# Audit
|
|
49
56
|
audit_level: summary
|
|
@@ -7,11 +7,17 @@ module KairosMcp
|
|
|
7
7
|
module SkillSets
|
|
8
8
|
module Agent
|
|
9
9
|
class CognitiveLoop
|
|
10
|
+
FALLBACK_PROVIDERS = %w[claude_code].freeze
|
|
11
|
+
|
|
12
|
+
attr_reader :total_calls
|
|
13
|
+
|
|
10
14
|
# @param caller_tool [BaseTool] the agent_step tool instance (has invoke_tool)
|
|
11
15
|
# @param session [Session] current agent session
|
|
12
16
|
def initialize(caller_tool, session)
|
|
13
17
|
@caller = caller_tool
|
|
14
18
|
@session = session
|
|
19
|
+
@fallback_attempted = false
|
|
20
|
+
@total_calls = 0
|
|
15
21
|
end
|
|
16
22
|
|
|
17
23
|
# Generic phase runner for ORIENT, REFLECT, and DECIDE_PREP.
|
|
@@ -29,14 +35,13 @@ module KairosMcp
|
|
|
29
35
|
'stop_reason' => 'budget' }
|
|
30
36
|
end
|
|
31
37
|
|
|
32
|
-
|
|
38
|
+
@total_calls += 1
|
|
39
|
+
parsed = call_llm_with_fallback(
|
|
33
40
|
'messages' => messages,
|
|
34
41
|
'system' => system_prompt,
|
|
35
42
|
'tools' => available_tools,
|
|
36
43
|
'invocation_context_json' => @session.invocation_context.to_json
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
parsed = JSON.parse(llm_result.map { |b| b[:text] || b['text'] }.compact.join)
|
|
44
|
+
)
|
|
40
45
|
return { 'error' => parsed['error'] } if parsed['status'] == 'error'
|
|
41
46
|
|
|
42
47
|
response = parsed['response']
|
|
@@ -79,14 +84,14 @@ module KairosMcp
|
|
|
79
84
|
return { 'error' => 'Budget exceeded for DECIDE phase' }
|
|
80
85
|
end
|
|
81
86
|
|
|
82
|
-
|
|
87
|
+
@total_calls += 1
|
|
88
|
+
|
|
89
|
+
parsed = call_llm_with_fallback(
|
|
83
90
|
'messages' => messages,
|
|
84
91
|
'system' => system_prompt,
|
|
85
92
|
'tools' => [],
|
|
86
93
|
'invocation_context_json' => @session.invocation_context.to_json
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
parsed = JSON.parse(llm_result.map { |b| b[:text] || b['text'] }.compact.join)
|
|
94
|
+
)
|
|
90
95
|
return { 'error' => parsed['error'] } if parsed['status'] == 'error'
|
|
91
96
|
|
|
92
97
|
response = parsed['response']
|
|
@@ -110,7 +115,7 @@ module KairosMcp
|
|
|
110
115
|
begin
|
|
111
116
|
decision = JSON.parse(json_str)
|
|
112
117
|
task_json_str = JSON.generate(decision['task_json'])
|
|
113
|
-
Autoexec::TaskDsl.from_json(task_json_str)
|
|
118
|
+
::Autoexec::TaskDsl.from_json(task_json_str)
|
|
114
119
|
return { 'decision_payload' => decision }
|
|
115
120
|
rescue => e
|
|
116
121
|
if attempts >= max_repair
|
|
@@ -127,6 +132,61 @@ module KairosMcp
|
|
|
127
132
|
|
|
128
133
|
private
|
|
129
134
|
|
|
135
|
+
# Call llm_call with automatic provider fallback on auth errors.
|
|
136
|
+
# Tries the configured provider first. On auth_error, switches to
|
|
137
|
+
# fallback providers (claude_code) via llm_configure, then retries once.
|
|
138
|
+
def call_llm_with_fallback(arguments)
|
|
139
|
+
llm_result = @caller.invoke_tool('llm_call', arguments,
|
|
140
|
+
context: @session.invocation_context)
|
|
141
|
+
parsed = JSON.parse(llm_result.map { |b| b[:text] || b['text'] }.compact.join)
|
|
142
|
+
|
|
143
|
+
# If not an auth error, or already tried fallback, return as-is
|
|
144
|
+
error_info = parsed['error']
|
|
145
|
+
if !error_info || !error_info.is_a?(Hash) || error_info['type'] != 'auth_error' || @fallback_attempted
|
|
146
|
+
return parsed
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Attempt provider fallback
|
|
150
|
+
original_provider = error_info['provider'] || 'configured'
|
|
151
|
+
warn "[agent] Auth error from #{original_provider}, attempting provider fallback"
|
|
152
|
+
|
|
153
|
+
FALLBACK_PROVIDERS.each do |fallback|
|
|
154
|
+
@fallback_attempted = true
|
|
155
|
+
configure_result = try_configure_provider(fallback)
|
|
156
|
+
next unless configure_result
|
|
157
|
+
|
|
158
|
+
warn "[agent] Switched to provider: #{fallback}"
|
|
159
|
+
retry_result = @caller.invoke_tool('llm_call', arguments,
|
|
160
|
+
context: @session.invocation_context)
|
|
161
|
+
retry_parsed = JSON.parse(retry_result.map { |b| b[:text] || b['text'] }.compact.join)
|
|
162
|
+
|
|
163
|
+
# If this provider also fails with auth_error, try next
|
|
164
|
+
retry_error = retry_parsed['error']
|
|
165
|
+
if retry_error.is_a?(Hash) && retry_error['type'] == 'auth_error'
|
|
166
|
+
warn "[agent] Fallback provider #{fallback} also failed: #{retry_error['message']}"
|
|
167
|
+
next
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
return retry_parsed
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# All fallbacks exhausted — return original error with fallback info
|
|
174
|
+
parsed['error']['fallback_attempted'] = true
|
|
175
|
+
parsed['error']['fallback_exhausted'] = true
|
|
176
|
+
parsed
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def try_configure_provider(provider)
|
|
180
|
+
args = { 'provider' => provider }
|
|
181
|
+
result = @caller.invoke_tool('llm_configure', args,
|
|
182
|
+
context: @session.invocation_context)
|
|
183
|
+
parsed = JSON.parse(result.map { |b| b[:text] || b['text'] }.compact.join)
|
|
184
|
+
parsed['status'] == 'ok'
|
|
185
|
+
rescue StandardError => e
|
|
186
|
+
warn "[agent] Failed to configure provider #{provider}: #{e.message}"
|
|
187
|
+
false
|
|
188
|
+
end
|
|
189
|
+
|
|
130
190
|
def extract_json(content)
|
|
131
191
|
JSON.parse(content)
|
|
132
192
|
content
|
|
@@ -11,7 +11,7 @@ module KairosMcp
|
|
|
11
11
|
def self.to_mandate_proposal(decision_payload)
|
|
12
12
|
{
|
|
13
13
|
autoexec_task: {
|
|
14
|
-
steps: (decision_payload
|
|
14
|
+
steps: (decision_payload.dig('task_json', 'steps') || []).map { |s|
|
|
15
15
|
{ risk: s['risk'] || 'low', tool_name: s['tool_name'] }
|
|
16
16
|
}
|
|
17
17
|
},
|
|
@@ -8,19 +8,25 @@ module KairosMcp
|
|
|
8
8
|
module Agent
|
|
9
9
|
class Session
|
|
10
10
|
attr_reader :session_id, :mandate_id, :goal_name, :invocation_context,
|
|
11
|
-
:state, :cycle_number, :config
|
|
11
|
+
:state, :cycle_number, :config, :autonomous
|
|
12
12
|
|
|
13
|
-
def initialize(session_id:, mandate_id:, goal_name:, invocation_context:, config
|
|
13
|
+
def initialize(session_id:, mandate_id:, goal_name:, invocation_context:, config:,
|
|
14
|
+
autonomous: false)
|
|
14
15
|
@session_id = session_id
|
|
15
16
|
@mandate_id = mandate_id
|
|
16
17
|
@goal_name = goal_name
|
|
17
18
|
@invocation_context = invocation_context
|
|
18
19
|
@config = config
|
|
20
|
+
@autonomous = autonomous
|
|
19
21
|
@state = 'created'
|
|
20
22
|
@cycle_number = 0
|
|
21
23
|
@snapshots = []
|
|
22
24
|
end
|
|
23
25
|
|
|
26
|
+
def autonomous?
|
|
27
|
+
@autonomous == true
|
|
28
|
+
end
|
|
29
|
+
|
|
24
30
|
# Per-phase budget configuration.
|
|
25
31
|
# Returns defaults if the phase is not configured.
|
|
26
32
|
def phase_config(phase_name)
|
|
@@ -109,7 +115,7 @@ module KairosMcp
|
|
|
109
115
|
data = {
|
|
110
116
|
session_id: @session_id, mandate_id: @mandate_id,
|
|
111
117
|
goal_name: @goal_name, state: @state, cycle_number: @cycle_number,
|
|
112
|
-
config: @config,
|
|
118
|
+
config: @config, autonomous: @autonomous,
|
|
113
119
|
invocation_context: @invocation_context.to_h
|
|
114
120
|
}
|
|
115
121
|
File.write(state_path, JSON.pretty_generate(data))
|
|
@@ -133,7 +139,8 @@ module KairosMcp
|
|
|
133
139
|
mandate_id: data['mandate_id'],
|
|
134
140
|
goal_name: data['goal_name'],
|
|
135
141
|
invocation_context: ctx,
|
|
136
|
-
config: data['config']
|
|
142
|
+
config: data['config'],
|
|
143
|
+
autonomous: data['autonomous'] || false
|
|
137
144
|
)
|
|
138
145
|
session.instance_variable_set(:@state, data['state'])
|
|
139
146
|
session.instance_variable_set(:@cycle_number, data['cycle_number'] || 0)
|
|
@@ -52,6 +52,12 @@ module KairosMcp
|
|
|
52
52
|
risk_budget: {
|
|
53
53
|
type: 'string',
|
|
54
54
|
description: 'Maximum risk level: "low" or "medium" (default: "low")'
|
|
55
|
+
},
|
|
56
|
+
autonomous: {
|
|
57
|
+
type: 'boolean',
|
|
58
|
+
description: 'Enable autonomous mode (default: false). ' \
|
|
59
|
+
'Session starts at [observed] regardless. ' \
|
|
60
|
+
'Autonomous loop begins on first agent_step(approve).'
|
|
55
61
|
}
|
|
56
62
|
},
|
|
57
63
|
required: ['goal_name']
|
|
@@ -63,10 +69,14 @@ module KairosMcp
|
|
|
63
69
|
max_cycles = arguments['max_cycles'] || 3
|
|
64
70
|
checkpoint_every = arguments['checkpoint_every'] || 1
|
|
65
71
|
risk_budget = arguments['risk_budget'] || 'low'
|
|
72
|
+
autonomous = arguments['autonomous'] == true
|
|
73
|
+
|
|
74
|
+
# Pre-resolve goal content for content-based drift detection hash
|
|
75
|
+
pre_obs = run_observe(goal_name)
|
|
76
|
+
goal_content_for_hash = pre_obs['goal_content'] || goal_name
|
|
77
|
+
goal_hash = Digest::SHA256.hexdigest(goal_content_for_hash)[0..15]
|
|
66
78
|
|
|
67
|
-
|
|
68
|
-
goal_hash = Digest::SHA256.hexdigest(goal_name)[0..15]
|
|
69
|
-
mandate = Autonomos::Mandate.create(
|
|
79
|
+
mandate = ::Autonomos::Mandate.create(
|
|
70
80
|
goal_name: goal_name,
|
|
71
81
|
goal_hash: goal_hash,
|
|
72
82
|
max_cycles: max_cycles,
|
|
@@ -85,23 +95,30 @@ module KairosMcp
|
|
|
85
95
|
mandate_id: mandate[:mandate_id],
|
|
86
96
|
goal_name: goal_name,
|
|
87
97
|
invocation_context: ctx,
|
|
88
|
-
config: config
|
|
98
|
+
config: config,
|
|
99
|
+
autonomous: autonomous
|
|
89
100
|
)
|
|
90
101
|
|
|
91
|
-
# OBSERVE (
|
|
92
|
-
observation =
|
|
102
|
+
# OBSERVE: reuse pre-resolved observation (avoids duplicate L2/L1 lookups)
|
|
103
|
+
observation = pre_obs
|
|
93
104
|
|
|
94
105
|
session.save_observation(observation)
|
|
95
106
|
session.update_state('observed')
|
|
96
107
|
session.save
|
|
97
108
|
|
|
98
|
-
|
|
109
|
+
result = {
|
|
99
110
|
'status' => 'ok',
|
|
100
111
|
'session_id' => session_id,
|
|
101
112
|
'mandate_id' => mandate[:mandate_id],
|
|
102
113
|
'state' => 'observed',
|
|
114
|
+
'autonomous' => autonomous,
|
|
103
115
|
'observation' => observation
|
|
104
|
-
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
# Advisory: suggest permission mode for autonomous operation
|
|
119
|
+
result['permission_advisory'] = permission_advisory_message
|
|
120
|
+
|
|
121
|
+
text_content(JSON.generate(result))
|
|
105
122
|
rescue ArgumentError => e
|
|
106
123
|
text_content(JSON.generate({ 'status' => 'error', 'error' => e.message }))
|
|
107
124
|
rescue StandardError => e
|
|
@@ -126,14 +143,30 @@ module KairosMcp
|
|
|
126
143
|
)
|
|
127
144
|
end
|
|
128
145
|
|
|
146
|
+
def permission_advisory_message
|
|
147
|
+
<<~MSG.strip
|
|
148
|
+
This agent session will execute tools autonomously.
|
|
149
|
+
For smoother operation, consider adjusting your permission mode:
|
|
150
|
+
|
|
151
|
+
1. Normal (default) — ask for each command. Safest, but interrupts flow.
|
|
152
|
+
2. Auto-allow — pre-approved commands only. Balanced.
|
|
153
|
+
Configure in .claude/settings.local.json permissions.allow array.
|
|
154
|
+
3. Auto-accept — allow everything. Fastest for trusted tasks.
|
|
155
|
+
Run /permissions and select auto mode, or start with --dangerously-skip-permissions.
|
|
156
|
+
|
|
157
|
+
Recommendation: For implementation + multi-LLM review workflows, auto-allow
|
|
158
|
+
with ruby/codex/agent commands pre-approved provides the best balance.
|
|
159
|
+
MSG
|
|
160
|
+
end
|
|
161
|
+
|
|
129
162
|
def run_observe(goal_name)
|
|
130
163
|
# Gather observation data without LLM
|
|
131
164
|
observation = { 'goal_name' => goal_name, 'timestamp' => Time.now.iso8601 }
|
|
132
165
|
|
|
133
|
-
#
|
|
134
|
-
if defined?(Autonomos::Ooda)
|
|
166
|
+
# Load environment data via Autonomos::Ooda
|
|
167
|
+
if defined?(::Autonomos::Ooda)
|
|
135
168
|
begin
|
|
136
|
-
helper = Class.new { include Autonomos::Ooda }.new
|
|
169
|
+
helper = Class.new { include ::Autonomos::Ooda }.new
|
|
137
170
|
ooda_obs = helper.observe(goal_name)
|
|
138
171
|
observation.merge!(ooda_obs.transform_keys(&:to_s)) if ooda_obs.is_a?(Hash)
|
|
139
172
|
rescue StandardError => e
|
|
@@ -141,8 +174,47 @@ module KairosMcp
|
|
|
141
174
|
end
|
|
142
175
|
end
|
|
143
176
|
|
|
177
|
+
# Load goal content from L2/L1 so Orient has context to analyze
|
|
178
|
+
begin
|
|
179
|
+
if defined?(::Autonomos::Ooda)
|
|
180
|
+
helper = Class.new { include ::Autonomos::Ooda }.new
|
|
181
|
+
goal = helper.load_goal(goal_name)
|
|
182
|
+
else
|
|
183
|
+
goal = load_goal_fallback(goal_name)
|
|
184
|
+
end
|
|
185
|
+
if goal && goal[:found]
|
|
186
|
+
observation['goal_content'] = goal[:content]
|
|
187
|
+
observation['goal_source'] = goal[:source].to_s
|
|
188
|
+
end
|
|
189
|
+
rescue StandardError => e
|
|
190
|
+
observation['goal_load_error'] = e.message
|
|
191
|
+
end
|
|
192
|
+
|
|
144
193
|
observation
|
|
145
194
|
end
|
|
195
|
+
|
|
196
|
+
def load_goal_fallback(goal_name)
|
|
197
|
+
# Direct L2/L1 lookup when Autonomos::Ooda is unavailable
|
|
198
|
+
if defined?(KairosMcp::ContextManager)
|
|
199
|
+
ctx_mgr = KairosMcp::ContextManager.new
|
|
200
|
+
ctx_mgr.list_sessions.each do |session|
|
|
201
|
+
entry = ctx_mgr.get_context(session[:session_id], goal_name)
|
|
202
|
+
if entry && entry.respond_to?(:content) && entry.content && !entry.content.strip.empty?
|
|
203
|
+
return { content: entry.content, found: true, source: :l2 }
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
if defined?(KairosMcp::KnowledgeProvider)
|
|
208
|
+
provider = KairosMcp::KnowledgeProvider.new(nil)
|
|
209
|
+
result = provider.get(goal_name)
|
|
210
|
+
if result && result[:content] && !result[:content].strip.empty?
|
|
211
|
+
return { content: result[:content], found: true, source: :l1 }
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
{ content: nil, found: false }
|
|
215
|
+
rescue StandardError
|
|
216
|
+
{ content: nil, found: false }
|
|
217
|
+
end
|
|
146
218
|
end
|
|
147
219
|
end
|
|
148
220
|
end
|