Class: Pikuri::Agent::Control::Cancellable
- Inherits:
-
Object
- Object
- Pikuri::Agent::Control::Cancellable
- Defined in:
- lib/pikuri/agent/control/cancellable.rb
Overview
Cooperative cancellation token. The instance is normally constructed on the main thread and handed to Pikuri::Agent#initialize via the cancellable: kwarg; an out-of-band caller (a SIGINT trap, a TUI key binding, an IPC handler) calls #cancel! to flip the flag, and the next #check! on the run thread raises Cancelled —which Pikuri::Agent#run_loop catches, normalizes into an Event::Cancelled on the listener stream, and re-raises so the caller’s REPL can return control to the user.
Cancellation boundary
The Agent calls #check! from its before_tool_call wiring — between an LLM response that requested a tool and the actual tool invocation — which is the only point at which the conversation state is consistent (no in-flight subprocess, no half-applied write). An in-flight LLM HTTP call is not interrupted; the response lands, then the next tool-call boundary trips. An in-flight tool (notably Bash) is also not interrupted — cancellation lands after the tool returns. Both are intentional v1 scope: the “gentle cancel” semantic that pikuri promises.
Thread safety
#cancel! is intended to be called from a thread other than the one running Pikuri::Agent#run_loop (the typical case is a SIGINT trap handler on the main thread while the agent runs on a worker, or vice versa). A plain boolean ivar is sufficient under MRI: writes and reads of a single reference are atomic with respect to the GVL, and the only state transition we care about is false → true before the next #check! fires. There is no double-cancel hazard; repeated #cancel! calls are idempotent.
Sub-agent semantics
#for_sub_agent returns self — the same instance is shared by reference across the parent, every sub-agent, and the synthesizer rescue. One #cancel! call stops the whole tree. This contrasts with StepLimit, which gives each agent its own counter (because step budgets are per-agent concerns) — cancellation is a global signal from the user, so it must propagate down.
Defined Under Namespace
Classes: Cancelled
Instance Method Summary collapse
-
#cancel! ⇒ void
Flip the flag.
- #cancelled? ⇒ Boolean
-
#check! ⇒ void
Raise Cancelled when the flag is set; otherwise no-op.
-
#for_sub_agent ⇒ Cancellable
Sub-agent variant: the same instance, shared by reference, so a single #cancel! stops the parent, every running sub-agent, and the synthesizer rescue.
-
#initialize ⇒ Cancellable
constructor
A new instance of Cancellable.
-
#reset! ⇒ void
Reset the flag back to armed.
-
#to_s ⇒ String
Short label for Pikuri::Agent#to_s; reflects the current flag state so a startup banner or debug print can tell an armed token apart from one that has already tripped.
Constructor Details
#initialize ⇒ Cancellable
Returns a new instance of Cancellable.
63 64 65 |
# File 'lib/pikuri/agent/control/cancellable.rb', line 63 def initialize @cancelled = false end |
Instance Method Details
#cancel! ⇒ void
This method returns an undefined value.
Flip the flag. Safe to call from a thread other than the one running the agent loop, and safe to call multiple times (idempotent). Takes effect at the next #check! on the run thread — see the class header for the “gentle cancel” caveats.
74 75 76 |
# File 'lib/pikuri/agent/control/cancellable.rb', line 74 def cancel! @cancelled = true end |
#cancelled? ⇒ Boolean
80 81 82 |
# File 'lib/pikuri/agent/control/cancellable.rb', line 80 def cancelled? @cancelled end |
#check! ⇒ void
This method returns an undefined value.
Raise Cancelled when the flag is set; otherwise no-op. Called by Pikuri::Agent from its before_tool_call wiring.
90 91 92 |
# File 'lib/pikuri/agent/control/cancellable.rb', line 90 def check! raise Cancelled if @cancelled end |
#for_sub_agent ⇒ Cancellable
Sub-agent variant: the same instance, shared by reference, so a single #cancel! stops the parent, every running sub-agent, and the synthesizer rescue. See the class header for the rationale.
114 115 116 |
# File 'lib/pikuri/agent/control/cancellable.rb', line 114 def for_sub_agent(**) self end |
#reset! ⇒ void
This method returns an undefined value.
Reset the flag back to armed. Called by Pikuri::Agent at the start of each turn so a stale cancellation from a prior turn does not poison the next one. Mid-loop Interloper injections deliberately do not trigger a reset — otherwise the cancel-then-inject ordering would lose the cancellation: cancel! sets the flag, the injection lands and resets it, and the next before_tool_call no longer raises.
104 105 106 |
# File 'lib/pikuri/agent/control/cancellable.rb', line 104 def reset! @cancelled = false end |
#to_s ⇒ String
Returns short label for Pikuri::Agent#to_s; reflects the current flag state so a startup banner or debug print can tell an armed token apart from one that has already tripped.
122 123 124 |
# File 'lib/pikuri/agent/control/cancellable.rb', line 122 def to_s "Cancellable(#{@cancelled ? 'cancelled' : 'armed'})" end |