diff --git a/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/builtins/instagram/__init__.py b/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/builtins/instagram/__init__.py new file mode 100644 index 000000000..5e3857c86 --- /dev/null +++ b/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/builtins/instagram/__init__.py @@ -0,0 +1 @@ +"""``instagram`` builtin subagent: structured Instagram posts, comments, and details.""" diff --git a/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/builtins/instagram/agent.py b/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/builtins/instagram/agent.py new file mode 100644 index 000000000..27a3e1bdb --- /dev/null +++ b/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/builtins/instagram/agent.py @@ -0,0 +1,43 @@ +"""``instagram`` route: ``SurfSenseSubagentSpec`` builder for deepagents.""" + +from __future__ import annotations + +from typing import Any + +from langchain_core.language_models import BaseChatModel +from langchain_core.tools import BaseTool + +from app.agents.chat.multi_agent_chat.subagents.shared.md_file_reader import ( + read_md_file, +) +from app.agents.chat.multi_agent_chat.subagents.shared.spec import SurfSenseSubagentSpec +from app.agents.chat.multi_agent_chat.subagents.shared.subagent_builder import ( + pack_subagent, +) + +from .tools.index import NAME, RULESET, load_tools + + +def build_subagent( + *, + dependencies: dict[str, Any], + model: BaseChatModel | None = None, + middleware_stack: dict[str, Any] | None = None, + mcp_tools: list[BaseTool] | None = None, +) -> SurfSenseSubagentSpec: + tools = [*load_tools(dependencies=dependencies), *(mcp_tools or [])] + description = ( + read_md_file(__package__, "description").strip() + or "Pulls structured data from Instagram posts, reels, comments, and profiles." + ) + system_prompt = read_md_file(__package__, "system_prompt").strip() + return pack_subagent( + name=NAME, + description=description, + system_prompt=system_prompt, + tools=tools, + ruleset=RULESET, + dependencies=dependencies, + model=model, + middleware_stack=middleware_stack, + ) diff --git a/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/builtins/instagram/description.md b/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/builtins/instagram/description.md new file mode 100644 index 000000000..9efedc52a --- /dev/null +++ b/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/builtins/instagram/description.md @@ -0,0 +1,2 @@ +Instagram specialist: pulls structured data from Instagram — posts and reels (caption, likes, comments count, media URLs, owner, timestamp), a post's comments and replies, and profile/hashtag/place details (follower and post counts, bio, hashtag volume and top posts, place coordinates). Finds content by hashtag, profile, or place search, and compares fresh Instagram data against earlier findings in this chat. +Use whenever the task involves Instagram content or an instagram.com link. Triggers include "get this Instagram profile/post/reel", "find posts about X on Instagram", "how many followers/likes", "get the comments on this post", "what are people saying about this reel", "look up this hashtag/location", and comparisons against earlier Instagram results in this chat. Not for general web pages (use the web crawling specialist for non-Instagram URLs). diff --git a/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/builtins/instagram/system_prompt.md b/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/builtins/instagram/system_prompt.md new file mode 100644 index 000000000..7d5278afa --- /dev/null +++ b/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/builtins/instagram/system_prompt.md @@ -0,0 +1,68 @@ +You are the SurfSense Instagram sub-agent. +You receive delegated instructions from a supervisor agent and return structured results for supervisor synthesis. + + +Answer the delegated question from live Instagram data gathered with your verbs, comparing against earlier results already in this conversation when the task calls for it. + + + +- `instagram_scrape` +- `instagram_comments` +- `instagram_details` +- `read_run` / `search_run` (free readers for stored scrape output) + + + +- Known profile/post/reel/hashtag/place links: call `instagram_scrape` with the links in `urls` (use `result_type` to pick posts, reels, or mentions). +- Finding content on a topic: call `instagram_scrape` with `search_queries` and the matching `search_type` (hashtag, profile, or place). +- Comments / sentiment on specific posts or reels: call `instagram_comments` with the post `urls`. +- Profile, hashtag, or place metadata (follower counts, bio, hashtag volume, coordinates): call `instagram_details`. +- Batch multiple URLs (or queries) into one call rather than many single-item calls. + +- Multi-post comment analysis: a batched comments result lists posts in order, so a truncated preview usually shows only the first post(s). Before summarizing, page the stored run (or `search_run` by post id) until you have read real comments for EVERY post in the batch — never infer one post's sentiment from another's, and never report a post as "limited data" while its comments sit unread in the run. +- Comparison requests: pull the current values, compare against prior values already in this conversation's earlier tool results, and report concrete deltas (added, removed, old -> new). + + + +- Use only tools in ``. +- An item whose `status` is not `success` returned no data — report it unavailable, never invent it. +- Anonymous Instagram access can be rate-limited or blocked; if a verb returns no data, report it unavailable and suggest a narrower retry rather than fabricating. +- Report only deltas you can point to in the evidence. Never fabricate facts, counts, quotes, or URLs. + + + +- Do not generate deliverables or perform connector mutations; return findings for the supervisor to act on. +- Non-Instagram web pages belong to the web crawling specialist, not here. + + + +- Report uncertainty explicitly when evidence is incomplete or conflicting. +- Never present unverified claims as facts. + + + +- Underspecified request — no usable URL or search query — return `status=blocked` with the missing fields. +- Tool failure or access block: return `status=error` with a concise recovery `next_step`. +- No useful evidence: return `status=blocked` with a narrower query or the URLs you still need. + + + +Return **only** one JSON object (no markdown/prose): +{ + "status": "success" | "partial" | "blocked" | "error", + "action_summary": string, + "evidence": { + "findings": string[], + "sources": string[], + "confidence": "high" | "medium" | "low" + }, + "next_step": string | null, + "missing_fields": string[] | null, + "assumptions": string[] | null +} + +Route-specific rules: +- `evidence.findings`: max 10 entries, each a single sentence stating one distinct fact or delta. Do not paste raw payloads. +- `evidence.sources`: max 10 URLs, one per finding when applicable. List each URL once. + + diff --git a/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/builtins/instagram/tools/__init__.py b/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/builtins/instagram/tools/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/builtins/instagram/tools/index.py b/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/builtins/instagram/tools/index.py new file mode 100644 index 000000000..1bfbabad9 --- /dev/null +++ b/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/builtins/instagram/tools/index.py @@ -0,0 +1,29 @@ +"""``instagram`` sub-agent tools: the three Instagram capability verbs.""" + +from __future__ import annotations + +from typing import Any + +from langchain_core.tools import BaseTool + +from app.agents.chat.multi_agent_chat.shared.permissions import Ruleset +from app.capabilities.core.access.agent import build_capability_tools +from app.capabilities.instagram.comments.definition import INSTAGRAM_COMMENTS +from app.capabilities.instagram.details.definition import INSTAGRAM_DETAILS +from app.capabilities.instagram.scrape.definition import INSTAGRAM_SCRAPE + +NAME = "instagram" + +RULESET = Ruleset(origin=NAME, rules=[]) + +_CI_VERBS = [INSTAGRAM_SCRAPE, INSTAGRAM_COMMENTS, INSTAGRAM_DETAILS] + + +def load_tools( + *, dependencies: dict[str, Any] | None = None, **kwargs: Any +) -> list[BaseTool]: + d = {**(dependencies or {}), **kwargs} + return build_capability_tools( + workspace_id=d.get("workspace_id"), + capabilities=_CI_VERBS, + ) diff --git a/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/registry.py b/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/registry.py index 34895a514..7a9b5b1f7 100644 --- a/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/registry.py +++ b/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/registry.py @@ -21,6 +21,9 @@ from app.agents.chat.multi_agent_chat.subagents.builtins.google_maps.agent impor from app.agents.chat.multi_agent_chat.subagents.builtins.google_search.agent import ( build_subagent as build_google_search_subagent, ) +from app.agents.chat.multi_agent_chat.subagents.builtins.instagram.agent import ( + build_subagent as build_instagram_subagent, +) from app.agents.chat.multi_agent_chat.subagents.builtins.knowledge_base.agent import ( build_subagent as build_knowledge_base_subagent, ) @@ -79,6 +82,7 @@ SUBAGENT_BUILDERS_BY_NAME: dict[str, SubagentBuilder] = { "google_drive": build_google_drive_subagent, "google_maps": build_google_maps_subagent, "google_search": build_google_search_subagent, + "instagram": build_instagram_subagent, "knowledge_base": build_knowledge_base_subagent, "mcp_discovery": build_mcp_discovery_subagent, "memory": build_memory_subagent,