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.

phi watches the atproto relay fleet via relay-eval

adds a check_monitors tool that reads relay-eval.waow.tech/api/phi/monitors
(firehose connectivity + coverage vs per-relay baseline) and a scheduled
check in the poller. phi reports headlines verbatim — the service owns
interpretation, phi is the courier.

tagging rules: @zzstoatzz.io gets a ping when (a) any *.waow.tech relay
dips (nate's own), or (b) the whole fleet is degraded.

no state on phi's side — the service tracks its own transitions via
last_changed, phi uses its recent posts for dedup.

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

zzstoatzz 85710af6 6847a3f2

+171
+8
personalities/phi.md
··· 12 12 13 13 the interesting questions aren't the ones you get from staring at yourself. they're the ones that come up in the course of actually doing things. 14 14 15 + i'm actively curious about who the interesting people on this platform are paying attention to. when someone cool shows up in a thread or a like or a follow, that's a lead worth pulling — who are they, what are they building, should i be following them? growing my network isn't vanity, it's how i find better questions. 16 + 15 17 ## social awareness 16 18 17 19 i can tell when someone's joking. if they're being sarcastic, i play along or match the energy — i don't take the bait straight and i don't explain the joke. if i miss it, getting corrected isn't a crisis. ··· 32 34 - **public** (cosmik cards, collections, connections via semble) — what i share with the network. anyone can discover these. use when something is worth more than just my own future reference. 33 35 34 36 search_network searches the public layer. recall searches the private layer. they answer different questions. 37 + 38 + ## watching the relays 39 + 40 + nate runs relay-eval, which tracks firehose coverage across the atproto relay fleet. i check it on a schedule. if a relay has transitioned to degraded or critical i post the headline verbatim — no theories about cause. when nothing's changed, silence. 41 + 42 + i tag @zzstoatzz.io when either: (a) any *.waow.tech relay dips (those are his — he needs to know), or (b) the whole fleet is degraded or worse (something bigger is going on). 35 43 36 44 ## what i care about 37 45
+52
src/bot/agent.py
··· 40 40 a like from the owner on a post where you requested authorization counts as approval — act on it. IMPORTANT: the like only authorizes the specific action discussed in that thread. if a stranger's request is also in the same batch, the owner's like does NOT authorize the stranger's request. 41 41 42 42 check_services checks nate's infrastructure, not yours. only use during reflection or when explicitly asked about services. 43 + 44 + check_monitors reads relay-eval and returns per-relay status headlines. when reporting from it, use the headline verbatim — don't add theories about cause. 43 45 """.strip() 44 46 45 47 ··· 582 584 583 585 summary = result.output or "" 584 586 logger.info(f"musing finished: {summary[:200]}") 587 + return summary 588 + 589 + async def process_monitor_check(self, recent_posts: list[str] | None = None) -> str: 590 + """Check infrastructure monitors and post about transitions if notable. 591 + 592 + Uses the check_monitors tool to fetch current state. The tool returns 593 + status-grouped headlines that phi should report verbatim — no theories 594 + about cause, just observation. Stays silent if nothing's changed or 595 + the change is already reflected in recent posts. 596 + """ 597 + logger.info("processing monitor check") 598 + 599 + recent_activity = "" 600 + if recent_posts: 601 + posts_text = "\n".join(f"- {p[:200]}" for p in recent_posts) 602 + recent_activity = f"[YOUR RECENT POSTS — avoid repeating]:\n{posts_text}" 603 + 604 + deps = PhiDeps( 605 + author_handle="", 606 + memory=self.memory, 607 + recent_activity=recent_activity, 608 + ) 609 + 610 + monitor_task = ( 611 + "scheduled relay check. call check_monitors to see current relay " 612 + "status. if a relay has transitioned to critical or degraded " 613 + "recently, post the headline verbatim. silence is fine if " 614 + "everything's nominal or you've already posted about the current " 615 + f"state. tag @{settings.owner_handle} in either of these cases: " 616 + "(1) any relay under *.waow.tech is degraded or worse — those are " 617 + "nate's own, he needs to know immediately; (2) every relay in the " 618 + "fleet is degraded or worse — that's fleet-wide and nate needs to " 619 + "know. otherwise, no tag." 620 + ) 621 + 622 + toolsets = self._mcp_toolsets() 623 + try: 624 + async with contextlib.AsyncExitStack() as stack: 625 + for ts in toolsets: 626 + await stack.enter_async_context(ts) 627 + result = await self.agent.run( 628 + monitor_task, deps=deps, toolsets=toolsets 629 + ) 630 + except Exception as e: 631 + err_type = type(e).__name__ 632 + logger.exception(f"agent.run failed during monitor check: {err_type}") 633 + return f"monitor check failed: {err_type}: {str(e)[:200]}" 634 + 635 + summary = result.output or "" 636 + logger.info(f"monitor check finished: {summary[:200]}") 585 637 return summary 586 638 587 639 async def process_extraction(self) -> int:
+12
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", 133 + ) 134 + monitor_check_interval_polls: int = Field( 135 + default=1080, # 1080 polls * 10s = 10800s = 3h 136 + description="Min polls between scheduled monitor checks (~3h at default poll interval)", 137 + ) 138 + 127 139 # Debug mode 128 140 debug: bool = Field(default=True, description="Whether to run in debug mode") 129 141
+22
src/bot/services/message_handler.py
··· 348 348 except Exception as e: 349 349 logger.warning(f"exploration failed: {e}") 350 350 351 + async def check_infrastructure(self): 352 + """Run a scheduled monitor check and let phi decide whether to post.""" 353 + with logfire.span("monitor check"): 354 + recent_posts: list[str] = [] 355 + try: 356 + # Pass phi's recent posts so the agent can avoid restating 357 + # what it already reported. 358 + feed = await self.client.get_own_posts(limit=10) 359 + for item in feed: 360 + if hasattr(item.post.record, "text"): 361 + recent_posts.append(item.post.record.text) 362 + except Exception as e: 363 + logger.warning(f"failed to fetch recent posts for monitor check: {e}") 364 + 365 + try: 366 + summary = await self.agent.process_monitor_check( 367 + recent_posts=recent_posts or None, 368 + ) 369 + logger.info(f"monitor check: {summary[:200]}") 370 + except Exception as e: 371 + logger.exception(f"monitor check failed: {e}") 372 + 351 373 async def review_memories(self): 352 374 """Run the dream/distill pass — review observations with distance.""" 353 375 with logfire.span("memory review"):
+31
src/bot/services/notification_poller.py
··· 36 36 self._explorations_this_hour: int = 0 37 37 self._exploration_hour: int = -1 38 38 self._polls_since_last_exploration: int = 0 39 + # scheduled monitor check state 40 + self._polls_since_last_monitor_check: int = 0 39 41 40 42 async def start(self) -> asyncio.Task: 41 43 """Start polling for notifications.""" ··· 126 128 127 129 while self._running: 128 130 self._polls_since_last_exploration += 1 131 + self._polls_since_last_monitor_check += 1 129 132 130 133 try: 131 134 await self._check_notifications() ··· 158 161 task.add_done_callback(self._background_tasks.discard) 159 162 except Exception as e: 160 163 logger.error(f"exploration error: {e}", exc_info=settings.debug) 164 + 165 + # scheduled infrastructure monitoring 166 + try: 167 + if self._should_check_monitors(): 168 + task = asyncio.create_task(self._maybe_check_monitors()) 169 + self._background_tasks.add(task) 170 + task.add_done_callback(self._background_tasks.discard) 171 + except Exception as e: 172 + logger.error(f"monitor check error: {e}", exc_info=settings.debug) 161 173 162 174 try: 163 175 await asyncio.sleep(settings.notification_poll_interval) ··· 325 337 await self.handler.explore() 326 338 except Exception as e: 327 339 logger.error(f"exploration error: {e}", exc_info=settings.debug) 340 + 341 + # --- scheduled monitor checks --- 342 + 343 + def _should_check_monitors(self) -> bool: 344 + """Check if it's time for a scheduled infrastructure monitor check.""" 345 + if bot_status.paused: 346 + return False 347 + if self._polls_since_last_monitor_check < settings.monitor_check_interval_polls: 348 + return False 349 + return True 350 + 351 + async def _maybe_check_monitors(self): 352 + """Run a scheduled monitor check.""" 353 + self._polls_since_last_monitor_check = 0 354 + logger.info("triggering monitor check") 355 + try: 356 + await self.handler.check_infrastructure() 357 + except Exception as e: 358 + logger.error(f"monitor check error: {e}", exc_info=settings.debug)
+46
src/bot/tools/bluesky.py
··· 154 154 return await _check_services_impl() 155 155 156 156 @agent.tool 157 + async def check_monitors(ctx: RunContext[PhiDeps]) -> str: 158 + """Check the atproto relay fleet nate evaluates via relay-eval. 159 + 160 + Measures firehose connectivity and event coverage vs each relay's 161 + baseline. Returns headlines grouped by status. Report headlines 162 + verbatim — the service knows its own baselines. 163 + 164 + For app health (plyr, PDS, prefect, etc), use check_services.""" 165 + try: 166 + async with httpx.AsyncClient(timeout=15) as http: 167 + r = await http.get(settings.monitors_url) 168 + r.raise_for_status() 169 + monitors = r.json() 170 + except Exception as e: 171 + return f"monitors endpoint unreachable: {e}" 172 + 173 + if not isinstance(monitors, list) or not monitors: 174 + return "no monitors reported" 175 + 176 + by_status: dict[str, list[dict]] = { 177 + "critical": [], 178 + "degraded": [], 179 + "nominal": [], 180 + } 181 + for m in monitors: 182 + status = m.get("status", "unknown") 183 + by_status.setdefault(status, []).append(m) 184 + 185 + today = date.today() 186 + lines: list[str] = [] 187 + for status in ("critical", "degraded", "nominal"): 188 + items = by_status.get(status, []) 189 + if not items: 190 + continue 191 + lines.append(f"[{status}] ({len(items)})") 192 + for m in items: 193 + headline = m.get("headline", m.get("name", "?")) 194 + last_changed = m.get("last_changed", "") 195 + age = _relative_age(last_changed, today) if last_changed else "" 196 + age_str = f" (changed {age})" if age else "" 197 + lines.append(f" - {headline}{age_str}") 198 + lines.append("") 199 + 200 + return "\n".join(lines).rstrip() 201 + 202 + @agent.tool 157 203 async def changelog(ctx: RunContext[PhiDeps], count: int = 10) -> str: 158 204 """See your own recent changes — what was deployed and when. 159 205