Class: Ask::Agent::ApprovalQueue
- Inherits:
-
Object
- Object
- Ask::Agent::ApprovalQueue
- Defined in:
- lib/ask/agent/approval_queue.rb
Overview
A queue of tool actions awaiting human approval.
When an agent calls a tool that requires approval, the action is queued instead of executed — the agent gets a pending result and continues. The user approves or rejects actions later, in bulk or one-by-one.
queue = Ask::Agent::ApprovalQueue.new
id = queue.submit(tool_call_id: "call_1", tool_name: "send_email",
args: { "to" => "x@y.com" }, auto_approvable: false)
queue.pending_actions # => [{id: 1, tool_name: "send_email", ...}]
queue.approve(1) # applies action 1 via the on_approve callback
queue.reject(1)
Auto-approval is a dual signal: the action must be marked
auto_approvable (by the tool) AND the queue must have a matching
auto-approval rule enabled by the user. Otherwise the action queues for
human review.
Actions are applied in id order and the drainer never applies past a non-auto-approvable (manual) gate — nothing is silently approved.
Defined Under Namespace
Classes: Action
Instance Method Summary collapse
-
#[](id) ⇒ Action?
Look up an action by id.
-
#any_pending? ⇒ Boolean
True while at least one action awaits a decision.
-
#approve(*ids) ⇒ Array<Action>
Approve specific actions (by id), applying them in id order.
-
#approve_all ⇒ Array<Action>
Approve all currently pending actions, in id order.
-
#initialize(on_approve: nil, on_reject: nil, auto_approve: nil) ⇒ ApprovalQueue
constructor
Create a new approval queue.
-
#pending?(id) ⇒ Boolean
Whether the action is still awaiting a decision.
-
#pending_actions ⇒ Array<Action>
All actions still awaiting a decision, in id order.
-
#reject(*ids) ⇒ Array<Action>
Reject specific actions (by id).
-
#reject_all ⇒ Array<Action>
Reject all currently pending actions, in id order.
-
#submit(tool_call_id:, tool_name:, args: {}, auto_approvable: false, message: nil) ⇒ Integer
Queue a tool action for approval.
Constructor Details
#initialize(on_approve: nil, on_reject: nil, auto_approve: nil) ⇒ ApprovalQueue
Create a new approval queue.
58 59 60 61 62 63 64 65 66 |
# File 'lib/ask/agent/approval_queue.rb', line 58 def initialize(on_approve: nil, on_reject: nil, auto_approve: nil) @on_approve = on_approve @on_reject = on_reject @auto_approve = auto_approve || {} @actions = {} @next_id = 1 @mutex = Mutex.new @draining = false end |
Instance Method Details
#[](id) ⇒ Action?
Look up an action by id.
124 125 126 |
# File 'lib/ask/agent/approval_queue.rb', line 124 def [](id) @mutex.synchronize { @actions[id] } end |
#any_pending? ⇒ Boolean
Returns true while at least one action awaits a decision.
116 117 118 |
# File 'lib/ask/agent/approval_queue.rb', line 116 def any_pending? pending_actions.any? end |
#approve(*ids) ⇒ Array<Action>
Approve specific actions (by id), applying them in id order.
132 133 134 135 136 137 138 |
# File 'lib/ask/agent/approval_queue.rb', line 132 def approve(*ids) actions = ids.flatten.filter_map { |id| @mutex.synchronize { @actions[id] } } actions.select! { |a| a.status == :pending } actions.sort_by!(&:id) actions.each { |a| apply(a) } actions end |
#approve_all ⇒ Array<Action>
Approve all currently pending actions, in id order.
155 156 157 |
# File 'lib/ask/agent/approval_queue.rb', line 155 def approve_all approve(*pending_actions.map(&:id)) end |
#pending?(id) ⇒ Boolean
Returns whether the action is still awaiting a decision.
108 109 110 111 112 113 |
# File 'lib/ask/agent/approval_queue.rb', line 108 def pending?(id) @mutex.synchronize do a = @actions[id] a && a.status == :pending end end |
#pending_actions ⇒ Array<Action>
All actions still awaiting a decision, in id order.
100 101 102 103 104 |
# File 'lib/ask/agent/approval_queue.rb', line 100 def pending_actions @mutex.synchronize do @actions.values.select { |a| a.status == :pending }.sort_by(&:id) end end |
#reject(*ids) ⇒ Array<Action>
Reject specific actions (by id).
144 145 146 147 148 149 150 |
# File 'lib/ask/agent/approval_queue.rb', line 144 def reject(*ids) actions = ids.flatten.filter_map { |id| @mutex.synchronize { @actions[id] } } actions.select! { |a| a.status == :pending } actions.sort_by!(&:id) actions.each { |a| reject_action(a) } actions end |
#reject_all ⇒ Array<Action>
Reject all currently pending actions, in id order.
162 163 164 |
# File 'lib/ask/agent/approval_queue.rb', line 162 def reject_all reject(*pending_actions.map(&:id)) end |
#submit(tool_call_id:, tool_name:, args: {}, auto_approvable: false, message: nil) ⇒ Integer
Queue a tool action for approval.
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/ask/agent/approval_queue.rb', line 76 def submit(tool_call_id:, tool_name:, args: {}, auto_approvable: false, message: nil) action = @mutex.synchronize do action = Action.new( id: @next_id, tool_call_id: tool_call_id, tool_name: tool_name, args: args || {}, auto_approvable: auto_approvable, status: :pending, submitted_at: Time.now, message: ) @next_id += 1 @actions[action.id] = action action end drain action.id end |