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.

fix tool call loop — cap request limit to 15, soften URL check instruction

phi was hitting the default 50-request limit calling check_urls in a loop
when asked to read a web page (a task it can't actually do). logfire trace
019d5004 shows 30 consecutive check_urls calls.

three fixes:
- usage_limits=UsageLimits(request_limit=15) on both agent.run() calls
- check_urls docstring explicitly says HEAD-only, no retries
- system prompt: don't verify URLs the user gave you, accept results,
say so if you can't fulfill a request with available tools

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

+16 -5
+16 -5
src/bot/agent.py
··· 16 16 from pydantic import BaseModel, Field 17 17 from pydantic_ai import Agent, ImageUrl, RunContext 18 18 from pydantic_ai.mcp import MCPServerStreamableHTTP 19 + from pydantic_ai.usage import UsageLimits 19 20 20 21 from bot.config import settings 21 22 from bot.core.atproto_client import bot_client ··· 97 98 """Build operational instructions with the current owner handle interpolated.""" 98 99 return f""" 99 100 indicate your response action via the structured output — do not use atproto tools to post, like, or repost directly. 100 - when sharing URLs, verify them with check_urls first and always include https://. 101 + when sharing URLs you found yourself (not ones the user gave you), you can verify them with check_urls. always include https://. never call check_urls more than once per URL — accept the result. 101 102 102 103 you receive all notification types — mentions, replies, quotes, likes, reposts, and follows. 103 104 for mentions, replies, and quotes: someone is talking to you or about you. respond if you have something to say. ··· 142 143 only use this during daily reflection, or when someone explicitly asks about infrastructure, services, or uptime of specific apps. 143 144 if something is down, post about it and tag @{settings.owner_handle}. 144 145 145 - IMPORTANT: never paginate through list_records repeatedly. if you need more data than one call returns, work with what you have. endless pagination wastes your request budget and produces no response. 146 + IMPORTANT: you have a limited tool call budget per message. do not call the same tool repeatedly with the same or similar arguments — accept the result you get. never paginate through list_records repeatedly. if you need more data than one call returns, work with what you have. if you cannot fulfill a request with the tools you have (e.g. reading a web page), say so instead of looping. 146 147 """.strip() 147 148 148 149 ··· 640 641 641 642 @self.agent.tool 642 643 async def check_urls(ctx: RunContext[PhiDeps], urls: list[str]) -> str: 643 - """Check whether URLs are reachable. Use this before sharing links to verify they actually work. Accepts full URLs (https://...) or bare domains (example.com/path).""" 644 + """Check whether URLs are reachable (HEAD request only — cannot read page content). Never call this more than once per URL. If a URL returns an error, accept that result and move on.""" 644 645 645 646 async def _check(client: httpx.AsyncClient, url: str) -> str: 646 647 if not url.startswith(("http://", "https://")): ··· 856 857 async with contextlib.AsyncExitStack() as stack: 857 858 for ts in toolsets: 858 859 await stack.enter_async_context(ts) 859 - result = await self.agent.run(user_prompt, deps=deps, toolsets=toolsets) 860 + result = await self.agent.run( 861 + user_prompt, 862 + deps=deps, 863 + toolsets=toolsets, 864 + usage_limits=UsageLimits(request_limit=15), 865 + ) 860 866 logger.info( 861 867 f"agent decided: {result.output.action}" 862 868 + (f" - {result.output.text[:80]}" if result.output.text else "") ··· 935 941 async with contextlib.AsyncExitStack() as stack: 936 942 for ts in toolsets: 937 943 await stack.enter_async_context(ts) 938 - result = await self.agent.run(reflection_task, deps=deps, toolsets=toolsets) 944 + result = await self.agent.run( 945 + reflection_task, 946 + deps=deps, 947 + toolsets=toolsets, 948 + usage_limits=UsageLimits(request_limit=15), 949 + ) 939 950 940 951 logger.info( 941 952 f"reflection decided: {result.output.action}"