Class: Pikuri::Agent::Control::Cancellable

Inherits:
Object
  • Object
show all
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

Constructor Details

#initializeCancellable

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

Returns whether #cancel! has been called since the last #reset!; observable from any thread.

Returns:

  • (Boolean)

    whether #cancel! has been called since the last #reset!; observable from any thread.



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.

Raises:



90
91
92
# File 'lib/pikuri/agent/control/cancellable.rb', line 90

def check!
  raise Cancelled if @cancelled
end

#for_sub_agentCancellable

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.

Returns:



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_sString

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.

Returns:

  • (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.



122
123
124
# File 'lib/pikuri/agent/control/cancellable.rb', line 122

def to_s
  "Cancellable(#{@cancelled ? 'cancelled' : 'armed'})"
end