Class: Rubino::Tools::ProbeTool

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

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_keyObject

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

#descriptionObject



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_schemaObject



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

#nameObject



43
44
45
# File 'lib/rubino/tools/probe_tool.rb', line 43

def name
  "probe"
end

#risk_levelObject



81
82
83
# File 'lib/rubino/tools/probe_tool.rb', line 81

def risk_level
  :low
end