Class: Ask::Agent::CheckpointStore
- Inherits:
-
Object
- Object
- Ask::Agent::CheckpointStore
- Defined in:
- lib/ask/agent/checkpoint_store.rb
Overview
Versioned, durable session checkpoints on any State::Adapter.
Every checkpoint is a full session snapshot (the same payload
Session#save writes) stored under a sequential key. The adapter only
needs the minimal KV contract — get, set, delete — so this works
with every state provider (SQLite, Redis, Postgres, MySQL) and with
custom adapters that implement nothing else. No list primitives are
required.
Keys (session id + suffix):
"<id>:checkpoint:<seq>" — one key per checkpoint
"<id>:checkpoint:head" — the current seq (the active timeline)
Rolling back moves the head pointer; later checkpoints are kept, so a session can roll forward again (time travel). Forking copies the checkpoints up to a point into a new session id — the branch diverges from there.
Constant Summary collapse
- CHECKPOINT_KEY =
":checkpoint:"- HEAD_KEY =
":checkpoint:head"
Instance Attribute Summary collapse
-
#state ⇒ Ask::State::Adapter
readonly
The underlying adapter.
Instance Method Summary collapse
-
#checkpoint(session_id, data) ⇒ Integer
Append a checkpoint.
-
#delete(session_id) ⇒ void
Remove every checkpoint for a session.
-
#fork(session_id, at_seq: nil, new_id: SecureRandom.uuid) ⇒ String
Copy the checkpoints up to
at_seqinto a new session id — a branch that diverges from that point. -
#head(session_id) ⇒ Integer?
Current head seq, or nil when the session has no checkpoints.
-
#history(session_id) ⇒ Array<Integer>
All checkpoint seqs, oldest first.
-
#initialize(state_adapter) ⇒ CheckpointStore
constructor
A new instance of CheckpointStore.
-
#load(session_id, seq: nil) ⇒ Hash?
Load a checkpoint's data.
-
#rollback(session_id, seq) ⇒ Integer
Move the head pointer to an earlier (or later) checkpoint.
Constructor Details
#initialize(state_adapter) ⇒ CheckpointStore
Returns a new instance of CheckpointStore.
29 30 31 |
# File 'lib/ask/agent/checkpoint_store.rb', line 29 def initialize(state_adapter) @state = state_adapter end |
Instance Attribute Details
#state ⇒ Ask::State::Adapter (readonly)
Returns the underlying adapter.
34 35 36 |
# File 'lib/ask/agent/checkpoint_store.rb', line 34 def state @state end |
Instance Method Details
#checkpoint(session_id, data) ⇒ Integer
Append a checkpoint. The new checkpoint becomes the head.
41 42 43 44 45 46 |
# File 'lib/ask/agent/checkpoint_store.rb', line 41 def checkpoint(session_id, data) seq = (head(session_id) || 0) + 1 @state.set(checkpoint_key(session_id, seq), data) @state.set(head_key(session_id), seq) seq end |
#delete(session_id) ⇒ void
This method returns an undefined value.
Remove every checkpoint for a session.
114 115 116 117 118 119 120 |
# File 'lib/ask/agent/checkpoint_store.rb', line 114 def delete(session_id) history(session_id).each do |seq| @state.delete(checkpoint_key(session_id, seq)) end @state.delete(head_key(session_id)) nil end |
#fork(session_id, at_seq: nil, new_id: SecureRandom.uuid) ⇒ String
Copy the checkpoints up to at_seq into a new session id — a branch
that diverges from that point.
98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/ask/agent/checkpoint_store.rb', line 98 def fork(session_id, at_seq: nil, new_id: SecureRandom.uuid) at_seq ||= head(session_id) raise ArgumentError, "no checkpoint #{at_seq}" unless at_seq && @state.get(checkpoint_key(session_id, at_seq)) (1..at_seq).each do |seq| data = @state.get(checkpoint_key(session_id, seq)) @state.set(checkpoint_key(new_id, seq), data) end @state.set(head_key(new_id), at_seq) new_id end |
#head(session_id) ⇒ Integer?
Returns current head seq, or nil when the session has no checkpoints.
51 52 53 |
# File 'lib/ask/agent/checkpoint_store.rb', line 51 def head(session_id) @state.get(head_key(session_id)) end |
#history(session_id) ⇒ Array<Integer>
Returns all checkpoint seqs, oldest first.
57 58 59 60 |
# File 'lib/ask/agent/checkpoint_store.rb', line 57 def history(session_id) current = head(session_id) current ? (1..current).to_a : [] end |
#load(session_id, seq: nil) ⇒ Hash?
Load a checkpoint's data.
67 68 69 70 71 72 |
# File 'lib/ask/agent/checkpoint_store.rb', line 67 def load(session_id, seq: nil) seq ||= head(session_id) return nil unless seq @state.get(checkpoint_key(session_id, seq)) end |
#rollback(session_id, seq) ⇒ Integer
Move the head pointer to an earlier (or later) checkpoint. Later checkpoints are kept so the session can roll forward again.
81 82 83 84 85 86 |
# File 'lib/ask/agent/checkpoint_store.rb', line 81 def rollback(session_id, seq) raise ArgumentError, "no checkpoint #{seq}" unless @state.get(checkpoint_key(session_id, seq)) @state.set(head_key(session_id), seq) seq end |