Class: RobotLab::To::DecisionManager

Inherits:
Object
  • Object
show all
Defined in:
lib/robot_lab/to/decision_manager.rb

Overview

Reads and writes decision files under <run_dir>/decisions/.

Parallels NotesManager: the orchestrator records decisions the robot raises and queries their status each iteration; a human resolves them by editing the file between iterations (or between cron ticks).

Each decision is a markdown file with a YAML front matter block:

---
id: d-20260701-143022-a1b2c3
status: pending
blocking: true
created_at: 2026-07-01T14:30:22Z
created_iteration: 7
resolved_at:
resolution:
---
# Decision: <question>
...

All writes go through AtomicFile so a crash never leaves a torn file.

Constant Summary collapse

GLOB =
"d-*.md"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(dir) ⇒ DecisionManager

Returns a new instance of DecisionManager.



34
35
36
# File 'lib/robot_lab/to/decision_manager.rb', line 34

def initialize(dir)
  @dir = Pathname.new(dir)
end

Instance Attribute Details

#dirObject (readonly)

Returns the value of attribute dir.



32
33
34
# File 'lib/robot_lab/to/decision_manager.rb', line 32

def dir
  @dir
end

Instance Method Details

#allObject

All decisions on disk, oldest first (ids sort chronologically).



53
54
55
# File 'lib/robot_lab/to/decision_manager.rb', line 53

def all
  Dir.glob(@dir.join(GLOB)).map { |p| parse(p) }.compact
end

#blocking_pendingObject



60
# File 'lib/robot_lab/to/decision_manager.rb', line 60

def blocking_pending = all.select { |d| d.pending? && d.blocking? }

#blocking_pending?Boolean

Returns:

  • (Boolean)


61
# File 'lib/robot_lab/to/decision_manager.rb', line 61

def blocking_pending? = blocking_pending.any?

#close(decision) ⇒ Object

Mark a resolved decision closed: its resolution has been delivered to a robot and committed, so it should no longer be re-injected. Flips only the status line, preserving whatever the human wrote in the body.



69
70
71
72
# File 'lib/robot_lab/to/decision_manager.rb', line 69

def close(decision)
  raw = File.read(decision.path)
  AtomicFile.write(decision.path, flip_status(raw, "closed"))
end

#pendingObject



57
58
# File 'lib/robot_lab/to/decision_manager.rb', line 57

def pending       = all.select(&:pending?)
# resolved but not yet closed

#record(question:, situation: "", options: [], recommendation: "", blocking: false, iteration: 0) ⇒ Object

Persist a decision the robot raised. Returns the parsed Decision.



43
44
45
46
47
48
49
50
# File 'lib/robot_lab/to/decision_manager.rb', line 43

def record(question:, situation: "", options: [], recommendation: "", blocking: false, iteration: 0)
  id   = generate_id
  path = @dir.join("#{id}.md")
  AtomicFile.write(path, render_new(id: id, question: question, situation: situation,
                                    options: Array(options), recommendation: recommendation.to_s,
                                    blocking: blocking ? true : false, iteration: iteration.to_i))
  parse(path)
end

#reload(decision) ⇒ Object

Re-read a single decision from disk (a human may have edited it).



64
# File 'lib/robot_lab/to/decision_manager.rb', line 64

def reload(decision) = parse(decision.path)

#resolved_openObject

resolved but not yet closed



59
# File 'lib/robot_lab/to/decision_manager.rb', line 59

def resolved_open = all.select(&:resolved?)

#setupObject



38
39
40
# File 'lib/robot_lab/to/decision_manager.rb', line 38

def setup
  @dir.mkpath
end