Class: Rubino::Tools::SteerTool
- Defined in:
- lib/rubino/tools/steer_tool.rb
Overview
steer — the MODEL-callable parent->child steering note (S2). The model counterpart of the human ‘/agents <id> steer “…”` affordance: a parent agent parks a note onto one of ITS OWN running children; the note is folded into that child’s context at its next turn boundary (Loop#inject_steered_input via the child’s steer_queue) and PERSISTS — it changes the child’s trajectory, unlike the ephemeral ‘probe`.
SCOPED AT CALL (the S1 correction): steer is registered for ALL agents and authorized by OWNERSHIP at call time. The caller is the thread-local Rubino.current_subagent_id (nil ⇒ the human / top-level agent). The target must be the caller’s OWN DIRECT child (BackgroundTasks.owned_by?), so a node with no children simply gets a “not your child” error. This tool does NOT touch the human CLI path (executor.rb’s steer_agent stays unscoped) and is NOT on any strip list.
Mechanism reuse: it wraps BackgroundTasks#steer verbatim (the SAME wire the human CLI uses) — no new transport, no new state.
Instance Attribute Summary
Attributes inherited from Base
#cancel_token, #read_tracker, #stream_chunk
Instance Method Summary collapse
- #call(arguments) ⇒ Object
-
#config_key ⇒ Object
Gated by the same ‘tools.task` delegation key — steering a child is meaningless without the delegation substrate.
- #description ⇒ Object
- #input_schema ⇒ Object
- #name ⇒ Object
-
#risk_level ⇒ Object
Steering a child is a low-risk, non-destructive nudge (the child carries its own approval/risk gates for anything it does next).
Methods inherited from Base
#cancellation_requested?, #emit_chunk, #risky?, #to_tool_definition, workspace_root, workspace_roots
Instance Method Details
#call(arguments) ⇒ Object
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/rubino/tools/steer_tool.rb', line 62 def call(arguments) task_id = (arguments["task_id"] || arguments[:task_id]).to_s.strip note = (arguments["note"] || arguments[:note]).to_s.strip return "Error: note is required" if note.empty? caller_id = Rubino.current_subagent_id registry = BackgroundTasks.instance entry = task_id.empty? ? nil : registry.find(task_id) # No such id at all → it is not a steerable running subagent. return "Cannot steer #{task_id} — no such running subagent." unless entry # Self-steer is meaningless and would loop a note into your own context. return "Error: cannot steer yourself." if task_id == caller_id # Ownership: only a DIRECT child of the caller may be steered. unless registry.owned_by?(caller_id, task_id) return "Error: #{task_id} is not one of your subagents — you can only steer children you started." end # A finished child has no live loop to fold the note into. return "Cannot steer #{task_id} — it already finished (#{entry.status})." unless live?(entry.status) # Wraps the SAME wire the human CLI uses. A false here means the child's # queue vanished between checks (a just-finished child) — treat as gone. return "Cannot steer #{task_id} — no such running subagent." unless registry.steer(task_id, note) # A child parked on a BLOCKING ask_parent has no next turn until the ask # is answered — the note IS queued (deliver-on-unblock), but saying # "enters child context next turn" would let the parent believe the # redirect took effect (#198). Be honest and point at the one action # that unblocks the child. if parked_on_ask?(entry) return "steer ▸ #{task_id} ← #{Rubino::Util::Output.elide(note, 80)} (queued — but #{task_id} is BLOCKED " \ "on ask_parent and will NOT see it until you answer its question: " \ "#{Rubino::Util::Output.elide(entry.ask_question, 120)} — unblock it with " \ "answer_child(task_id: \"#{task_id}\", answer: \"…\"))" end "steer ▸ #{task_id} ← #{Rubino::Util::Output.elide(note, 80)} (parked · enters child context next turn)" end |
#config_key ⇒ Object
Gated by the same ‘tools.task` delegation key — steering a child is meaningless without the delegation substrate. Disabling delegation disables steer too.
30 31 32 |
# File 'lib/rubino/tools/steer_tool.rb', line 30 def config_key "task" end |
#description ⇒ Object
34 35 36 37 38 39 40 41 42 |
# File 'lib/rubino/tools/steer_tool.rb', line 34 def description "Steer one of YOUR OWN running subagents: park a short note that is " \ "folded into that child's context at its NEXT turn (it persists and " \ "changes what the child does). Use it to course-correct a child you " \ "started — add a constraint, narrow the scope, flag something it missed. " \ "You can ONLY steer subagents you started (your direct children); you " \ "cannot steer yourself, a sibling, or a finished child. The note is " \ "queued, not delivered instantly — the child sees it between turns." end |
#input_schema ⇒ Object
44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/rubino/tools/steer_tool.rb', line 44 def input_schema { type: "object", properties: { task_id: { type: "string", description: "The id (sa_…) of YOUR running subagent to steer." }, note: { type: "string", description: "The steering note to fold into the child's next turn. Keep it short and self-contained." } }, required: %w[task_id note] } end |
#name ⇒ Object
23 24 25 |
# File 'lib/rubino/tools/steer_tool.rb', line 23 def name "steer" end |
#risk_level ⇒ Object
Steering a child is a low-risk, non-destructive nudge (the child carries its own approval/risk gates for anything it does next).
58 59 60 |
# File 'lib/rubino/tools/steer_tool.rb', line 58 def risk_level :low end |