diff --git a/backend/apps/agents/agent_manager.py b/backend/apps/agents/agent_manager.py index dd0f28dc..598233cd 100644 --- a/backend/apps/agents/agent_manager.py +++ b/backend/apps/agents/agent_manager.py @@ -486,6 +486,23 @@ class AgentManager: f"\"{tool.connected_account_email}\" automatically — do NOT ask the user." ) + # Instagram and LinkedIn enforce strict per-account rate limits to + # prevent platform bans. When a tool call comes back with + # rate_limited: true OR a deny reason mentioning "RATE LIMIT HIT", + # the agent MUST stop the task, tell the user the retry-after, and + # NOT retry. Without this guidance, agents tend to loop trying + # alternative tools or even shell out to filesystem search. + if tool.name.lower() in ("instagram", "linkedin"): + lines.append( + f" RATE LIMIT BEHAVIOR (HARD RULE): If a {tool.name} tool returns " + "rate_limited: true, or any tool call here returns a 'deny' with " + "'RATE LIMIT HIT' in the message, this is FINAL for the current turn. " + "Do NOT retry the same tool. Do NOT try alternative tools to accomplish " + "the same goal. Do NOT shell out to Bash/curl/find to look up the package " + "source. Tell the user the retry-after time in plain English and END the " + "task. Looping makes the platform ban risk worse, not better." + ) + # Discord guild scoping — hard restriction. The bot may technically # be in other servers (across other OpenSwarm users), but this # specific user only authorized these guild IDs. diff --git a/backend/apps/instagram_mcp/rate_limiter.py b/backend/apps/instagram_mcp/rate_limiter.py index e2fa567c..f41a0f42 100644 --- a/backend/apps/instagram_mcp/rate_limiter.py +++ b/backend/apps/instagram_mcp/rate_limiter.py @@ -155,8 +155,11 @@ def rate_limited(category: str) -> Callable[[Callable[..., Dict[str, Any]]], Cal "rate_limited": True, "category": category, "message": ( - "Rate limit hit to protect this Instagram account from being " - f"flagged for automation: {reason}" + f"RATE LIMIT HIT — STOP HERE. {reason} DO NOT retry this tool. " + "DO NOT try alternative tools to accomplish the same goal. DO NOT " + "search the filesystem or look up the package source. Tell the user " + "the retry-after time in plain English and END the task. This " + "protects the Instagram account from being flagged for automation." ), "retry_after_seconds": retry_after, "limits": {k: limits[k] for k in ("per_minute", "per_hour", "per_day")}, diff --git a/backend/apps/tools_lib/mcp_rate_limiter.py b/backend/apps/tools_lib/mcp_rate_limiter.py index 3d9244ac..8851224d 100644 --- a/backend/apps/tools_lib/mcp_rate_limiter.py +++ b/backend/apps/tools_lib/mcp_rate_limiter.py @@ -138,12 +138,14 @@ def check(mcp_server: str, tool: str) -> dict[str, Any] | None: retry_after = int((oldest + window_s) - now) + 1 label = window_name.replace("per_", "") reason = ( - f"OpenSwarm rate limit ({mcp_server}/{category}): {cap}/{label} cap reached " - f"(currently {len(in_window)}). Retry in {_fmt_duration(retry_after)}. " - f"This protects the connected account from anti-abuse bans. Override with " - f"{mcp_server.upper()}_RATE_LIMIT_{category.upper()}_{window_name.upper()}=N." + f"RATE LIMIT HIT — STOP HERE. {mcp_server}/{category} cap of {cap}/{label} reached " + f"(currently {len(in_window)}). The cap resets in {_fmt_duration(retry_after)}. " + f"DO NOT retry this tool. DO NOT try alternative tools to accomplish the same goal. " + f"DO NOT search the filesystem or look up the package source. Instead, tell the user: " + f"'I hit the {mcp_server} {category} rate limit. Try again in {_fmt_duration(retry_after)}.' " + f"Then END the task. This protects the connected account from anti-abuse bans." ) - logger.warning(f"[mcp-rate-limit] BLOCK {mcp_server}/{tool}: {reason}") + logger.warning(f"[mcp-rate-limit] BLOCK {mcp_server}/{tool}: {cap}/{label} cap") return {"deny": reason, "retry_after_seconds": retry_after} state[state_key] = stamps + [now]