Class: RobotLab::To::Guards::Checkpoint
- Inherits:
-
Hook
- Object
- Hook
- RobotLab::To::Guards::Checkpoint
- Defined in:
- lib/robot_lab/to/guards/checkpoint.rb
Overview
First-write-wins file snapshot before Write/Edit, ported from little-coder checkpoint. Finer-grained than robot_lab-to's git rollback: it survives WITHIN an iteration, so the model (or a future repair step) can revert a single botched edit without discarding the iteration's good work. Best-effort — never raises into the tool path.
Backups land under <run_dir>/checkpoints/, falling back to a temp dir
when no Run is supplied. Files that did not exist get a .absent
sentinel. The snapshotted-set spans calls, so it lives in RunStore.
Constant Summary collapse
- KEY =
:robot_lab_to_checkpoint- MUTATING_TOOLS =
%w[write edit].freeze
Class Method Summary collapse
- .before_run(_ctx) ⇒ Object
- .before_tool_call(ctx) ⇒ Object
-
.checkpoint_dir(ctx) ⇒ String
Directory for this run's checkpoints (created).
-
.safe_name(path) ⇒ String
Flatten an absolute path into a single safe filename.
-
.snapshot(path, dir) ⇒ String?
Copy
path's current bytes intodir, once per run (first-write wins).
Class Method Details
.before_run(_ctx) ⇒ Object
27 28 29 |
# File 'lib/robot_lab/to/guards/checkpoint.rb', line 27 def before_run(_ctx) RunStore.reset(KEY, []) end |
.before_tool_call(ctx) ⇒ Object
31 32 33 34 35 36 37 38 |
# File 'lib/robot_lab/to/guards/checkpoint.rb', line 31 def before_tool_call(ctx) return unless MUTATING_TOOLS.include?(ctx.tool_name.to_s.downcase) path = PathResolution.resolve(ctx.tool_args) return unless path snapshot(path, checkpoint_dir(ctx)) end |
.checkpoint_dir(ctx) ⇒ String
Returns directory for this run's checkpoints (created).
64 65 66 67 68 69 70 |
# File 'lib/robot_lab/to/guards/checkpoint.rb', line 64 def checkpoint_dir(ctx) run = ctx.local.run base = run.respond_to?(:run_dir) ? run.run_dir : Dir.tmpdir dir = File.join(base.to_s, "checkpoints") FileUtils.mkdir_p(dir) dir end |
.safe_name(path) ⇒ String
Flatten an absolute path into a single safe filename.
76 77 78 79 |
# File 'lib/robot_lab/to/guards/checkpoint.rb', line 76 def safe_name(path) flat = path.gsub(/[^A-Za-z0-9._-]/, "_") flat.length > 200 ? flat[-200..] : flat end |
.snapshot(path, dir) ⇒ String?
Copy path's current bytes into dir, once per run (first-write
wins). Swallows all I/O errors — checkpointing must never break a
tool call.
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/robot_lab/to/guards/checkpoint.rb', line 45 def snapshot(path, dir) tracked = RunStore.fetch(KEY, []) return if tracked.include?(path) tracked << path FileUtils.mkdir_p(dir) dest = File.join(dir, safe_name(path)) if File.exist?(path) FileUtils.cp(path, dest) dest else File.write("#{dest}.absent", "") "#{dest}.absent" end rescue SystemCallError, IOError nil end |