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 ./pluginsprobescope 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
- Startup: fails the whole startup if any
*.starfile fails to load (syntax error, aregister_rulecall missing a required argument, ...). Fix or remove the broken file. [r]key (reload): never hard-fails. Each file is retried independently — a file that still fails contributes zero rules, but every other file's rules keep working.- Rules are evaluated at most once per second.
- Cooldowns and flags are keyed by rule name and host id, not by the in-memory rule object, so reloading doesn't reset them.
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:
set_flag(host_id, color, text="")— colors a host's row.coloris one ofred,yellow,green,blue,orange,gray,cyan,magenta. Never self-clears — pair everyset_flagwith aclear_flagin a separate rule matched to the same edge.clear_flag(host_id)— reverts a host's flag to default.log(message)— sets the footer's "last plugin message" line.terminate(exit_code=0, exit_message="")— requests the process exit with a specific code — the only plugin action that controls the process's actual exit status.notify_ntfy(url, title="", message="", priority=0, tags=[])— POSTs to an ntfy.sh-compatible topic URL.notify_webhook(url, payload)— generic HTTP POST for anything else (Slack, Discord, PagerDuty, ...);payloadis typicallyjson.encode({...}).
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
- No mutable state across ticks — Starlark freezes every top-level value once a file finishes loading. Detect edges (a host just going down, just recovering) from the context itself:
period_startbeing recent means "this just happened." upvs.last_ok—upis debounced and matches the main display;last_okis the raw last ping. Usehistoryfor anything in between.- A file can register multiple rules — a "flag it" rule and its complementary "clear it" rule side by side is a common pattern.
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.