a digital entity named phi that roams bsky phi.zzstoatzz.io
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

wire phi to the reshaped relay-eval API + three-mode check_relays

- config: monitors_url -> relays_url (points at /api/relays base)
- tool: three explicit modes
- snapshot (no args): current fleet status
- history (name=<host>): timeseries; narrow with since/until,
or fall back to recent-N via limit. renders the full series
(downsampled past 200 points) instead of truncating to 5.
- transitions (transitions=True): fleet-wide status-change log
from /api/relays/events. answers "when did X happen."
- all params use Annotated + Field(description=...) so the LLM
sees what each does.
- operational instructions updated to name the three modes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+113 -35
+1 -1
src/bot/agent.py
··· 41 41 42 42 check_services checks nate's infrastructure, not yours. only use during reflection or when explicitly asked about services. 43 43 44 - check_relays reads relay-eval. no args = fleet snapshot; name="<host>" = history for that relay. when reporting, use headlines verbatim — don't add theories about cause. 44 + check_relays reads relay-eval. modes: no args = fleet snapshot; name="<host>" = coverage history for that relay (narrow with since/until for precision); transitions=True = fleet-wide status-change log (best for "when did X happen"). use headlines verbatim — no theories about cause. 45 45 """.strip() 46 46 47 47
+9 -8
src/bot/config.py
··· 124 124 description="Handle of the bot's owner (for permission-gated tools)", 125 125 ) 126 126 127 - # Infrastructure monitoring — phi polls this endpoint on a schedule and 128 - # reports status transitions. Service is the source of truth for what's 129 - # being monitored; phi is just the courier. 130 - monitors_url: str = Field( 131 - default="https://relay-eval.waow.tech/api/phi/monitors", 132 - description="Endpoint returning infrastructure monitor states", 127 + # Relay fleet monitoring — phi polls relay-eval on a schedule and 128 + # reports status transitions. The service is the source of truth; 129 + # phi is the courier. This is the base URL; /history and /events 130 + # are derived from it. 131 + relays_url: str = Field( 132 + default="https://relay-eval.waow.tech/api/relays", 133 + description="Base URL for relay-eval's relay API (snapshot endpoint)", 133 134 ) 134 - monitor_check_interval_polls: int = Field( 135 + relay_check_interval_polls: int = Field( 135 136 default=1080, # 1080 polls * 10s = 10800s = 3h 136 - description="Min polls between scheduled monitor checks (~3h at default poll interval)", 137 + description="Min polls between scheduled relay checks (~3h at default poll interval)", 137 138 ) 138 139 139 140 # Debug mode
+1 -1
src/bot/services/notification_poller.py
··· 344 344 """Check if it's time for a scheduled infrastructure monitor check.""" 345 345 if bot_status.paused: 346 346 return False 347 - if self._polls_since_last_monitor_check < settings.monitor_check_interval_polls: 347 + if self._polls_since_last_monitor_check < settings.relay_check_interval_polls: 348 348 return False 349 349 return True 350 350
+102 -25
src/bot/tools/bluesky.py
··· 33 33 return _relay_names_cache["names"] 34 34 try: 35 35 async with httpx.AsyncClient(timeout=10) as http: 36 - r = await http.get(settings.monitors_url) 36 + r = await http.get(settings.relays_url) 37 37 r.raise_for_status() 38 38 names = sorted({m.get("name", "") for m in r.json() if m.get("name")}) 39 39 _relay_names_cache["names"] = names ··· 188 188 str | None, 189 189 Field( 190 190 description=( 191 - "Specific relay hostname to query history for " 192 - "(e.g. 'zlay.waow.tech'). Omit for a fleet-wide " 193 - "snapshot of current status." 191 + "Relay hostname (e.g. 'zlay.waow.tech'). In history " 192 + "mode, required. In transitions mode, optional filter. " 193 + "Valid hostnames are in [KNOWN RELAYS]." 194 194 ) 195 195 ), 196 196 ] = None, 197 + since: Annotated[ 198 + str | None, 199 + Field( 200 + description=( 201 + "Start of window, ISO 8601 UTC (e.g. '2026-04-16T00:00:00Z'). " 202 + "Use with history or transitions to bound the time range." 203 + ) 204 + ), 205 + ] = None, 206 + until: Annotated[ 207 + str | None, 208 + Field(description="End of window, ISO 8601 UTC. Pairs with since."), 209 + ] = None, 210 + transitions: Annotated[ 211 + bool, 212 + Field( 213 + description=( 214 + "If True, return status-change events instead of coverage " 215 + "points. Best for 'when did X happen' questions." 216 + ) 217 + ), 218 + ] = False, 197 219 limit: Annotated[ 198 220 int | None, 199 221 Field( 200 222 description=( 201 - "Max history points to return when name is set. " 202 - "Default ~288 = one day at ~5-min cadence." 223 + "Recent-N fallback for history mode when since/until " 224 + "aren't set. Default ~288 = one day at 5-min cadence." 203 225 ) 204 226 ), 205 227 ] = None, 206 228 ) -> str: 207 229 """Check the atproto relay fleet nate evaluates via relay-eval. 208 230 209 - Default (no name): current snapshot of every relay, grouped by status. 210 - Report headlines verbatim. 231 + Three modes: 232 + - snapshot (default, no args): current status of every relay. 233 + - history (name=<host>): coverage timeseries for one relay. Bound 234 + with since/until for a precise window, or use limit for recent-N. 235 + - transitions (transitions=True): status-change events across the 236 + fleet. Answers "when did X happen." Optionally filter by name. 237 + 238 + Report headlines verbatim — the service owns interpretation. 239 + For app health (plyr, PDS, prefect, etc), use check_services.""" 240 + base = settings.relays_url 241 + 242 + if transitions: 243 + params: dict[str, str | int] = {} 244 + if name: 245 + params["name"] = name 246 + if since: 247 + params["since"] = since 248 + if until: 249 + params["until"] = until 250 + try: 251 + async with httpx.AsyncClient(timeout=15) as http: 252 + r = await http.get(f"{base}/events", params=params) 253 + r.raise_for_status() 254 + events = r.json() 255 + except Exception as e: 256 + return f"events endpoint unreachable: {e}" 257 + 258 + if not events: 259 + window = f"{since} → {until}" if since or until else "last 24h" 260 + scope = f" for {name}" if name else "" 261 + return f"no transitions{scope} in {window}" 211 262 212 - With name: recent coverage history for one relay — summary stats + 213 - recent points. Valid hostnames are listed in the [KNOWN RELAYS] 214 - system-prompt block; pass one of those exactly. 263 + scope = f" for {name}" if name else " (fleet)" 264 + lines = [f"transitions{scope}: {len(events)}"] 265 + for e in events: 266 + ts = e.get("ts", "")[:16].replace("T", " ") 267 + n = e.get("name", "?") 268 + from_s = e.get("from_status", "?") 269 + to_s = e.get("to_status", "?") 270 + headline = e.get("headline", "") 271 + lines.append(f" {ts} {n} {from_s} → {to_s}") 272 + if headline: 273 + lines.append(f' "{headline}"') 274 + return "\n".join(lines) 215 275 216 - For app health (plyr, PDS, prefect, etc), use check_services.""" 217 276 if name: 218 - history_url = settings.monitors_url.replace("/monitors", "/history") 219 - params: dict[str, str | int] = {"name": name} 277 + params = {"name": name} 278 + if since: 279 + params["since"] = since 280 + if until: 281 + params["until"] = until 220 282 if limit: 221 283 params["limit"] = limit 222 284 try: 223 285 async with httpx.AsyncClient(timeout=15) as http: 224 - r = await http.get(history_url, params=params) 286 + r = await http.get(f"{base}/history", params=params) 225 287 r.raise_for_status() 226 288 data = r.json() 227 289 except Exception as e: ··· 238 300 connected = summary.get("connected_runs", 0) 239 301 total = summary.get("total_runs", 0) 240 302 303 + first_ts = (points[0].get("ts", "") or "")[:16].replace("T", " ") 304 + last_ts = (points[-1].get("ts", "") or "")[:16].replace("T", " ") 305 + 306 + # downsample if the series is large; phi can narrow the window 307 + # with since/until for finer detail. 308 + max_display = 200 309 + if len(points) <= max_display: 310 + display = points 311 + downsample_note = "" 312 + else: 313 + step = max(1, len(points) // max_display) 314 + display = points[::step] 315 + downsample_note = f" (downsampled: 1 in {step})" 316 + 241 317 lines = [ 242 - f"history for {name} ({total} runs):", 243 - f" mean {mean:.2f}% | min {lo:.2f}% | max {hi:.2f}%", 244 - f" connected {connected}/{total} runs", 318 + f"history for {name}", 319 + f" window: {first_ts} → {last_ts} ({total} points)", 320 + f" mean {mean:.2f}% | min {lo:.2f}% | max {hi:.2f}% | " 321 + f"connected {connected}/{total}", 245 322 "", 246 - "recent:", 323 + f" series ({len(display)} shown){downsample_note}:", 247 324 ] 248 - for p in points[-5:]: 249 - ts = p.get("ts", "")[:16].replace("T", " ") 325 + for p in display: 326 + ts = (p.get("ts", "") or "")[:16].replace("T", " ") 250 327 pct = p.get("coverage_pct", 0) 251 - conn = "connected" if p.get("connected") else "disconnected" 252 - lines.append(f" {ts} — {pct:.2f}% ({conn})") 328 + conn = "ok" if p.get("connected") else "DISCONNECTED" 329 + lines.append(f" {ts} {pct:5.2f}% {conn}") 253 330 return "\n".join(lines) 254 331 255 332 # snapshot mode 256 333 try: 257 334 async with httpx.AsyncClient(timeout=15) as http: 258 - r = await http.get(settings.monitors_url) 335 + r = await http.get(base) 259 336 r.raise_for_status() 260 337 monitors = r.json() 261 338 except Exception as e: 262 - return f"monitors endpoint unreachable: {e}" 339 + return f"relay endpoint unreachable: {e}" 263 340 264 341 if not isinstance(monitors, list) or not monitors: 265 342 return "no monitors reported"