Class: Rubino::Tools::ProbeTool
- Defined in:
- lib/rubino/tools/probe_tool.rb
Overview
probe — the MODEL-callable EPHEMERAL peek into one of the caller’s OWN running children (S3). The model counterpart of the human ‘/agents <id> probe “…”`. Two paths, both read-only (they append NOTHING to the child’s session — the EPHEMERAL invariant):
live:false (DEFAULT, FREE): build the answer from the registry's
live-progress fields ONLY (status / tool_count / last_activity + the
bounded activity_log ring the /agents drill-in already tails). NO model
call — unlimited.
live:true (BILLED): run ONE side-inference over a read-only snapshot of
the child's transcript (SubagentProbe#peek) and return the answer. This
costs a model round-trip, so it is BUDGETED per child
(tasks.max_live_probes_per_child, default 5). Over budget → the model is
told to use the free snapshot.
SCOPED AT CALL (the S1 correction): probe is registered for ALL agents and authorized by OWNERSHIP at call time — the target must be the caller’s OWN direct child (BackgroundTasks.owned_by?). Registered normally, NOT on any strip list. Does NOT touch the human CLI probe path (executor.rb).
Constant Summary collapse
- RECENT_MAX =
How many activity_log lines the cheap snapshot renders (matches the /agents drill-in’s ‘recent:` ring).
6- JUST_STARTED_HINT =
A probe is a snapshot at this instant: a child probed right after spawn has run nothing yet and honestly reports an empty/confused context, which reads as broken without this hint (#112).
"(snapshot at this instant — the child just started and its " \ "context is still empty; probe again in a moment)"
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 — probing a child is meaningless without the delegation substrate.
- #description ⇒ Object
-
#initialize(probe: nil) ⇒ ProbeTool
constructor
A new instance of ProbeTool.
- #input_schema ⇒ Object
- #name ⇒ Object
- #risk_level ⇒ Object
Methods inherited from Base
#cancellation_requested?, #emit_chunk, #risky?, #to_tool_definition, workspace_root, workspace_roots
Constructor Details
#initialize(probe: nil) ⇒ ProbeTool
Returns a new instance of ProbeTool.
37 38 39 40 41 |
# File 'lib/rubino/tools/probe_tool.rb', line 37 def initialize(probe: nil) # Test seam: inject a SubagentProbe (or any object responding to #peek) # so the live path can be driven without a real model. @probe = probe end |
Instance Method Details
#call(arguments) ⇒ Object
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/rubino/tools/probe_tool.rb', line 85 def call(arguments) task_id = (arguments["task_id"] || arguments[:task_id]).to_s.strip question = (arguments["question"] || arguments[:question]).to_s.strip live = live_arg(arguments) caller_id = Rubino.current_subagent_id registry = BackgroundTasks.instance entry = task_id.empty? ? nil : registry.find(task_id) return "Cannot probe #{task_id} — no such subagent." unless entry return "Error: question is required" if question.empty? unless registry.owned_by?(caller_id, task_id) return "Error: #{task_id} is not one of your subagents — you can only probe children you started." end # A child parked on a BLOCKING ask_parent has no live activity to peek # at — its pending tool_use is not in the persisted snapshot, so a # billed live peek would honestly answer "I never called ask_parent" # while task_result says blocked (#198). Short-circuit with the parked # question and the one action that unblocks it (no billed peek). return blocked_on_ask_answer(entry) if parked_on_ask?(entry) live ? probe_live(registry, entry, question) : probe_cheap(entry) end |
#config_key ⇒ Object
Gated by the same ‘tools.task` delegation key — probing a child is meaningless without the delegation substrate.
49 50 51 |
# File 'lib/rubino/tools/probe_tool.rb', line 49 def config_key "task" end |
#description ⇒ Object
53 54 55 56 57 58 59 60 61 62 |
# File 'lib/rubino/tools/probe_tool.rb', line 53 def description "Check on one of YOUR OWN running subagents WITHOUT disturbing it (this " \ "is read-only — it changes nothing about what the child does). By default " \ "(live:false) it returns a FREE instant snapshot: the child's status, how " \ "many tools it has run, its last activity, and a few recent lines — no " \ "model call. Set live:true to ask the child a specific question answered " \ "from its current context by a one-shot model peek (this costs a billed " \ "round-trip and is budgeted per child; prefer the free snapshot). You can " \ "ONLY probe subagents you started (your direct children)." end |
#input_schema ⇒ Object
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/rubino/tools/probe_tool.rb', line 64 def input_schema { type: "object", properties: { task_id: { type: "string", description: "The id (sa_…) of YOUR subagent to probe." }, question: { type: "string", description: "What you want to know. For a free snapshot this frames the check; for live:true it is the question the child answers from its context." }, live: { type: "boolean", description: "false (default) = FREE instant snapshot from the registry, no model call. " \ "true = billed one-shot model peek over the child's transcript (budgeted per child)." } }, required: %w[task_id question] } end |
#name ⇒ Object
43 44 45 |
# File 'lib/rubino/tools/probe_tool.rb', line 43 def name "probe" end |
#risk_level ⇒ Object
81 82 83 |
# File 'lib/rubino/tools/probe_tool.rb', line 81 def risk_level :low end |