Class: Mistri::Child
- Inherits:
-
Object
- Object
- Mistri::Child
- Defined in:
- lib/mistri/child.rb
Overview
A sub-agent as its parent (or the host UI) sees it: a window onto the child's own session. Everything here is derived from the store, so it reads the same from any process, while the child runs and forever after.
session.children # => [#<Mistri::Child Magpie done>, ...]
child.status # queued, running, interrupted, or terminal
child.report # the terminal entry's report, once finished
child.transcript(tail: 20) # recent entries, image bytes stripped
child.say("Also check their pricing page")
Status is a walk over the child's own entries: a terminal entry wins; a started child is :running while its lease holds and :interrupted once it lapses (with a lock adapter; without one there is no liveness signal and no-terminal stays :running); a dispatched-but-never-started child is :queued, honestly, because the host's queue owns that gap.
Constant Summary collapse
- TERMINAL =
"subagent_result"- DISPATCHED =
"subagent_dispatched"- STARTED =
"subagent_started"- LIVE =
Work currently scheduled or queued, used for capacity and steering. Interrupted work is nonterminal but not actively live.
%i[running queued].freeze
Instance Attribute Summary collapse
-
#name ⇒ Object
readonly
Returns the value of attribute name.
-
#session_id ⇒ Object
readonly
Returns the value of attribute session_id.
Class Method Summary collapse
Instance Method Summary collapse
-
#error ⇒ Object
The terminal entry's error string, once failed.
-
#finished? ⇒ Boolean
A terminal entry exists: the child ended as done, stopped, or failed, so a later matching delivery must not run it again.
-
#initialize(name:, session_id:, store:, parent_session_id: nil) ⇒ Child
constructor
A new instance of Child.
- #inspect ⇒ Object
- #report ⇒ Object
-
#say(text) ⇒ Object
Queue a message the child folds at its next turn boundary.
- #status ⇒ Object
-
#stop ⇒ Object
Ask a child to stop, from any process.
- #to_h ⇒ Object
-
#transcript(tail: 20) ⇒ Object
Recent entries, oldest first, with inline image bytes replaced by a marker: transcripts are for reading and re-sending, not for hauling screenshots back into a context window.
Constructor Details
#initialize(name:, session_id:, store:, parent_session_id: nil) ⇒ Child
Returns a new instance of Child.
33 34 35 36 37 38 |
# File 'lib/mistri/child.rb', line 33 def initialize(name:, session_id:, store:, parent_session_id: nil) @name = name @session_id = session_id @store = store @parent_session_id = parent_session_id end |
Instance Attribute Details
#name ⇒ Object (readonly)
Returns the value of attribute name.
27 28 29 |
# File 'lib/mistri/child.rb', line 27 def name @name end |
#session_id ⇒ Object (readonly)
Returns the value of attribute session_id.
27 28 29 |
# File 'lib/mistri/child.rb', line 27 def session_id @session_id end |
Class Method Details
.lease_key(session_id) ⇒ Object
29 |
# File 'lib/mistri/child.rb', line 29 def self.lease_key(session_id) = "child:#{session_id}" |
.stop_key(session_id) ⇒ Object
31 |
# File 'lib/mistri/child.rb', line 31 def self.stop_key(session_id) = "child-stop:#{session_id}" |
.strip_images(value) ⇒ Object
107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/mistri/child.rb', line 107 def self.strip_images(value) case value when Hash if value["data"] && value["mime_type"] value.except("data").merge("omitted" => true) else value.transform_values { |nested| strip_images(nested) } end when Array then value.map { |nested| strip_images(nested) } else value end end |
Instance Method Details
#error ⇒ Object
The terminal entry's error string, once failed.
65 66 67 |
# File 'lib/mistri/child.rb', line 65 def error terminal_entry&.fetch("error", nil) end |
#finished? ⇒ Boolean
A terminal entry exists: the child ended as done, stopped, or failed, so a later matching delivery must not run it again. The inverse (started but no terminal) is what makes a crashed child re-runnable.
56 57 58 |
# File 'lib/mistri/child.rb', line 56 def finished? !terminal_entry.nil? end |
#inspect ⇒ Object
103 104 105 |
# File 'lib/mistri/child.rb', line 103 def inspect "#<Mistri::Child #{name} #{status}>" end |
#report ⇒ Object
60 61 62 |
# File 'lib/mistri/child.rb', line 60 def report terminal_entry&.fetch("report", nil) end |
#say(text) ⇒ Object
Queue a message the child folds at its next turn boundary. Delivery is honest, not instant: a child mid-step sees it after that step, and a child that finishes first never sees it.
81 82 83 |
# File 'lib/mistri/child.rb', line 81 def say(text) session.steer(text) end |
#status ⇒ Object
40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/mistri/child.rb', line 40 def status log = session.entries terminal = log.reverse_each.find { |entry| entry["type"] == TERMINAL } return terminal["status"].to_sym if terminal if log.any? { |entry| entry["type"] == DISPATCHED } && log.none? { |entry| entry["type"] == STARTED } return :queued end return :interrupted if Mistri.locks && !Mistri.locks.held?(self.class.lease_key(@session_id)) :running end |
#stop ⇒ Object
Ask a child to stop, from any process. An inactive dispatched child is cancelled durably under its lease; an inline or already-running child receives the stop flag. Repeating stop on a stopped dispatched child reconciles a missing parent report. Stop is cross-process by nature, so it needs a lock adapter; without one this returns false. An action that reports acceptance, not a predicate.
91 92 93 94 95 96 97 |
# File 'lib/mistri/child.rb', line 91 def stop # rubocop:disable Naming/PredicateMethod return false unless Mistri.locks outcome = cancel_inactive Mistri.locks.set_flag(self.class.stop_key(@session_id)) if outcome == :contended true end |
#to_h ⇒ Object
99 100 101 |
# File 'lib/mistri/child.rb', line 99 def to_h { "name" => name, "session_id" => session_id, "status" => status.to_s } end |
#transcript(tail: 20) ⇒ Object
Recent entries, oldest first, with inline image bytes replaced by a marker: transcripts are for reading and re-sending, not for hauling screenshots back into a context window.
72 73 74 75 76 |
# File 'lib/mistri/child.rb', line 72 def transcript(tail: 20) entries = session.entries entries = entries.last(tail) if tail entries.map { |entry| self.class.strip_images(entry) } end |