Tech Specs / Plugins

Plugin system

--plugins <dir> (default ./plugins) loads every *.star file in dir as a Starlark plugin. Each file registers one or more rules — a condition_func that inspects the current fleet state once per second, and an action_func that runs when it triggers.

probescope run --hosts 1.1.1.1,8.8.8.8 --plugins ./plugins

probescope init --plugin writes a working example plugin plus a documented examples/ directory into ./plugins, so you never have to write a rule from a blank file.

Loading and reloading

Rule model: fleet-wide, not per-host

A rule's condition_func(ctx) is called once per tick against the whole fleet, not once per host — this is what makes cross-host correlation possible (e.g. "the network interface went down, so 12 hosts failing right now is one incident, not 12" — see the interface flapping topic).

def cond(ctx):
    return [h for h in ctx.hosts if not h.up]
def act(ctx, hosts):
    for h in hosts:
        set_flag(h.id, "red", "DOWN")
register_rule(
    name = "host_down",
    condition = cond,
    action = act,
    cooldown_seconds = 60,
)

condition_func can return a list of host structs, a list of plain ints (for a fleet-wide trigger with no specific host — see FLEET_ID below), a single unwrapped value, or nothing (None, False, []) to signal no trigger this tick. action_func(ctx, hosts) receives the cooldown-filtered subset of whatever the condition returned.

Cooldown is checked per (rule_name, host_id) independently — a host in cooldown is silently dropped from what action_func sees, without suppressing a different host triggering the same rule the same tick.

A Starlark runtime error in one rule is logged and that rule contributes nothing that tick — it is never auto-disabled; every other rule keeps running, and the failing one is retried next tick. Every evaluation is bounded to 1,000,000 execution steps, so a buggy infinite loop can't hang the process.

Context (ctx)

Passed fresh every tick. Key fields on ctx.hosts[i]: id, host, addr, label, ip, method, up (debounced, threshold-confirmed state), last_ok (raw last-ping result), period_start, last_down_duration/last_down_ended_at, last_up_duration/last_up_ended_at, total, fails, fail_pct, cur_rtt, avg_rtt/p95_rtt, and history (up to 100 entries, newest first, independent of the up/down threshold — lets a rule see flakiness the threshold is designed to hide).

ctx.interface mirrors the interface badge (name, monitored, up). ctx.run carries started_at, now, threshold_up/threshold_down, interval, and version.

Actions

Available as predeclared globals, no import needed:

Both notify_* actions fire asynchronously with a 10s timeout, so a slow endpoint can't block ProbeScope. There is deliberately no run_command-style action and no direct --db write action — the sandboxing is intentional.

Tier gating

Some actions and limits scale with license tier (see the pricing page for current numbers): the number of *.star files allowed, the maximum size of a single file, and which of the notify/terminate actions are available at each tier. A script referencing a gated action still loads fine — the check happens when the action actually runs, not at load time.

Design notes

Related

  • Detection — the debounced state and raw ping history plugins read.
  • Interface Flapping — ctx.interface, for correlating local link state with host failures.
  • Integrations — notify_webhook/terminate are often the bridge between a plugin rule and an external system.