Module: Xeno::Inputs
- Defined in:
- lib/xeno/inputs.rb
Overview
Resolving parked work: approvals, denials, and question answers. Each resolution updates the action and — once no other action on the turn is still awaiting input — flips the turn back to pending and enqueues the resume job. A parked session holds zero compute; this is the only way back in.
Class Method Summary collapse
- .answer!(action, answer, principal: nil) ⇒ Object
- .approve!(action, principal: nil) ⇒ Object
- .deny!(action, reason: nil, principal: nil) ⇒ Object
-
.resolve(action) ⇒ Object
Shared plumbing: guard the state, apply the resolution, resume when the turn has nothing else to wait for.
-
.resume_turn(turn) ⇒ Object
Resuming with unanswered inputs would generate against partial tool results (an invalid provider state) — the runner would just re-park, so don't bother waking it until everything is resolved.
Class Method Details
.answer!(action, answer, principal: nil) ⇒ Object
28 29 30 31 32 33 34 35 36 37 38 39 |
# File 'lib/xeno/inputs.rb', line 28 def answer!(action, answer, principal: nil) raise ArgumentError, "not a question: #{action.tool_name}" unless action.kind == "question" resolve(action) do action.update!( status: "completed", output: { "content" => JSON.generate({ answer: answer }) }, resolved_at: Time.current, resolved_by: principal ) end end |
.approve!(action, principal: nil) ⇒ Object
10 11 12 13 14 |
# File 'lib/xeno/inputs.rb', line 10 def approve!(action, principal: nil) resolve(action) do action.update!(status: "approved", resolved_at: Time.current, resolved_by: principal) end end |
.deny!(action, reason: nil, principal: nil) ⇒ Object
16 17 18 19 20 21 22 23 24 25 26 |
# File 'lib/xeno/inputs.rb', line 16 def deny!(action, reason: nil, principal: nil) resolve(action) do content = JSON.generate({ denied: true, reason: reason || "denied by user" }) action.update!( status: "denied", output: { "content" => content }, resolved_at: Time.current, resolved_by: principal ) end end |
.resolve(action) ⇒ Object
Shared plumbing: guard the state, apply the resolution, resume when the turn has nothing else to wait for. Resolution, status flip, and the resume enqueue commit in ONE transaction: on a DB-backed queue (Solid Queue shares the app database, and ActiveJob 8.1 enqueues in-transaction by default) a crash can never separate "approved" from "job exists" — either everything landed or the input is still pending and the human just retries. Backends that defer or lose the enqueue fall back to the reaper's sweeps.
49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/xeno/inputs.rb', line 49 def resolve(action) unless action.status == "pending_approval" raise Xeno::Error, "action #{action.id} is not awaiting input (status: #{action.status})" end ActiveRecord::Base.transaction do yield resume_turn(action.turn) end action end |
.resume_turn(turn) ⇒ Object
Resuming with unanswered inputs would generate against partial tool results (an invalid provider state) — the runner would just re-park, so don't bother waking it until everything is resolved.
64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/xeno/inputs.rb', line 64 def resume_turn(turn) return if turn.actions.where(status: "pending_approval").exists? # Resumes are human-driven and unbounded — counted apart from the # failure `attempts` so approvals can never poison the turn (H2). resumed = Turn.where(id: turn.id, status: "waiting") .update_all("status = 'pending', resumes = resumes + 1") return unless resumed == 1 turn.session.update!(status: "running") if turn.session.status == "waiting" turn.enqueue! turn end |