lex-agentic-self 0.1.9 → 0.1.11
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 +18 -0
- data/Gemfile +2 -0
- data/lex-agentic-self.gemspec +3 -3
- data/lib/legion/extensions/agentic/self/identity/runners/entra.rb +5 -2
- data/lib/legion/extensions/agentic/self/relationship_arc/client.rb +20 -0
- data/lib/legion/extensions/agentic/self/relationship_arc/helpers/arc_engine.rb +135 -0
- data/lib/legion/extensions/agentic/self/relationship_arc/helpers/constants.rb +39 -0
- data/lib/legion/extensions/agentic/self/relationship_arc/helpers/milestone.rb +42 -0
- data/lib/legion/extensions/agentic/self/relationship_arc/runners/relationship_arc.rb +80 -0
- data/lib/legion/extensions/agentic/self/relationship_arc/version.rb +13 -0
- data/lib/legion/extensions/agentic/self/relationship_arc.rb +19 -0
- data/lib/legion/extensions/agentic/self/self_talk/runners/self_talk.rb +43 -5
- data/lib/legion/extensions/agentic/self/version.rb +1 -1
- data/lib/legion/extensions/agentic/self.rb +1 -0
- data/spec/legion/extensions/agentic/self/identity/runners/entra_spec.rb +53 -0
- data/spec/legion/extensions/agentic/self/relationship_arc/helpers/arc_engine_spec.rb +165 -0
- data/spec/legion/extensions/agentic/self/relationship_arc/helpers/constants_spec.rb +31 -0
- data/spec/legion/extensions/agentic/self/relationship_arc/helpers/milestone_spec.rb +46 -0
- data/spec/legion/extensions/agentic/self/relationship_arc/runners/relationship_arc_spec.rb +64 -0
- data/spec/legion/extensions/agentic/self/self_talk/runners/self_talk_llm_spec.rb +2 -2
- data/spec/legion/extensions/agentic/self/self_talk/runners/self_talk_spec.rb +60 -0
- metadata +24 -13
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: aa196a32fb12a9051c899a77c904db1b019bcf20fa722522f4560aa06325c1c5
|
|
4
|
+
data.tar.gz: 21ea04fbbd1a0bb7ccbaf1baeb93c062c036818cecd316f907d82ba4607fc640
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2e5f6191d0093ad34c915a5368d71b1457f9774e393fff5f9d0424784ca38c250ce3c3ae3c3be6c0fc4c9b4a43ac8b2148bd3917b3ed37e102181aa2beae1e1a
|
|
7
|
+
data.tar.gz: 2bc178f3bfc1001ccc64af277a3e9fac685fea350964107883b6d001a4fe8da31fd3287b18e3a4b6838bd293798737c1c8eeb9fac3c77d44010be734e97279d0
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.11] - 2026-03-31
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- `ArcEngine#to_h` now returns the last computed `relationship_health` score instead of always nil; `relationship_health()` caches result in `@last_health`
|
|
7
|
+
- `ArcEngine#arc_state_hash` now includes `milestones_today` key (milestones with `created_at` matching today in local time)
|
|
8
|
+
- `SelfTalk::Runners::SelfTalk#stub_turn_content` replaced by `mechanical_turn_content` backed by `VOICE_BANK` — produces real, meaningful content for critic/advocate/explorer/pragmatist voices; unknown types fall back to `VOICE_BANK_GENERIC`
|
|
9
|
+
- `SelfTalk::Runners::SelfTalk#generate_summary_for_dialogue` replaces static "Dialogue concluded" fallback with `mechanical_summary` — includes turn count, voices, and dominant position
|
|
10
|
+
- `Identity::Runners::Entra#rotate_client_secret` now emits a `Legion::Logging.warn` when `rotation_enabled: true` but Graph API rotation is not yet implemented, and returns `action_required` with instructions instead of a silent error
|
|
11
|
+
|
|
12
|
+
## [0.1.10] - 2026-03-31
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- RelationshipArc sub-module for Phase C relational intelligence
|
|
16
|
+
- Constants: chapters, milestone types, health weights, chapter thresholds
|
|
17
|
+
- Milestone: typed data class with UUID, significance clamping, serialization
|
|
18
|
+
- ArcEngine: chapter progression, milestone tracking, relationship health, Apollo Local persistence
|
|
19
|
+
- RelationshipArc runner: record_milestone, update_arc, arc_stats with NarrativeIdentity episode stamping
|
|
20
|
+
|
|
3
21
|
## [0.1.9] - 2026-03-31
|
|
4
22
|
|
|
5
23
|
### Added
|
data/Gemfile
CHANGED
data/lex-agentic-self.gemspec
CHANGED
|
@@ -34,7 +34,7 @@ Gem::Specification.new do |spec|
|
|
|
34
34
|
|
|
35
35
|
spec.add_development_dependency 'faraday', '~> 2.0'
|
|
36
36
|
spec.add_development_dependency 'rspec', '~> 3.13'
|
|
37
|
-
spec.add_development_dependency 'rubocop'
|
|
38
|
-
spec.add_development_dependency 'rubocop-legion'
|
|
39
|
-
spec.add_development_dependency 'rubocop-rspec'
|
|
37
|
+
spec.add_development_dependency 'rubocop'
|
|
38
|
+
spec.add_development_dependency 'rubocop-legion'
|
|
39
|
+
spec.add_development_dependency 'rubocop-rspec'
|
|
40
40
|
end
|
|
@@ -293,8 +293,11 @@ module Legion
|
|
|
293
293
|
|
|
294
294
|
return { rotated: false, worker_id: worker_id, dry_run: true, would_rotate: true } if dry_run
|
|
295
295
|
|
|
296
|
-
# Graph API rotation
|
|
297
|
-
|
|
296
|
+
# Graph API rotation is blocked on Azure Application.ReadWrite.All permission.
|
|
297
|
+
# Emit a loud warning so callers are never silently misled.
|
|
298
|
+
Legion::Logging.warn "[identity:entra] Client secret rotation is enabled but Graph API rotation is not yet implemented. Worker: #{worker_id}"
|
|
299
|
+
{ rotated: false, worker_id: worker_id, error: 'graph_api_rotation_not_implemented',
|
|
300
|
+
action_required: 'Manual rotation needed — Graph API write permission not yet granted' }
|
|
298
301
|
end
|
|
299
302
|
|
|
300
303
|
def credential_refresh_cycle(**)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/agentic/self/relationship_arc/helpers/constants'
|
|
4
|
+
require 'legion/extensions/agentic/self/relationship_arc/helpers/milestone'
|
|
5
|
+
require 'legion/extensions/agentic/self/relationship_arc/helpers/arc_engine'
|
|
6
|
+
require 'legion/extensions/agentic/self/relationship_arc/runners/relationship_arc'
|
|
7
|
+
|
|
8
|
+
module Legion
|
|
9
|
+
module Extensions
|
|
10
|
+
module Agentic
|
|
11
|
+
module Self
|
|
12
|
+
module RelationshipArc
|
|
13
|
+
class Client
|
|
14
|
+
include Runners::RelationshipArc
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Agentic
|
|
6
|
+
module Self
|
|
7
|
+
module RelationshipArc
|
|
8
|
+
module Helpers
|
|
9
|
+
class ArcEngine
|
|
10
|
+
attr_reader :agent_id, :current_chapter, :milestones
|
|
11
|
+
|
|
12
|
+
BOND_STAGE_ORDER = %i[initial forming established deep].freeze
|
|
13
|
+
private_constant :BOND_STAGE_ORDER
|
|
14
|
+
|
|
15
|
+
def initialize(agent_id:)
|
|
16
|
+
@agent_id = agent_id
|
|
17
|
+
@current_chapter = :formative
|
|
18
|
+
@milestones = []
|
|
19
|
+
@dirty = false
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def add_milestone(type:, description:, significance:, **)
|
|
23
|
+
ms = Milestone.new(type: type, description: description, significance: significance)
|
|
24
|
+
@milestones << ms
|
|
25
|
+
@milestones.shift while @milestones.size > Constants::MAX_MILESTONES
|
|
26
|
+
@dirty = true
|
|
27
|
+
ms
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def update_chapter!(bond_stage: :initial, **)
|
|
31
|
+
new_chapter = derive_chapter(bond_stage)
|
|
32
|
+
return if Constants::CHAPTERS.index(new_chapter) <= Constants::CHAPTERS.index(@current_chapter)
|
|
33
|
+
|
|
34
|
+
@current_chapter = new_chapter
|
|
35
|
+
@dirty = true
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def relationship_health(attachment_strength: 0.0, reciprocity_balance: 0.5,
|
|
39
|
+
communication_consistency: 0.5, **)
|
|
40
|
+
w = Constants::HEALTH_WEIGHTS
|
|
41
|
+
score = (attachment_strength.to_f * w[:attachment_strength]) +
|
|
42
|
+
(reciprocity_balance.to_f * w[:reciprocity_balance]) +
|
|
43
|
+
(communication_consistency.to_f * w[:communication_consistency])
|
|
44
|
+
@last_health = score.clamp(0.0, 1.0)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def dirty?
|
|
48
|
+
@dirty
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def mark_clean!
|
|
52
|
+
@dirty = false
|
|
53
|
+
self
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def to_apollo_entries
|
|
57
|
+
tags = Constants::TAG_PREFIX.dup + [@agent_id]
|
|
58
|
+
tags << 'partner' if partner?(@agent_id)
|
|
59
|
+
[{ content: serialize(arc_state_hash), tags: tags }]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def from_apollo(store:)
|
|
63
|
+
result = store.query(text: 'relationship_arc', tags: Constants::TAG_PREFIX + [@agent_id])
|
|
64
|
+
return false unless result[:success] && result[:results]&.any?
|
|
65
|
+
|
|
66
|
+
parsed = deserialize(result[:results].first[:content])
|
|
67
|
+
return false unless parsed
|
|
68
|
+
|
|
69
|
+
@current_chapter = parsed[:current_chapter]&.to_sym || :formative
|
|
70
|
+
@milestones = (parsed[:milestones] || []).map { |mh| Milestone.from_h(mh.transform_keys(&:to_sym)) }
|
|
71
|
+
true
|
|
72
|
+
rescue StandardError => e
|
|
73
|
+
warn "[arc_engine] from_apollo error: #{e.message}"
|
|
74
|
+
false
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def to_h
|
|
78
|
+
{ agent_id: @agent_id, current_chapter: @current_chapter,
|
|
79
|
+
milestones: @milestones.map(&:to_h),
|
|
80
|
+
relationship_health: @last_health, milestone_count: @milestones.size }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def arc_state_hash
|
|
86
|
+
{ agent_id: @agent_id, current_chapter: @current_chapter,
|
|
87
|
+
milestones: @milestones.map(&:to_h),
|
|
88
|
+
milestones_today: @milestones.select { |m| milestone_today?(m) }.map(&:to_h) }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def milestone_today?(milestone)
|
|
92
|
+
ts = milestone.respond_to?(:created_at) ? milestone.created_at : milestone[:created_at]
|
|
93
|
+
return false unless ts
|
|
94
|
+
|
|
95
|
+
today = ::Time.now
|
|
96
|
+
t = ts.is_a?(::Time) ? ts.localtime : ::Time.parse(ts.to_s)
|
|
97
|
+
t.year == today.year && t.mon == today.mon && t.mday == today.mday
|
|
98
|
+
rescue StandardError => e
|
|
99
|
+
warn "[arc_engine] milestone_today? error: #{e.message}"
|
|
100
|
+
false
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def derive_chapter(bond_stage)
|
|
104
|
+
Constants::CHAPTER_THRESHOLDS.each_key.reverse_each do |chapter|
|
|
105
|
+
threshold = Constants::CHAPTER_THRESHOLDS[chapter]
|
|
106
|
+
stage_idx = BOND_STAGE_ORDER.index(threshold[:stage]) || 0
|
|
107
|
+
current_stage_idx = BOND_STAGE_ORDER.index(bond_stage) || 0
|
|
108
|
+
|
|
109
|
+
return chapter if @milestones.size >= threshold[:milestones] &&
|
|
110
|
+
current_stage_idx >= stage_idx
|
|
111
|
+
end
|
|
112
|
+
:formative
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def partner?(agent_id)
|
|
116
|
+
defined?(Legion::Gaia::BondRegistry) && Legion::Gaia::BondRegistry.partner?(agent_id)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def serialize(hash)
|
|
120
|
+
defined?(Legion::JSON) ? Legion::JSON.dump(hash) : ::JSON.dump(hash)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def deserialize(content)
|
|
124
|
+
parsed = defined?(Legion::JSON) ? Legion::JSON.parse(content) : ::JSON.parse(content, symbolize_names: true)
|
|
125
|
+
parsed.is_a?(Hash) ? parsed.transform_keys(&:to_sym) : nil
|
|
126
|
+
rescue StandardError => _e
|
|
127
|
+
nil
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Agentic
|
|
6
|
+
module Self
|
|
7
|
+
module RelationshipArc
|
|
8
|
+
module Helpers
|
|
9
|
+
module Constants
|
|
10
|
+
CHAPTERS = %i[formative developing established deepening].freeze
|
|
11
|
+
|
|
12
|
+
MILESTONE_TYPES = %i[
|
|
13
|
+
first_interaction first_direct_address stage_transition
|
|
14
|
+
prediction_accuracy communication_shift absence_return
|
|
15
|
+
].freeze
|
|
16
|
+
|
|
17
|
+
HEALTH_WEIGHTS = {
|
|
18
|
+
attachment_strength: 0.4,
|
|
19
|
+
reciprocity_balance: 0.3,
|
|
20
|
+
communication_consistency: 0.3
|
|
21
|
+
}.freeze
|
|
22
|
+
|
|
23
|
+
MAX_MILESTONES = 200
|
|
24
|
+
|
|
25
|
+
CHAPTER_THRESHOLDS = {
|
|
26
|
+
developing: { milestones: 3, stage: :forming },
|
|
27
|
+
established: { milestones: 10, stage: :established },
|
|
28
|
+
deepening: { milestones: 25, stage: :deep }
|
|
29
|
+
}.freeze
|
|
30
|
+
|
|
31
|
+
TAG_PREFIX = %w[bond relationship_arc].freeze
|
|
32
|
+
MILESTONE_TAG_PREFIX = %w[bond milestone].freeze
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module Agentic
|
|
8
|
+
module Self
|
|
9
|
+
module RelationshipArc
|
|
10
|
+
module Helpers
|
|
11
|
+
class Milestone
|
|
12
|
+
attr_reader :id, :type, :description, :significance, :created_at
|
|
13
|
+
|
|
14
|
+
def initialize(type:, description:, significance:, id: nil, created_at: nil)
|
|
15
|
+
@id = id || SecureRandom.uuid
|
|
16
|
+
@type = type.to_sym
|
|
17
|
+
@description = description
|
|
18
|
+
@significance = significance.to_f.clamp(0.0, 1.0)
|
|
19
|
+
@created_at = created_at || Time.now.utc
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def to_h
|
|
23
|
+
{ id: @id, type: @type, description: @description,
|
|
24
|
+
significance: @significance, created_at: @created_at.iso8601 }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.from_h(hash)
|
|
28
|
+
new(
|
|
29
|
+
id: hash[:id],
|
|
30
|
+
type: hash[:type],
|
|
31
|
+
description: hash[:description],
|
|
32
|
+
significance: hash[:significance],
|
|
33
|
+
created_at: hash[:created_at] ? Time.parse(hash[:created_at].to_s) : nil
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Agentic
|
|
6
|
+
module Self
|
|
7
|
+
module RelationshipArc
|
|
8
|
+
module Runners
|
|
9
|
+
module RelationshipArc
|
|
10
|
+
include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers) &&
|
|
11
|
+
Legion::Extensions::Helpers.const_defined?(:Lex, false)
|
|
12
|
+
|
|
13
|
+
def record_milestone(agent_id:, type:, description:, significance:, **)
|
|
14
|
+
unless Helpers::Constants::MILESTONE_TYPES.include?(type.to_sym)
|
|
15
|
+
return { success: false,
|
|
16
|
+
error: "unknown type: #{type}" }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
engine = arc_engine_for(agent_id)
|
|
20
|
+
ms = engine.add_milestone(type: type, description: description, significance: significance)
|
|
21
|
+
|
|
22
|
+
stamp_narrative_episode(ms)
|
|
23
|
+
|
|
24
|
+
{ success: true, milestone: ms.to_h }
|
|
25
|
+
rescue StandardError => e
|
|
26
|
+
{ success: false, error: e.message }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def update_arc(agent_id:, attachment_state: {}, **)
|
|
30
|
+
engine = arc_engine_for(agent_id)
|
|
31
|
+
engine.update_chapter!(bond_stage: attachment_state[:bond_stage] || :initial)
|
|
32
|
+
|
|
33
|
+
{ success: true, current_chapter: engine.current_chapter,
|
|
34
|
+
milestone_count: engine.milestones.size }
|
|
35
|
+
rescue StandardError => e
|
|
36
|
+
{ success: false, error: e.message }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def arc_stats(agent_id:, **)
|
|
40
|
+
engine = arc_engine_for(agent_id)
|
|
41
|
+
engine.to_h
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def arc_engine_for(agent_id)
|
|
47
|
+
@arc_engines ||= {}
|
|
48
|
+
@arc_engines[agent_id.to_s] ||= Helpers::ArcEngine.new(agent_id: agent_id.to_s)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def stamp_narrative_episode(milestone)
|
|
52
|
+
narrator = resolve_narrative_identity
|
|
53
|
+
return unless narrator
|
|
54
|
+
|
|
55
|
+
narrator.record_episode(
|
|
56
|
+
content: milestone.description,
|
|
57
|
+
episode_type: :relationship,
|
|
58
|
+
emotional_valence: 0.3,
|
|
59
|
+
significance: milestone.significance,
|
|
60
|
+
domain: :relationship,
|
|
61
|
+
tags: ['partner', 'milestone', milestone.type.to_s]
|
|
62
|
+
)
|
|
63
|
+
rescue StandardError => e
|
|
64
|
+
warn "[relationship_arc] narrative stamp failed: #{e.message}"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def resolve_narrative_identity
|
|
68
|
+
return nil unless defined?(Legion::Extensions::Agentic::Self::NarrativeIdentity::Client)
|
|
69
|
+
|
|
70
|
+
@narrative_client ||= Legion::Extensions::Agentic::Self::NarrativeIdentity::Client.new
|
|
71
|
+
rescue StandardError => _e
|
|
72
|
+
nil
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'relationship_arc/version'
|
|
4
|
+
require_relative 'relationship_arc/helpers/constants'
|
|
5
|
+
require_relative 'relationship_arc/helpers/milestone'
|
|
6
|
+
require_relative 'relationship_arc/helpers/arc_engine'
|
|
7
|
+
require_relative 'relationship_arc/runners/relationship_arc'
|
|
8
|
+
require_relative 'relationship_arc/client'
|
|
9
|
+
|
|
10
|
+
module Legion
|
|
11
|
+
module Extensions
|
|
12
|
+
module Agentic
|
|
13
|
+
module Self
|
|
14
|
+
module RelationshipArc
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -104,6 +104,35 @@ module Legion
|
|
|
104
104
|
{ decayed: decayed, voices: voice_list }
|
|
105
105
|
end
|
|
106
106
|
|
|
107
|
+
VOICE_BANK = {
|
|
108
|
+
critic: [
|
|
109
|
+
{ content: 'What could go wrong with this approach?', position: :challenge },
|
|
110
|
+
{ content: 'Are we overlooking any risks here?', position: :challenge },
|
|
111
|
+
{ content: 'This needs more careful consideration.', position: :caution }
|
|
112
|
+
],
|
|
113
|
+
advocate: [
|
|
114
|
+
{ content: 'This aligns with our core values.', position: :support },
|
|
115
|
+
{ content: 'The potential benefits outweigh the risks.', position: :support },
|
|
116
|
+
{ content: 'We should move forward with this.', position: :affirm }
|
|
117
|
+
],
|
|
118
|
+
explorer: [
|
|
119
|
+
{ content: 'What alternatives have we not considered?', position: :explore },
|
|
120
|
+
{ content: 'There may be an unconventional approach here.', position: :explore },
|
|
121
|
+
{ content: 'Let us examine this from another angle.', position: :clarify }
|
|
122
|
+
],
|
|
123
|
+
pragmatist: [
|
|
124
|
+
{ content: 'What is the simplest path forward?', position: :simplify },
|
|
125
|
+
{ content: 'Focus on what is actionable now.', position: :prioritize },
|
|
126
|
+
{ content: 'We need concrete next steps.', position: :clarify }
|
|
127
|
+
]
|
|
128
|
+
}.freeze
|
|
129
|
+
|
|
130
|
+
VOICE_BANK_GENERIC = [
|
|
131
|
+
{ content: 'Let us think this through carefully.', position: :clarify },
|
|
132
|
+
{ content: 'More reflection is needed on this topic.', position: :clarify },
|
|
133
|
+
{ content: 'What do we know for certain here?', position: :clarify }
|
|
134
|
+
].freeze
|
|
135
|
+
|
|
107
136
|
private
|
|
108
137
|
|
|
109
138
|
def engine
|
|
@@ -124,7 +153,7 @@ module Legion
|
|
|
124
153
|
)
|
|
125
154
|
return [llm_result, :llm] if llm_result
|
|
126
155
|
end
|
|
127
|
-
[
|
|
156
|
+
[mechanical_turn_content(voice_data.voice_type, dialogue_data.topic), :mechanical]
|
|
128
157
|
end
|
|
129
158
|
|
|
130
159
|
def build_prior_turns(dialogue_data)
|
|
@@ -135,13 +164,14 @@ module Legion
|
|
|
135
164
|
end
|
|
136
165
|
end
|
|
137
166
|
|
|
138
|
-
def
|
|
139
|
-
|
|
167
|
+
def mechanical_turn_content(voice_type, _topic)
|
|
168
|
+
bank = VOICE_BANK.fetch(voice_type.to_sym, VOICE_BANK_GENERIC)
|
|
169
|
+
bank.sample
|
|
140
170
|
end
|
|
141
171
|
|
|
142
172
|
def generate_summary_for_dialogue(dialogue_id)
|
|
143
173
|
dialogue_data = engine.dialogues[dialogue_id]
|
|
144
|
-
return
|
|
174
|
+
return mechanical_summary(nil) unless dialogue_data
|
|
145
175
|
|
|
146
176
|
if Helpers::LlmEnhancer.available?
|
|
147
177
|
turns = dialogue_data.turns.map do |t|
|
|
@@ -161,7 +191,15 @@ module Legion
|
|
|
161
191
|
return llm_result[:summary] if llm_result
|
|
162
192
|
end
|
|
163
193
|
|
|
164
|
-
|
|
194
|
+
mechanical_summary(dialogue_data)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def mechanical_summary(dialogue_data)
|
|
198
|
+
turns = dialogue_data&.turns || []
|
|
199
|
+
voices = turns.filter_map { |t| t.respond_to?(:voice_id) ? engine.voices[t.voice_id]&.name || t.voice_id : t[:voice_id] }.uniq
|
|
200
|
+
positions = turns.filter_map { |t| t.respond_to?(:position) ? t.position : t[:position] }.tally
|
|
201
|
+
dominant = positions.max_by { |_, count| count }&.first
|
|
202
|
+
"#{turns.size} turns across #{voices.join(', ')} voices. Dominant position: #{dominant || 'none'}."
|
|
165
203
|
end
|
|
166
204
|
end
|
|
167
205
|
end
|
|
@@ -338,6 +338,59 @@ RSpec.describe Legion::Extensions::Agentic::Self::Identity::Runners::Entra do
|
|
|
338
338
|
expect(result[:action_required]).to be false
|
|
339
339
|
expect(result[:days_remaining]).to be > 30
|
|
340
340
|
end
|
|
341
|
+
|
|
342
|
+
context 'when rotation_enabled is true and secret is expiring (fix 5)' do
|
|
343
|
+
before do
|
|
344
|
+
stub_const('Legion::Settings', Class.new do
|
|
345
|
+
def self.dig(*keys)
|
|
346
|
+
map = {
|
|
347
|
+
%i[identity entra rotation_enabled] => true,
|
|
348
|
+
%i[identity entra rotation_buffer_days] => 30
|
|
349
|
+
}
|
|
350
|
+
map[keys]
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def self.[](_key) = {}
|
|
354
|
+
end)
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
it 'emits a warning log when graph api rotation is not implemented' do
|
|
358
|
+
vault_mod = Module.new do
|
|
359
|
+
def self.read_client_secret(**)
|
|
360
|
+
{ client_secret: 'val', client_secret_expires_at: (Time.now + (86_400 * 10)).iso8601 }
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
stub_const('Legion::Extensions::Agentic::Self::Identity::Helpers::VaultSecrets', vault_mod)
|
|
364
|
+
|
|
365
|
+
warned = false
|
|
366
|
+
allow(Legion::Logging).to receive(:warn) do |msg|
|
|
367
|
+
warned = true if msg.include?('graph_api_rotation_not_implemented') ||
|
|
368
|
+
msg.include?('Graph API rotation is not yet implemented')
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
result = client.rotate_client_secret(worker_id: 'w1')
|
|
372
|
+
|
|
373
|
+
expect(warned).to be true
|
|
374
|
+
expect(result[:rotated]).to be false
|
|
375
|
+
expect(result[:error]).to eq('graph_api_rotation_not_implemented')
|
|
376
|
+
expect(result[:action_required]).to include('Manual rotation needed')
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
it 'returns dry_run result without warning when dry_run: true' do
|
|
380
|
+
vault_mod = Module.new do
|
|
381
|
+
def self.read_client_secret(**)
|
|
382
|
+
{ client_secret: 'val', client_secret_expires_at: (Time.now + (86_400 * 10)).iso8601 }
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
stub_const('Legion::Extensions::Agentic::Self::Identity::Helpers::VaultSecrets', vault_mod)
|
|
386
|
+
|
|
387
|
+
expect(Legion::Logging).not_to receive(:warn).with(a_string_including('not yet implemented'))
|
|
388
|
+
|
|
389
|
+
result = client.rotate_client_secret(worker_id: 'w1', dry_run: true)
|
|
390
|
+
expect(result[:dry_run]).to be true
|
|
391
|
+
expect(result[:would_rotate]).to be true
|
|
392
|
+
end
|
|
393
|
+
end
|
|
341
394
|
end
|
|
342
395
|
|
|
343
396
|
# ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'legion/extensions/agentic/self/relationship_arc/helpers/constants'
|
|
5
|
+
require 'legion/extensions/agentic/self/relationship_arc/helpers/milestone'
|
|
6
|
+
require 'legion/extensions/agentic/self/relationship_arc/helpers/arc_engine'
|
|
7
|
+
|
|
8
|
+
RSpec.describe Legion::Extensions::Agentic::Self::RelationshipArc::Helpers::ArcEngine do
|
|
9
|
+
subject(:engine) { described_class.new(agent_id: 'partner-1') }
|
|
10
|
+
|
|
11
|
+
describe '#initialize' do
|
|
12
|
+
it 'starts in formative chapter' do
|
|
13
|
+
expect(engine.current_chapter).to eq(:formative)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'starts with empty milestones' do
|
|
17
|
+
expect(engine.milestones).to be_empty
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
describe '#add_milestone' do
|
|
22
|
+
it 'adds a milestone' do
|
|
23
|
+
engine.add_milestone(type: :first_interaction, description: 'Hello', significance: 0.8)
|
|
24
|
+
expect(engine.milestones.size).to eq(1)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'returns the milestone' do
|
|
28
|
+
ms = engine.add_milestone(type: :first_interaction, description: 'Hello', significance: 0.8)
|
|
29
|
+
expect(ms).to be_a(Legion::Extensions::Agentic::Self::RelationshipArc::Helpers::Milestone)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'marks dirty' do
|
|
33
|
+
engine.add_milestone(type: :first_interaction, description: 'Hello', significance: 0.8)
|
|
34
|
+
expect(engine).to be_dirty
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'caps at MAX_MILESTONES' do
|
|
38
|
+
201.times { |i| engine.add_milestone(type: :first_interaction, description: "ms #{i}", significance: 0.1) }
|
|
39
|
+
expect(engine.milestones.size).to eq(200)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
describe '#update_chapter!' do
|
|
44
|
+
it 'transitions to developing after enough milestones' do
|
|
45
|
+
4.times { engine.add_milestone(type: :first_interaction, description: 'x', significance: 0.5) }
|
|
46
|
+
engine.update_chapter!(bond_stage: :forming)
|
|
47
|
+
expect(engine.current_chapter).to eq(:developing)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'never regresses' do
|
|
51
|
+
engine.instance_variable_set(:@current_chapter, :established)
|
|
52
|
+
engine.update_chapter!(bond_stage: :initial)
|
|
53
|
+
expect(engine.current_chapter).to eq(:established)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
describe '#relationship_health' do
|
|
58
|
+
it 'computes weighted health score' do
|
|
59
|
+
health = engine.relationship_health(
|
|
60
|
+
attachment_strength: 0.8,
|
|
61
|
+
reciprocity_balance: 0.6,
|
|
62
|
+
communication_consistency: 0.7
|
|
63
|
+
)
|
|
64
|
+
expected = (0.8 * 0.4) + (0.6 * 0.3) + (0.7 * 0.3)
|
|
65
|
+
expect(health).to be_within(0.01).of(expected)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it 'clamps to 0.0..1.0' do
|
|
69
|
+
health = engine.relationship_health(
|
|
70
|
+
attachment_strength: 1.5,
|
|
71
|
+
reciprocity_balance: 1.5,
|
|
72
|
+
communication_consistency: 1.5
|
|
73
|
+
)
|
|
74
|
+
expect(health).to eq(1.0)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
describe '#dirty? and #mark_clean!' do
|
|
79
|
+
it 'starts clean' do
|
|
80
|
+
expect(engine).not_to be_dirty
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it 'cleans up' do
|
|
84
|
+
engine.add_milestone(type: :first_interaction, description: 'x', significance: 0.5)
|
|
85
|
+
engine.mark_clean!
|
|
86
|
+
expect(engine).not_to be_dirty
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
describe '#to_apollo_entries' do
|
|
91
|
+
before { engine.add_milestone(type: :first_interaction, description: 'Hello', significance: 0.8) }
|
|
92
|
+
|
|
93
|
+
it 'returns entries with arc state' do
|
|
94
|
+
entries = engine.to_apollo_entries
|
|
95
|
+
expect(entries).to be_an(Array)
|
|
96
|
+
expect(entries.first[:tags]).to include('bond', 'relationship_arc', 'partner-1')
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
describe '#from_apollo' do
|
|
101
|
+
let(:mock_store) { double('apollo_local') }
|
|
102
|
+
|
|
103
|
+
it 'restores state from Apollo' do
|
|
104
|
+
engine.add_milestone(type: :first_interaction, description: 'Hello', significance: 0.8)
|
|
105
|
+
engine.instance_variable_set(:@current_chapter, :developing)
|
|
106
|
+
content = engine.send(:serialize, engine.send(:arc_state_hash))
|
|
107
|
+
|
|
108
|
+
new_engine = described_class.new(agent_id: 'partner-1')
|
|
109
|
+
allow(mock_store).to receive(:query)
|
|
110
|
+
.and_return({ success: true, results: [{ content: content, tags: %w[bond relationship_arc partner-1] }] })
|
|
111
|
+
|
|
112
|
+
expect(new_engine.from_apollo(store: mock_store)).to be true
|
|
113
|
+
expect(new_engine.current_chapter).to eq(:developing)
|
|
114
|
+
expect(new_engine.milestones.size).to eq(1)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
describe '#to_h' do
|
|
119
|
+
it 'includes all fields' do
|
|
120
|
+
h = engine.to_h
|
|
121
|
+
expect(h).to include(:agent_id, :current_chapter, :milestones, :relationship_health)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
it 'relationship_health is nil before relationship_health() is called' do
|
|
125
|
+
expect(engine.to_h[:relationship_health]).to be_nil
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it 'relationship_health reflects the last computed value' do
|
|
129
|
+
engine.relationship_health(
|
|
130
|
+
attachment_strength: 0.8,
|
|
131
|
+
reciprocity_balance: 0.6,
|
|
132
|
+
communication_consistency: 0.7
|
|
133
|
+
)
|
|
134
|
+
h = engine.to_h
|
|
135
|
+
expected = (0.8 * 0.4) + (0.6 * 0.3) + (0.7 * 0.3)
|
|
136
|
+
expect(h[:relationship_health]).to be_within(0.01).of(expected)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
it 'includes milestone_count' do
|
|
140
|
+
engine.add_milestone(type: :first_interaction, description: 'x', significance: 0.5)
|
|
141
|
+
expect(engine.to_h[:milestone_count]).to eq(1)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
describe '#arc_state_hash (via to_apollo_entries)' do
|
|
146
|
+
it 'includes milestones_today for milestones added today' do
|
|
147
|
+
engine.add_milestone(type: :first_interaction, description: 'today', significance: 0.9)
|
|
148
|
+
entries = engine.to_apollo_entries
|
|
149
|
+
parsed = JSON.parse(entries.first[:content], symbolize_names: true)
|
|
150
|
+
expect(parsed).to have_key(:milestones_today)
|
|
151
|
+
expect(parsed[:milestones_today].size).to eq(1)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
it 'milestones_today is empty when milestones have old timestamps' do
|
|
155
|
+
ms = Legion::Extensions::Agentic::Self::RelationshipArc::Helpers::Milestone.new(
|
|
156
|
+
type: :first_interaction, description: 'old', significance: 0.5,
|
|
157
|
+
created_at: Time.now.utc - (2 * 86_400)
|
|
158
|
+
)
|
|
159
|
+
engine.instance_variable_get(:@milestones) << ms
|
|
160
|
+
entries = engine.to_apollo_entries
|
|
161
|
+
parsed = JSON.parse(entries.first[:content], symbolize_names: true)
|
|
162
|
+
expect(parsed[:milestones_today]).to eq([])
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'legion/extensions/agentic/self/relationship_arc/helpers/constants'
|
|
5
|
+
|
|
6
|
+
RSpec.describe Legion::Extensions::Agentic::Self::RelationshipArc::Helpers::Constants do
|
|
7
|
+
describe 'CHAPTERS' do
|
|
8
|
+
it 'defines 4 chapters in order' do
|
|
9
|
+
expect(described_class::CHAPTERS).to eq(%i[formative developing established deepening])
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
describe 'MILESTONE_TYPES' do
|
|
14
|
+
it 'includes expected types' do
|
|
15
|
+
expect(described_class::MILESTONE_TYPES).to include(:first_interaction, :stage_transition,
|
|
16
|
+
:prediction_accuracy, :absence_return)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
describe 'HEALTH_WEIGHTS' do
|
|
21
|
+
it 'sums to 1.0' do
|
|
22
|
+
expect(described_class::HEALTH_WEIGHTS.values.sum).to eq(1.0)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
describe 'MAX_MILESTONES' do
|
|
27
|
+
it 'caps at 200' do
|
|
28
|
+
expect(described_class::MAX_MILESTONES).to eq(200)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'legion/extensions/agentic/self/relationship_arc/helpers/constants'
|
|
5
|
+
require 'legion/extensions/agentic/self/relationship_arc/helpers/milestone'
|
|
6
|
+
|
|
7
|
+
RSpec.describe Legion::Extensions::Agentic::Self::RelationshipArc::Helpers::Milestone do
|
|
8
|
+
subject(:milestone) do
|
|
9
|
+
described_class.new(type: :first_interaction, description: 'First hello', significance: 0.8)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
describe '#initialize' do
|
|
13
|
+
it 'sets type' do
|
|
14
|
+
expect(milestone.type).to eq(:first_interaction)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'generates a UUID' do
|
|
18
|
+
expect(milestone.id).to match(/\A[0-9a-f-]{36}\z/)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'clamps significance to 0.0..1.0' do
|
|
22
|
+
ms = described_class.new(type: :first_interaction, description: 'test', significance: 1.5)
|
|
23
|
+
expect(ms.significance).to eq(1.0)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it 'records timestamp' do
|
|
27
|
+
expect(milestone.created_at).to be_a(Time)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
describe '#to_h' do
|
|
32
|
+
it 'returns complete hash' do
|
|
33
|
+
h = milestone.to_h
|
|
34
|
+
expect(h).to include(:id, :type, :description, :significance, :created_at)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
describe '.from_h' do
|
|
39
|
+
it 'round-trips' do
|
|
40
|
+
restored = described_class.from_h(milestone.to_h)
|
|
41
|
+
expect(restored.type).to eq(milestone.type)
|
|
42
|
+
expect(restored.description).to eq(milestone.description)
|
|
43
|
+
expect(restored.significance).to eq(milestone.significance)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'legion/extensions/agentic/self/relationship_arc/helpers/constants'
|
|
5
|
+
require 'legion/extensions/agentic/self/relationship_arc/helpers/milestone'
|
|
6
|
+
require 'legion/extensions/agentic/self/relationship_arc/helpers/arc_engine'
|
|
7
|
+
require 'legion/extensions/agentic/self/relationship_arc/runners/relationship_arc'
|
|
8
|
+
|
|
9
|
+
RSpec.describe Legion::Extensions::Agentic::Self::RelationshipArc::Runners::RelationshipArc do
|
|
10
|
+
let(:described_module) { described_class }
|
|
11
|
+
let(:host) { Object.new.extend(described_module) }
|
|
12
|
+
|
|
13
|
+
before { host.instance_variable_set(:@arc_engines, nil) }
|
|
14
|
+
|
|
15
|
+
describe '#record_milestone' do
|
|
16
|
+
it 'records a milestone for an agent' do
|
|
17
|
+
result = host.record_milestone(agent_id: 'p1', type: :first_interaction,
|
|
18
|
+
description: 'Hello', significance: 0.8)
|
|
19
|
+
expect(result[:success]).to be true
|
|
20
|
+
expect(result[:milestone][:type]).to eq(:first_interaction)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'returns error for unknown type' do
|
|
24
|
+
result = host.record_milestone(agent_id: 'p1', type: :bogus,
|
|
25
|
+
description: 'x', significance: 0.5)
|
|
26
|
+
expect(result[:success]).to be false
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'calls NarrativeIdentity record_episode when available' do
|
|
30
|
+
narrator = double('narrator')
|
|
31
|
+
allow(host).to receive(:resolve_narrative_identity).and_return(narrator)
|
|
32
|
+
allow(narrator).to receive(:record_episode).and_return({ success: true })
|
|
33
|
+
|
|
34
|
+
host.record_milestone(agent_id: 'p1', type: :first_interaction,
|
|
35
|
+
description: 'Hello', significance: 0.8)
|
|
36
|
+
expect(narrator).to have_received(:record_episode)
|
|
37
|
+
.with(hash_including(episode_type: :relationship, significance: 0.8))
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
describe '#update_arc' do
|
|
42
|
+
it 'updates chapter based on attachment state' do
|
|
43
|
+
3.times do
|
|
44
|
+
host.record_milestone(agent_id: 'p1', type: :first_interaction,
|
|
45
|
+
description: 'x', significance: 0.5)
|
|
46
|
+
end
|
|
47
|
+
result = host.update_arc(agent_id: 'p1', attachment_state: { bond_stage: :forming })
|
|
48
|
+
expect(result[:success]).to be true
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it 'returns arc summary' do
|
|
52
|
+
result = host.update_arc(agent_id: 'p1', attachment_state: {})
|
|
53
|
+
expect(result).to have_key(:current_chapter)
|
|
54
|
+
expect(result).to have_key(:milestone_count)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
describe '#arc_stats' do
|
|
59
|
+
it 'returns stats' do
|
|
60
|
+
result = host.arc_stats(agent_id: 'p1')
|
|
61
|
+
expect(result).to have_key(:current_chapter)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -137,7 +137,7 @@ RSpec.describe Legion::Extensions::Agentic::Self::SelfTalk::Runners::SelfTalk, '
|
|
|
137
137
|
result = client.conclude_dialogue(dialogue_id: did)
|
|
138
138
|
expect(result[:concluded]).to be true
|
|
139
139
|
report = client.dialogue_report(dialogue_id: did)
|
|
140
|
-
expect(report[:dialogue][:conclusion]).to
|
|
140
|
+
expect(report[:dialogue][:conclusion]).to match(/turns across .* voices\. Dominant position:/)
|
|
141
141
|
end
|
|
142
142
|
end
|
|
143
143
|
|
|
@@ -151,7 +151,7 @@ RSpec.describe Legion::Extensions::Agentic::Self::SelfTalk::Runners::SelfTalk, '
|
|
|
151
151
|
result = client.conclude_dialogue(dialogue_id: did)
|
|
152
152
|
expect(result[:concluded]).to be true
|
|
153
153
|
report = client.dialogue_report(dialogue_id: did)
|
|
154
|
-
expect(report[:dialogue][:conclusion]).to
|
|
154
|
+
expect(report[:dialogue][:conclusion]).to match(/turns across .* voices\. Dominant position:/)
|
|
155
155
|
end
|
|
156
156
|
end
|
|
157
157
|
|
|
@@ -193,4 +193,64 @@ RSpec.describe Legion::Extensions::Agentic::Self::SelfTalk::Runners::SelfTalk do
|
|
|
193
193
|
expect(voice[:name]).to eq('Critic')
|
|
194
194
|
end
|
|
195
195
|
end
|
|
196
|
+
|
|
197
|
+
describe 'mechanical voice bank (fix 3)' do
|
|
198
|
+
let(:runner_class) do
|
|
199
|
+
Class.new do
|
|
200
|
+
include Legion::Extensions::Agentic::Self::SelfTalk::Runners::SelfTalk
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
let(:runner) { runner_class.new }
|
|
204
|
+
|
|
205
|
+
it 'produces real content for known voice types' do
|
|
206
|
+
%i[critic advocate explorer pragmatist].each do |type|
|
|
207
|
+
result = runner.send(:mechanical_turn_content, type, 'test topic')
|
|
208
|
+
expect(result[:content]).not_to be_empty
|
|
209
|
+
expect(result[:content]).not_to match(/\A\[/)
|
|
210
|
+
expect(result[:position]).not_to be_nil
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
it 'falls back to generic bank for unknown voice types' do
|
|
215
|
+
result = runner.send(:mechanical_turn_content, :unknown_voice, 'some topic')
|
|
216
|
+
expect(result[:content]).not_to be_empty
|
|
217
|
+
expect(result[:position]).not_to be_nil
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
it 'VOICE_BANK covers critic, advocate, explorer, pragmatist' do
|
|
221
|
+
bank = Legion::Extensions::Agentic::Self::SelfTalk::Runners::SelfTalk::VOICE_BANK
|
|
222
|
+
expect(bank.keys).to include(:critic, :advocate, :explorer, :pragmatist)
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
describe 'mechanical summary (fix 4)' do
|
|
227
|
+
let(:runner_class) do
|
|
228
|
+
Class.new do
|
|
229
|
+
include Legion::Extensions::Agentic::Self::SelfTalk::Runners::SelfTalk
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
let(:runner) { runner_class.new }
|
|
233
|
+
|
|
234
|
+
it 'produces a non-empty summary when dialogue_data is nil' do
|
|
235
|
+
summary = runner.send(:mechanical_summary, nil)
|
|
236
|
+
expect(summary).to be_a(String)
|
|
237
|
+
expect(summary).not_to be_empty
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
it 'includes turn count in summary' do
|
|
241
|
+
did = start_dialogue(topic: 'summary test')
|
|
242
|
+
vid = register_voice(name: 'Critic', type: :critic)
|
|
243
|
+
client.add_turn(dialogue_id: did, voice_id: vid, content: 'Point one', position: :challenge)
|
|
244
|
+
client.add_turn(dialogue_id: did, voice_id: vid, content: 'Point two', position: :challenge)
|
|
245
|
+
|
|
246
|
+
result = client.conclude_dialogue(dialogue_id: did)
|
|
247
|
+
expect(result[:concluded]).to be true
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
it 'conclude_dialogue without summary uses mechanical_summary' do
|
|
251
|
+
did = start_dialogue(topic: 'no summary')
|
|
252
|
+
result = client.conclude_dialogue(dialogue_id: did)
|
|
253
|
+
expect(result[:concluded]).to be true
|
|
254
|
+
end
|
|
255
|
+
end
|
|
196
256
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lex-agentic-self
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.11
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -139,44 +139,44 @@ dependencies:
|
|
|
139
139
|
name: rubocop
|
|
140
140
|
requirement: !ruby/object:Gem::Requirement
|
|
141
141
|
requirements:
|
|
142
|
-
- - "
|
|
142
|
+
- - ">="
|
|
143
143
|
- !ruby/object:Gem::Version
|
|
144
|
-
version: '
|
|
144
|
+
version: '0'
|
|
145
145
|
type: :development
|
|
146
146
|
prerelease: false
|
|
147
147
|
version_requirements: !ruby/object:Gem::Requirement
|
|
148
148
|
requirements:
|
|
149
|
-
- - "
|
|
149
|
+
- - ">="
|
|
150
150
|
- !ruby/object:Gem::Version
|
|
151
|
-
version: '
|
|
151
|
+
version: '0'
|
|
152
152
|
- !ruby/object:Gem::Dependency
|
|
153
153
|
name: rubocop-legion
|
|
154
154
|
requirement: !ruby/object:Gem::Requirement
|
|
155
155
|
requirements:
|
|
156
|
-
- - "
|
|
156
|
+
- - ">="
|
|
157
157
|
- !ruby/object:Gem::Version
|
|
158
|
-
version: '0
|
|
158
|
+
version: '0'
|
|
159
159
|
type: :development
|
|
160
160
|
prerelease: false
|
|
161
161
|
version_requirements: !ruby/object:Gem::Requirement
|
|
162
162
|
requirements:
|
|
163
|
-
- - "
|
|
163
|
+
- - ">="
|
|
164
164
|
- !ruby/object:Gem::Version
|
|
165
|
-
version: '0
|
|
165
|
+
version: '0'
|
|
166
166
|
- !ruby/object:Gem::Dependency
|
|
167
167
|
name: rubocop-rspec
|
|
168
168
|
requirement: !ruby/object:Gem::Requirement
|
|
169
169
|
requirements:
|
|
170
|
-
- - "
|
|
170
|
+
- - ">="
|
|
171
171
|
- !ruby/object:Gem::Version
|
|
172
|
-
version: '
|
|
172
|
+
version: '0'
|
|
173
173
|
type: :development
|
|
174
174
|
prerelease: false
|
|
175
175
|
version_requirements: !ruby/object:Gem::Requirement
|
|
176
176
|
requirements:
|
|
177
|
-
- - "
|
|
177
|
+
- - ">="
|
|
178
178
|
- !ruby/object:Gem::Version
|
|
179
|
-
version: '
|
|
179
|
+
version: '0'
|
|
180
180
|
description: 'LEX agentic self domain: identity, metacognition, self-model'
|
|
181
181
|
email:
|
|
182
182
|
- matthewdiverson@gmail.com
|
|
@@ -309,6 +309,13 @@ files:
|
|
|
309
309
|
- lib/legion/extensions/agentic/self/reflection/helpers/reflection_store.rb
|
|
310
310
|
- lib/legion/extensions/agentic/self/reflection/runners/reflection.rb
|
|
311
311
|
- lib/legion/extensions/agentic/self/reflection/version.rb
|
|
312
|
+
- lib/legion/extensions/agentic/self/relationship_arc.rb
|
|
313
|
+
- lib/legion/extensions/agentic/self/relationship_arc/client.rb
|
|
314
|
+
- lib/legion/extensions/agentic/self/relationship_arc/helpers/arc_engine.rb
|
|
315
|
+
- lib/legion/extensions/agentic/self/relationship_arc/helpers/constants.rb
|
|
316
|
+
- lib/legion/extensions/agentic/self/relationship_arc/helpers/milestone.rb
|
|
317
|
+
- lib/legion/extensions/agentic/self/relationship_arc/runners/relationship_arc.rb
|
|
318
|
+
- lib/legion/extensions/agentic/self/relationship_arc/version.rb
|
|
312
319
|
- lib/legion/extensions/agentic/self/self_model.rb
|
|
313
320
|
- lib/legion/extensions/agentic/self/self_model/client.rb
|
|
314
321
|
- lib/legion/extensions/agentic/self/self_model/helpers/capability.rb
|
|
@@ -416,6 +423,10 @@ files:
|
|
|
416
423
|
- spec/legion/extensions/agentic/self/reflection/helpers/reflection_spec.rb
|
|
417
424
|
- spec/legion/extensions/agentic/self/reflection/helpers/reflection_store_spec.rb
|
|
418
425
|
- spec/legion/extensions/agentic/self/reflection/runners/reflection_spec.rb
|
|
426
|
+
- spec/legion/extensions/agentic/self/relationship_arc/helpers/arc_engine_spec.rb
|
|
427
|
+
- spec/legion/extensions/agentic/self/relationship_arc/helpers/constants_spec.rb
|
|
428
|
+
- spec/legion/extensions/agentic/self/relationship_arc/helpers/milestone_spec.rb
|
|
429
|
+
- spec/legion/extensions/agentic/self/relationship_arc/runners/relationship_arc_spec.rb
|
|
419
430
|
- spec/legion/extensions/agentic/self/self_model/client_spec.rb
|
|
420
431
|
- spec/legion/extensions/agentic/self/self_model/helpers/capability_spec.rb
|
|
421
432
|
- spec/legion/extensions/agentic/self/self_model/helpers/knowledge_domain_spec.rb
|