lex-cognitive-labyrinth 0.1.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 +7 -0
- data/.github/workflows/ci.yml +16 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rubocop.yml +64 -0
- data/CLAUDE.md +129 -0
- data/Gemfile +13 -0
- data/README.md +56 -0
- data/lex-cognitive-labyrinth.gemspec +31 -0
- data/lib/legion/extensions/cognitive_labyrinth/actors/thread_walker.rb +41 -0
- data/lib/legion/extensions/cognitive_labyrinth/client.rb +19 -0
- data/lib/legion/extensions/cognitive_labyrinth/helpers/constants.rb +35 -0
- data/lib/legion/extensions/cognitive_labyrinth/helpers/labyrinth.rb +134 -0
- data/lib/legion/extensions/cognitive_labyrinth/helpers/labyrinth_engine.rb +173 -0
- data/lib/legion/extensions/cognitive_labyrinth/helpers/node.rb +60 -0
- data/lib/legion/extensions/cognitive_labyrinth/runners/cognitive_labyrinth.rb +124 -0
- data/lib/legion/extensions/cognitive_labyrinth/version.rb +9 -0
- data/lib/legion/extensions/cognitive_labyrinth.rb +19 -0
- metadata +79 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 8e80874f5d806cabd4e0b5a3873fc35e6ceffda2e32268105fd2ae694fcdc3d0
|
|
4
|
+
data.tar.gz: ac099ce9a1c5365b456d09933fb5d09183ac4526ac0b362c7bd2972b1cca7117
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 2662fd410758e942642ee4a9aeac9d01c1d2512706c6aac86442ed7a11e000a443fa6bcc780c838dc17fb66c2fe48f38d0b898e3c34f6c18bc14078940b7d83c
|
|
7
|
+
data.tar.gz: c4c1114b70ba860416567281e4123193d7194f4edad034feb42c127015907bf5f795d51b8a96dfbdfd532279e4fd749b65fad3c410ef170fbf2dbce1fa8fe288
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [main]
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
ci:
|
|
9
|
+
uses: LegionIO/.github/.github/workflows/ci.yml@main
|
|
10
|
+
|
|
11
|
+
release:
|
|
12
|
+
needs: ci
|
|
13
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
14
|
+
uses: LegionIO/.github/.github/workflows/release.yml@main
|
|
15
|
+
secrets:
|
|
16
|
+
rubygems-api-key: ${{ secrets.RUBYGEMS_API_KEY }}
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
TargetRubyVersion: 3.4
|
|
3
|
+
NewCops: enable
|
|
4
|
+
SuggestExtensions: false
|
|
5
|
+
|
|
6
|
+
Layout/LineLength:
|
|
7
|
+
Max: 160
|
|
8
|
+
|
|
9
|
+
Layout/SpaceAroundEqualsInParameterDefault:
|
|
10
|
+
EnforcedStyle: space
|
|
11
|
+
|
|
12
|
+
Layout/HashAlignment:
|
|
13
|
+
EnforcedHashRocketStyle: table
|
|
14
|
+
EnforcedColonStyle: table
|
|
15
|
+
|
|
16
|
+
Metrics/MethodLength:
|
|
17
|
+
Max: 25
|
|
18
|
+
|
|
19
|
+
Metrics/ClassLength:
|
|
20
|
+
Max: 150
|
|
21
|
+
|
|
22
|
+
Metrics/ModuleLength:
|
|
23
|
+
Max: 150
|
|
24
|
+
|
|
25
|
+
Metrics/BlockLength:
|
|
26
|
+
Max: 40
|
|
27
|
+
Exclude:
|
|
28
|
+
- 'spec/**/*'
|
|
29
|
+
|
|
30
|
+
Metrics/AbcSize:
|
|
31
|
+
Max: 25
|
|
32
|
+
|
|
33
|
+
Metrics/CyclomaticComplexity:
|
|
34
|
+
Max: 15
|
|
35
|
+
|
|
36
|
+
Metrics/PerceivedComplexity:
|
|
37
|
+
Max: 17
|
|
38
|
+
|
|
39
|
+
Metrics/ParameterLists:
|
|
40
|
+
Max: 8
|
|
41
|
+
MaxOptionalParameters: 8
|
|
42
|
+
|
|
43
|
+
Style/Documentation:
|
|
44
|
+
Enabled: false
|
|
45
|
+
|
|
46
|
+
Style/SymbolArray:
|
|
47
|
+
Enabled: true
|
|
48
|
+
|
|
49
|
+
Style/FrozenStringLiteralComment:
|
|
50
|
+
Enabled: true
|
|
51
|
+
EnforcedStyle: always
|
|
52
|
+
|
|
53
|
+
Style/OneClassPerFile:
|
|
54
|
+
Exclude:
|
|
55
|
+
- 'spec/spec_helper.rb'
|
|
56
|
+
|
|
57
|
+
Naming/FileName:
|
|
58
|
+
Enabled: false
|
|
59
|
+
|
|
60
|
+
Naming/PredicateMethod:
|
|
61
|
+
Enabled: false
|
|
62
|
+
|
|
63
|
+
Naming/PredicatePrefix:
|
|
64
|
+
Enabled: false
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# lex-cognitive-labyrinth
|
|
2
|
+
|
|
3
|
+
**Level 3 Leaf Documentation**
|
|
4
|
+
- **Parent**: `/Users/miverso2/rubymine/legion/extensions-agentic/CLAUDE.md`
|
|
5
|
+
|
|
6
|
+
## Purpose
|
|
7
|
+
|
|
8
|
+
Maze-like problem space navigation engine. Models a cognitive labyrinth as a directed graph of typed nodes (corridors, junctions, dead ends, entrances, exits, minotaur lairs). The agent explores by moving between nodes, dropping breadcrumbs for backtracking, following Ariadne's thread toward the exit, and encountering the Minotaur (dangerous misconceptions) at specially typed nodes.
|
|
9
|
+
|
|
10
|
+
## Gem Info
|
|
11
|
+
|
|
12
|
+
- **Gem name**: `lex-cognitive-labyrinth`
|
|
13
|
+
- **Module**: `Legion::Extensions::CognitiveLabyrinth`
|
|
14
|
+
- **Version**: `0.1.0`
|
|
15
|
+
- **Ruby**: `>= 3.4`
|
|
16
|
+
- **License**: MIT
|
|
17
|
+
|
|
18
|
+
## File Structure
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
lib/legion/extensions/cognitive_labyrinth/
|
|
22
|
+
version.rb
|
|
23
|
+
client.rb
|
|
24
|
+
helpers/
|
|
25
|
+
constants.rb
|
|
26
|
+
node.rb
|
|
27
|
+
labyrinth.rb
|
|
28
|
+
labyrinth_engine.rb
|
|
29
|
+
runners/
|
|
30
|
+
cognitive_labyrinth.rb
|
|
31
|
+
actors/
|
|
32
|
+
thread_walker.rb
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Key Constants
|
|
36
|
+
|
|
37
|
+
| Constant | Value | Purpose |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| `NODE_TYPES` | `%i[corridor junction dead_end entrance exit minotaur_lair]` | Valid node type values |
|
|
40
|
+
| `MAX_LABYRINTHS` | `20` | Per-engine labyrinth capacity |
|
|
41
|
+
| `MAX_NODES` | `200` | Per-engine node capacity |
|
|
42
|
+
| `DEFAULT_DANGER_LEVEL` | `0.0` | Starting danger for non-minotaur nodes |
|
|
43
|
+
| `MINOTAUR_DANGER_LEVEL` | `0.9` | Danger applied to `minotaur_lair` nodes |
|
|
44
|
+
| `DEAD_END_COMPLEXITY_HIT` | `0.1` | Complexity added when entering a dead end |
|
|
45
|
+
| `COMPLEXITY_LABELS` | range hash | Labels from `:trivial` to `:impossible` |
|
|
46
|
+
| `DANGER_LABELS` | range hash | Labels from `:safe` to `:lethal` |
|
|
47
|
+
|
|
48
|
+
## Helpers
|
|
49
|
+
|
|
50
|
+
### `Helpers::Node`
|
|
51
|
+
Graph node with `id`, `label`, `node_type`, `connections` (array of neighbor IDs), and `danger_level`.
|
|
52
|
+
|
|
53
|
+
- `connect!(other_node_id)` — adds neighbor (idempotent)
|
|
54
|
+
- `disconnect!(other_node_id)` — removes neighbor
|
|
55
|
+
- `dead_end?` — true if `node_type == :dead_end`
|
|
56
|
+
- `junction?` — true if `node_type == :junction`
|
|
57
|
+
- `dangerous?` — true if `danger_level > 0.5`
|
|
58
|
+
- `to_h` — serializes to hash
|
|
59
|
+
|
|
60
|
+
### `Helpers::Labyrinth`
|
|
61
|
+
Single labyrinth instance. Holds nodes hash, current position, breadcrumb trail, and Ariadne's thread.
|
|
62
|
+
|
|
63
|
+
- `add_node(node)` — registers node
|
|
64
|
+
- `move_to!(node_id)` — changes current position, drops breadcrumb, increments dead-end complexity
|
|
65
|
+
- `backtrack!` — pops breadcrumb stack to return to previous node
|
|
66
|
+
- `drop_breadcrumb` — records current position in trail
|
|
67
|
+
- `follow_thread` — returns path toward exit node (thread set on entrance)
|
|
68
|
+
- `solved?` — current node is the exit
|
|
69
|
+
- `lost?` — no breadcrumbs and not at entrance
|
|
70
|
+
- `path_length` — breadcrumb count
|
|
71
|
+
- `complexity` / `complexity_label`
|
|
72
|
+
- `to_h`
|
|
73
|
+
|
|
74
|
+
### `Helpers::LabyrinthEngine`
|
|
75
|
+
Top-level in-memory store. Enforces `MAX_LABYRINTHS` and `MAX_NODES`.
|
|
76
|
+
|
|
77
|
+
- `create_labyrinth(name:)` → labyrinth or `{error: :capacity_exceeded}`
|
|
78
|
+
- `add_node_to(labyrinth_id:, label:, node_type:, danger_level:)` → node or error
|
|
79
|
+
- `connect_nodes(labyrinth_id:, from_id:, to_id:)` → status hash
|
|
80
|
+
- `move(labyrinth_id:, node_id:)` → position hash
|
|
81
|
+
- `backtrack(labyrinth_id:)` → position hash or `:at_entrance`
|
|
82
|
+
- `follow_thread(labyrinth_id:)` → thread path
|
|
83
|
+
- `check_minotaur(labyrinth_id:)` → danger result for current node
|
|
84
|
+
- `labyrinth_report(labyrinth_id:)` → full status hash
|
|
85
|
+
- `list_labyrinths` → array of labyrinth summaries
|
|
86
|
+
- `delete_labyrinth(labyrinth_id:)` → boolean
|
|
87
|
+
|
|
88
|
+
## Runners
|
|
89
|
+
|
|
90
|
+
Module: `Runners::CognitiveLaby rinth` (included via `Client`)
|
|
91
|
+
|
|
92
|
+
| Runner Method | Description |
|
|
93
|
+
|---|---|
|
|
94
|
+
| `create_labyrinth(name:)` | Create a new labyrinth |
|
|
95
|
+
| `add_node(labyrinth_id:, label:, node_type:, danger_level:)` | Add a node |
|
|
96
|
+
| `connect_nodes(labyrinth_id:, from_id:, to_id:)` | Connect two nodes |
|
|
97
|
+
| `move(labyrinth_id:, node_id:)` | Move current position |
|
|
98
|
+
| `backtrack(labyrinth_id:)` | Backtrack along breadcrumbs |
|
|
99
|
+
| `follow_thread(labyrinth_id:)` | Follow Ariadne's thread toward exit |
|
|
100
|
+
| `check_minotaur(labyrinth_id:)` | Check danger at current node |
|
|
101
|
+
| `labyrinth_report(labyrinth_id:)` | Full labyrinth state |
|
|
102
|
+
| `list_labyrinths` | All labyrinths summary |
|
|
103
|
+
| `delete_labyrinth(labyrinth_id:)` | Remove labyrinth |
|
|
104
|
+
|
|
105
|
+
All runners return `{success: true/false, ...}` hashes.
|
|
106
|
+
|
|
107
|
+
## Actors
|
|
108
|
+
|
|
109
|
+
### `Actors::ThreadWalker`
|
|
110
|
+
- Subclass of `Legion::Extensions::Actors::Every`
|
|
111
|
+
- Interval: `600` seconds
|
|
112
|
+
- Calls `Runners::CognitiveLaby rinth#list_labyrinths`
|
|
113
|
+
- `run_now? = false`, `use_runner? = false`
|
|
114
|
+
- Passive periodic survey of all open labyrinths
|
|
115
|
+
|
|
116
|
+
## Integration Points
|
|
117
|
+
|
|
118
|
+
- No direct dependencies on other agentic LEX gems
|
|
119
|
+
- Can integrate with `lex-tick` via `action_selection` phase handler to gate actions when the agent is "lost" in a problem space
|
|
120
|
+
- Works alongside `lex-memory` for persisting explored paths as semantic traces
|
|
121
|
+
- Minotaur encounters (high danger) can feed into `lex-emotion` arousal signal
|
|
122
|
+
|
|
123
|
+
## Development Notes
|
|
124
|
+
|
|
125
|
+
- `Client` instantiates `@labyrinth_engine = Helpers::LabyrinthEngine.new` — all state is per-client-instance
|
|
126
|
+
- Ariadne's thread is set at entrance node creation and points toward the exit node ID
|
|
127
|
+
- `backtrack!` returns `:at_entrance` when the breadcrumb stack is empty
|
|
128
|
+
- Node connections are directed (one-way unless connected in both directions)
|
|
129
|
+
- `MAX_LABYRINTHS` and `MAX_NODES` are enforced at engine level; runners receive error hashes on overflow
|
data/Gemfile
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
source 'https://rubygems.org'
|
|
4
|
+
|
|
5
|
+
gemspec
|
|
6
|
+
|
|
7
|
+
group :test do
|
|
8
|
+
gem 'rspec', '~> 3.13'
|
|
9
|
+
gem 'rubocop', '~> 1.75', require: false
|
|
10
|
+
gem 'rubocop-rspec', require: false
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
gem 'legion-gaia', path: '../../legion-gaia'
|
data/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# lex-cognitive-labyrinth
|
|
2
|
+
|
|
3
|
+
Maze-like problem space navigation for LegionIO cognitive agents. Models complex reasoning challenges as labyrinths with typed nodes, directional connections, breadcrumb backtracking, and Ariadne's thread guidance toward the exit.
|
|
4
|
+
|
|
5
|
+
## What It Does
|
|
6
|
+
|
|
7
|
+
- Creates named labyrinths as directed graphs of cognitive nodes
|
|
8
|
+
- Node types: `corridor`, `junction`, `dead_end`, `entrance`, `exit`, `minotaur_lair`
|
|
9
|
+
- Navigation: move to nodes, drop breadcrumbs, backtrack along the crumb trail
|
|
10
|
+
- Ariadne's thread: pre-computed guidance path toward the exit node
|
|
11
|
+
- Minotaur detection: checks danger level at current position (minotaur lairs default to 0.9 danger)
|
|
12
|
+
- Complexity tracking: increases when entering dead ends
|
|
13
|
+
- Periodic survey via `ThreadWalker` actor (every 600s)
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
# Create a labyrinth
|
|
19
|
+
result = runner.create_labyrinth(name: 'problem_analysis')
|
|
20
|
+
labyrinth_id = result[:labyrinth][:id]
|
|
21
|
+
|
|
22
|
+
# Add nodes
|
|
23
|
+
entrance = runner.add_node(labyrinth_id: labyrinth_id, label: 'start', node_type: :entrance, danger_level: 0.0)
|
|
24
|
+
junction = runner.add_node(labyrinth_id: labyrinth_id, label: 'fork', node_type: :junction, danger_level: 0.0)
|
|
25
|
+
danger = runner.add_node(labyrinth_id: labyrinth_id, label: 'false assumption', node_type: :minotaur_lair, danger_level: 0.9)
|
|
26
|
+
exit_n = runner.add_node(labyrinth_id: labyrinth_id, label: 'solution', node_type: :exit, danger_level: 0.0)
|
|
27
|
+
|
|
28
|
+
# Connect and navigate
|
|
29
|
+
runner.connect_nodes(labyrinth_id: labyrinth_id, from_id: entrance[:node][:id], to_id: junction[:node][:id])
|
|
30
|
+
runner.move(labyrinth_id: labyrinth_id, node_id: junction[:node][:id])
|
|
31
|
+
|
|
32
|
+
# Check for danger
|
|
33
|
+
runner.check_minotaur(labyrinth_id: labyrinth_id)
|
|
34
|
+
# => { success: true, danger_level: 0.0, dangerous: false, ... }
|
|
35
|
+
|
|
36
|
+
# Backtrack if stuck
|
|
37
|
+
runner.backtrack(labyrinth_id: labyrinth_id)
|
|
38
|
+
|
|
39
|
+
# Follow the thread toward exit
|
|
40
|
+
runner.follow_thread(labyrinth_id: labyrinth_id)
|
|
41
|
+
|
|
42
|
+
# Full status
|
|
43
|
+
runner.labyrinth_report(labyrinth_id: labyrinth_id)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Development
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
bundle install
|
|
50
|
+
bundle exec rspec
|
|
51
|
+
bundle exec rubocop
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## License
|
|
55
|
+
|
|
56
|
+
MIT
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/legion/extensions/cognitive_labyrinth/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'lex-cognitive-labyrinth'
|
|
7
|
+
spec.version = Legion::Extensions::CognitiveLabyrinth::VERSION
|
|
8
|
+
spec.authors = ['Esity']
|
|
9
|
+
spec.email = ['matthewdiverson@gmail.com']
|
|
10
|
+
|
|
11
|
+
spec.summary = 'LEX Cognitive Labyrinth'
|
|
12
|
+
spec.description = 'Maze-like problem spaces for agentic reasoning — paths, dead ends, backtracking, ' \
|
|
13
|
+
'breadcrumb trails, Ariadne\'s thread (guiding heuristic), and Minotaur encounters ' \
|
|
14
|
+
'(dangerous misconceptions)'
|
|
15
|
+
spec.homepage = 'https://github.com/LegionIO/lex-cognitive-labyrinth'
|
|
16
|
+
spec.license = 'MIT'
|
|
17
|
+
spec.required_ruby_version = '>= 3.4'
|
|
18
|
+
|
|
19
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
20
|
+
spec.metadata['source_code_uri'] = 'https://github.com/LegionIO/lex-cognitive-labyrinth'
|
|
21
|
+
spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/lex-cognitive-labyrinth'
|
|
22
|
+
spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/lex-cognitive-labyrinth'
|
|
23
|
+
spec.metadata['bug_tracker_uri'] = 'https://github.com/LegionIO/lex-cognitive-labyrinth/issues'
|
|
24
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
25
|
+
|
|
26
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
27
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
28
|
+
end
|
|
29
|
+
spec.require_paths = ['lib']
|
|
30
|
+
spec.add_development_dependency 'legion-gaia'
|
|
31
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/actors/every'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module CognitiveLabyrinth
|
|
8
|
+
module Actor
|
|
9
|
+
class ThreadWalker < Legion::Extensions::Actors::Every
|
|
10
|
+
def runner_class
|
|
11
|
+
Legion::Extensions::CognitiveLabyrinth::Runners::CognitiveLabyrinth
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def runner_function
|
|
15
|
+
'list_labyrinths'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def time
|
|
19
|
+
600
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def run_now?
|
|
23
|
+
false
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def use_runner?
|
|
27
|
+
false
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def check_subtask?
|
|
31
|
+
false
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def generate_task?
|
|
35
|
+
false
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveLabyrinth
|
|
6
|
+
class Client
|
|
7
|
+
include Runners::CognitiveLabyrinth
|
|
8
|
+
|
|
9
|
+
def initialize(**)
|
|
10
|
+
@labyrinth_engine = Helpers::LabyrinthEngine.new
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
attr_reader :labyrinth_engine
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveLabyrinth
|
|
6
|
+
module Helpers
|
|
7
|
+
module Constants
|
|
8
|
+
NODE_TYPES = %i[corridor junction dead_end entrance exit minotaur_lair].freeze
|
|
9
|
+
|
|
10
|
+
MAX_LABYRINTHS = 20
|
|
11
|
+
MAX_NODES = 200
|
|
12
|
+
|
|
13
|
+
COMPLEXITY_LABELS = {
|
|
14
|
+
(0.0...0.2) => :trivial,
|
|
15
|
+
(0.2...0.4) => :simple,
|
|
16
|
+
(0.4...0.6) => :moderate,
|
|
17
|
+
(0.6...0.8) => :complex,
|
|
18
|
+
(0.8..1.0) => :labyrinthine
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
DANGER_LABELS = {
|
|
22
|
+
(0.0...0.25) => :safe,
|
|
23
|
+
(0.25...0.5) => :uncertain,
|
|
24
|
+
(0.5...0.75) => :hazardous,
|
|
25
|
+
(0.75..1.0) => :lethal
|
|
26
|
+
}.freeze
|
|
27
|
+
|
|
28
|
+
DEFAULT_DANGER_LEVEL = 0.0
|
|
29
|
+
MINOTAUR_DANGER_LEVEL = 0.9
|
|
30
|
+
DEAD_END_COMPLEXITY_HIT = 0.1
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveLabyrinth
|
|
6
|
+
module Helpers
|
|
7
|
+
class Labyrinth
|
|
8
|
+
attr_reader :labyrinth_id, :name, :domain, :nodes, :breadcrumbs, :current_node_id
|
|
9
|
+
attr_accessor :solved
|
|
10
|
+
|
|
11
|
+
def initialize(labyrinth_id:, name:, domain: nil)
|
|
12
|
+
@labyrinth_id = labyrinth_id
|
|
13
|
+
@name = name
|
|
14
|
+
@domain = domain
|
|
15
|
+
@nodes = {}
|
|
16
|
+
@breadcrumbs = []
|
|
17
|
+
@current_node_id = nil
|
|
18
|
+
@solved = false
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def add_node(node)
|
|
22
|
+
raise ArgumentError, "nodes must be a #{Node}" unless node.is_a?(Node)
|
|
23
|
+
raise ArgumentError, "max nodes (#{Constants::MAX_NODES}) reached" if @nodes.size >= Constants::MAX_NODES
|
|
24
|
+
|
|
25
|
+
@nodes[node.node_id] = node
|
|
26
|
+
@current_node_id ||= node.node_id if node.node_type == :entrance
|
|
27
|
+
node
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def move_to!(node_id)
|
|
31
|
+
node = @nodes.fetch(node_id) { raise ArgumentError, "node #{node_id.inspect} not found in labyrinth" }
|
|
32
|
+
|
|
33
|
+
current = current_node
|
|
34
|
+
unless current.nil? || current.connections.include?(node_id)
|
|
35
|
+
raise ArgumentError, "cannot move to #{node_id.inspect}: not connected from current node #{@current_node_id.inspect}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
drop_breadcrumb
|
|
39
|
+
node.visited = true
|
|
40
|
+
@current_node_id = node_id
|
|
41
|
+
@solved = true if node.node_type == :exit
|
|
42
|
+
node
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def backtrack!
|
|
46
|
+
return nil if @breadcrumbs.empty?
|
|
47
|
+
|
|
48
|
+
target_id = @breadcrumbs.pop
|
|
49
|
+
@current_node_id = target_id
|
|
50
|
+
current_node
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def drop_breadcrumb
|
|
54
|
+
return unless @current_node_id
|
|
55
|
+
|
|
56
|
+
current = @nodes[@current_node_id]
|
|
57
|
+
current.visited = true if current
|
|
58
|
+
@breadcrumbs << @current_node_id if @breadcrumbs.last != @current_node_id
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def follow_thread
|
|
62
|
+
return nil if current_node.nil?
|
|
63
|
+
|
|
64
|
+
unvisited = current_node.connections.find do |conn_id|
|
|
65
|
+
node = @nodes[conn_id]
|
|
66
|
+
node && !node.visited
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
return nil unless unvisited
|
|
70
|
+
|
|
71
|
+
move_to!(unvisited)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def solved?
|
|
75
|
+
@solved
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def lost?
|
|
79
|
+
return false if @current_node_id.nil?
|
|
80
|
+
return false if solved?
|
|
81
|
+
|
|
82
|
+
current = current_node
|
|
83
|
+
return true if current.nil?
|
|
84
|
+
|
|
85
|
+
unvisited_exits = current.connections.any? do |conn_id|
|
|
86
|
+
node = @nodes[conn_id]
|
|
87
|
+
node && !node.visited
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
!unvisited_exits && !current.dead_end? && @breadcrumbs.empty?
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def path_length
|
|
94
|
+
@breadcrumbs.size
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def current_node
|
|
98
|
+
@nodes[@current_node_id]
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def complexity
|
|
102
|
+
return 0.0 if @nodes.empty?
|
|
103
|
+
|
|
104
|
+
dead_end_count = @nodes.values.count(&:dead_end?)
|
|
105
|
+
junction_count = @nodes.values.count(&:junction?)
|
|
106
|
+
minotaur_count = @nodes.values.count { |n| n.node_type == :minotaur_lair }
|
|
107
|
+
|
|
108
|
+
raw = ((dead_end_count * 0.3) + (junction_count * 0.2) + (minotaur_count * 0.5)) / @nodes.size.to_f
|
|
109
|
+
raw.clamp(0.0, 1.0).round(10)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def complexity_label
|
|
113
|
+
score = complexity
|
|
114
|
+
Constants::COMPLEXITY_LABELS.find { |range, _| range.cover?(score) }&.last || :labyrinthine
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def to_h
|
|
118
|
+
{
|
|
119
|
+
labyrinth_id: @labyrinth_id,
|
|
120
|
+
name: @name,
|
|
121
|
+
domain: @domain,
|
|
122
|
+
node_count: @nodes.size,
|
|
123
|
+
current_node_id: @current_node_id,
|
|
124
|
+
path_length: path_length,
|
|
125
|
+
solved: @solved,
|
|
126
|
+
complexity: complexity,
|
|
127
|
+
complexity_label: complexity_label
|
|
128
|
+
}
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveLabyrinth
|
|
6
|
+
module Helpers
|
|
7
|
+
class LabyrinthEngine
|
|
8
|
+
attr_reader :labyrinths
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@labyrinths = {}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def create_labyrinth(name:, domain: nil, labyrinth_id: nil)
|
|
15
|
+
raise ArgumentError, "max labyrinths (#{Constants::MAX_LABYRINTHS}) reached" if @labyrinths.size >= Constants::MAX_LABYRINTHS
|
|
16
|
+
|
|
17
|
+
id = labyrinth_id || SecureRandom.uuid
|
|
18
|
+
labyrinth = Labyrinth.new(labyrinth_id: id, name: name, domain: domain)
|
|
19
|
+
@labyrinths[id] = labyrinth
|
|
20
|
+
Legion::Logging.debug "[cognitive_labyrinth] created labyrinth id=#{id[0..7]} name=#{name}"
|
|
21
|
+
labyrinth
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def add_node_to(labyrinth_id:, node_type:, content: nil, danger_level: nil, node_id: nil)
|
|
25
|
+
labyrinth = fetch_labyrinth!(labyrinth_id)
|
|
26
|
+
id = node_id || SecureRandom.uuid
|
|
27
|
+
level = (danger_level || default_danger_for(node_type)).clamp(0.0, 1.0)
|
|
28
|
+
|
|
29
|
+
node = Node.new(node_id: id, node_type: node_type, content: content, danger_level: level)
|
|
30
|
+
labyrinth.add_node(node)
|
|
31
|
+
Legion::Logging.debug "[cognitive_labyrinth] added node #{id[0..7]} type=#{node_type} to labyrinth #{labyrinth_id[0..7]}"
|
|
32
|
+
node
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def connect_nodes(labyrinth_id:, from_id:, to_id:, bidirectional: true)
|
|
36
|
+
labyrinth = fetch_labyrinth!(labyrinth_id)
|
|
37
|
+
from_node = fetch_node!(labyrinth, from_id)
|
|
38
|
+
to_node = fetch_node!(labyrinth, to_id)
|
|
39
|
+
|
|
40
|
+
from_node.connect!(to_id)
|
|
41
|
+
to_node.connect!(from_id) if bidirectional
|
|
42
|
+
{ connected: true, from: from_id, to: to_id, bidirectional: bidirectional }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def move(labyrinth_id:, node_id:, **)
|
|
46
|
+
labyrinth = fetch_labyrinth!(labyrinth_id)
|
|
47
|
+
node = labyrinth.move_to!(node_id)
|
|
48
|
+
|
|
49
|
+
minotaur_result = check_minotaur(labyrinth_id: labyrinth_id)
|
|
50
|
+
Legion::Logging.debug "[cognitive_labyrinth] moved to #{node_id[0..7]} type=#{node.node_type}"
|
|
51
|
+
|
|
52
|
+
{
|
|
53
|
+
success: true,
|
|
54
|
+
node_id: node.node_id,
|
|
55
|
+
node_type: node.node_type,
|
|
56
|
+
content: node.content,
|
|
57
|
+
danger_level: node.danger_level,
|
|
58
|
+
solved: labyrinth.solved?,
|
|
59
|
+
minotaur: minotaur_result,
|
|
60
|
+
path_length: labyrinth.path_length
|
|
61
|
+
}
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def backtrack(labyrinth_id:, **)
|
|
65
|
+
labyrinth = fetch_labyrinth!(labyrinth_id)
|
|
66
|
+
node = labyrinth.backtrack!
|
|
67
|
+
|
|
68
|
+
if node
|
|
69
|
+
Legion::Logging.debug "[cognitive_labyrinth] backtracked to #{node.node_id[0..7]}"
|
|
70
|
+
{ success: true, node_id: node.node_id, node_type: node.node_type, path_length: labyrinth.path_length }
|
|
71
|
+
else
|
|
72
|
+
Legion::Logging.debug '[cognitive_labyrinth] backtrack failed: no breadcrumbs'
|
|
73
|
+
{ success: false, reason: :no_breadcrumbs }
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def follow_thread(labyrinth_id:, **)
|
|
78
|
+
labyrinth = fetch_labyrinth!(labyrinth_id)
|
|
79
|
+
node = labyrinth.follow_thread
|
|
80
|
+
|
|
81
|
+
if node
|
|
82
|
+
minotaur_result = check_minotaur(labyrinth_id: labyrinth_id)
|
|
83
|
+
Legion::Logging.debug "[cognitive_labyrinth] thread followed to #{node.node_id[0..7]}"
|
|
84
|
+
{
|
|
85
|
+
success: true,
|
|
86
|
+
node_id: node.node_id,
|
|
87
|
+
node_type: node.node_type,
|
|
88
|
+
content: node.content,
|
|
89
|
+
danger_level: node.danger_level,
|
|
90
|
+
solved: labyrinth.solved?,
|
|
91
|
+
minotaur: minotaur_result
|
|
92
|
+
}
|
|
93
|
+
else
|
|
94
|
+
Legion::Logging.debug '[cognitive_labyrinth] thread exhausted: no unvisited nodes from current'
|
|
95
|
+
{ success: false, reason: :thread_exhausted }
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def check_minotaur(labyrinth_id:, **)
|
|
100
|
+
labyrinth = fetch_labyrinth!(labyrinth_id)
|
|
101
|
+
node = labyrinth.current_node
|
|
102
|
+
return { encountered: false } unless node
|
|
103
|
+
|
|
104
|
+
if node.node_type == :minotaur_lair
|
|
105
|
+
Legion::Logging.debug "[cognitive_labyrinth] MINOTAUR ENCOUNTERED at #{node.node_id[0..7]}"
|
|
106
|
+
{
|
|
107
|
+
encountered: true,
|
|
108
|
+
node_id: node.node_id,
|
|
109
|
+
danger_level: node.danger_level,
|
|
110
|
+
danger_label: danger_label_for(node.danger_level),
|
|
111
|
+
misconception: node.content
|
|
112
|
+
}
|
|
113
|
+
elsif node.dangerous?
|
|
114
|
+
{
|
|
115
|
+
encountered: false,
|
|
116
|
+
warning: true,
|
|
117
|
+
danger_level: node.danger_level,
|
|
118
|
+
danger_label: danger_label_for(node.danger_level)
|
|
119
|
+
}
|
|
120
|
+
else
|
|
121
|
+
{ encountered: false }
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def labyrinth_report(labyrinth_id:, **)
|
|
126
|
+
labyrinth = fetch_labyrinth!(labyrinth_id)
|
|
127
|
+
nodes_by_type = labyrinth.nodes.values.group_by(&:node_type).transform_values(&:count)
|
|
128
|
+
|
|
129
|
+
labyrinth.to_h.merge(
|
|
130
|
+
nodes_by_type: nodes_by_type,
|
|
131
|
+
visited_count: labyrinth.nodes.values.count(&:visited),
|
|
132
|
+
breadcrumb_trail: labyrinth.breadcrumbs.dup,
|
|
133
|
+
lost: labyrinth.lost?
|
|
134
|
+
)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def list_labyrinths(**)
|
|
138
|
+
@labyrinths.values.map(&:to_h)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def delete_labyrinth(labyrinth_id:, **)
|
|
142
|
+
raise ArgumentError, "labyrinth #{labyrinth_id.inspect} not found" unless @labyrinths.key?(labyrinth_id)
|
|
143
|
+
|
|
144
|
+
@labyrinths.delete(labyrinth_id)
|
|
145
|
+
{ deleted: true, labyrinth_id: labyrinth_id }
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
private
|
|
149
|
+
|
|
150
|
+
def fetch_labyrinth!(labyrinth_id)
|
|
151
|
+
@labyrinths.fetch(labyrinth_id) do
|
|
152
|
+
raise ArgumentError, "labyrinth #{labyrinth_id.inspect} not found"
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def fetch_node!(labyrinth, node_id)
|
|
157
|
+
labyrinth.nodes.fetch(node_id) do
|
|
158
|
+
raise ArgumentError, "node #{node_id.inspect} not found in labyrinth #{labyrinth.labyrinth_id.inspect}"
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def default_danger_for(node_type)
|
|
163
|
+
node_type == :minotaur_lair ? Constants::MINOTAUR_DANGER_LEVEL : Constants::DEFAULT_DANGER_LEVEL
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def danger_label_for(level)
|
|
167
|
+
Constants::DANGER_LABELS.find { |range, _| range.cover?(level) }&.last || :lethal
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveLabyrinth
|
|
6
|
+
module Helpers
|
|
7
|
+
class Node
|
|
8
|
+
attr_reader :node_id, :node_type, :content, :connections, :danger_level
|
|
9
|
+
attr_accessor :visited
|
|
10
|
+
|
|
11
|
+
def initialize(node_id:, node_type:, content: nil, danger_level: Constants::DEFAULT_DANGER_LEVEL)
|
|
12
|
+
unless Constants::NODE_TYPES.include?(node_type)
|
|
13
|
+
raise ArgumentError, "unknown node_type: #{node_type.inspect}; must be one of #{Constants::NODE_TYPES.inspect}"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
@node_id = node_id
|
|
17
|
+
@node_type = node_type
|
|
18
|
+
@content = content
|
|
19
|
+
@connections = []
|
|
20
|
+
@visited = false
|
|
21
|
+
@danger_level = danger_level.clamp(0.0, 1.0)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def connect!(other_id)
|
|
25
|
+
@connections << other_id unless @connections.include?(other_id)
|
|
26
|
+
self
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def disconnect!(other_id)
|
|
30
|
+
@connections.delete(other_id)
|
|
31
|
+
self
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def dead_end?
|
|
35
|
+
@node_type == :dead_end || (@connections.empty? && @node_type != :entrance && @node_type != :exit)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def junction?
|
|
39
|
+
@node_type == :junction || @connections.size >= 3
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def dangerous?
|
|
43
|
+
@node_type == :minotaur_lair || @danger_level >= 0.5
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def to_h
|
|
47
|
+
{
|
|
48
|
+
node_id: @node_id,
|
|
49
|
+
node_type: @node_type,
|
|
50
|
+
content: @content,
|
|
51
|
+
connections: @connections.dup,
|
|
52
|
+
visited: @visited,
|
|
53
|
+
danger_level: @danger_level
|
|
54
|
+
}
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveLabyrinth
|
|
6
|
+
module Runners
|
|
7
|
+
module CognitiveLabyrinth
|
|
8
|
+
extend self
|
|
9
|
+
|
|
10
|
+
include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
|
|
11
|
+
|
|
12
|
+
def create_labyrinth(name:, domain: nil, labyrinth_id: nil, engine: nil, **)
|
|
13
|
+
raise ArgumentError, 'name is required' if name.nil? || name.to_s.strip.empty?
|
|
14
|
+
|
|
15
|
+
result = resolve_engine(engine).create_labyrinth(name: name, domain: domain, labyrinth_id: labyrinth_id)
|
|
16
|
+
Legion::Logging.debug "[cognitive_labyrinth] runner: created labyrinth #{result.labyrinth_id[0..7]}"
|
|
17
|
+
{ success: true, labyrinth_id: result.labyrinth_id, name: result.name, domain: result.domain }
|
|
18
|
+
rescue ArgumentError => e
|
|
19
|
+
Legion::Logging.debug "[cognitive_labyrinth] create_labyrinth error: #{e.message}"
|
|
20
|
+
{ success: false, error: e.message }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def add_node(labyrinth_id:, node_type:, content: nil, danger_level: nil, node_id: nil, engine: nil, **)
|
|
24
|
+
raise ArgumentError, 'labyrinth_id is required' if labyrinth_id.nil?
|
|
25
|
+
raise ArgumentError, 'node_type is required' if node_type.nil?
|
|
26
|
+
|
|
27
|
+
node = resolve_engine(engine).add_node_to(
|
|
28
|
+
labyrinth_id: labyrinth_id, node_type: node_type,
|
|
29
|
+
content: content, danger_level: danger_level, node_id: node_id
|
|
30
|
+
)
|
|
31
|
+
{ success: true, node_id: node.node_id, node_type: node.node_type }
|
|
32
|
+
rescue ArgumentError => e
|
|
33
|
+
Legion::Logging.debug "[cognitive_labyrinth] add_node error: #{e.message}"
|
|
34
|
+
{ success: false, error: e.message }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def connect_nodes(labyrinth_id:, from_id:, to_id:, bidirectional: true, engine: nil, **)
|
|
38
|
+
raise ArgumentError, 'labyrinth_id is required' if labyrinth_id.nil?
|
|
39
|
+
|
|
40
|
+
resolve_engine(engine).connect_nodes(
|
|
41
|
+
labyrinth_id: labyrinth_id, from_id: from_id, to_id: to_id, bidirectional: bidirectional
|
|
42
|
+
)
|
|
43
|
+
rescue ArgumentError => e
|
|
44
|
+
Legion::Logging.debug "[cognitive_labyrinth] connect_nodes error: #{e.message}"
|
|
45
|
+
{ success: false, error: e.message }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def move(labyrinth_id:, node_id:, engine: nil, **)
|
|
49
|
+
raise ArgumentError, 'labyrinth_id is required' if labyrinth_id.nil?
|
|
50
|
+
raise ArgumentError, 'node_id is required' if node_id.nil?
|
|
51
|
+
|
|
52
|
+
resolve_engine(engine).move(labyrinth_id: labyrinth_id, node_id: node_id)
|
|
53
|
+
rescue ArgumentError => e
|
|
54
|
+
Legion::Logging.debug "[cognitive_labyrinth] move error: #{e.message}"
|
|
55
|
+
{ success: false, error: e.message }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def backtrack(labyrinth_id:, engine: nil, **)
|
|
59
|
+
raise ArgumentError, 'labyrinth_id is required' if labyrinth_id.nil?
|
|
60
|
+
|
|
61
|
+
resolve_engine(engine).backtrack(labyrinth_id: labyrinth_id)
|
|
62
|
+
rescue ArgumentError => e
|
|
63
|
+
Legion::Logging.debug "[cognitive_labyrinth] backtrack error: #{e.message}"
|
|
64
|
+
{ success: false, error: e.message }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def follow_thread(labyrinth_id:, engine: nil, **)
|
|
68
|
+
raise ArgumentError, 'labyrinth_id is required' if labyrinth_id.nil?
|
|
69
|
+
|
|
70
|
+
resolve_engine(engine).follow_thread(labyrinth_id: labyrinth_id)
|
|
71
|
+
rescue ArgumentError => e
|
|
72
|
+
Legion::Logging.debug "[cognitive_labyrinth] follow_thread error: #{e.message}"
|
|
73
|
+
{ success: false, error: e.message }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def check_minotaur(labyrinth_id:, engine: nil, **)
|
|
77
|
+
raise ArgumentError, 'labyrinth_id is required' if labyrinth_id.nil?
|
|
78
|
+
|
|
79
|
+
resolve_engine(engine).check_minotaur(labyrinth_id: labyrinth_id)
|
|
80
|
+
rescue ArgumentError => e
|
|
81
|
+
Legion::Logging.debug "[cognitive_labyrinth] check_minotaur error: #{e.message}"
|
|
82
|
+
{ success: false, error: e.message }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def labyrinth_report(labyrinth_id:, engine: nil, **)
|
|
86
|
+
raise ArgumentError, 'labyrinth_id is required' if labyrinth_id.nil?
|
|
87
|
+
|
|
88
|
+
result = resolve_engine(engine).labyrinth_report(labyrinth_id: labyrinth_id)
|
|
89
|
+
{ success: true }.merge(result)
|
|
90
|
+
rescue ArgumentError => e
|
|
91
|
+
Legion::Logging.debug "[cognitive_labyrinth] labyrinth_report error: #{e.message}"
|
|
92
|
+
{ success: false, error: e.message }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def list_labyrinths(engine: nil, **)
|
|
96
|
+
result = resolve_engine(engine).list_labyrinths
|
|
97
|
+
{ success: true, labyrinths: result, count: result.size }
|
|
98
|
+
rescue ArgumentError => e
|
|
99
|
+
{ success: false, error: e.message }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def delete_labyrinth(labyrinth_id:, engine: nil, **)
|
|
103
|
+
raise ArgumentError, 'labyrinth_id is required' if labyrinth_id.nil?
|
|
104
|
+
|
|
105
|
+
resolve_engine(engine).delete_labyrinth(labyrinth_id: labyrinth_id)
|
|
106
|
+
rescue ArgumentError => e
|
|
107
|
+
Legion::Logging.debug "[cognitive_labyrinth] delete_labyrinth error: #{e.message}"
|
|
108
|
+
{ success: false, error: e.message }
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
private
|
|
112
|
+
|
|
113
|
+
def labyrinth_engine
|
|
114
|
+
@labyrinth_engine ||= Helpers::LabyrinthEngine.new
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def resolve_engine(engine)
|
|
118
|
+
engine || labyrinth_engine
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
require_relative 'cognitive_labyrinth/version'
|
|
6
|
+
require_relative 'cognitive_labyrinth/helpers/constants'
|
|
7
|
+
require_relative 'cognitive_labyrinth/helpers/node'
|
|
8
|
+
require_relative 'cognitive_labyrinth/helpers/labyrinth'
|
|
9
|
+
require_relative 'cognitive_labyrinth/helpers/labyrinth_engine'
|
|
10
|
+
require_relative 'cognitive_labyrinth/runners/cognitive_labyrinth'
|
|
11
|
+
require_relative 'cognitive_labyrinth/client'
|
|
12
|
+
|
|
13
|
+
module Legion
|
|
14
|
+
module Extensions
|
|
15
|
+
module CognitiveLabyrinth
|
|
16
|
+
extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: lex-cognitive-labyrinth
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Esity
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: legion-gaia
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :development
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
description: Maze-like problem spaces for agentic reasoning — paths, dead ends, backtracking,
|
|
27
|
+
breadcrumb trails, Ariadne's thread (guiding heuristic), and Minotaur encounters
|
|
28
|
+
(dangerous misconceptions)
|
|
29
|
+
email:
|
|
30
|
+
- matthewdiverson@gmail.com
|
|
31
|
+
executables: []
|
|
32
|
+
extensions: []
|
|
33
|
+
extra_rdoc_files: []
|
|
34
|
+
files:
|
|
35
|
+
- ".github/workflows/ci.yml"
|
|
36
|
+
- ".gitignore"
|
|
37
|
+
- ".rspec"
|
|
38
|
+
- ".rubocop.yml"
|
|
39
|
+
- CLAUDE.md
|
|
40
|
+
- Gemfile
|
|
41
|
+
- README.md
|
|
42
|
+
- lex-cognitive-labyrinth.gemspec
|
|
43
|
+
- lib/legion/extensions/cognitive_labyrinth.rb
|
|
44
|
+
- lib/legion/extensions/cognitive_labyrinth/actors/thread_walker.rb
|
|
45
|
+
- lib/legion/extensions/cognitive_labyrinth/client.rb
|
|
46
|
+
- lib/legion/extensions/cognitive_labyrinth/helpers/constants.rb
|
|
47
|
+
- lib/legion/extensions/cognitive_labyrinth/helpers/labyrinth.rb
|
|
48
|
+
- lib/legion/extensions/cognitive_labyrinth/helpers/labyrinth_engine.rb
|
|
49
|
+
- lib/legion/extensions/cognitive_labyrinth/helpers/node.rb
|
|
50
|
+
- lib/legion/extensions/cognitive_labyrinth/runners/cognitive_labyrinth.rb
|
|
51
|
+
- lib/legion/extensions/cognitive_labyrinth/version.rb
|
|
52
|
+
homepage: https://github.com/LegionIO/lex-cognitive-labyrinth
|
|
53
|
+
licenses:
|
|
54
|
+
- MIT
|
|
55
|
+
metadata:
|
|
56
|
+
homepage_uri: https://github.com/LegionIO/lex-cognitive-labyrinth
|
|
57
|
+
source_code_uri: https://github.com/LegionIO/lex-cognitive-labyrinth
|
|
58
|
+
documentation_uri: https://github.com/LegionIO/lex-cognitive-labyrinth
|
|
59
|
+
changelog_uri: https://github.com/LegionIO/lex-cognitive-labyrinth
|
|
60
|
+
bug_tracker_uri: https://github.com/LegionIO/lex-cognitive-labyrinth/issues
|
|
61
|
+
rubygems_mfa_required: 'true'
|
|
62
|
+
rdoc_options: []
|
|
63
|
+
require_paths:
|
|
64
|
+
- lib
|
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
66
|
+
requirements:
|
|
67
|
+
- - ">="
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: '3.4'
|
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0'
|
|
75
|
+
requirements: []
|
|
76
|
+
rubygems_version: 3.6.9
|
|
77
|
+
specification_version: 4
|
|
78
|
+
summary: LEX Cognitive Labyrinth
|
|
79
|
+
test_files: []
|