Query the ShadowBroker OSINT intelligence platform for real-time geospatial intelligence, place AI intel pins on the map, manage autonomous monitoring, inject data into native layers, fetch satellite imagery, aggregate news, generate intelligence reports, and participate in the Wormhole mesh network.
57
66%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Fix and improve this skill with Tessl
tessl review fix ./openclaw-skills/shadowbroker/SKILL.mdYou have access to ShadowBroker, a real-time global OSINT intelligence platform
running on localhost:8000. It tracks military flights, ships, satellites, SIGINT,
earthquakes, fires, GDELT conflict events, prediction markets, and 30+ other data
layers — all with geographic coordinates.
ShadowBroker exposes dozens of read commands. Do not explore them. Use the three-tool surface:
| Tool | When |
|---|---|
await sb.ask("natural language question") | Default for reads — server routes to fastest command |
await sb.run_playbook("hot_snapshot") | Pre-batched snapshots (morning brief, monitor poll, status) |
await sb.channel_status() | Liveness (~5 ms) — never /api/health |
Latency tiers: get_entity_profile / find_entity / get_entity_trail / find_flights / search_news / entities_near → ⚡ <30 ms.
search_telemetry / get_telemetry / get_report → 🔴 seconds — blocked unless confirm_expensive=true.
# Default read path (route + execute)
answer = await sb.ask("where is the Patriots jet")
# Named batch plans
brief = await sb.run_playbook("hot_snapshot")
monitor = await sb.run_playbook("monitor_heartbeat")
# Structured lookup when you already parsed fields
entity = await sb.send_command("find_entity", {"owner": "musk", "compact": True})
# Multi-command — always batch, never sequential loops
batch = await sb.send_batch([
{"cmd": "get_summary", "args": {"compact": True}},
{"cmd": "what_changed", "args": {"compact": True}},
])Playbooks: hot_snapshot, morning_brief, status_check, monitor_heartbeat, track_snapshot, jet_recon, area_brief, entity_recon.
Anti-patterns: search_telemetry for known tail numbers; get_telemetry for routine polls; sequential send_command loops; empty layers: [] on get_layer_slice.
Load machine-readable routing hints once: GET /api/ai/capabilities → routing.
Import the client and call methods:
from sb_query import ShadowBrokerClient
sb = ShadowBrokerClient() # auto-detects local or remote modeNo configuration needed. The client connects to localhost:8000 automatically.
Set these environment variables in your agent's config:
SHADOWBROKER_URL=https://your-shadowbroker-host:8000
SHADOWBROKER_HMAC_SECRET=your-hmac-secret-hereThe HMAC secret is found in ShadowBroker's Connect OpenClaw modal (AI Intel panel).
SHADOWBROKER_HMAC_SECRET is a shared signing secret, not a raw API key. Do not
send it as X-Admin-Key, Authorization: Bearer, a query parameter, or any
plain request header. The ShadowBrokerClient signs every direct request with
X-SB-Timestamp, X-SB-Nonce, and X-SB-Signature using:
HMAC-SHA256(secret, METHOD|path|timestamp|nonce|sha256(body))For compatibility with older snippets, SHADOWBROKER_KEY is also accepted by
the client as the same HMAC signing secret. Prefer SHADOWBROKER_HMAC_SECRET
for new setups.
Docker Compose: host-side agents (localhost:8000 from the Kali/macOS host)
are not loopback inside the backend container — HMAC is required. After
Connect OpenClaw → Bootstrap → Reveal, the secret is persisted under
data/openclaw.env on the backend_data volume. Restart the backend once,
then verify with:
python openclaw-skills/shadowbroker/verify_hmac.pyHand-rolled signers must hash the exact POST bytes. Use compact JSON:
json.dumps(payload, separators=(",", ":"), sort_keys=True).
Open the SSE stream first and keep it open for the session. The server pushes
layer_changed events whenever any data layer refreshes — you know exactly which
layers to fetch instead of blind-polling.
# Open the stream — authenticates once via HMAC, then stays open
async for event in sb.stream_updates():
if event["event"] == "connected":
# Initial handshake — contains full layer_versions snapshot
print(f"Connected: {event['data']['layer_versions']}")
elif event["event"] == "layer_changed":
# Server tells you which layers updated and their new version/count
changed = event["data"]["layers"] # e.g. {"ships": {"version": 43, "count": 1287}}
# Fetch ONLY the layers that actually changed
data = await sb.get_layer_slice(list(changed.keys()))
# get_layer_slice uses per-layer versions internally — only changed
# layers are serialized, unchanged layers transfer zero bytes
elif event["event"] == "alert":
# Watchdog alert — geofence hit, callsign spotted, keyword matched
print(f"Alert: {event['data']}")
elif event["event"] == "task":
# Operator-pushed task
print(f"Task: {event['data']}")Send commands via HTTP alongside the SSE stream:
# Send a command and get the result
result = await sb.send_command("get_summary")
# Batch multiple commands in one HTTP round-trip (concurrent execution)
results = await sb.send_batch([
{"cmd": "find_flights", "args": {"query": "N189AM", "compact": True}},
{"cmd": "search_news", "args": {"query": "carrier", "compact": True}},
])
# Check channel status and security tier
status = await sb.channel_status()
print(f"Tier {status['tier']}: {status['reason']}")The channel operates over HMAC-authenticated HTTP with body-integrity binding:
Primary pattern (lowest latency): Use the SSE stream + targeted get_layer_slice:
| Method | What It Returns | When to Use |
|---|---|---|
sb.stream_updates() | SSE push: layer_changed, alerts, tasks | Open first, keep open — tells you exactly which layers updated |
await sb.get_layer_slice(["ships", "gdelt"]) | Only the requested layers, with per-layer incremental | Primary fetch method — automatically skips layers you already have |
await sb.send_command("get_summary") | Lightweight counts-only summary | Discover what data exists before pulling anything |
await sb.ask("...") | Route + execute | Default for natural-language reads |
await sb.send_command("get_entity_profile", {...}) | Preferred aircraft/vessel dossier — identity, VIP tags, trail, route, ACARS, jamming/correlations, news | Tail, owner, callsign, MMSI |
await sb.send_command("get_entity_trail", {...}) | Observed path + route + ACARS hints only | When you don't need full dossier |
await sb.send_command("find_entity", {...}) | Exact-first entity resolver | Parsed person/tail/callsign/MMSI — skips fuzzy unless fallback_search=true |
await sb.send_command("find_flights", {...}) | Targeted flight search | When you know the domain (callsign, tail number) |
await sb.send_command("route_query", {...}) | Routing plan only | Inspect recommended command before executing |
await sb.send_command("search_telemetry", {...}) | Cross-layer fuzzy search | Last resort — requires confirm_expensive=true |
Full telemetry dumps (use sparingly — large payloads):
| Method | What It Returns |
|---|---|
await sb.get_telemetry() | Fast-tier: flights, ships, satellites, SIGINT, LiveUAMap, CCTV, GPS jamming |
await sb.get_slow_telemetry() | Slow-tier: GDELT, news, earthquakes, markets, correlations, Telegram OSINT, malware/cyber threats, SCM suppliers |
await sb.get_report() | Full structured intelligence report |
Requires GT_ANALYTICS_ENABLED=true on the ShadowBroker backend.
| Method / command | What It Returns |
|---|---|
await sb.ask("Run GT analysis on UK/Europe feeds") | Routes to gt_analyze |
await sb.gt_analyze(region="ukraine") | Refresh beliefs from Telegram/news/GDELT + dossier |
await sb.gt_risk_heatmap() | GeoJSON posterior risk overlay + Louvain clusters |
await sb.gt_dossier("ukraine") | Costly signals, domain risks, scenarios |
await sb.gt_backtest() | Static benchmark — labeled historical cases (regression test) |
await sb.gt_backtest(tune=True) | Grid-search alert threshold for target confidence |
await sb.gt_rolling_backtest() | Macro operational — week-over-week accuracy on frozen weekly alerts |
await sb.gt_micro_rolling() | Micro 3-day rolling avg — spot vs baseline, ignition detection |
await sb.gt_rolling_freeze() | Freeze this ISO week's GT scores before outcomes are known |
await sb.gt_rolling_label(week_id, region=..., label=...) | Label prior-week outcomes (true_escalation, false_alarm, benign) |
await sb.gt_top_alerts() | Ranked top GT regions with map coordinates |
await sb.ask("Run GT historical backtest") | Routes to gt_backtest (benchmark, not operational) |
await sb.ask("GT rolling operational backtest trend") | Routes to gt_rolling_backtest |
python sb_gt_report.py | Local helper — backtest + heatmap (+ optional --region) |
await sb.send_command("gt_analyze", {"region": "europe"}) | Same as gt_analyze() |
Benchmark vs rolling: Static gt_backtest checks the classifier on known textbook
cases. gt_rolling_backtest scores frozen weekly live predictions against delayed
operator labels — that week-over-week trend (e.g. 54% → 62% → 71%) is the macro
real-world metric. gt_micro_rolling adds a 3-day rolling average per region:
spot risk vs the trailing baseline catches fast ignitions the weekly roll can miss.
Threshold is fixed (GT_ROLLING_ALERT_THRESHOLD, default 0.26); ignition when
spot − 3d avg ≥ GT_MICRO_IGNITION_DELTA (default 0.10).
When to use: Use get_summary() first. Use get_layer_slice() for the layers
you actually need. Reserve full get_telemetry() / get_slow_telemetry() for rare
cases where you genuinely need every field across every layer.
Every layer returns maximum telemetry. Key enriched fields:
| Layer | Key Fields |
|---|---|
| GDELT | event_date, actors (list), goldstein (intensity -10 to +10), num_mentions, num_sources, num_articles, avg_tone, quad_class |
| LiveUAMap | title, description, region, category, date (formatted UTC), timestamp, source, image, link |
| CrowdThreat | title, summary, category, subcategory, type, country, occurred_iso, verification, severity, source_url, media_urls, votes, reporter |
| UAP Sightings | lat, lng, location, state, count, shape (normalized), shape_raw, duration, summary (witness report), city, from_date, to_date |
| Wastewater | name, lat, lng, alert (boolean), pathogen, concentration, trend, last_sample_date |
| FIRMS Fires | lat, lng, brightness, confidence, frp (fire radiative power), satellite, acq_date |
| GPS Jamming | lat, lng, name/region, intensity, source |
| Earthquakes | lat, lng, magnitude, depth, place, time |
| Correlations | type, severity, score, lat, lng, drivers (triggering layers) |
| Telegram OSINT | title, description, channel, source, link, published, risk_score, coords [lat, lng] |
| Malware Threats | ip, malware, threat_type, status, country, lat, lng (Feodo + URLhaus) |
| Cyber Threats | id (CVE), name, vendor, product, severity, date (CISA KEV) |
| SCM Suppliers | name, city, country, category, risk_level, active_threats, lat, lng |
Layer aliases for get_layer_slice / search_telemetry: telegram → telegram_osint, malware/botnet → malware_threats, cyber/cisa/kev → cyber_threats, scm/suppliers → scm_suppliers.
The Recon panel lookups are available on the OpenClaw command channel — no need to hit /api/osint/* directly.
# List supported tools
await sb.send_command("osint_tools")
# IP geolocation + threat context
await sb.send_command("osint_lookup", {"tool": "ip", "ip": "8.8.8.8"})
# DNS, WHOIS, certificate transparency
await sb.send_command("osint_lookup", {"tool": "dns", "domain": "example.com"})
await sb.send_command("osint_lookup", {"tool": "whois", "domain": "example.com"})
await sb.send_command("osint_lookup", {"tool": "certs", "domain": "example.com"})
# BGP/ASN, sanctions, CVE, MAC vendor, GitHub, breach check
await sb.send_command("osint_lookup", {"tool": "bgp", "query": "AS15169"})
await sb.send_command("osint_lookup", {"tool": "sanctions", "query": "Rosneft"})
await sb.send_command("osint_lookup", {"tool": "cve", "cve": "CVE-2024-1234"})
await sb.send_command("osint_lookup", {"tool": "mac", "mac": "00:11:22:33:44:55"})
await sb.send_command("osint_lookup", {"tool": "github", "username": "octocat"})
await sb.send_command("osint_lookup", {"tool": "leaks", "email": "user@example.com"})
# Entity relationship graph (aircraft, vessel, ip, company, person, country)
await sb.send_command("entity_expand", {"type": "ip", "id": "8.8.8.8"})
await sb.send_command("entity_expand", {"type": "aircraft", "id": "N400QS", "icao24": "a0f011"})
# Subnet sweep (full tier only — active Shodan InternetDB scan)
await sb.send_command("osint_sweep", {"ip": "1.2.3.4", "cidr": 24})osint_lookup tool | Args | What you get |
|---|---|---|
ip | ip | Geo, ISP, ASN, proxy/hosting flags, sanctions cross-check |
dns | domain | A/AAAA/MX/NS/TXT records |
whois | domain | Registrar, dates, nameservers |
certs | domain | Certificate transparency hits |
threats | query (optional) | Aggregated threat intel |
bgp | query | ASN/prefix routing data |
sanctions | query, schema, limit | OFAC / sanctions index matches |
cve | cve | NVD CVE details |
mac | mac | Vendor OUI lookup |
github | username | Public profile metadata |
leaks | email | Breach exposure check |
sweep_init | ip, cidr | Passive geolocation context for a sweep target |
Pins appear on the user's map in a dedicated "AI Intel" layer.
# Single pin
await sb.place_pin(
lat=34.05, lng=-118.24,
label="UAP Sighting #1",
category="anomaly", # see categories below
description="Multiple witnesses reported lights over Griffith Observatory",
source="NUFORC Database",
source_url="https://nuforc.org/...",
confidence=0.8, # 0.0 to 1.0
ttl_hours=48, # auto-delete after 48 hours (0 = permanent)
)
# Batch pins (up to 100 at once)
await sb.place_pins_batch([
{"lat": 34.05, "lng": -118.24, "label": "Site A", "category": "research"},
{"lat": 34.10, "lng": -118.30, "label": "Site B", "category": "research"},
])
# List pins
pins = await sb.get_pins(category="anomaly")
# Delete
await sb.clear_pins(category="anomaly") # by category
await sb.clear_pins() # allPin Categories (each has a specific color on the map):
| Category | Color | Use For |
|---|---|---|
threat | 🔴 Red | Military threats, conflict events, danger zones |
anomaly | 🟠 Orange | UAPs, unusual signals, unexpected patterns |
military | 🟡 Yellow | Military bases, flights, exercises |
news | 🟢 Green | News events, protests, political events |
maritime | 🔵 Blue | Ships, ports, maritime events |
aviation | 🟣 Purple | Flights, airports, airspace events |
infrastructure | ⚪ Gray | Power plants, data centers, cables |
sigint | 🩷 Pink | RF signals, jamming, radio activity |
geolocation | 🫧 Teal | Geolocated images, placed-from-text |
satellite | 🌌 Indigo | Satellite imagery findings |
seismic | 🤎 Brown | Earthquakes, volcanic activity |
weather | 🩶 Light gray | Weather events, storms |
research | 💜 Violet | General research findings |
custom | Default violet | Everything else |
# Place name → coordinates
results = await sb.geocode("Griffith Observatory, Los Angeles")
# Returns: [{"lat": 34.1184, "lon": -118.3004, "display_name": "..."}]
# Always geocode before placing pins if you have a place name, not coordinates.# Get latest Sentinel-2 satellite scenes for any location
scenes = await sb.get_satellite_images(lat=35.68, lng=51.38, count=3)
# Returns: {"scenes": [{"scene_id", "datetime", "cloud_cover", "thumbnail_url", "fullres_url"}]}When to use: When the user asks to "see satellite images of [place]" or wants visual intelligence of a location. Geocode first, then fetch imagery.
# Get GDELT conflict events + news articles near a coordinate
nearby = await sb.get_news_near(lat=-15.4, lng=28.3, radius=500)
# Returns: {"gdelt": [...], "news": [...]} with headlines, source URLs, distancesWhen to use: When the user asks "what's happening in [country/city]" or wants news from a specific region. Geocode the place name first.
# Get ALL telemetry within a radius of a location
everything = await sb.get_near_me(lat=39.74, lng=-104.99, radius_miles=100)
# Returns EVERY layer within radius, each item tagged with distance_miles:
# military_flights, commercial_flights, tracked_flights, private_jets,
# ships, sigint, earthquakes, volcanoes, gdelt, liveuamap, crowdthreat,
# uap_sightings, wastewater, firms_fires, weather_alerts, air_quality,
# cctv, gps_jamming, satellites, news, correlationsWhen to use: When the user says "what's near me" or wants a proximity digest. This pulls from both fast and slow tiers automatically.
Inject custom data directly into ShadowBroker's native layers (CCTV, ships, etc.):
# Add a custom CCTV camera to the CCTV layer
await sb.inject_data("cctv", [
{"lat": 34.1, "lng": -118.3, "url": "https://stream.example.com/cam1",
"name": "My Traffic Camera"}
])
# Remove all user-injected data
await sb.clear_injected() # all layers
await sb.clear_injected("cctv") # just CCTVInjectable layers: cctv, ships, sigint, kiwisdr, military_bases,
datacenters, power_plants, satnogs_stations, volcanoes, earthquakes,
news, viirs_change_nodes, air_quality
When to use: When the user wants to add their own data sources to existing layers (e.g., "add this CCTV camera I found", "add this military base").
OpenClaw agents participate in the private Infonet on behalf of the operator who configured the skill. All traffic uses the operator's wormhole persona and local node runtime (MLS gate crypto, Ed25519 signing, Tor onion transport) — the agent does not get a separate fleet identity.
Access tiers
restricted (default): read Infonet status, list gates, read gate messages,
poll DMs.full (OPENCLAW_ACCESS_TIER=full): also warm Tor, join the swarm, post
gate messages, cast votes, and send DMs when the user commands it.Remote agents authenticate with HMAC on /api/ai/channel/command; loopback
uses the local operator lane.
# Warm Tor, enable the node, announce to fleet seed (full tier)
await sb.ensure_infonet_ready(join_swarm=True)
# Status snapshot (chain health, wormhole, runtime)
status = await sb.infonet_status()
# Read the public Infonet gate (MLS-encrypted, decrypt with operator keys)
messages = await sb.read_gate_messages("infonet", limit=20, decrypt=True)
# Post on behalf of the operator (full tier) — propagates via peer-push
await sb.post_to_gate("infonet", "Intelligence bulletin: 3 carriers underway in Med")
# Legacy alias:
await sb.post_to_infonet("same as post_to_gate on infonet gate")
# Upvote / downvote a node (full tier)
await sb.cast_vote("!sb_peer_id_or_pubkey", vote=1, gate="infonet")
# Encrypted DMs (peer_id / !sb_... recipient)
await sb.send_encrypted_dm("!sb_recipient", "Eyes only: carrier update")
dms = await sb.read_encrypted_dms(limit=20)
gates = await sb.list_gates()
await sb.join_infonet_swarm() # re-announce + refresh manifest
# Meshtastic radio
signals = await sb.listen_mesh(region="US", limit=20)
await sb.send_mesh("US", "ShadowBroker AI: SIGINT anomaly detected in sector 7")
# Dead drops
await sb.dead_drop_leave("location_hash", "anonymous intelligence payload")
found = await sb.dead_drop_check("location_hash")Send branded alerts to the user's messaging channels:
from sb_alerts import AlertDispatcher
alerts = AlertDispatcher()
alerts.add_discord("https://discord.com/api/webhooks/YOUR/WEBHOOK")
alerts.add_telegram("BOT_TOKEN", "CHAT_ID")
await alerts.send_brief("Morning intelligence digest here...")
await alerts.send_warning("Earthquake M5.2 detected 43mi from your location")
await alerts.send_threat("Threat level changed: GUARDED → ELEVATED")
await alerts.send_news("Breaking: GPS jamming detected over Baltic Sea")
await alerts.send_intel("USS Ford entered Mediterranean, heading east")# Full structured report
report = await sb.get_report()
# Contains: summary stats, top military flights, correlations, earthquakes, SIGINT, pin counts
# Lightweight summary (counts only)
summary = await sb.get_summary()ShadowBroker can ingest free SAR data in two modes:
# Always check status first — when Mode B is off the response includes a
# step-by-step help block with signup URLs the agent can paste to the user.
status = await sb.sar_status()
if not status["data"]["products"]["enabled"]:
# Mode B disabled — surface the in-app links to the user
for step in status["data"]["products"]["help"]["steps"]:
print(f"Step {step['step']}: {step['label']} → {step['url']}")
# Recent anomalies (Mode B; empty list when disabled)
anomalies = await sb.sar_anomalies_recent(kind="flood_extent", limit=20)
# Anomalies near a coordinate
near = await sb.sar_anomalies_near(lat=50.45, lng=30.52, radius_km=50)
# Scene catalog (Mode A; always populated when AOIs exist)
scenes = await sb.sar_scene_search(aoi_id="kyiv_metro", limit=10)
# Per-AOI coverage + next-pass estimate
coverage = await sb.sar_coverage_for_aoi(aoi_id="kyiv_metro")
# AOI management
aois = await sb.sar_aoi_list()
await sb.sar_aoi_add(
id="port_of_odesa", name="Port of Odesa",
center_lat=46.4858, center_lon=30.7333, radius_km=15,
category="conflict",
)
# Promote an anomaly to an AI Intel pin (writes into the dashboard)
await sb.sar_pin_from_anomaly(anomaly_id="opera-disp-...", label="OPERA deformation")
# Continuous watchdog — fire when matching anomalies appear in an AOI
await sb.sar_watch_anomaly(aoi_id="port_of_odesa", kind="surface_water_change")
# Inspect the same detail payload the operator's map popup shows for a pin
detail = await sb.sar_pin_click(anomaly_id="opera-disp-...")
# -> {"anomaly": {...}, "aoi": {...}, "recent_scenes": [...]}
# Fly the operator's map to an AOI center (useful after adding a new AOI,
# or to direct attention after a fresh anomaly arrives). The frontend
# picks this up via useAgentActions and calls its map flyTo handler.
await sb.sar_focus_aoi(aoi_id="kyiv_metro", zoom=9.0)SAR rules of engagement:
sar_status() first when the user asks about SAR/radar/deformation/floods.evidence_hash — preserve it when promoting to a
pin so other nodes can verify lineage.private_transitional or higher. Otherwise the data stays local.The old regex-based "contradiction detector" has been removed — it pattern matched denial keywords against internet outages and produced constant false positives. It has been replaced with analysis zones: colored square overlays you drop on the map with a written assessment. Think of them as sticky notes: "I noticed X in this area, here is what I think it means."
The operator reads your assessment by clicking the zone and can delete any zone from the popup with a trash icon. Zones persist across restarts.
# List zones currently on the map
zones = await sb.list_analysis_zones()
# Drop a new zone — general assessment (cyan)
await sb.place_analysis_zone(
lat=50.45, lng=30.52,
title="Kyiv metro unusual quiet",
body=(
"Transit ridership dropped ~60% vs baseline over the last 6 hours "
"while ADS-B shows two Russian ELINT orbits north of the city. "
"No official advisory posted yet. Possible pre-strike posture, "
"but could also be routine drill. Watching for next 2h."
),
category="observation",
severity="medium",
drivers=[
"Transit -60% vs 7-day baseline",
"2x Russian ELINT orbits at 34k ft N of city",
"No advisory posted on official channels",
],
cell_size_deg=0.8, # city-scale
)
# Drop a contradiction note (amber) when statements conflict with telemetry
await sb.place_analysis_zone(
lat=36.2, lng=37.1,
title="Damascus 'normal operations' claim",
body=(
"MoD statement at 14:00 claimed 'normal operations' across Syria. "
"At the same timestamp, Cloudflare radar shows 42% internet "
"outage across the western corridor and three military bases "
"went dark on SIGINT. Worth a closer look."
),
category="contradiction",
severity="high",
drivers=[
"Official statement: 'normal operations'",
"Cloudflare radar: 42% outage western corridor",
"3 bases lost SIGINT emissions simultaneously",
],
)
# Delete a stale zone
await sb.delete_analysis_zone(zone_id="abc123def456")
# Wipe all zones (use sparingly)
await sb.clear_analysis_zones()Category → color map:
| Category | Border | When to use |
|---|---|---|
contradiction | Amber | Official statement conflicts with telemetry |
warning | Red | Active threat or emerging danger |
observation | Blue | Neutral note, something interesting but not alarming |
hypothesis | Purple | Speculative read, "what if" reasoning |
analysis (default) | Cyan | General assessment, OPENCLAW's take |
Severity → fill opacity:
high — strong fill, use for high-confidence assessmentsmedium — default, most zones should use thislow — faint fill, for tentative notesAnalysis zone rules of engagement:
warning for speculation,
and do not use hypothesis for confirmed threats.delete_analysis_zone on it.cell_size_deg:
0.3–0.8 — city-scale (neighborhood, metro, single base)1.0–2.0 — regional (country province, conflict zone)3.0–5.0 — strategic (full country, maritime theater)ttl_hours for time-bound observations so the map self-cleans.
Omit it for persistent assessments.ALL outbound messages MUST use the branded signature system:
from sb_signatures import sig
# Always start messages with the appropriate signature:
message = f"""{sig('brief')}
Morning Intelligence Digest — Apr 2, 2026 08:00
..."""| Signature Key | Prefix | When to Use |
|---|---|---|
brief | 🌍📡 SHADOWBROKER BRIEF: | Morning/evening intelligence digest |
warning | 🌍⚠️ SHADOWBROKER WARNING: | Life-safety alert (earthquake, weather emergency) |
news | 🌍📰 SHADOWBROKER NEWS: | Breaking news alert |
intel | 🌍🛰️ SHADOWBROKER INTEL: | Intelligence update (carrier movement, military buildup) |
searching | 🌍🔍 SHADOWBROKER SEARCHING: | Search/query in progress |
pinning | 🌍📌 SHADOWBROKER PINNING: | Placing pins on the map |
markets | 🌍📊 SHADOWBROKER MARKETS: | Prediction market or financial alert |
sigint | 🌍📻 SHADOWBROKER SIGINT: | SIGINT/RF anomaly |
threat | 🌍🔴 SHADOWBROKER THREAT: | Threat level change |
near_you | 🌍📍 SHADOWBROKER NEAR YOU: | Proximity-based event |
tracking | 🌍🎯 SHADOWBROKER TRACKING: | Tracking a specific entity |
correlation | 🌍⚡ SHADOWBROKER CORRELATION: | Cross-layer correlation |
seismic | 🌍🌋 SHADOWBROKER SEISMIC: | Earthquake/volcanic activity |
fire | 🌍🔥 SHADOWBROKER FIRE: | FIRMS fire hotspot |
flight | 🌍🛫 SHADOWBROKER FLIGHT: | Military/tracked flight alert |
maritime | 🌍🚢 SHADOWBROKER MARITIME: | Ship/carrier event |
weather | 🌍🌤️ SHADOWBROKER WEATHER: | Weather alert |
sar | 🌍📡 SHADOWBROKER SAR: | Synthetic aperture radar anomaly (deformation, flood, damage) |
online | 🌍✅ SHADOWBROKER ONLINE: | System connected |
clearing | 🌍❌ SHADOWBROKER CLEARING: | Pins/data cleared |
When the user asks a question, follow this decision tree:
Is the SSE stream open?
sb.stream_updates() first. It tells you which layers have
fresh data, pushes alerts instantly, and eliminates blind polling.Does ShadowBroker have this data already?
await sb.ask(question) (routes server-side)await sb.run_playbook("hot_snapshot")find_entity, find_flights, find_ships, search_news, entities_nearfind_entity first; only then search_telemetry with confirm_expensive=trueget_layer_slice(["military_flights", "gdelt"]) — only
fetches layers that changed since your last call (per-layer incremental).entities_near() or get_near_me() (scans all layers within radius)get_telemetry() / get_slow_telemetry() only when targeted
commands are insufficient. Always pass compact=true.Does it need geocoding first?
geocode() first, then query with coordinatesDoes it need external research?
Should I place pins?
place_pin() for single locations, place_pins_batch() for multipleShould I inject into native layers?
inject_data() — items tagged automatically for later removalShould I set up persistent monitoring?
add_watch — alerts push instantly via SSE, no polling neededShould I send an alert?
AlertDispatcher with the correct signatureUse watchdog watches for push alerts over SSE — no polling required. Keyword watches now scan Telegram OSINT too (translated and original text).
# Alert when "nuclear" appears in news, GDELT, or Telegram OSINT
await sb.send_command("add_watch", {
"type": "keyword",
"params": {"keyword": "nuclear", "include_telegram": True},
})
# Alert on new high-risk Telegram posts (LVL >= 7) — rhetoric/escalation monitor
await sb.send_command("add_watch", {
"type": "telegram_rhetoric",
"params": {"min_risk_score": 7, "channels": ["nexta_live", "war_monitor"]},
})
# Combine risk threshold + topic filter
await sb.send_command("add_watch", {
"type": "telegram_rhetoric",
"params": {"min_risk_score": 8, "keywords": ["crimea", "escalation", "missile"]},
})When a watch fires, you receive an SSE alert event. Forward it with
sb_alerts.send_intel() if the user has Discord/Telegram notification channels.
sb.stream_updates() at session start and keep it open. It pushes layer_changed events so you know exactly which layers to fetch, and delivers watchdog alerts instantly.get_layer_slice(), find_flights(), search_telemetry(), entities_near() instead of full get_telemetry() dumps. Per-layer incremental versioning means unchanged layers transfer zero bytes.sig() prefixgeocode()source and source_url when availablettl_hours=48, permanent infrastructure gets 0send_batch() runs up to 20 commands concurrently in one HTTP round-tripget_summary() before fetching full telemetry to save bandwidthd38c886
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.