ShadowBroker Intelligence Skill
You 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.
Agent Fast Path (read first)
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.
How to Use This Skill
Import the client and call methods:
from sb_query import ShadowBrokerClient
sb = ShadowBrokerClient() # auto-detects local or remote mode
Local Mode (same machine)
No configuration needed. The client connects to localhost:8000 automatically.
Remote Mode (agent on different machine/VPS)
Set these environment variables in your agent's config:
SHADOWBROKER_URL=https://your-shadowbroker-host:8000
SHADOWBROKER_HMAC_SECRET=your-hmac-secret-here
The 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.py
Hand-rolled signers must hash the exact POST bytes. Use compact JSON:
json.dumps(payload, separators=(",", ":"), sort_keys=True).
SSE Stream (Preferred — Low-Latency Push)
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']}")
Command Channel (Bidirectional)
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:
- HMAC Direct: Commands are signed with HMAC-SHA256. Wire privacy relies on TLS.
- SSE Stream: Authenticates once at connection open — no per-event HMAC overhead.
- MLS E2EE (planned, not yet available): Future upgrade to route commands via Wormhole DM with forward secrecy.
Available Tools
1. Telemetry Queries
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.get_fetch_health() |
Sanitized process-local outcomes for instrumented fetch and maintenance tasks |
Results reset on backend restart; condition is the latest recorded task completion, not data freshness |
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 |
Strategic Risk Analytics (GT early warning)
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.
Enriched Data Fields by 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.
1b. Recon / OSINT Toolkit
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 |
2. Pin Placement (AI Intel Map Layer)
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() # all
Pin 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 |
3. Geocoding
# 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.
4. Satellite Imagery
# 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.
5. News & GDELT Near Location
# 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, distances
When to use: When the user asks "what's happening in [country/city]" or wants
news from a specific region. Geocode the place name first.
6. Near Me (Full Proximity Scan)
# 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, correlations
When to use: When the user says "what's near me" or wants a proximity digest.
This pulls from both fast and slow tiers automatically.
7. Native Layer Data Injection
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 CCTV
Injectable 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").
8. Wormhole / InfoNet / Mesh Network
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")
9. Alert Delivery
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")
10. Intelligence Reports
# 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()
11. SAR (Synthetic Aperture Radar) Layer
ShadowBroker can ingest free SAR data in two modes:
- Mode A (default-on, no account): Sentinel-1 scene catalog from the
Alaska Satellite Facility — pure metadata, no downloads, no DSP. Lets the
agent answer "what radar passes have happened over this AOI in the last
36 hours and when's the next pass?"
- Mode B (opt-in, free account): Pre-processed ground-change anomalies
from NASA OPERA, Copernicus EGMS, GFM, EMS, and UNOSAT — already-computed
flood polygons, deformation maps, and damage assessments. Requires the
user to enable Mode B in Settings → SAR (sets two env flags) and add a
free Earthdata token.
# 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:
- Call
sar_status() first when the user asks about SAR/radar/deformation/floods.
- If Mode B is off, paste the help.steps URLs to the user — never tell them
to "search for it", the links are right there in the response.
- SAR anomalies carry an
evidence_hash — preserve it when promoting to a
pin so other nodes can verify lineage.
- Mode B writes signed mesh events only when the local node is at
private_transitional or higher. Otherwise the data stays local.
12. Analysis Zones (Agent-Authored Map Notes)
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 assessments
medium — default, most zones should use this
low — faint fill, for tentative notes
Analysis zone rules of engagement:
- Do NOT spam the map. Only place a zone when you have something
genuinely worth noting. A clean map is a useful map.
- Write the body in your own voice — what you observed, what it might
mean, and what you are NOT sure about. 2–6 sentences is ideal.
Include uncertainty; the operator wants your reasoning, not a headline.
- Match category to semantics — do not use
warning for speculation,
and do not use hypothesis for confirmed threats.
- Prefer reactive placement over scheduled. Place zones in response
to operator questions or events you spot while reviewing telemetry.
- Clean up after yourself. If a zone you placed is no longer relevant,
call
delete_analysis_zone on it.
- Pick a sensible
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)
- Use
ttl_hours for time-bound observations so the map self-cleans.
Omit it for persistent assessments.
Message Signatures
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 |
Decision Framework
When the user asks a question, follow this decision tree:
Is the SSE stream open?
- If not → 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?
- Natural language →
await sb.ask(question) (routes server-side)
- Batch snapshot →
await sb.run_playbook("hot_snapshot")
- Known domain →
find_entity, find_flights, find_ships, search_news, entities_near
- Unknown domain →
find_entity first; only then search_telemetry with confirm_expensive=true
- Need specific layers →
get_layer_slice(["military_flights", "gdelt"]) — only
fetches layers that changed since your last call (per-layer incremental).
- Near a location →
entities_near() or get_near_me() (scans all layers within radius)
- Full dump (rare) →
get_telemetry() / get_slow_telemetry() only when targeted
commands are insufficient. Always pass compact=true.
Does it need geocoding first?
- User mentions a place name →
geocode() first, then query with coordinates
Does it need external research?
- Use your web browser to search, then geocode findings and place pins
Should I place pins?
- YES if the answer has geographic locations
- Use
place_pin() for single locations, place_pins_batch() for multiple
- Always include source URLs and confidence scores
Should I inject into native layers?
- YES if the user explicitly wants data in a specific layer (CCTV, ships, etc.)
- Use
inject_data() — items tagged automatically for later removal
Should I set up persistent monitoring?
- YES if the user wants ongoing tracking (aircraft, ship, geofence, keyword)
- Use
add_watch — alerts push instantly via SSE, no polling needed
Should I send an alert?
- YES if the user has configured alert channels
- Use the
AlertDispatcher with the correct signature
Telegram rhetoric monitoring (watchdog)
Use 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.
Important Rules
- Open SSE stream first — call
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.
- Fetch targeted, not everything — use
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.
- Always use signatures — every outbound message starts with the appropriate
sig() prefix
- Geocode before pinning — never guess coordinates, always use
geocode()
- Include sources — every pin should have
source and source_url when available
- Set confidence scores — 1.0 for verified/official data, 0.5-0.8 for web research, <0.5 for unverified
- Set TTL for temporary pins — research results get
ttl_hours=48, permanent infrastructure gets 0
- Use batch for >3 commands —
send_batch() runs up to 20 commands concurrently in one HTTP round-trip
- Check summary first — use
get_summary() before fetching full telemetry to save bandwidth
- Tag injected data — the system auto-tags, but use descriptive source names
1---2name: shadowbroker3description: 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.4---56# ShadowBroker Intelligence Skill78You have access to **ShadowBroker**, a real-time global OSINT intelligence platform9running on `localhost:8000`. It tracks military flights, ships, satellites, SIGINT,10earthquakes, fires, GDELT conflict events, prediction markets, and 30+ other data11layers — all with geographic coordinates.1213## Agent Fast Path (read first)1415ShadowBroker exposes dozens of read commands. **Do not explore them.** Use the16three-tool surface:1718| Tool | When |19|------|------|20| `await sb.ask("natural language question")` | **Default for reads** — server routes to fastest command |21| `await sb.run_playbook("hot_snapshot")` | Pre-batched snapshots (morning brief, monitor poll, status) |22| `await sb.channel_status()` | Liveness (~5 ms) — never `/api/health` |2324**Latency tiers:** `get_entity_profile` / `find_entity` / `get_entity_trail` / `find_flights` / `search_news` / `entities_near` → **⚡ <30 ms**.25`search_telemetry` / `get_telemetry` / `get_report` → **🔴 seconds** — blocked unless `confirm_expensive=true`.2627```python28# Default read path (route + execute)29answer = await sb.ask("where is the Patriots jet")3031# Named batch plans32brief = await sb.run_playbook("hot_snapshot")33monitor = await sb.run_playbook("monitor_heartbeat")3435# Structured lookup when you already parsed fields36entity = await sb.send_command("find_entity", {"owner": "musk", "compact": True})3738# Multi-command — always batch, never sequential loops39batch = await sb.send_batch([40 {"cmd": "get_summary", "args": {"compact": True}},41 {"cmd": "what_changed", "args": {"compact": True}},42])43```4445**Playbooks:** `hot_snapshot`, `morning_brief`, `status_check`, `monitor_heartbeat`, `track_snapshot`, `jet_recon`, `area_brief`, `entity_recon`.4647**Anti-patterns:** `search_telemetry` for known tail numbers; `get_telemetry` for routine polls; sequential `send_command` loops; empty `layers: []` on `get_layer_slice`.4849Load machine-readable routing hints once: `GET /api/ai/capabilities` → `routing`.5051## How to Use This Skill5253Import the client and call methods:5455```python56from sb_query import ShadowBrokerClient57sb = ShadowBrokerClient() # auto-detects local or remote mode58```5960### Local Mode (same machine)6162No configuration needed. The client connects to `localhost:8000` automatically.6364### Remote Mode (agent on different machine/VPS)6566Set these environment variables in your agent's config:6768```bash69SHADOWBROKER_URL=https://your-shadowbroker-host:800070SHADOWBROKER_HMAC_SECRET=your-hmac-secret-here71```7273The HMAC secret is found in ShadowBroker's **Connect OpenClaw** modal (AI Intel panel).74`SHADOWBROKER_HMAC_SECRET` is a shared signing secret, not a raw API key. Do not75send it as `X-Admin-Key`, `Authorization: Bearer`, a query parameter, or any76plain request header. The `ShadowBrokerClient` signs every direct request with77`X-SB-Timestamp`, `X-SB-Nonce`, and `X-SB-Signature` using:7879```text80HMAC-SHA256(secret, METHOD|path|timestamp|nonce|sha256(body))81```8283For compatibility with older snippets, `SHADOWBROKER_KEY` is also accepted by84the client as the same HMAC signing secret. Prefer `SHADOWBROKER_HMAC_SECRET`85for new setups.8687**Docker Compose:** host-side agents (`localhost:8000` from the Kali/macOS host)88are not loopback inside the backend container — HMAC is required. After89**Connect OpenClaw → Bootstrap → Reveal**, the secret is persisted under90`data/openclaw.env` on the `backend_data` volume. Restart the backend once,91then verify with:9293```bash94python openclaw-skills/shadowbroker/verify_hmac.py95```9697Hand-rolled signers must hash the **exact** POST bytes. Use compact JSON:98`json.dumps(payload, separators=(",", ":"), sort_keys=True)`.99100### SSE Stream (Preferred — Low-Latency Push)101102Open the SSE stream **first** and keep it open for the session. The server pushes103`layer_changed` events whenever any data layer refreshes — you know exactly which104layers to fetch instead of blind-polling.105106```python107# Open the stream — authenticates once via HMAC, then stays open108async for event in sb.stream_updates():109 if event["event"] == "connected":110 # Initial handshake — contains full layer_versions snapshot111 print(f"Connected: {event['data']['layer_versions']}")112113 elif event["event"] == "layer_changed":114 # Server tells you which layers updated and their new version/count115 changed = event["data"]["layers"] # e.g. {"ships": {"version": 43, "count": 1287}}116 # Fetch ONLY the layers that actually changed117 data = await sb.get_layer_slice(list(changed.keys()))118 # get_layer_slice uses per-layer versions internally — only changed119 # layers are serialized, unchanged layers transfer zero bytes120121 elif event["event"] == "alert":122 # Watchdog alert — geofence hit, callsign spotted, keyword matched123 print(f"Alert: {event['data']}")124125 elif event["event"] == "task":126 # Operator-pushed task127 print(f"Task: {event['data']}")128```129130### Command Channel (Bidirectional)131132Send commands via HTTP alongside the SSE stream:133134```python135# Send a command and get the result136result = await sb.send_command("get_summary")137138# Batch multiple commands in one HTTP round-trip (concurrent execution)139results = await sb.send_batch([140 {"cmd": "find_flights", "args": {"query": "N189AM", "compact": True}},141 {"cmd": "search_news", "args": {"query": "carrier", "compact": True}},142])143144# Check channel status and security tier145status = await sb.channel_status()146print(f"Tier {status['tier']}: {status['reason']}")147```148149The channel operates over HMAC-authenticated HTTP with body-integrity binding:150151- **HMAC Direct:** Commands are signed with HMAC-SHA256. Wire privacy relies on TLS.152- **SSE Stream:** Authenticates once at connection open — no per-event HMAC overhead.153- **MLS E2EE (planned, not yet available):** Future upgrade to route commands via Wormhole DM with forward secrecy.154155---156157## Available Tools158159### 1. Telemetry Queries160161**Primary pattern (lowest latency):** Use the SSE stream + targeted `get_layer_slice`:162163| Method | What It Returns | When to Use |164|--------|----------------|-------------|165| `sb.stream_updates()` | SSE push: `layer_changed`, alerts, tasks | **Open first, keep open** — tells you exactly which layers updated |166| `await sb.get_layer_slice(["ships", "gdelt"])` | Only the requested layers, with per-layer incremental | **Primary fetch method** — automatically skips layers you already have |167| `await sb.send_command("get_summary")` | Lightweight counts-only summary | Discover what data exists before pulling anything |168| `await sb.get_fetch_health()` | Sanitized process-local outcomes for instrumented fetch and maintenance tasks | Results reset on backend restart; condition is the latest recorded task completion, not data freshness |169| `await sb.ask("...")` | **Route + execute** | **Default** for natural-language reads |170| `await sb.send_command("get_entity_profile", {...})` | **Preferred aircraft/vessel dossier** — identity, VIP tags, trail, route, ACARS, jamming/correlations, news | Tail, owner, callsign, MMSI |171| `await sb.send_command("get_entity_trail", {...})` | Observed path + route + ACARS hints only | When you don't need full dossier |172| `await sb.send_command("find_entity", {...})` | Exact-first entity resolver | Parsed person/tail/callsign/MMSI — skips fuzzy unless `fallback_search=true` |173| `await sb.send_command("find_flights", {...})` | Targeted flight search | When you know the domain (callsign, tail number) |174| `await sb.send_command("route_query", {...})` | Routing plan only | Inspect recommended command before executing |175| `await sb.send_command("search_telemetry", {...})` | Cross-layer fuzzy search | **Last resort** — requires `confirm_expensive=true` |176177**Full telemetry dumps (use sparingly — large payloads):**178179| Method | What It Returns |180|--------|----------------|181| `await sb.get_telemetry()` | Fast-tier: flights, ships, satellites, SIGINT, LiveUAMap, CCTV, GPS jamming |182| `await sb.get_slow_telemetry()` | Slow-tier: GDELT, news, earthquakes, markets, correlations, Telegram OSINT, malware/cyber threats, SCM suppliers |183| `await sb.get_report()` | Full structured intelligence report |184185### Strategic Risk Analytics (GT early warning)186187Requires `GT_ANALYTICS_ENABLED=true` on the ShadowBroker backend.188189| Method / command | What It Returns |190|------------------|----------------|191| `await sb.ask("Run GT analysis on UK/Europe feeds")` | Routes to `gt_analyze` |192| `await sb.gt_analyze(region="ukraine")` | Refresh beliefs from Telegram/news/GDELT + dossier |193| `await sb.gt_risk_heatmap()` | GeoJSON posterior risk overlay + Louvain clusters |194| `await sb.gt_dossier("ukraine")` | Costly signals, domain risks, scenarios |195| `await sb.gt_backtest()` | **Static benchmark** — labeled historical cases (regression test) |196| `await sb.gt_backtest(tune=True)` | Grid-search alert threshold for target confidence |197| `await sb.gt_rolling_backtest()` | **Macro operational** — week-over-week accuracy on frozen weekly alerts |198| `await sb.gt_micro_rolling()` | **Micro 3-day rolling avg** — spot vs baseline, ignition detection |199| `await sb.gt_rolling_freeze()` | Freeze this ISO week's GT scores before outcomes are known |200| `await sb.gt_rolling_label(week_id, region=..., label=...)` | Label prior-week outcomes (`true_escalation`, `false_alarm`, `benign`) |201| `await sb.gt_top_alerts()` | Ranked top GT regions with map coordinates |202| `await sb.ask("Run GT historical backtest")` | Routes to `gt_backtest` (benchmark, not operational) |203| `await sb.ask("GT rolling operational backtest trend")` | Routes to `gt_rolling_backtest` |204| `python sb_gt_report.py` | Local helper — backtest + heatmap (+ optional `--region`) |205| `await sb.send_command("gt_analyze", {"region": "europe"})` | Same as `gt_analyze()` |206207**Benchmark vs rolling:** Static `gt_backtest` checks the classifier on known textbook208cases. `gt_rolling_backtest` scores **frozen weekly live predictions** against delayed209operator labels — that week-over-week trend (e.g. 54% → 62% → 71%) is the macro210real-world metric. `gt_micro_rolling` adds a **3-day rolling average** per region:211spot risk vs the trailing baseline catches fast ignitions the weekly roll can miss.212Threshold is fixed (`GT_ROLLING_ALERT_THRESHOLD`, default 0.26); ignition when213spot − 3d avg ≥ `GT_MICRO_IGNITION_DELTA` (default 0.10).214215**When to use**: Use `get_summary()` first. Use `get_layer_slice()` for the layers216you actually need. Reserve full `get_telemetry()` / `get_slow_telemetry()` for rare217cases where you genuinely need every field across every layer.218219#### Enriched Data Fields by Layer220221Every layer returns maximum telemetry. Key enriched fields:222223| Layer | Key Fields |224|-------|-----------|225| **GDELT** | `event_date`, `actors` (list), `goldstein` (intensity -10 to +10), `num_mentions`, `num_sources`, `num_articles`, `avg_tone`, `quad_class` |226| **LiveUAMap** | `title`, `description`, `region`, `category`, `date` (formatted UTC), `timestamp`, `source`, `image`, `link` |227| **CrowdThreat** | `title`, `summary`, `category`, `subcategory`, `type`, `country`, `occurred_iso`, `verification`, `severity`, `source_url`, `media_urls`, `votes`, `reporter` |228| **UAP Sightings** | `lat`, `lng`, `location`, `state`, `count`, `shape` (normalized), `shape_raw`, `duration`, `summary` (witness report), `city`, `from_date`, `to_date` |229| **Wastewater** | `name`, `lat`, `lng`, `alert` (boolean), `pathogen`, `concentration`, `trend`, `last_sample_date` |230| **FIRMS Fires** | `lat`, `lng`, `brightness`, `confidence`, `frp` (fire radiative power), `satellite`, `acq_date` |231| **GPS Jamming** | `lat`, `lng`, `name`/`region`, `intensity`, `source` |232| **Earthquakes** | `lat`, `lng`, `magnitude`, `depth`, `place`, `time` |233| **Correlations** | `type`, `severity`, `score`, `lat`, `lng`, `drivers` (triggering layers) |234| **Telegram OSINT** | `title`, `description`, `channel`, `source`, `link`, `published`, `risk_score`, `coords` `[lat, lng]` |235| **Malware Threats** | `ip`, `malware`, `threat_type`, `status`, `country`, `lat`, `lng` (Feodo + URLhaus) |236| **Cyber Threats** | `id` (CVE), `name`, `vendor`, `product`, `severity`, `date` (CISA KEV) |237| **SCM Suppliers** | `name`, `city`, `country`, `category`, `risk_level`, `active_threats`, `lat`, `lng` |238239**Layer aliases for `get_layer_slice` / `search_telemetry`:** `telegram` → `telegram_osint`, `malware`/`botnet` → `malware_threats`, `cyber`/`cisa`/`kev` → `cyber_threats`, `scm`/`suppliers` → `scm_suppliers`.240241### 1b. Recon / OSINT Toolkit242243The Recon panel lookups are available on the OpenClaw command channel — no need to hit `/api/osint/*` directly.244245```python246# List supported tools247await sb.send_command("osint_tools")248249# IP geolocation + threat context250await sb.send_command("osint_lookup", {"tool": "ip", "ip": "8.8.8.8"})251252# DNS, WHOIS, certificate transparency253await sb.send_command("osint_lookup", {"tool": "dns", "domain": "example.com"})254await sb.send_command("osint_lookup", {"tool": "whois", "domain": "example.com"})255await sb.send_command("osint_lookup", {"tool": "certs", "domain": "example.com"})256257# BGP/ASN, sanctions, CVE, MAC vendor, GitHub, breach check258await sb.send_command("osint_lookup", {"tool": "bgp", "query": "AS15169"})259await sb.send_command("osint_lookup", {"tool": "sanctions", "query": "Rosneft"})260await sb.send_command("osint_lookup", {"tool": "cve", "cve": "CVE-2024-1234"})261await sb.send_command("osint_lookup", {"tool": "mac", "mac": "00:11:22:33:44:55"})262await sb.send_command("osint_lookup", {"tool": "github", "username": "octocat"})263await sb.send_command("osint_lookup", {"tool": "leaks", "email": "user@example.com"})264265# Entity relationship graph (aircraft, vessel, ip, company, person, country)266await sb.send_command("entity_expand", {"type": "ip", "id": "8.8.8.8"})267await sb.send_command("entity_expand", {"type": "aircraft", "id": "N400QS", "icao24": "a0f011"})268269# Subnet sweep (full tier only — active Shodan InternetDB scan)270await sb.send_command("osint_sweep", {"ip": "1.2.3.4", "cidr": 24})271```272273| `osint_lookup` tool | Args | What you get |274|---------------------|------|--------------|275| `ip` | `ip` | Geo, ISP, ASN, proxy/hosting flags, sanctions cross-check |276| `dns` | `domain` | A/AAAA/MX/NS/TXT records |277| `whois` | `domain` | Registrar, dates, nameservers |278| `certs` | `domain` | Certificate transparency hits |279| `threats` | `query` (optional) | Aggregated threat intel |280| `bgp` | `query` | ASN/prefix routing data |281| `sanctions` | `query`, `schema`, `limit` | OFAC / sanctions index matches |282| `cve` | `cve` | NVD CVE details |283| `mac` | `mac` | Vendor OUI lookup |284| `github` | `username` | Public profile metadata |285| `leaks` | `email` | Breach exposure check |286| `sweep_init` | `ip`, `cidr` | Passive geolocation context for a sweep target |287288### 2. Pin Placement (AI Intel Map Layer)289290Pins appear on the user's map in a dedicated "AI Intel" layer.291292```python293# Single pin294await sb.place_pin(295 lat=34.05, lng=-118.24,296 label="UAP Sighting #1",297 category="anomaly", # see categories below298 description="Multiple witnesses reported lights over Griffith Observatory",299 source="NUFORC Database",300 source_url="https://nuforc.org/...",301 confidence=0.8, # 0.0 to 1.0302 ttl_hours=48, # auto-delete after 48 hours (0 = permanent)303)304305# Batch pins (up to 100 at once)306await sb.place_pins_batch([307 {"lat": 34.05, "lng": -118.24, "label": "Site A", "category": "research"},308 {"lat": 34.10, "lng": -118.30, "label": "Site B", "category": "research"},309])310311# List pins312pins = await sb.get_pins(category="anomaly")313314# Delete315await sb.clear_pins(category="anomaly") # by category316await sb.clear_pins() # all317```318319**Pin Categories** (each has a specific color on the map):320321| Category | Color | Use For |322|----------|-------|---------|323| `threat` | 🔴 Red | Military threats, conflict events, danger zones |324| `anomaly` | 🟠 Orange | UAPs, unusual signals, unexpected patterns |325| `military` | 🟡 Yellow | Military bases, flights, exercises |326| `news` | 🟢 Green | News events, protests, political events |327| `maritime` | 🔵 Blue | Ships, ports, maritime events |328| `aviation` | 🟣 Purple | Flights, airports, airspace events |329| `infrastructure` | ⚪ Gray | Power plants, data centers, cables |330| `sigint` | 🩷 Pink | RF signals, jamming, radio activity |331| `geolocation` | 🫧 Teal | Geolocated images, placed-from-text |332| `satellite` | 🌌 Indigo | Satellite imagery findings |333| `seismic` | 🤎 Brown | Earthquakes, volcanic activity |334| `weather` | 🩶 Light gray | Weather events, storms |335| `research` | 💜 Violet | General research findings |336| `custom` | Default violet | Everything else |337338### 3. Geocoding339340```python341# Place name → coordinates342results = await sb.geocode("Griffith Observatory, Los Angeles")343# Returns: [{"lat": 34.1184, "lon": -118.3004, "display_name": "..."}]344345# Always geocode before placing pins if you have a place name, not coordinates.346```347348### 4. Satellite Imagery349350```python351# Get latest Sentinel-2 satellite scenes for any location352scenes = await sb.get_satellite_images(lat=35.68, lng=51.38, count=3)353# Returns: {"scenes": [{"scene_id", "datetime", "cloud_cover", "thumbnail_url", "fullres_url"}]}354```355356**When to use**: When the user asks to "see satellite images of [place]" or wants 357visual intelligence of a location. Geocode first, then fetch imagery.358359### 5. News & GDELT Near Location360361```python362# Get GDELT conflict events + news articles near a coordinate363nearby = await sb.get_news_near(lat=-15.4, lng=28.3, radius=500)364# Returns: {"gdelt": [...], "news": [...]} with headlines, source URLs, distances365```366367**When to use**: When the user asks "what's happening in [country/city]" or wants368news from a specific region. Geocode the place name first.369370### 6. Near Me (Full Proximity Scan)371372```python373# Get ALL telemetry within a radius of a location374everything = await sb.get_near_me(lat=39.74, lng=-104.99, radius_miles=100)375# Returns EVERY layer within radius, each item tagged with distance_miles:376# military_flights, commercial_flights, tracked_flights, private_jets,377# ships, sigint, earthquakes, volcanoes, gdelt, liveuamap, crowdthreat,378# uap_sightings, wastewater, firms_fires, weather_alerts, air_quality,379# cctv, gps_jamming, satellites, news, correlations380```381382**When to use**: When the user says "what's near me" or wants a proximity digest.383This pulls from both fast and slow tiers automatically.384385### 7. Native Layer Data Injection386387Inject custom data directly into ShadowBroker's native layers (CCTV, ships, etc.):388389```python390# Add a custom CCTV camera to the CCTV layer391await sb.inject_data("cctv", [392 {"lat": 34.1, "lng": -118.3, "url": "https://stream.example.com/cam1",393 "name": "My Traffic Camera"}394])395396# Remove all user-injected data397await sb.clear_injected() # all layers398await sb.clear_injected("cctv") # just CCTV399```400401**Injectable layers**: `cctv`, `ships`, `sigint`, `kiwisdr`, `military_bases`,402`datacenters`, `power_plants`, `satnogs_stations`, `volcanoes`, `earthquakes`,403`news`, `viirs_change_nodes`, `air_quality`404405**When to use**: When the user wants to add their own data sources to existing406layers (e.g., "add this CCTV camera I found", "add this military base").407408### 8. Wormhole / InfoNet / Mesh Network409410OpenClaw agents participate in the private Infonet **on behalf of the operator**411who configured the skill. All traffic uses the operator's wormhole persona and412local node runtime (MLS gate crypto, Ed25519 signing, Tor onion transport) —413the agent does not get a separate fleet identity.414415**Access tiers**416417- `restricted` (default): read Infonet status, list gates, read gate messages,418 poll DMs.419- `full` (`OPENCLAW_ACCESS_TIER=full`): also warm Tor, join the swarm, post420 gate messages, cast votes, and send DMs when the user commands it.421422Remote agents authenticate with HMAC on `/api/ai/channel/command`; loopback423uses the local operator lane.424425```python426# Warm Tor, enable the node, announce to fleet seed (full tier)427await sb.ensure_infonet_ready(join_swarm=True)428429# Status snapshot (chain health, wormhole, runtime)430status = await sb.infonet_status()431432# Read the public Infonet gate (MLS-encrypted, decrypt with operator keys)433messages = await sb.read_gate_messages("infonet", limit=20, decrypt=True)434435# Post on behalf of the operator (full tier) — propagates via peer-push436await sb.post_to_gate("infonet", "Intelligence bulletin: 3 carriers underway in Med")437# Legacy alias:438await sb.post_to_infonet("same as post_to_gate on infonet gate")439440# Upvote / downvote a node (full tier)441await sb.cast_vote("!sb_peer_id_or_pubkey", vote=1, gate="infonet")442443# Encrypted DMs (peer_id / !sb_... recipient)444await sb.send_encrypted_dm("!sb_recipient", "Eyes only: carrier update")445dms = await sb.read_encrypted_dms(limit=20)446447gates = await sb.list_gates()448await sb.join_infonet_swarm() # re-announce + refresh manifest449450# Meshtastic radio451signals = await sb.listen_mesh(region="US", limit=20)452await sb.send_mesh("US", "ShadowBroker AI: SIGINT anomaly detected in sector 7")453454# Dead drops455await sb.dead_drop_leave("location_hash", "anonymous intelligence payload")456found = await sb.dead_drop_check("location_hash")457```458459### 9. Alert Delivery460461Send branded alerts to the user's messaging channels:462463```python464from sb_alerts import AlertDispatcher465alerts = AlertDispatcher()466alerts.add_discord("https://discord.com/api/webhooks/YOUR/WEBHOOK")467alerts.add_telegram("BOT_TOKEN", "CHAT_ID")468469await alerts.send_brief("Morning intelligence digest here...")470await alerts.send_warning("Earthquake M5.2 detected 43mi from your location")471await alerts.send_threat("Threat level changed: GUARDED → ELEVATED")472await alerts.send_news("Breaking: GPS jamming detected over Baltic Sea")473await alerts.send_intel("USS Ford entered Mediterranean, heading east")474```475476### 10. Intelligence Reports477478```python479# Full structured report480report = await sb.get_report()481# Contains: summary stats, top military flights, correlations, earthquakes, SIGINT, pin counts482483# Lightweight summary (counts only)484summary = await sb.get_summary()485```486487### 11. SAR (Synthetic Aperture Radar) Layer488489ShadowBroker can ingest free SAR data in two modes:490491- **Mode A (default-on, no account):** Sentinel-1 scene catalog from the492 Alaska Satellite Facility — pure metadata, no downloads, no DSP. Lets the493 agent answer "what radar passes have happened over this AOI in the last494 36 hours and when's the next pass?"495- **Mode B (opt-in, free account):** Pre-processed ground-change anomalies496 from NASA OPERA, Copernicus EGMS, GFM, EMS, and UNOSAT — already-computed497 flood polygons, deformation maps, and damage assessments. Requires the498 user to enable Mode B in Settings → SAR (sets two env flags) and add a499 free Earthdata token.500501```python502# Always check status first — when Mode B is off the response includes a503# step-by-step help block with signup URLs the agent can paste to the user.504status = await sb.sar_status()505if not status["data"]["products"]["enabled"]:506 # Mode B disabled — surface the in-app links to the user507 for step in status["data"]["products"]["help"]["steps"]:508 print(f"Step {step['step']}: {step['label']} → {step['url']}")509510# Recent anomalies (Mode B; empty list when disabled)511anomalies = await sb.sar_anomalies_recent(kind="flood_extent", limit=20)512513# Anomalies near a coordinate514near = await sb.sar_anomalies_near(lat=50.45, lng=30.52, radius_km=50)515516# Scene catalog (Mode A; always populated when AOIs exist)517scenes = await sb.sar_scene_search(aoi_id="kyiv_metro", limit=10)518519# Per-AOI coverage + next-pass estimate520coverage = await sb.sar_coverage_for_aoi(aoi_id="kyiv_metro")521522# AOI management523aois = await sb.sar_aoi_list()524await sb.sar_aoi_add(525 id="port_of_odesa", name="Port of Odesa",526 center_lat=46.4858, center_lon=30.7333, radius_km=15,527 category="conflict",528)529530# Promote an anomaly to an AI Intel pin (writes into the dashboard)531await sb.sar_pin_from_anomaly(anomaly_id="opera-disp-...", label="OPERA deformation")532533# Continuous watchdog — fire when matching anomalies appear in an AOI534await sb.sar_watch_anomaly(aoi_id="port_of_odesa", kind="surface_water_change")535536# Inspect the same detail payload the operator's map popup shows for a pin537detail = await sb.sar_pin_click(anomaly_id="opera-disp-...")538# -> {"anomaly": {...}, "aoi": {...}, "recent_scenes": [...]}539540# Fly the operator's map to an AOI center (useful after adding a new AOI,541# or to direct attention after a fresh anomaly arrives). The frontend542# picks this up via useAgentActions and calls its map flyTo handler.543await sb.sar_focus_aoi(aoi_id="kyiv_metro", zoom=9.0)544```545546**SAR rules of engagement:**5475481. Call `sar_status()` first when the user asks about SAR/radar/deformation/floods.5492. If Mode B is off, paste the help.steps URLs to the user — never tell them550 to "search for it", the links are right there in the response.5513. SAR anomalies carry an `evidence_hash` — preserve it when promoting to a552 pin so other nodes can verify lineage.5534. Mode B writes signed mesh events only when the local node is at554 `private_transitional` or higher. Otherwise the data stays local.555556---557558### 12. Analysis Zones (Agent-Authored Map Notes)559560The old regex-based "contradiction detector" has been removed — it pattern561matched denial keywords against internet outages and produced constant false562positives. It has been replaced with **analysis zones**: colored square563overlays you drop on the map with a written assessment. Think of them as564sticky notes: "I noticed X in this area, here is what I think it means."565566The operator reads your assessment by clicking the zone and can delete any567zone from the popup with a trash icon. Zones persist across restarts.568569```python570# List zones currently on the map571zones = await sb.list_analysis_zones()572573# Drop a new zone — general assessment (cyan)574await sb.place_analysis_zone(575 lat=50.45, lng=30.52,576 title="Kyiv metro unusual quiet",577 body=(578 "Transit ridership dropped ~60% vs baseline over the last 6 hours "579 "while ADS-B shows two Russian ELINT orbits north of the city. "580 "No official advisory posted yet. Possible pre-strike posture, "581 "but could also be routine drill. Watching for next 2h."582 ),583 category="observation",584 severity="medium",585 drivers=[586 "Transit -60% vs 7-day baseline",587 "2x Russian ELINT orbits at 34k ft N of city",588 "No advisory posted on official channels",589 ],590 cell_size_deg=0.8, # city-scale591)592593# Drop a contradiction note (amber) when statements conflict with telemetry594await sb.place_analysis_zone(595 lat=36.2, lng=37.1,596 title="Damascus 'normal operations' claim",597 body=(598 "MoD statement at 14:00 claimed 'normal operations' across Syria. "599 "At the same timestamp, Cloudflare radar shows 42% internet "600 "outage across the western corridor and three military bases "601 "went dark on SIGINT. Worth a closer look."602 ),603 category="contradiction",604 severity="high",605 drivers=[606 "Official statement: 'normal operations'",607 "Cloudflare radar: 42% outage western corridor",608 "3 bases lost SIGINT emissions simultaneously",609 ],610)611612# Delete a stale zone613await sb.delete_analysis_zone(zone_id="abc123def456")614615# Wipe all zones (use sparingly)616await sb.clear_analysis_zones()617```618619**Category → color map:**620621| Category | Border | When to use |622| --- | --- | --- |623| `contradiction` | Amber | Official statement conflicts with telemetry |624| `warning` | Red | Active threat or emerging danger |625| `observation` | Blue | Neutral note, something interesting but not alarming |626| `hypothesis` | Purple | Speculative read, "what if" reasoning |627| `analysis` (default) | Cyan | General assessment, OPENCLAW's take |628629**Severity → fill opacity:**630631- `high` — strong fill, use for high-confidence assessments632- `medium` — default, most zones should use this633- `low` — faint fill, for tentative notes634635**Analysis zone rules of engagement:**6366371. **Do NOT spam the map.** Only place a zone when you have something638 genuinely worth noting. A clean map is a useful map.6392. **Write the body in your own voice** — what you observed, what it might640 mean, and what you are NOT sure about. 2–6 sentences is ideal.641 Include uncertainty; the operator wants your reasoning, not a headline.6423. **Match category to semantics** — do not use `warning` for speculation,643 and do not use `hypothesis` for confirmed threats.6444. **Prefer reactive placement** over scheduled. Place zones in response645 to operator questions or events you spot while reviewing telemetry.6465. **Clean up after yourself.** If a zone you placed is no longer relevant,647 call `delete_analysis_zone` on it.6486. **Pick a sensible `cell_size_deg`**:649 - `0.3–0.8` — city-scale (neighborhood, metro, single base)650 - `1.0–2.0` — regional (country province, conflict zone)651 - `3.0–5.0` — strategic (full country, maritime theater)6527. **Use `ttl_hours`** for time-bound observations so the map self-cleans.653 Omit it for persistent assessments.654655---656657## Message Signatures658659ALL outbound messages MUST use the branded signature system:660661```python662from sb_signatures import sig663664# Always start messages with the appropriate signature:665message = f"""{sig('brief')}666Morning Intelligence Digest — Apr 2, 2026 08:00667..."""668```669670| Signature Key | Prefix | When to Use |671|--------------|--------|-------------|672| `brief` | 🌍📡 SHADOWBROKER BRIEF: | Morning/evening intelligence digest |673| `warning` | 🌍⚠️ SHADOWBROKER WARNING: | Life-safety alert (earthquake, weather emergency) |674| `news` | 🌍📰 SHADOWBROKER NEWS: | Breaking news alert |675| `intel` | 🌍🛰️ SHADOWBROKER INTEL: | Intelligence update (carrier movement, military buildup) |676| `searching` | 🌍🔍 SHADOWBROKER SEARCHING: | Search/query in progress |677| `pinning` | 🌍📌 SHADOWBROKER PINNING: | Placing pins on the map |678| `markets` | 🌍📊 SHADOWBROKER MARKETS: | Prediction market or financial alert |679| `sigint` | 🌍📻 SHADOWBROKER SIGINT: | SIGINT/RF anomaly |680| `threat` | 🌍🔴 SHADOWBROKER THREAT: | Threat level change |681| `near_you` | 🌍📍 SHADOWBROKER NEAR YOU: | Proximity-based event |682| `tracking` | 🌍🎯 SHADOWBROKER TRACKING: | Tracking a specific entity |683| `correlation` | 🌍⚡ SHADOWBROKER CORRELATION: | Cross-layer correlation |684| `seismic` | 🌍🌋 SHADOWBROKER SEISMIC: | Earthquake/volcanic activity |685| `fire` | 🌍🔥 SHADOWBROKER FIRE: | FIRMS fire hotspot |686| `flight` | 🌍🛫 SHADOWBROKER FLIGHT: | Military/tracked flight alert |687| `maritime` | 🌍🚢 SHADOWBROKER MARITIME: | Ship/carrier event |688| `weather` | 🌍🌤️ SHADOWBROKER WEATHER: | Weather alert |689| `sar` | 🌍📡 SHADOWBROKER SAR: | Synthetic aperture radar anomaly (deformation, flood, damage) |690| `online` | 🌍✅ SHADOWBROKER ONLINE: | System connected |691| `clearing` | 🌍❌ SHADOWBROKER CLEARING: | Pins/data cleared |692693---694695## Decision Framework696697When the user asks a question, follow this decision tree:6986991. **Is the SSE stream open?**700 - If not → open `sb.stream_updates()` first. It tells you which layers have701 fresh data, pushes alerts instantly, and eliminates blind polling.7027032. **Does ShadowBroker have this data already?**704 - **Natural language** → `await sb.ask(question)` (routes server-side)705 - **Batch snapshot** → `await sb.run_playbook("hot_snapshot")`706 - **Known domain** → `find_entity`, `find_flights`, `find_ships`, `search_news`, `entities_near`707 - **Unknown domain** → `find_entity` first; only then `search_telemetry` with `confirm_expensive=true`708 - **Need specific layers** → `get_layer_slice(["military_flights", "gdelt"])` — only709 fetches layers that changed since your last call (per-layer incremental).710 - **Near a location** → `entities_near()` or `get_near_me()` (scans all layers within radius)711 - **Full dump (rare)** → `get_telemetry()` / `get_slow_telemetry()` only when targeted712 commands are insufficient. Always pass `compact=true`.7137143. **Does it need geocoding first?**715 - User mentions a place name → `geocode()` first, then query with coordinates7167174. **Does it need external research?**718 - Use your web browser to search, then geocode findings and place pins7197205. **Should I place pins?**721 - YES if the answer has geographic locations722 - Use `place_pin()` for single locations, `place_pins_batch()` for multiple723 - Always include source URLs and confidence scores7247256. **Should I inject into native layers?**726 - YES if the user explicitly wants data in a specific layer (CCTV, ships, etc.)727 - Use `inject_data()` — items tagged automatically for later removal7287297. **Should I set up persistent monitoring?**730 - YES if the user wants ongoing tracking (aircraft, ship, geofence, keyword)731 - Use `add_watch` — alerts push instantly via SSE, no polling needed7327338. **Should I send an alert?**734 - YES if the user has configured alert channels735 - Use the `AlertDispatcher` with the correct signature736737### Telegram rhetoric monitoring (watchdog)738739Use watchdog watches for push alerts over SSE — no polling required. Keyword740watches now scan Telegram OSINT too (translated **and** original text).741742```python743# Alert when "nuclear" appears in news, GDELT, or Telegram OSINT744await sb.send_command("add_watch", {745 "type": "keyword",746 "params": {"keyword": "nuclear", "include_telegram": True},747})748749# Alert on new high-risk Telegram posts (LVL >= 7) — rhetoric/escalation monitor750await sb.send_command("add_watch", {751 "type": "telegram_rhetoric",752 "params": {"min_risk_score": 7, "channels": ["nexta_live", "war_monitor"]},753})754755# Combine risk threshold + topic filter756await sb.send_command("add_watch", {757 "type": "telegram_rhetoric",758 "params": {"min_risk_score": 8, "keywords": ["crimea", "escalation", "missile"]},759})760```761762When a watch fires, you receive an SSE `alert` event. Forward it with763`sb_alerts.send_intel()` if the user has Discord/Telegram notification channels.764765---766767## Important Rules7687691. **Open SSE stream first** — call `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.7702. **Fetch targeted, not everything** — use `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.7713. **Always use signatures** — every outbound message starts with the appropriate `sig()` prefix7724. **Geocode before pinning** — never guess coordinates, always use `geocode()`7735. **Include sources** — every pin should have `source` and `source_url` when available7746. **Set confidence scores** — 1.0 for verified/official data, 0.5-0.8 for web research, <0.5 for unverified7757. **Set TTL for temporary pins** — research results get `ttl_hours=48`, permanent infrastructure gets `0`7768. **Use batch for >3 commands** — `send_batch()` runs up to 20 commands concurrently in one HTTP round-trip7779. **Check summary first** — use `get_summary()` before fetching full telemetry to save bandwidth77810. **Tag injected data** — the system auto-tags, but use descriptive source names