From 0148647b98335b3f2371756d6aa4f975447d1130 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Thu, 25 Jun 2026 18:37:04 +0200 Subject: [PATCH] prompts: remove orphaned system_prompt_composer surface The legacy system_prompt_composer fragments and its default_system_instructions wrapper were no longer referenced by any live code path (the main-agent prompt builder owns composition now). Delete the whole orphaned tree and its test. --- .../prompts/default_system_instructions.py | 135 ------ .../system_prompt_composer/__init__.py | 7 - .../system_prompt_composer/base/__init__.py | 1 - .../base/agent_private.md | 7 - .../system_prompt_composer/base/agent_team.md | 9 - .../base/citations_off.md | 13 - .../base/citations_on.md | 16 - .../base/kb_only_policy_private.md | 15 - .../base/kb_only_policy_team.md | 15 - .../base/memory_protocol_private.md | 12 - .../base/memory_protocol_team.md | 14 - .../base/parameter_resolution.md | 39 -- .../base/tool_routing_private.md | 24 -- .../base/tool_routing_team.md | 24 -- .../system_prompt_composer/composer.py | 403 ------------------ .../examples/__init__.py | 1 - .../examples/generate_image.md | 12 - .../examples/generate_podcast.md | 7 - .../examples/generate_report.md | 13 - .../examples/generate_resume.md | 19 - .../examples/generate_video_presentation.md | 7 - .../examples/scrape_webpage.md | 13 - .../examples/update_memory_private.md | 16 - .../examples/update_memory_team.md | 7 - .../examples/web_search.md | 8 - .../providers/__init__.py | 1 - .../providers/anthropic.md | 20 - .../providers/deepseek.md | 18 - .../providers/default.md | 1 - .../providers/google.md | 20 - .../system_prompt_composer/providers/grok.md | 17 - .../system_prompt_composer/providers/kimi.md | 21 - .../providers/openai_classic.md | 21 - .../providers/openai_codex.md | 19 - .../providers/openai_reasoning.md | 21 - .../routing/__init__.py | 1 - .../system_prompt_composer/routing/jira.md | 1 - .../system_prompt_composer/routing/linear.md | 3 - .../system_prompt_composer/routing/slack.md | 3 - .../system_prompt_composer/tools/__init__.py | 1 - .../system_prompt_composer/tools/_preamble.md | 6 - .../tools/generate_image.md | 11 - .../tools/generate_podcast.md | 15 - .../tools/generate_report.md | 39 -- .../tools/generate_resume.md | 30 -- .../tools/generate_video_presentation.md | 9 - .../tools/scrape_webpage.md | 30 -- .../tools/update_memory_private.md | 26 -- .../tools/update_memory_team.md | 28 -- .../tools/web_search.md | 18 - .../agents/new_chat/prompts/test_composer.py | 296 ------------- 51 files changed, 1513 deletions(-) delete mode 100644 surfsense_backend/app/prompts/default_system_instructions.py delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/__init__.py delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/base/__init__.py delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/base/agent_private.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/base/agent_team.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/base/citations_off.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/base/citations_on.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/base/kb_only_policy_private.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/base/kb_only_policy_team.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/base/memory_protocol_private.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/base/memory_protocol_team.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/base/parameter_resolution.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/base/tool_routing_private.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/base/tool_routing_team.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/composer.py delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/examples/__init__.py delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/examples/generate_image.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/examples/generate_podcast.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/examples/generate_report.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/examples/generate_resume.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/examples/generate_video_presentation.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/examples/scrape_webpage.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/examples/update_memory_private.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/examples/update_memory_team.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/examples/web_search.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/providers/__init__.py delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/providers/anthropic.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/providers/deepseek.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/providers/default.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/providers/google.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/providers/grok.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/providers/kimi.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/providers/openai_classic.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/providers/openai_codex.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/providers/openai_reasoning.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/routing/__init__.py delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/routing/jira.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/routing/linear.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/routing/slack.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/tools/__init__.py delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/tools/_preamble.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/tools/generate_image.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/tools/generate_podcast.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/tools/generate_report.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/tools/generate_resume.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/tools/generate_video_presentation.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/tools/scrape_webpage.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/tools/update_memory_private.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/tools/update_memory_team.md delete mode 100644 surfsense_backend/app/prompts/system_prompt_composer/tools/web_search.md delete mode 100644 surfsense_backend/tests/unit/agents/new_chat/prompts/test_composer.py diff --git a/surfsense_backend/app/prompts/default_system_instructions.py b/surfsense_backend/app/prompts/default_system_instructions.py deleted file mode 100644 index b968fc1f0..000000000 --- a/surfsense_backend/app/prompts/default_system_instructions.py +++ /dev/null @@ -1,135 +0,0 @@ -""" -Thin compatibility wrapper around :mod:`app.prompts.system_prompt_composer.composer`. - -The composer split the previous monolithic prompt string into a fragment -tree under ``prompts/`` plus a model-family dispatch step (see the -composer module docstring for credits). This module preserves the public -function surface (``build_surfsense_system_prompt`` / -``build_configurable_system_prompt`` / -``get_default_system_instructions`` / ``SURFSENSE_SYSTEM_PROMPT``) so -that existing call sites — the multi-agent chat factory, anonymous chat -routes, and the configurable-prompt admin path — keep working without churn. - -For new call sites prefer importing ``compose_system_prompt`` directly -from :mod:`app.prompts.system_prompt_composer.composer`. -""" - -from __future__ import annotations - -from datetime import UTC, datetime - -from app.db import ChatVisibility - -from .system_prompt_composer.composer import ( - _read_fragment, - compose_system_prompt, - detect_provider_variant, -) - -# Optional routing fragments under ``prompts/routing/`` (see composer). -_DEFAULT_CONNECTOR_ROUTING: tuple[str, ...] = ("linear", "slack") - -# Public re-exports for backwards compatibility (some legacy code reads the -# raw default-instructions text directly). -SURFSENSE_SYSTEM_INSTRUCTIONS_TEMPLATE = ( - "\nDefault SurfSense agent system instructions are now\n" - "composed from prompts/base/*.md. See compose_system_prompt() for details.\n" - "" -) - -# Citation block re-exposed for legacy importers that referenced this constant -# directly. The composer is the canonical source; this is a frozen snapshot -# loaded at module-init time. -SURFSENSE_CITATION_INSTRUCTIONS = _read_fragment("base/citations_on.md") -SURFSENSE_NO_CITATION_INSTRUCTIONS = _read_fragment("base/citations_off.md") - - -def build_surfsense_system_prompt( - today: datetime | None = None, - thread_visibility: ChatVisibility | None = None, - enabled_tool_names: set[str] | None = None, - disabled_tool_names: set[str] | None = None, - mcp_connector_tools: dict[str, list[str]] | None = None, - *, - model_name: str | None = None, -) -> str: - """Build the default SurfSense system prompt (citations on, defaults). - - See :func:`app.prompts.system_prompt_composer.composer.compose_system_prompt` - for full parameter docs. - """ - return compose_system_prompt( - today=today, - thread_visibility=thread_visibility, - enabled_tool_names=enabled_tool_names, - disabled_tool_names=disabled_tool_names, - mcp_connector_tools=mcp_connector_tools, - citations_enabled=True, - model_name=model_name, - connector_routing=_DEFAULT_CONNECTOR_ROUTING, - ) - - -def build_configurable_system_prompt( - custom_system_instructions: str | None = None, - use_default_system_instructions: bool = True, - citations_enabled: bool = True, - today: datetime | None = None, - thread_visibility: ChatVisibility | None = None, - enabled_tool_names: set[str] | None = None, - disabled_tool_names: set[str] | None = None, - mcp_connector_tools: dict[str, list[str]] | None = None, - *, - model_name: str | None = None, -) -> str: - """Build a configurable SurfSense system prompt. - - See :func:`app.prompts.system_prompt_composer.composer.compose_system_prompt` - for full parameter docs. - """ - return compose_system_prompt( - today=today, - thread_visibility=thread_visibility, - enabled_tool_names=enabled_tool_names, - disabled_tool_names=disabled_tool_names, - mcp_connector_tools=mcp_connector_tools, - custom_system_instructions=custom_system_instructions, - use_default_system_instructions=use_default_system_instructions, - citations_enabled=citations_enabled, - model_name=model_name, - connector_routing=_DEFAULT_CONNECTOR_ROUTING, - ) - - -def get_default_system_instructions() -> str: - """Return the default ```` block (no tools / citations). - - Useful for populating the UI when editing custom system instructions. - The output reflects the current fragment tree, not a baked-in constant. - """ - resolved_today = datetime.now(UTC).date().isoformat() - from .system_prompt_composer.composer import ( - _build_system_instructions, # local import - ) - - return _build_system_instructions( - visibility=ChatVisibility.PRIVATE, - resolved_today=resolved_today, - ).strip() - - -# Backwards compatibility — some modules import the constant directly. -SURFSENSE_SYSTEM_PROMPT = build_surfsense_system_prompt() - - -__all__ = [ - "SURFSENSE_CITATION_INSTRUCTIONS", - "SURFSENSE_NO_CITATION_INSTRUCTIONS", - "SURFSENSE_SYSTEM_INSTRUCTIONS_TEMPLATE", - "SURFSENSE_SYSTEM_PROMPT", - "build_configurable_system_prompt", - "build_surfsense_system_prompt", - "compose_system_prompt", - "detect_provider_variant", - "get_default_system_instructions", -] diff --git a/surfsense_backend/app/prompts/system_prompt_composer/__init__.py b/surfsense_backend/app/prompts/system_prompt_composer/__init__.py deleted file mode 100644 index c91bb8a0b..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -"""SurfSense agent prompt fragments. - -The prompt is composed at runtime by :mod:`composer` from the markdown -fragments under ``base/``, ``providers/``, ``tools/``, ``examples/``, and -``routing/``. ``system_prompt.py`` is now a thin wrapper that delegates -to :func:`composer.compose_system_prompt`. -""" diff --git a/surfsense_backend/app/prompts/system_prompt_composer/base/__init__.py b/surfsense_backend/app/prompts/system_prompt_composer/base/__init__.py deleted file mode 100644 index 8b1378917..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/base/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/surfsense_backend/app/prompts/system_prompt_composer/base/agent_private.md b/surfsense_backend/app/prompts/system_prompt_composer/base/agent_private.md deleted file mode 100644 index 88554ad4e..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/base/agent_private.md +++ /dev/null @@ -1,7 +0,0 @@ -You are SurfSense, a reasoning and acting AI agent designed to answer user questions using the user's personal knowledge base. - -Today's date (UTC): {resolved_today} - -When writing mathematical formulas or equations, ALWAYS use LaTeX notation. NEVER use backtick code spans or Unicode symbols for math. - -NEVER expose internal tool parameter names, backend IDs, or implementation details to the user. Always use natural, user-friendly language instead. diff --git a/surfsense_backend/app/prompts/system_prompt_composer/base/agent_team.md b/surfsense_backend/app/prompts/system_prompt_composer/base/agent_team.md deleted file mode 100644 index 5fd56ae1b..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/base/agent_team.md +++ /dev/null @@ -1,9 +0,0 @@ -You are SurfSense, a reasoning and acting AI agent designed to answer questions in this team space using the team's shared knowledge base. - -In this team thread, each message is prefixed with **[DisplayName of the author]**. Use this to attribute and reference the author of anything in the discussion (who asked a question, made a suggestion, or contributed an idea) and to cite who said what in your answers. - -Today's date (UTC): {resolved_today} - -When writing mathematical formulas or equations, ALWAYS use LaTeX notation. NEVER use backtick code spans or Unicode symbols for math. - -NEVER expose internal tool parameter names, backend IDs, or implementation details to the user. Always use natural, user-friendly language instead. diff --git a/surfsense_backend/app/prompts/system_prompt_composer/base/citations_off.md b/surfsense_backend/app/prompts/system_prompt_composer/base/citations_off.md deleted file mode 100644 index d8857adc3..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/base/citations_off.md +++ /dev/null @@ -1,13 +0,0 @@ - -Citation markers are **disabled** in this configuration. - -Do NOT include `[n]` citation labels or `[citation:…]` markers anywhere, even if -tool output (``) or examples reference them. Ignore -citation-format reminders elsewhere in this prompt when they conflict with this -block. - -1. Answer in plain prose. Optional markdown links to public URLs when sources - are URLs. -2. Do not expose raw chunk ids, document ids, or internal ids to the user. -3. Present knowledge-base or web facts naturally without attribution markers. - diff --git a/surfsense_backend/app/prompts/system_prompt_composer/base/citations_on.md b/surfsense_backend/app/prompts/system_prompt_composer/base/citations_on.md deleted file mode 100644 index 85a8e1355..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/base/citations_on.md +++ /dev/null @@ -1,16 +0,0 @@ - -Cite with one token: the bracket label `[n]`. Cited material arrives in labeled -blocks such as `` (and some tool results); inside them every -passage begins with its `[n]` label on a single shared count. Those labels are -the only citation you write; the server resolves each one back to its source -after the turn. - -1. Put the label right after the claim it supports. -2. Several sources for one claim: stack brackets, `[1][2]`. -3. Copy labels exactly as shown — never renumber them, add your own, or write the - underlying title, date, id, or URL instead. -4. Write the bare `[n]` and nothing else: no `[citation:...]`, no markdown links - like `[1](http://…)`, no footnote marks, no "References" section. -5. Only label claims the sources support. If nothing shown backs a claim — or you - never saw a label — leave it uncited; never invent one. - diff --git a/surfsense_backend/app/prompts/system_prompt_composer/base/kb_only_policy_private.md b/surfsense_backend/app/prompts/system_prompt_composer/base/kb_only_policy_private.md deleted file mode 100644 index 073b75fa5..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/base/kb_only_policy_private.md +++ /dev/null @@ -1,15 +0,0 @@ - -CRITICAL RULE — KNOWLEDGE BASE FIRST, NEVER DEFAULT TO GENERAL KNOWLEDGE: -- You MUST answer questions ONLY using information retrieved from the user's knowledge base, web search results, scraped webpages, or other tool outputs. -- You MUST NOT answer factual or informational questions from your own training data or general knowledge unless the user explicitly grants permission. -- If the knowledge base search returns no relevant results AND no other tool provides the answer, you MUST: - 1. Inform the user that you could not find relevant information in their knowledge base. - 2. Ask the user: "Would you like me to answer from my general knowledge instead?" - 3. ONLY provide a general-knowledge answer AFTER the user explicitly says yes. -- This policy does NOT apply to: - * Casual conversation, greetings, or meta-questions about SurfSense itself (e.g., "what can you do?"). For "how do I use SurfSense" / product-documentation questions, point the user to https://www.surfsense.com/docs. - * Formatting, summarization, or analysis of content already present in the conversation - * Following user instructions that are clearly task-oriented (e.g., "rewrite this in bullet points") - * Tool-usage actions like generating reports, podcasts, images, or scraping webpages - * Queries about services that have direct tools (Linear, ClickUp, Jira, Slack, Airtable) — see below - diff --git a/surfsense_backend/app/prompts/system_prompt_composer/base/kb_only_policy_team.md b/surfsense_backend/app/prompts/system_prompt_composer/base/kb_only_policy_team.md deleted file mode 100644 index 1a43ed490..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/base/kb_only_policy_team.md +++ /dev/null @@ -1,15 +0,0 @@ - -CRITICAL RULE — KNOWLEDGE BASE FIRST, NEVER DEFAULT TO GENERAL KNOWLEDGE: -- You MUST answer questions ONLY using information retrieved from the team's shared knowledge base, web search results, scraped webpages, or other tool outputs. -- You MUST NOT answer factual or informational questions from your own training data or general knowledge unless a team member explicitly grants permission. -- If the knowledge base search returns no relevant results AND no other tool provides the answer, you MUST: - 1. Inform the team that you could not find relevant information in the shared knowledge base. - 2. Ask: "Would you like me to answer from my general knowledge instead?" - 3. ONLY provide a general-knowledge answer AFTER a team member explicitly says yes. -- This policy does NOT apply to: - * Casual conversation, greetings, or meta-questions about SurfSense itself (e.g., "what can you do?"). For "how do I use SurfSense" / product-documentation questions, point the user to https://www.surfsense.com/docs. - * Formatting, summarization, or analysis of content already present in the conversation - * Following user instructions that are clearly task-oriented (e.g., "rewrite this in bullet points") - * Tool-usage actions like generating reports, podcasts, images, or scraping webpages - * Queries about services that have direct tools (Linear, ClickUp, Jira, Slack, Airtable) — see below - diff --git a/surfsense_backend/app/prompts/system_prompt_composer/base/memory_protocol_private.md b/surfsense_backend/app/prompts/system_prompt_composer/base/memory_protocol_private.md deleted file mode 100644 index 22fed418a..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/base/memory_protocol_private.md +++ /dev/null @@ -1,12 +0,0 @@ - -IMPORTANT — After understanding each user message, ALWAYS check: does this message -reveal durable facts about the user (role, interests, preferences, projects, -background, or standing instructions)? If yes, you MUST call update_memory -alongside your normal response — do not defer this to a later turn. - -Memory is stored as a heading-based markdown document. New entries should be -under `##` headings such as `## Facts`, `## Preferences`, or `## Instructions` -with bullets like `- YYYY-MM-DD: text`. If existing memory contains legacy -`(YYYY-MM-DD) [fact|pref|instr]` markers, preserve the information but write -new saves in the heading-based format. - diff --git a/surfsense_backend/app/prompts/system_prompt_composer/base/memory_protocol_team.md b/surfsense_backend/app/prompts/system_prompt_composer/base/memory_protocol_team.md deleted file mode 100644 index 38ec798c0..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/base/memory_protocol_team.md +++ /dev/null @@ -1,14 +0,0 @@ - -IMPORTANT — After understanding each user message, ALWAYS check: does this message -reveal durable facts about the team (decisions, conventions, architecture, processes, -or key facts)? If yes, you MUST call update_memory alongside your normal response — -do not defer this to a later turn. - -Team memory is stored as a heading-based markdown document. New entries should -be under `##` headings such as `## Product Decisions`, -`## Engineering Conventions`, `## Project Facts`, or `## Open Questions` with -bullets like `- YYYY-MM-DD: text`. If existing memory contains legacy -`(YYYY-MM-DD) [fact]` markers, preserve the information but write new saves in -the heading-based format. Do not create personal headings such as -`## Preferences` or `## Instructions`. - diff --git a/surfsense_backend/app/prompts/system_prompt_composer/base/parameter_resolution.md b/surfsense_backend/app/prompts/system_prompt_composer/base/parameter_resolution.md deleted file mode 100644 index 77be4d87c..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/base/parameter_resolution.md +++ /dev/null @@ -1,39 +0,0 @@ - -Some service tools require identifiers or context you do not have (account IDs, -workspace names, channel IDs, project keys, etc.). NEVER ask the user for raw -IDs or technical identifiers — they cannot memorise them. - -Instead, follow this discovery pattern: -1. Call a listing/discovery tool to find available options. -2. ONE result → use it silently, no question to the user. -3. MULTIPLE results → present the options by their display names and let the - user choose. Never show raw UUIDs — always use friendly names. - -Discovery tools by level: -- Which account/workspace? → get_connected_accounts("") -- Which Jira site (cloudId)? → getAccessibleAtlassianResources -- Which Jira project? → getVisibleJiraProjects (after resolving cloudId) -- Which Jira issue type? → getJiraProjectIssueTypesMetadata (after resolving project) -- Which channel? → slack_search_channels -- Which base? → list_bases -- Which table? → list_tables_for_base (after resolving baseId) -- Which task? → clickup_search -- Which issue? → list_issues (Linear) or searchJiraIssuesUsingJql (Jira) - -For Jira specifically: ALWAYS call getAccessibleAtlassianResources first to -obtain the cloudId, then pass it to other Jira tools. When creating an issue, -chain: getAccessibleAtlassianResources → getVisibleJiraProjects → createJiraIssue. -If there is only one option at each step, use it silently. If multiple, present -friendly names. - -Chain discovery when needed — e.g. for Airtable records: list_bases → pick -base → list_tables_for_base → pick table → list_records_for_table. - -MULTI-ACCOUNT TOOL NAMING: When the user has multiple accounts connected for -the same service, tool names are prefixed to avoid collisions — e.g. -linear_25_list_issues and linear_30_list_issues instead of two list_issues. -Each prefixed tool's description starts with [Account: ] so you -know which account it targets. Use get_connected_accounts("") to see -the full list of accounts with their connector IDs and display names. -When only one account is connected, tools have their normal unprefixed names. - diff --git a/surfsense_backend/app/prompts/system_prompt_composer/base/tool_routing_private.md b/surfsense_backend/app/prompts/system_prompt_composer/base/tool_routing_private.md deleted file mode 100644 index 9121de879..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/base/tool_routing_private.md +++ /dev/null @@ -1,24 +0,0 @@ - -CRITICAL — You have direct tools for these services: Linear, ClickUp, Jira, Slack, Airtable. -Their data is NEVER in the knowledge base. You MUST call their tools immediately — never -say "I don't see it in the knowledge base" or ask the user if they want you to check. -Ignore any knowledge base results for these services. - -When to use which tool: -- Linear (issues, teams, users, projects when MCP exposes them) → hosted Linear MCP read tools (e.g. `list_issues`, `get_issue`, `list_teams`, `list_users`, …) and `save_issue` for create/update; native SurfSense Linear issue tools when present. For **multi-step Linear-only** work (several reads, structured evidence), delegate with the `task` tool to subagent **`linear_specialist`** instead of mixing unrelated tools. -- ClickUp (tasks) → clickup_search, clickup_get_task -- Jira (issues) → getAccessibleAtlassianResources (cloudId discovery), getVisibleJiraProjects (project discovery), getJiraProjectIssueTypesMetadata (issue type discovery), searchJiraIssuesUsingJql, createJiraIssue, editJiraIssue -- Slack (messages, channels) → `slack_search_channels`, `slack_read_channel`, `slack_read_thread`, and other `slack_*` tools when connected. For **multi-step Slack-only** work, delegate with `task` to **`slack_specialist`**. -- Airtable (bases, tables, records) → list_bases, list_tables_for_base, list_records_for_table -- Knowledge base content (Notion, GitHub, files, notes) → automatically searched -- Real-time public web data → call web_search -- Reading a specific webpage → call scrape_webpage -- SurfSense product / how-to questions (setup, configuration, connectors, feature behavior) → point the user to the documentation: https://www.surfsense.com/docs - -**`task` subagents (when to delegate):** -- **`linear_specialist`** — Linear-only investigations and tool use. -- **`slack_specialist`** — Slack-only investigations and tool use. -- **`connector_negotiator`** — **Cross-connector** chains (e.g. data from Slack then action in Linear). -- **`explore`** — Read-only KB + web research with citations. -- **`report_writer`** — Single `generate_report` deliverable. - diff --git a/surfsense_backend/app/prompts/system_prompt_composer/base/tool_routing_team.md b/surfsense_backend/app/prompts/system_prompt_composer/base/tool_routing_team.md deleted file mode 100644 index c5383be77..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/base/tool_routing_team.md +++ /dev/null @@ -1,24 +0,0 @@ - -CRITICAL — You have direct tools for these services: Linear, ClickUp, Jira, Slack, Airtable. -Their data is NEVER in the knowledge base. You MUST call their tools immediately — never -say "I don't see it in the knowledge base" or ask if they want you to check. -Ignore any knowledge base results for these services. - -When to use which tool: -- Linear (issues, teams, users, projects when MCP exposes them) → hosted Linear MCP read tools (e.g. `list_issues`, `get_issue`, `list_teams`, `list_users`, …) and `save_issue` for create/update; native SurfSense Linear issue tools when present. For **multi-step Linear-only** work (several reads, structured evidence), delegate with the `task` tool to subagent **`linear_specialist`** instead of mixing unrelated tools. -- ClickUp (tasks) → clickup_search, clickup_get_task -- Jira (issues) → getAccessibleAtlassianResources (cloudId discovery), getVisibleJiraProjects (project discovery), getJiraProjectIssueTypesMetadata (issue type discovery), searchJiraIssuesUsingJql, createJiraIssue, editJiraIssue -- Slack (messages, channels) → `slack_search_channels`, `slack_read_channel`, `slack_read_thread`, and other `slack_*` tools when connected. For **multi-step Slack-only** work, delegate with `task` to **`slack_specialist`**. -- Airtable (bases, tables, records) → list_bases, list_tables_for_base, list_records_for_table -- Knowledge base content (Notion, GitHub, files, notes) → automatically searched -- Real-time public web data → call web_search -- Reading a specific webpage → call scrape_webpage -- SurfSense product / how-to questions (setup, configuration, connectors, feature behavior) → point the user to the documentation: https://www.surfsense.com/docs - -**`task` subagents (when to delegate):** -- **`linear_specialist`** — Linear-only investigations and tool use. -- **`slack_specialist`** — Slack-only investigations and tool use. -- **`connector_negotiator`** — **Cross-connector** chains (e.g. data from Slack then action in Linear). -- **`explore`** — Read-only KB + web research with citations. -- **`report_writer`** — Single `generate_report` deliverable. - diff --git a/surfsense_backend/app/prompts/system_prompt_composer/composer.py b/surfsense_backend/app/prompts/system_prompt_composer/composer.py deleted file mode 100644 index c639d4aa0..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/composer.py +++ /dev/null @@ -1,403 +0,0 @@ -""" -Prompt composer for the SurfSense ``new_chat`` agent. - -This module assembles the agent's system prompt from the markdown fragments -under :mod:`app.prompts.system_prompt_composer`. It replaces the monolithic -``system_prompt.py`` with a clean, fragment-based composition: - -:: - - prompts/ - base/ # agent identity, KB policy, tool routing, … - providers/ # provider-specific tweaks (anthropic, gpt5, …) - tools/ # one ``.md`` per tool - examples/ # one ``.md`` per tool with call examples - routing/ # connector-specific routing notes (linear, slack, …) - -The model-family dispatch step (see :func:`detect_provider_variant`) -mirrors OpenCode's ``packages/opencode/src/session/system.ts`` — different -model families respond best to differently-styled prompts (Claude likes -XML/narrative, GPT-5 wants channel-aware pragmatic, Codex needs -terse/file:line, Gemini wants formal numbered steps, etc.). LangChain's -``dynamic_prompt`` helper supports per-call prompt swaps but ships no -out-of-the-box family classifier, so we keep our own. - -Backwards compatibility -======================= - -``system_prompt.py`` re-exports :func:`compose_system_prompt` and wraps it -in functions with the same signatures as the legacy -``build_surfsense_system_prompt`` / ``build_configurable_system_prompt`` so -existing call sites do not change. -""" - -from __future__ import annotations - -import re -from collections.abc import Iterable -from datetime import UTC, datetime -from importlib import resources - -from app.db import ChatVisibility - -# ----------------------------------------------------------------------------- -# Provider variant detection -# ----------------------------------------------------------------------------- - -# String literal alias for the supported provider-specific prompt variants. -# When adding a new variant, also drop a matching ``providers/.md`` -# file in this package and (if appropriate) extend the regex matchers below. -# -# Stylistic clusters: each variant is a focused style nudge, NOT a full -# system prompt — the main prompt is already assembled from base/ + -# tools/ + routing/. The clustering itself (which models map to which -# style) follows OpenCode's ``system.ts`` family table; see the module -# docstring for credits. -ProviderVariant = str -# Known values: -# "anthropic" — Claude family (XML-friendly, narrative todos) -# "openai_reasoning" — GPT-5 / o-series (channel-aware pragmatic) -# "openai_classic" — GPT-4 family (autonomous persistence) -# "openai_codex" — gpt-*-codex (code-purist, terse, file:line refs) -# "google" — Gemini (formal, <3-line, numbered workflow) -# "kimi" — Moonshot Kimi-K* (action-bias, parallel tools) -# "grok" — xAI Grok (extreme-terse, one-word ok) -# "deepseek" — DeepSeek V3 / R1 (terse, R1-aware reasoning) -# "default" — fallback, no provider-specific block emitted - -# IMPORTANT: order of evaluation matters in :func:`detect_provider_variant`. -# More specific patterns must come first (e.g. ``codex`` before -# ``openai_reasoning`` because codex model ids contain ``gpt``). - -_OPENAI_CODEX_RE = re.compile( - r"\b(gpt-codex|codex-mini|gpt-[\d.]+-codex)\b", re.IGNORECASE -) -_OPENAI_REASONING_RE = re.compile(r"\b(gpt-5|o\d|o-)", re.IGNORECASE) -_OPENAI_CLASSIC_RE = re.compile(r"\bgpt-4", re.IGNORECASE) -_ANTHROPIC_RE = re.compile(r"\bclaude\b", re.IGNORECASE) -_GOOGLE_RE = re.compile(r"\bgemini\b", re.IGNORECASE) -_KIMI_RE = re.compile(r"\b(kimi[-\d.]*|moonshot)\b", re.IGNORECASE) -_GROK_RE = re.compile(r"\bgrok\b", re.IGNORECASE) -_DEEPSEEK_RE = re.compile(r"\bdeepseek\b", re.IGNORECASE) - - -def detect_provider_variant(model_name: str | None) -> ProviderVariant: - """Pick a provider-specific prompt variant from a model id string. - - Heuristic match on the model id; returns ``"default"`` when nothing - matches so the composer can fall back to the empty placeholder file. - - Order is significant: more-specific patterns are tried first so - ``gpt-5-codex`` routes to ``"openai_codex"`` rather than - ``"openai_reasoning"`` — same dispatch order as OpenCode's - ``packages/opencode/src/session/system.ts``. - """ - if not model_name: - return "default" - name = model_name.strip() - if _OPENAI_CODEX_RE.search(name): - return "openai_codex" - if _OPENAI_REASONING_RE.search(name): - return "openai_reasoning" - if _OPENAI_CLASSIC_RE.search(name): - return "openai_classic" - if _ANTHROPIC_RE.search(name): - return "anthropic" - if _GOOGLE_RE.search(name): - return "google" - if _KIMI_RE.search(name): - return "kimi" - if _GROK_RE.search(name): - return "grok" - if _DEEPSEEK_RE.search(name): - return "deepseek" - return "default" - - -# ----------------------------------------------------------------------------- -# Fragment loading -# ----------------------------------------------------------------------------- - - -_PROMPTS_PACKAGE = "app.prompts.system_prompt_composer" - - -def _read_fragment(subpath: str) -> str: - """Read a fragment file from the ``prompts/`` resource tree. - - Returns the raw contents stripped of any single trailing newline so - composition can append explicit separators without compounding blank - lines. Missing files return an empty string so optional fragments - (e.g. provider hints) act as no-ops. - """ - parts = subpath.split("/") - try: - ref = resources.files(_PROMPTS_PACKAGE).joinpath(*parts) - if not ref.is_file(): - return "" - text = ref.read_text(encoding="utf-8") - except (FileNotFoundError, ModuleNotFoundError): - return "" - if text.endswith("\n"): - text = text[:-1] - return text - - -# ----------------------------------------------------------------------------- -# Tool ordering + memory variant resolution -# ----------------------------------------------------------------------------- - - -# Ordered for reading flow: fundamentals first, then artifact generators, -# then memory at the end (mirrors the legacy ``_ALL_TOOL_NAMES_ORDERED``). -ALL_TOOL_NAMES_ORDERED: tuple[str, ...] = ( - "web_search", - "generate_podcast", - "generate_video_presentation", - "generate_report", - "generate_resume", - "generate_image", - "scrape_webpage", - "update_memory", -) - - -_MEMORY_VARIANT_TOOLS: frozenset[str] = frozenset({"update_memory"}) - - -def _tool_fragment_path(tool_name: str, variant: str) -> str: - """Resolve a tool's instruction fragment path. - - Tools listed in :data:`_MEMORY_VARIANT_TOOLS` switch on the conversation - visibility and load ``tools/_.md``; everything else - falls back to ``tools/.md``. - """ - if tool_name in _MEMORY_VARIANT_TOOLS: - return f"tools/{tool_name}_{variant}.md" - return f"tools/{tool_name}.md" - - -def _example_fragment_path(tool_name: str, variant: str) -> str: - if tool_name in _MEMORY_VARIANT_TOOLS: - return f"examples/{tool_name}_{variant}.md" - return f"examples/{tool_name}.md" - - -def _format_tool_label(tool_name: str) -> str: - return tool_name.replace("_", " ").title() - - -# ----------------------------------------------------------------------------- -# Section builders -# ----------------------------------------------------------------------------- - - -def _build_system_instructions( - *, - visibility: ChatVisibility, - resolved_today: str, -) -> str: - """Reconstruct the legacy ```` block from fragments.""" - variant = "team" if visibility == ChatVisibility.SEARCH_SPACE else "private" - - sections = [ - _read_fragment(f"base/agent_{variant}.md"), - _read_fragment(f"base/kb_only_policy_{variant}.md"), - _read_fragment(f"base/tool_routing_{variant}.md"), - _read_fragment("base/parameter_resolution.md"), - _read_fragment(f"base/memory_protocol_{variant}.md"), - ] - body = "\n\n".join(s for s in sections if s) - block = f"\n\n{body}\n\n\n" - return block.format(resolved_today=resolved_today) - - -def _build_mcp_routing_block( - mcp_connector_tools: dict[str, list[str]] | None, -) -> str: - """Emit the ```` block when at least one MCP server is wired.""" - if not mcp_connector_tools: - return "" - lines: list[str] = [ - "\n", - "You also have direct tools from these user-connected MCP servers.", - "Their data is NEVER in the knowledge base — call their tools directly.", - "", - ] - for server_name, tool_names in mcp_connector_tools.items(): - lines.append(f"- {server_name} → {', '.join(tool_names)}") - lines.append("\n") - return "\n".join(lines) - - -def _build_tools_section( - *, - visibility: ChatVisibility, - enabled_tool_names: set[str] | None, - disabled_tool_names: set[str] | None, -) -> str: - """Reconstruct the ```` block + ```` block.""" - variant = "team" if visibility == ChatVisibility.SEARCH_SPACE else "private" - - parts: list[str] = [] - preamble = _read_fragment("tools/_preamble.md") - if preamble: - parts.append(preamble + "\n") - - examples: list[str] = [] - - for tool_name in ALL_TOOL_NAMES_ORDERED: - if enabled_tool_names is not None and tool_name not in enabled_tool_names: - continue - - instruction = _read_fragment(_tool_fragment_path(tool_name, variant)) - if instruction: - parts.append(instruction + "\n") - - example = _read_fragment(_example_fragment_path(tool_name, variant)) - if example: - examples.append(example + "\n") - - known_disabled = ( - set(disabled_tool_names) & set(ALL_TOOL_NAMES_ORDERED) - if disabled_tool_names - else set() - ) - if known_disabled: - disabled_list = ", ".join( - _format_tool_label(n) for n in ALL_TOOL_NAMES_ORDERED if n in known_disabled - ) - parts.append( - "\n" - "DISABLED TOOLS (by user):\n" - f"The following tools are available in SurfSense but have been disabled by the user for this session: {disabled_list}.\n" - "You do NOT have access to these tools and MUST NOT claim you can use them.\n" - "If the user asks about a capability provided by a disabled tool, let them know the relevant tool\n" - "is currently disabled and they can re-enable it.\n" - ) - - parts.append("\n\n") - - if examples: - parts.append("") - parts.extend(examples) - parts.append("\n") - - return "".join(parts) - - -def _build_provider_block(provider_variant: ProviderVariant) -> str: - """Optional provider-tuned hints. Empty for ``"default"``.""" - if not provider_variant or provider_variant == "default": - return "" - text = _read_fragment(f"providers/{provider_variant}.md") - return f"\n{text}\n" if text else "" - - -def _build_routing_block(connector_routing: Iterable[str] | None) -> str: - if not connector_routing: - return "" - fragments: list[str] = [] - for name in connector_routing: - text = _read_fragment(f"routing/{name}.md") - if text: - fragments.append(text) - if not fragments: - return "" - return "\n" + "\n\n".join(fragments) + "\n" - - -def _build_citation_block(citations_enabled: bool) -> str: - fragment = ( - _read_fragment("base/citations_on.md") - if citations_enabled - else _read_fragment("base/citations_off.md") - ) - return f"\n{fragment}\n" if fragment else "" - - -# ----------------------------------------------------------------------------- -# Public API -# ----------------------------------------------------------------------------- - - -def compose_system_prompt( - *, - today: datetime | None = None, - thread_visibility: ChatVisibility | None = None, - enabled_tool_names: set[str] | None = None, - disabled_tool_names: set[str] | None = None, - mcp_connector_tools: dict[str, list[str]] | None = None, - custom_system_instructions: str | None = None, - use_default_system_instructions: bool = True, - citations_enabled: bool = True, - provider_variant: ProviderVariant | None = None, - model_name: str | None = None, - connector_routing: Iterable[str] | None = None, -) -> str: - """Assemble the SurfSense system prompt from disk fragments. - - Args: - today: Optional clock injection for tests. - thread_visibility: Private vs shared (team) — drives memory wording - and a few base block variants. - enabled_tool_names: When provided, only these tools' instructions - are included; ``None`` keeps the legacy "include everything" - behavior. - disabled_tool_names: User-disabled tools (note appended to prompt). - mcp_connector_tools: ``{server_name: [tool_names...]}`` to inject - an explicit MCP routing block. - custom_system_instructions: Free-form instructions that override - the default ```` block. - use_default_system_instructions: When ``custom_system_instructions`` - is empty/None, fall back to defaults (legacy semantics). - citations_enabled: Include ``citations_on.md`` (true) or - ``citations_off.md`` (false). - provider_variant: Explicit provider variant override - (``"anthropic" | "openai_reasoning" | "openai_classic" | "google" | "default"``). - When ``None``, falls back to :func:`detect_provider_variant` - on ``model_name``. - model_name: Used to auto-detect ``provider_variant`` when not - provided explicitly. - connector_routing: Optional list of routing fragment names - (``["linear", "slack", ...]``) to include from - ``prompts/routing/``. - - Returns: - The fully composed system prompt string. - """ - resolved_today = (today or datetime.now(UTC)).astimezone(UTC).date().isoformat() - visibility = thread_visibility or ChatVisibility.PRIVATE - - if custom_system_instructions and custom_system_instructions.strip(): - sys_block = custom_system_instructions.format(resolved_today=resolved_today) - elif use_default_system_instructions: - sys_block = _build_system_instructions( - visibility=visibility, resolved_today=resolved_today - ) - else: - sys_block = "" - - sys_block += _build_mcp_routing_block(mcp_connector_tools) - - if provider_variant is None: - provider_variant = detect_provider_variant(model_name) - sys_block += _build_provider_block(provider_variant) - sys_block += _build_routing_block(connector_routing) - - tools_block = _build_tools_section( - visibility=visibility, - enabled_tool_names=enabled_tool_names, - disabled_tool_names=disabled_tool_names, - ) - citation_block = _build_citation_block(citations_enabled) - - return sys_block + tools_block + citation_block - - -__all__ = [ - "ALL_TOOL_NAMES_ORDERED", - "ProviderVariant", - "compose_system_prompt", - "detect_provider_variant", -] diff --git a/surfsense_backend/app/prompts/system_prompt_composer/examples/__init__.py b/surfsense_backend/app/prompts/system_prompt_composer/examples/__init__.py deleted file mode 100644 index 8b1378917..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/examples/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/surfsense_backend/app/prompts/system_prompt_composer/examples/generate_image.md b/surfsense_backend/app/prompts/system_prompt_composer/examples/generate_image.md deleted file mode 100644 index 216c2926a..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/examples/generate_image.md +++ /dev/null @@ -1,12 +0,0 @@ - -- User: "Generate an image of a cat" - - Call: `generate_image(prompt="A fluffy orange tabby cat sitting on a windowsill, bathed in warm golden sunlight, soft bokeh background with green houseplants, photorealistic style, cozy atmosphere")` - - The generated image will automatically be displayed in the chat. -- User: "Draw me a logo for a coffee shop called Bean Dream" - - Call: `generate_image(prompt="Minimalist modern logo design for a coffee shop called 'Bean Dream', featuring a stylized coffee bean with dream-like swirls of steam, clean vector style, warm brown and cream color palette, white background, professional branding")` - - The generated image will automatically be displayed in the chat. -- User: "Show me this image: https://example.com/image.png" - - Simply include it in your response using markdown: `![Image](https://example.com/image.png)` -- User uploads an image file and asks: "What is this image about?" - - The user's uploaded image is already visible in the chat. - - Simply analyze the image content and respond directly. diff --git a/surfsense_backend/app/prompts/system_prompt_composer/examples/generate_podcast.md b/surfsense_backend/app/prompts/system_prompt_composer/examples/generate_podcast.md deleted file mode 100644 index aabf8ce7a..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/examples/generate_podcast.md +++ /dev/null @@ -1,7 +0,0 @@ - -- User: "Give me a podcast about AI trends based on what we discussed" - - First search for relevant content, then call: `generate_podcast(source_content="Based on our conversation and search results: [detailed summary of chat + search findings]", podcast_title="AI Trends Podcast")` -- User: "Create a podcast summary of this conversation" - - Call: `generate_podcast(source_content="Complete conversation summary:\n\nUser asked about [topic 1]:\n[Your detailed response]\n\nUser then asked about [topic 2]:\n[Your detailed response]\n\n[Continue for all exchanges in the conversation]", podcast_title="Conversation Summary")` -- User: "Make a podcast about quantum computing" - - First explore `/documents/` (ls/glob/grep/read_file), then: `generate_podcast(source_content="Key insights about quantum computing from retrieved files:\n\n[Comprehensive summary of findings]", podcast_title="Quantum Computing Explained")` diff --git a/surfsense_backend/app/prompts/system_prompt_composer/examples/generate_report.md b/surfsense_backend/app/prompts/system_prompt_composer/examples/generate_report.md deleted file mode 100644 index 7e9d0a595..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/examples/generate_report.md +++ /dev/null @@ -1,13 +0,0 @@ - -- User: "Generate a report about AI trends" - - Call: `generate_report(topic="AI Trends Report", source_strategy="kb_search", search_queries=["AI trends recent developments", "artificial intelligence industry trends", "AI market growth and predictions"], report_style="detailed")` - - WHY: Has creation verb "generate" → call the tool. No prior discussion → use kb_search. -- User: "Write a research report from this conversation" - - Call: `generate_report(topic="Research Report", source_strategy="conversation", source_content="Complete conversation summary:\n\n...", report_style="deep_research")` - - WHY: Has creation verb "write" → call the tool. Conversation has the content → use source_strategy="conversation". -- User: (after a report on Climate Change was generated) "Add a section about carbon capture technologies" - - Call: `generate_report(topic="Climate Crisis: Causes, Impacts, and Solutions", source_strategy="conversation", source_content="[summary of conversation context if any]", parent_report_id=, user_instructions="Add a new section about carbon capture technologies")` - - WHY: Has modification verb "add" + specific deliverable target → call the tool with parent_report_id. -- User: (after a report was generated) "What else could we add to have more depth?" - - Do NOT call generate_report. Answer in chat with suggestions. - - WHY: No creation/modification verb directed at producing a deliverable. diff --git a/surfsense_backend/app/prompts/system_prompt_composer/examples/generate_resume.md b/surfsense_backend/app/prompts/system_prompt_composer/examples/generate_resume.md deleted file mode 100644 index d8a6c381e..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/examples/generate_resume.md +++ /dev/null @@ -1,19 +0,0 @@ - -- User: "Build me a resume. I'm John Doe, engineer at Acme Corp..." - - Call: `generate_resume(user_info="John Doe, engineer at Acme Corp...", max_pages=1)` - - WHY: Has creation verb "build" + resume → call the tool. -- User: "Create my CV with this info: [experience, education, skills]" - - Call: `generate_resume(user_info="[experience, education, skills]", max_pages=1)` -- User: "Build me a resume" (and there is a resume/CV document in the conversation context) - - Extract the FULL content from the document in context, then call: - `generate_resume(user_info="Name: John Doe\nEmail: john@example.com\n\nExperience:\n- Senior Engineer at Acme Corp (2020-2024)\n Led team of 5...\n\nEducation:\n- BS Computer Science, MIT (2016-2020)\n\nSkills: Python, TypeScript, AWS...", max_pages=1)` - - WHY: Document content is available in context — extract ALL of it into user_info. Do NOT ignore referenced documents. -- User: (after resume generated) "Change my title to Senior Engineer" - - Call: `generate_resume(user_info="", user_instructions="Change the job title to Senior Engineer", parent_report_id=, max_pages=1)` - - WHY: Modification verb "change" + refers to existing resume → set parent_report_id. -- User: (after resume generated) "Make this 2 pages and expand projects" - - Call: `generate_resume(user_info="", user_instructions="Expand projects and keep this to at most 2 pages", parent_report_id=, max_pages=2)` - - WHY: Explicit page increase request → set max_pages to 2. -- User: "How should I structure my resume?" - - Do NOT call generate_resume. Answer in chat with advice. - - WHY: No creation/modification verb. diff --git a/surfsense_backend/app/prompts/system_prompt_composer/examples/generate_video_presentation.md b/surfsense_backend/app/prompts/system_prompt_composer/examples/generate_video_presentation.md deleted file mode 100644 index 257ec86cf..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/examples/generate_video_presentation.md +++ /dev/null @@ -1,7 +0,0 @@ - -- User: "Give me a presentation about AI trends based on what we discussed" - - First search for relevant content, then call: `generate_video_presentation(source_content="Based on our conversation and search results: [detailed summary of chat + search findings]", video_title="AI Trends Presentation")` -- User: "Create slides summarizing this conversation" - - Call: `generate_video_presentation(source_content="Complete conversation summary:\n\nUser asked about [topic 1]:\n[Your detailed response]\n\nUser then asked about [topic 2]:\n[Your detailed response]\n\n[Continue for all exchanges in the conversation]", video_title="Conversation Summary")` -- User: "Make a video presentation about quantum computing" - - First explore `/documents/` (ls/glob/grep/read_file), then: `generate_video_presentation(source_content="Key insights about quantum computing from retrieved files:\n\n[Comprehensive summary of findings]", video_title="Quantum Computing Explained")` diff --git a/surfsense_backend/app/prompts/system_prompt_composer/examples/scrape_webpage.md b/surfsense_backend/app/prompts/system_prompt_composer/examples/scrape_webpage.md deleted file mode 100644 index 0f156bf24..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/examples/scrape_webpage.md +++ /dev/null @@ -1,13 +0,0 @@ - -- User: "Check out https://dev.to/some-article" - - Call: `scrape_webpage(url="https://dev.to/some-article")` - - Respond with a structured analysis — key points, takeaways. -- User: "Read this article and summarize it for me: https://example.com/blog/ai-trends" - - Call: `scrape_webpage(url="https://example.com/blog/ai-trends")` - - Respond with a thorough summary using headings and bullet points. -- User: (after discussing https://example.com/stats) "Can you get the live data from that page?" - - Call: `scrape_webpage(url="https://example.com/stats")` - - IMPORTANT: Always attempt scraping first. Never refuse before trying the tool. -- User: "https://example.com/blog/weekend-recipes" - - Call: `scrape_webpage(url="https://example.com/blog/weekend-recipes")` - - When a user sends just a URL with no instructions, scrape it and provide a concise summary of the content. diff --git a/surfsense_backend/app/prompts/system_prompt_composer/examples/update_memory_private.md b/surfsense_backend/app/prompts/system_prompt_composer/examples/update_memory_private.md deleted file mode 100644 index 496bdcae3..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/examples/update_memory_private.md +++ /dev/null @@ -1,16 +0,0 @@ - -- Alex, is empty. User: "I'm a space enthusiast, explain astrophage to me" - - The user casually shared a durable fact: - update_memory(updated_memory="## Facts\n- 2025-03-15: Alex is a space enthusiast\n") -- User: "Remember that I prefer concise answers over detailed explanations" - - Durable preference. Merge with existing memory: - update_memory(updated_memory="## Facts\n- 2025-03-15: Alex is a space enthusiast\n\n## Preferences\n- 2025-03-15: Alex prefers concise answers over detailed explanations\n") -- User: "I actually moved to Tokyo last month" - - Updated fact, date prefix reflects when recorded: - update_memory(updated_memory="## Facts\n- 2025-03-15: Alex lives in Tokyo (previously London)\n...") -- User: "I'm a freelance photographer working on a nature documentary" - - Durable background info under a fitting heading: - update_memory(updated_memory="...\n\n## Current Focus\n- 2025-03-15: Alex is a freelance photographer\n- 2025-03-15: Alex is working on a nature documentary\n") -- User: "Always respond in bullet points" - - Standing instruction: - update_memory(updated_memory="...\n\n## Instructions\n- 2025-03-15: Always respond to Alex in bullet points\n") diff --git a/surfsense_backend/app/prompts/system_prompt_composer/examples/update_memory_team.md b/surfsense_backend/app/prompts/system_prompt_composer/examples/update_memory_team.md deleted file mode 100644 index 16b90babf..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/examples/update_memory_team.md +++ /dev/null @@ -1,7 +0,0 @@ - -- User: "Let's remember that we decided to do weekly standup meetings on Mondays" - - Durable team decision: - update_memory(updated_memory="## Product Decisions\n- 2025-03-15: Weekly standup meetings happen on Mondays\n...") -- User: "Our office is in downtown Seattle, 5th floor" - - Durable team fact: - update_memory(updated_memory="## Project Facts\n- 2025-03-15: Office location is downtown Seattle, 5th floor\n...") diff --git a/surfsense_backend/app/prompts/system_prompt_composer/examples/web_search.md b/surfsense_backend/app/prompts/system_prompt_composer/examples/web_search.md deleted file mode 100644 index 6b9828ac7..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/examples/web_search.md +++ /dev/null @@ -1,8 +0,0 @@ - -- User: "What's the current USD to INR exchange rate?" - - Call: `web_search(query="current USD to INR exchange rate")` - - Then answer using the returned web results with citations. -- User: "What's the latest news about AI?" - - Call: `web_search(query="latest AI news today")` -- User: "What's the weather in New York?" - - Call: `web_search(query="weather New York today")` diff --git a/surfsense_backend/app/prompts/system_prompt_composer/providers/__init__.py b/surfsense_backend/app/prompts/system_prompt_composer/providers/__init__.py deleted file mode 100644 index 8b1378917..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/providers/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/surfsense_backend/app/prompts/system_prompt_composer/providers/anthropic.md b/surfsense_backend/app/prompts/system_prompt_composer/providers/anthropic.md deleted file mode 100644 index f574da541..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/providers/anthropic.md +++ /dev/null @@ -1,20 +0,0 @@ - -You are running on an Anthropic Claude model. - -Structured reasoning: -- Use XML tags liberally to organise intermediate reasoning when a task is non-trivial. `...` blocks are encouraged before tool calls or before producing a complex final answer. -- For multi-step requests, briefly outline a plan inside a `` block before issuing the first tool call. - -Professional objectivity: -- Prioritise technical accuracy over validating the user's beliefs. Provide direct, factual guidance without unnecessary superlatives, praise, or emotional validation. -- When uncertain, investigate (search the KB, fetch the page) rather than confirming the user's assumption. -- Disagree with the user when the evidence warrants it; respectful correction beats false agreement. - -Task management: -- For tasks with 3+ distinct steps use the todo / planning tool aggressively. Mark items in_progress before starting, completed immediately when finished — do not batch completions. -- Narrate progress through the todo list itself, not through chatty status lines. - -Tool calls: -- Run independent tool calls in parallel within one response. Sequence them only when a later call genuinely needs an earlier one's output. -- Never chain bash-like commands with `;` or `&&` to "narrate" — use prose between tool calls instead. - diff --git a/surfsense_backend/app/prompts/system_prompt_composer/providers/deepseek.md b/surfsense_backend/app/prompts/system_prompt_composer/providers/deepseek.md deleted file mode 100644 index 3e22f48bf..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/providers/deepseek.md +++ /dev/null @@ -1,18 +0,0 @@ - -You are running on a DeepSeek model (DeepSeek-V3 chat / DeepSeek-R1 reasoning). - -Reasoning hygiene (R1-aware): -- If the model surfaces explicit `` blocks, keep that internal scratch focused — do NOT restate the user's question inside it; jump straight to the analysis. -- Never paste the contents of `` into your final answer. Final answer should reflect only the conclusion, citations, and any user-facing rationale. -- Do not let chain-of-thought leak into tool-call arguments — keep tool inputs minimal and structural. - -Output style: -- Be concise. Default to a one-paragraph answer; expand only when the user asks for detail. -- Don't open with sycophantic phrasing ("Great question", "Sure, here you go"). Lead with the answer or the next action. -- For factual answers, cite once with the passage's `[n]` label and stop. - -Tool calls: -- Issue independent tool calls in parallel within a single turn. -- Prefer the knowledge-base search tools before any web-search; this model has strong recall but stale training data. -- Don't fabricate file paths, chunk ids, or URLs — only use values returned by tools or provided by the user. - diff --git a/surfsense_backend/app/prompts/system_prompt_composer/providers/default.md b/surfsense_backend/app/prompts/system_prompt_composer/providers/default.md deleted file mode 100644 index 8b1378917..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/providers/default.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/surfsense_backend/app/prompts/system_prompt_composer/providers/google.md b/surfsense_backend/app/prompts/system_prompt_composer/providers/google.md deleted file mode 100644 index cac3b328b..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/providers/google.md +++ /dev/null @@ -1,20 +0,0 @@ - -You are running on a Google Gemini model. - -Output style: -- Concise & direct. Aim for fewer than 3 lines of prose (excluding tool output, citations, and code/snippets) when the task allows. -- No conversational filler — skip openers like "Okay, I will now…" and closers like "I have finished the changes…". Get straight to the action or answer. -- Format with GitHub-flavoured Markdown; assume monospace rendering. -- For one-line factual answers, just answer. No headers, no bullets. - -Workflow for non-trivial tasks (Understand → Plan → Act → Verify): -1. **Understand:** read the user's request and the relevant KB / connector context. Use search and read tools (in parallel when independent) before assuming anything. -2. **Plan:** when the task touches multiple steps, share an extremely concise plan first. -3. **Act:** call the appropriate tools, strictly adhering to the prompts/routing already established for this agent. -4. **Verify:** confirm with a follow-up read or search where it materially de-risks the answer. - -Discipline: -- Do not take significant actions beyond the clear scope of the user's request without confirming first. -- Do not assume a connector / tool / file exists — check (e.g. via `get_connected_accounts`) before referencing it. -- Path arguments must be the exact strings returned by tools; do not synthesise file paths. - diff --git a/surfsense_backend/app/prompts/system_prompt_composer/providers/grok.md b/surfsense_backend/app/prompts/system_prompt_composer/providers/grok.md deleted file mode 100644 index 0368f4ae8..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/providers/grok.md +++ /dev/null @@ -1,17 +0,0 @@ - -You are running on an xAI Grok model. - -Maximum terseness: -- Answer in fewer than 4 lines unless the user asks for detail. One-word answers are best when they suffice. -- No preamble ("The answer is", "Here's what I'll do"), no postamble ("Hope that helps", "Let me know"). Get straight to the answer. -- Avoid restating the user's question. -- For factual lookups inside the knowledge base, give the answer with a single `[n]` label and stop. - -Tool discipline: -- Use exactly ONE tool per assistant turn when investigating; wait for the result before deciding the next call. Do not loop on the same tool with the same arguments — pick a result and act. -- For obviously parallelizable read-only batches (multiple independent searches), one turn with several tool calls is fine — but never chain into a fishing expedition. - -Style: -- No emojis unless the user asked. No nested bullets, no headers for short answers. -- If you can't help, say so in 1-2 sentences without explaining "why this could lead to…". - diff --git a/surfsense_backend/app/prompts/system_prompt_composer/providers/kimi.md b/surfsense_backend/app/prompts/system_prompt_composer/providers/kimi.md deleted file mode 100644 index c3c11ad5e..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/providers/kimi.md +++ /dev/null @@ -1,21 +0,0 @@ - -You are running on a Moonshot Kimi model (Kimi-K1.5 / Kimi-K2 / Kimi-K2.5+). - -Action bias: -- Default to taking action with tools rather than describing solutions in prose. If a tool can answer the question, call the tool. -- Don't narrate routine reads, searches, or obvious next steps. Combine related progress into one short status line. -- Be thorough in actions (test what you build, verify what you change). Be brief in explanations. - -Tool calls: -- Output multiple non-interfering tool calls in a SINGLE response — parallelism is a major efficiency win on this model. -- When the `task` tool is available, delegate focused subtasks to a subagent with full context (subagents don't inherit yours). -- Don't apologise or pre-announce tool calls. The tool call itself is self-explanatory. - -Language: -- Respond in the SAME language as the user's most recent turn unless explicitly instructed otherwise. - -Discipline: -- Stay on track. Never give the user more than what they asked for. -- Fact-check before stating anything as factual; don't fabricate citations. -- Keep it stupidly simple. Don't overcomplicate. - diff --git a/surfsense_backend/app/prompts/system_prompt_composer/providers/openai_classic.md b/surfsense_backend/app/prompts/system_prompt_composer/providers/openai_classic.md deleted file mode 100644 index 9128609e0..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/providers/openai_classic.md +++ /dev/null @@ -1,21 +0,0 @@ - -You are running on a classic OpenAI chat model (GPT-4 family). - -Persistence: -- Keep going until the user's query is completely resolved before yielding back. Don't end the turn at "I would do X" — actually do X. -- When you say "Next I will…" or "Now I will…", you MUST actually take that action in the same turn. -- If a tool call fails, diagnose and try again with corrected arguments; do not surface the raw error and stop. - -Planning: -- Plan extensively before each tool call and reflect briefly on the result of the previous call. For tasks with 3+ steps, use the todo / planning tool and mark items as `in_progress` / `completed` as you go. -- Always announce the next action in ONE concise sentence before making a non-trivial tool call ("I'll search the KB for the migration spec."). - -Output style: -- Conversational but professional. Plain prose for explanations, bullet points for findings, fenced code blocks (with language tags) for code. -- Don't dump tool output verbatim — summarise the relevant lines. -- Don't add a closing recap unless the user asked for one. After completing the work, just stop. - -Tool calls: -- Issue independent tool calls in parallel within one response. -- Use specialised tools over generic ones (e.g. KB search before web search; named connectors over MCP fallback). - diff --git a/surfsense_backend/app/prompts/system_prompt_composer/providers/openai_codex.md b/surfsense_backend/app/prompts/system_prompt_composer/providers/openai_codex.md deleted file mode 100644 index 6167d4b06..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/providers/openai_codex.md +++ /dev/null @@ -1,19 +0,0 @@ - -You are running on an OpenAI Codex-class model (gpt-codex / codex-mini / gpt-*-codex). - -Output style: -- Be concise. Don't dump fetched/searched content back at the user — reference paths or chunk ids instead. -- Reference sources as `path:line` (or `chunk:`) so they're clickable. Stand-alone paths per reference, even when repeated. -- Prefer numbered lists (`1.`, `2.`, `3.`) when offering options the user can pick by replying with a single number. -- Skip headers and heavy formatting for simple confirmations. -- No emojis, no em-dashes, no nested bullets. Single-level lists only. - -Code & structured-output tasks: -- Lead with a one-sentence explanation of the change before context. Don't open with "Summary:" — jump in. -- Suggest natural next steps (run tests, diff review, commit) only when they're genuinely the next move. -- For multi-line snippets use fenced code blocks with a language tag. - -Tool calls: -- Run independent tool calls in parallel; chain only when later calls need earlier results. -- Don't ask permission ("Should I proceed?") — proceed with the most reasonable default and state what you did. - diff --git a/surfsense_backend/app/prompts/system_prompt_composer/providers/openai_reasoning.md b/surfsense_backend/app/prompts/system_prompt_composer/providers/openai_reasoning.md deleted file mode 100644 index dd7a61536..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/providers/openai_reasoning.md +++ /dev/null @@ -1,21 +0,0 @@ - -You are running on an OpenAI reasoning model (GPT-5+ / o-series). - -Output style: -- Be terse and direct. Don't restate the user's request before answering. -- Don't begin with conversational openers ("Done!", "Got it", "Great question", "Sure thing"). Get to the answer or the action. -- Match response complexity to the task: simple questions → one-line answer; substantial work → lead with the outcome, then context, then any next steps. -- No nested bullets — keep lists flat (single level). For options the user can pick by replying with a number, use `1.` `2.` `3.`. -- Use inline backticks for paths/commands/identifiers; fenced code blocks (with language tags) for multi-line snippets. - -Channels (for clients that support them): -- `commentary` — short progress updates only when they add genuinely new information (a discovery, a tradeoff, a blocker, the start of a non-trivial step). Don't narrate routine reads or obvious next steps. -- `final` — the completed response. Keep it self-contained; no "see above" / "see below" cross-references. - -Tool calls: -- Parallelise independent tool calls in a single response (`multi_tool_use.parallel` where supported). Only sequence when a later call needs an earlier one's output. -- Don't ask permission ("Should I proceed?", "Do you want me to…?"). Pick the most reasonable default, do it, and state what you did. - -Autonomy: -- Persist until the task is fully resolved within the current turn whenever feasible. Don't stop at analysis when the user clearly wants the change applied. - diff --git a/surfsense_backend/app/prompts/system_prompt_composer/routing/__init__.py b/surfsense_backend/app/prompts/system_prompt_composer/routing/__init__.py deleted file mode 100644 index 8b1378917..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/routing/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/surfsense_backend/app/prompts/system_prompt_composer/routing/jira.md b/surfsense_backend/app/prompts/system_prompt_composer/routing/jira.md deleted file mode 100644 index 8b1378917..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/routing/jira.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/surfsense_backend/app/prompts/system_prompt_composer/routing/linear.md b/surfsense_backend/app/prompts/system_prompt_composer/routing/linear.md deleted file mode 100644 index 2f1bfacd9..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/routing/linear.md +++ /dev/null @@ -1,3 +0,0 @@ - -**Linear:** Prefer the `task` tool with subagent **`linear_specialist`** when the user’s request is **only about Linear** and may need several tool calls (list issues, inspect one issue, teams, users, statuses, comments, documents). Use **`connector_negotiator`** when Linear is one hop in a **multi-connector** workflow. Call Linear MCP tools directly from the parent when a **single** quick call is enough. - diff --git a/surfsense_backend/app/prompts/system_prompt_composer/routing/slack.md b/surfsense_backend/app/prompts/system_prompt_composer/routing/slack.md deleted file mode 100644 index 4b5d07a9a..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/routing/slack.md +++ /dev/null @@ -1,3 +0,0 @@ - -**Slack:** Prefer `task` with **`slack_specialist`** for **Slack-only** multi-step work (channels, threads, reads, writes that need approval in the specialist). Use **`connector_negotiator`** when Slack feeds another connector in one chain. Use direct `slack_*` tools from the parent for a **single** quick read or write when appropriate. - diff --git a/surfsense_backend/app/prompts/system_prompt_composer/tools/__init__.py b/surfsense_backend/app/prompts/system_prompt_composer/tools/__init__.py deleted file mode 100644 index 8b1378917..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/tools/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/surfsense_backend/app/prompts/system_prompt_composer/tools/_preamble.md b/surfsense_backend/app/prompts/system_prompt_composer/tools/_preamble.md deleted file mode 100644 index 2c169e015..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/tools/_preamble.md +++ /dev/null @@ -1,6 +0,0 @@ - -You have access to the following tools: - -IMPORTANT: You can ONLY use the tools listed below. If a capability is not listed here, you do NOT have it. -Do NOT claim you can do something if the corresponding tool is not listed. - diff --git a/surfsense_backend/app/prompts/system_prompt_composer/tools/generate_image.md b/surfsense_backend/app/prompts/system_prompt_composer/tools/generate_image.md deleted file mode 100644 index 8bde13f22..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/tools/generate_image.md +++ /dev/null @@ -1,11 +0,0 @@ - -- generate_image: Generate images from text descriptions using AI image models. - - Use this when the user asks you to create, generate, draw, design, or make an image. - - Trigger phrases: "generate an image of", "create a picture of", "draw me", "make an image", "design a logo", "create artwork" - - Args: - - prompt: A detailed text description of the image to generate. Be specific about subject, style, colors, composition, and mood. - - n: Number of images to generate (1-4, default: 1) - - Returns: A dictionary with the generated image metadata. The image will automatically be displayed in the chat. - - IMPORTANT: Write a detailed, descriptive prompt for best results. Don't just pass the user's words verbatim - - expand and improve the prompt with specific details about style, lighting, composition, and mood. - - If the user's request is vague (e.g., "make me an image of a cat"), enhance the prompt with artistic details. diff --git a/surfsense_backend/app/prompts/system_prompt_composer/tools/generate_podcast.md b/surfsense_backend/app/prompts/system_prompt_composer/tools/generate_podcast.md deleted file mode 100644 index 58be143d7..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/tools/generate_podcast.md +++ /dev/null @@ -1,15 +0,0 @@ - -- generate_podcast: Generate an audio podcast from provided content. - - Use this when the user asks to create, generate, or make a podcast. - - Trigger phrases: "give me a podcast about", "create a podcast", "generate a podcast", "make a podcast", "turn this into a podcast" - - Args: - - source_content: The text content to convert into a podcast. This MUST be comprehensive and include: - * If discussing the current conversation: Include a detailed summary of the FULL chat history (all user questions and your responses) - * If based on knowledge base search: Include the key findings and insights from the search results - * You can combine both: conversation context + search results for richer podcasts - * The more detailed the source_content, the better the podcast quality - - podcast_title: Optional title for the podcast (default: "SurfSense Podcast") - - user_prompt: Optional instructions for podcast style/format (e.g., "Make it casual and fun") - - Returns: A task_id for tracking. The podcast will be generated in the background. - - IMPORTANT: Only one podcast can be generated at a time. If a podcast is already being generated, the tool will return status "already_generating". - - After calling this tool, inform the user that podcast generation has started and they will see the player when it's ready (takes 3-5 minutes). diff --git a/surfsense_backend/app/prompts/system_prompt_composer/tools/generate_report.md b/surfsense_backend/app/prompts/system_prompt_composer/tools/generate_report.md deleted file mode 100644 index 8a285a433..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/tools/generate_report.md +++ /dev/null @@ -1,39 +0,0 @@ - -- generate_report: Generate or revise a structured Markdown report artifact. - - WHEN TO CALL THIS TOOL — the message must contain a creation or modification VERB directed at producing a deliverable: - * Creation verbs: write, create, generate, draft, produce, summarize into, turn into, make - * Modification verbs: revise, update, expand, add (a section), rewrite, make (it shorter/longer/formal) - * Example triggers: "generate a report about...", "write a document on...", "add a section about budget", "make the report shorter", "rewrite in formal tone" - - WHEN NOT TO CALL THIS TOOL (answer in chat instead): - * Questions or discussion about the report: "What can we add?", "What's missing?", "Is the data accurate?", "How could this be improved?" - * Suggestions or brainstorming: "What other topics could be covered?", "What else could be added?", "What would make this better?" - * Asking for explanations: "Can you explain section 2?", "Why did you include that?", "What does this part mean?" - * Quick follow-ups or critiques: "Is the conclusion strong enough?", "Are there any gaps?", "What about the competitors?" - * THE TEST: Does the message contain a creation/modification VERB (from the list above) directed at producing or changing a deliverable? If NO verb → answer conversationally in chat. Do NOT assume the user wants a revision just because a report exists in the conversation. - - IMPORTANT FORMAT RULE: Reports are ALWAYS generated in Markdown. - - Args: - - topic: Short title for the report (max ~8 words). - - source_content: The text content to base the report on. - * For source_strategy="conversation" or "provided": Include a comprehensive summary of the relevant content. - * For source_strategy="kb_search": Can be empty or minimal — the tool handles searching internally. - * For source_strategy="auto": Include what you have; the tool searches KB if it's not enough. - - source_strategy: Controls how the tool collects source material. One of: - * "conversation" — The conversation already contains enough context (prior Q&A, discussion, pasted text, scraped pages). Pass a thorough summary as source_content. - * "kb_search" — The tool will search the knowledge base internally. Provide search_queries with 1-5 targeted queries. - * "auto" — Use source_content if sufficient, otherwise fall back to internal KB search using search_queries. - * "provided" — Use only what is in source_content (default, backward-compatible). - - search_queries: When source_strategy is "kb_search" or "auto", provide 1-5 specific search queries for the knowledge base. These should be precise, not just the topic name repeated. - - report_style: Controls report depth. Options: "detailed" (DEFAULT), "deep_research", "brief". - Use "brief" ONLY when the user explicitly asks for a short/concise/one-page report (e.g., "one page", "keep it short", "brief report", "500 words"). Default to "detailed" for all other requests. - - user_instructions: Optional specific instructions (e.g., "focus on financial impacts", "include recommendations"). When revising (parent_report_id set), describe WHAT TO CHANGE. If the user mentions a length preference (e.g., "one page", "500 words", "2 pages"), include that VERBATIM here AND set report_style="brief". - - parent_report_id: Set this to the report_id from a previous generate_report result when the user wants to MODIFY an existing report. Do NOT set it for new reports or questions about reports. - - Returns: A dictionary with status "ready" or "failed", report_id, title, and word_count. - - The report is generated immediately in Markdown and displayed inline in the chat. - - Export/download formats (PDF, DOCX, HTML, LaTeX, EPUB, ODT, plain text) are produced from the generated Markdown report. - - SOURCE STRATEGY DECISION (HIGH PRIORITY — follow this exactly): - * If the conversation already has substantive Q&A / discussion on the topic → use source_strategy="conversation" with a comprehensive summary as source_content. - * If the user wants a report on a topic not yet discussed → use source_strategy="kb_search" with targeted search_queries. - * If you have some content but might need more → use source_strategy="auto" with both source_content and search_queries. - * When revising an existing report (parent_report_id set) and the conversation has relevant context → use source_strategy="conversation". The revision will use the previous report content plus your source_content. - * NEVER run a separate KB lookup step and then pass those results to generate_report. The tool handles KB search internally. - - AFTER CALLING THIS TOOL: Do NOT repeat, summarize, or reproduce the report content in the chat. The report is already displayed as an interactive card that the user can open, read, copy, and export. Simply confirm that the report was generated (e.g., "I've generated your report on [topic]. You can view the Markdown report now, and export it in various formats from the card."). NEVER write out the report text in the chat. diff --git a/surfsense_backend/app/prompts/system_prompt_composer/tools/generate_resume.md b/surfsense_backend/app/prompts/system_prompt_composer/tools/generate_resume.md deleted file mode 100644 index 321ea90c9..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/tools/generate_resume.md +++ /dev/null @@ -1,30 +0,0 @@ - -- generate_resume: Generate or revise a professional resume as a Typst document. - - WHEN TO CALL: The user asks to create, build, generate, write, or draft a resume or CV. - Also when they ask to modify, update, or revise an existing resume from this conversation. - - WHEN NOT TO CALL: General career advice, resume tips, cover letters, or reviewing - a resume without making changes. For cover letters, use generate_report instead. - - The tool produces Typst source code that is compiled to a PDF preview automatically. - - PAGE POLICY: - - Default behavior is ONE PAGE. For new resume creation, set max_pages=1 unless the user explicitly asks for more. - - If the user requests a longer resume (e.g., "make it 2 pages"), set max_pages to that value. - - Args: - - user_info: The user's resume content — work experience, education, skills, contact - info, etc. Can be structured or unstructured text. - CRITICAL: user_info must be COMPREHENSIVE. Do NOT just pass the user's raw message. - You MUST gather and consolidate ALL available information: - * Content from referenced/mentioned documents (e.g., uploaded resumes, CVs, LinkedIn profiles) - that appear in the conversation context — extract and include their FULL content. - * Information the user shared across multiple messages in the conversation. - * Any relevant details from knowledge base search results in the context. - The more complete the user_info, the better the resume. Include names, contact info, - work experience with dates, education, skills, projects, certifications — everything available. - - user_instructions: Optional style or content preferences (e.g. "emphasize leadership", - "keep it to one page"). For revisions, describe what to change. - - parent_report_id: Set this when the user wants to MODIFY an existing resume from - this conversation. Use the report_id from a previous generate_resume result. - - max_pages: Maximum resume length in pages (integer 1-5). Default is 1. - - Returns: Dict with status, report_id, title, and content_type. - - After calling: Give a brief confirmation. Do NOT paste resume content in chat. Do NOT mention report_id or any internal IDs — the resume card is shown automatically. - - VERSIONING: Same rules as generate_report — set parent_report_id for modifications - of an existing resume, leave as None for new resumes. diff --git a/surfsense_backend/app/prompts/system_prompt_composer/tools/generate_video_presentation.md b/surfsense_backend/app/prompts/system_prompt_composer/tools/generate_video_presentation.md deleted file mode 100644 index c3def88f2..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/tools/generate_video_presentation.md +++ /dev/null @@ -1,9 +0,0 @@ - -- generate_video_presentation: Generate a video presentation from provided content. - - Use this when the user asks to create a video, presentation, slides, or slide deck. - - Trigger phrases: "give me a presentation", "create slides", "generate a video", "make a slide deck", "turn this into a presentation" - - Args: - - source_content: The text content to turn into a presentation. The more detailed, the better. - - video_title: Optional title (default: "SurfSense Presentation") - - user_prompt: Optional style instructions (e.g., "Make it technical and detailed") - - After calling this tool, inform the user that generation has started and they will see the presentation when it's ready. diff --git a/surfsense_backend/app/prompts/system_prompt_composer/tools/scrape_webpage.md b/surfsense_backend/app/prompts/system_prompt_composer/tools/scrape_webpage.md deleted file mode 100644 index 46e299392..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/tools/scrape_webpage.md +++ /dev/null @@ -1,30 +0,0 @@ - -- scrape_webpage: Scrape and extract the main content from a webpage. - - Use this when the user wants you to READ and UNDERSTAND the actual content of a webpage. - - CRITICAL — WHEN TO USE (always attempt scraping, never refuse before trying): - * When a user asks to "get", "fetch", "pull", "grab", "scrape", or "read" content from a URL - * When the user wants live/dynamic data from a specific webpage (e.g., tables, scores, stats, prices) - * When a URL was mentioned earlier in the conversation and the user asks for its actual content - * When `/documents/` knowledge-base data is insufficient and the user wants more - - Trigger scenarios: - * "Read this article and summarize it" - * "What does this page say about X?" - * "Summarize this blog post for me" - * "Tell me the key points from this article" - * "What's in this webpage?" - * "Can you analyze this article?" - * "Can you get the live table/data from [URL]?" - * "Scrape it" / "Can you scrape that?" (referring to a previously mentioned URL) - * "Fetch the content from [URL]" - * "Pull the data from that page" - - Args: - - url: The URL of the webpage to scrape (must be HTTP/HTTPS) - - max_length: Maximum content length to return (default: 50000 chars) - - Returns: The page title, description, full content (in markdown), word count, and metadata - - After scraping, provide a comprehensive, well-structured summary with key takeaways using headings or bullet points. - - Reference the source using markdown links [descriptive text](url) — never bare URLs. - - IMAGES: The scraped content may contain image URLs in markdown format like `![alt text](image_url)`. - * When you find relevant/important images in the scraped content, include them in your response using standard markdown image syntax: `![alt text](image_url)`. - * This makes your response more visual and engaging. - * Prioritize showing: diagrams, charts, infographics, key illustrations, or images that help explain the content. - * Don't show every image - just the most relevant 1-3 images that enhance understanding. diff --git a/surfsense_backend/app/prompts/system_prompt_composer/tools/update_memory_private.md b/surfsense_backend/app/prompts/system_prompt_composer/tools/update_memory_private.md deleted file mode 100644 index 65de785e9..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/tools/update_memory_private.md +++ /dev/null @@ -1,26 +0,0 @@ - -- update_memory: Update your personal memory document about the user. - - Your current memory is already in in your context. The `chars` - and `limit` attributes show current usage and the maximum allowed size. - - This is curated long-term memory, not raw conversation logs. - - Call update_memory when the user explicitly asks to remember/forget - something or shares durable facts, preferences, or standing instructions. - - The user's first name is provided in . Use it in entries instead - of "the user" when helpful. Do not store the name alone as a memory entry. - - Do not store short-lived info: one-off questions, greetings, session - logistics, or things that only matter for the current task. - - Args: - - updated_memory: The FULL updated markdown document, not a diff. Merge new - facts with existing ones, update contradictions, remove outdated entries, - and consolidate instead of only appending. - - Use heading-based Markdown: - * Every entry must be under a `##` heading. - * Recommended headings: `## Facts`, `## Preferences`, `## Instructions`. - Specific natural headings are allowed when clearer. - * New bullets should use `- YYYY-MM-DD: text`. - * Each entry should be one concise but descriptive bullet. - - If existing memory uses legacy `(YYYY-MM-DD) [fact|pref|instr]` markers, - preserve the information but write the updated document in the new - heading-based format. - - During consolidation, prioritize durable instructions and preferences before - generic facts. diff --git a/surfsense_backend/app/prompts/system_prompt_composer/tools/update_memory_team.md b/surfsense_backend/app/prompts/system_prompt_composer/tools/update_memory_team.md deleted file mode 100644 index 79d4ead3a..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/tools/update_memory_team.md +++ /dev/null @@ -1,28 +0,0 @@ - -- update_memory: Update the team's shared memory document for this search space. - - Your current team memory is already in in your context. The - `chars` and `limit` attributes show current usage and the maximum allowed size. - - This is curated long-term team memory: decisions, conventions, architecture, - processes, and key shared facts. - - NEVER store personal memory in team memory: individual bios, personal - preferences, or user-only standing instructions. - - Call update_memory when a team member asks to remember/forget something, or - when the conversation surfaces durable team context that matters later. - - Do not store short-lived info: one-off questions, greetings, session - logistics, or things that only matter for the current task. - - Args: - - updated_memory: The FULL updated markdown document, not a diff. Merge new - facts with existing ones, update contradictions, remove outdated entries, - and consolidate instead of only appending. - - Use heading-based Markdown: - * Every entry must be under a `##` heading. - * Recommended headings: `## Product Decisions`, `## Engineering Conventions`, - `## Project Facts`, `## Open Questions`. - * New bullets should use `- YYYY-MM-DD: text`. - * Each entry should be one concise but descriptive bullet. - - If existing memory uses legacy `(YYYY-MM-DD) [fact]` markers, preserve the - information but write the updated document in the new heading-based format. - - Do not create personal headings such as `## Preferences`, `## Instructions`, - `## Personal Notes`, or `## Personal Instructions`. - - During consolidation, prioritize decisions/conventions, then key facts, then - current priorities. diff --git a/surfsense_backend/app/prompts/system_prompt_composer/tools/web_search.md b/surfsense_backend/app/prompts/system_prompt_composer/tools/web_search.md deleted file mode 100644 index 7ed7c332d..000000000 --- a/surfsense_backend/app/prompts/system_prompt_composer/tools/web_search.md +++ /dev/null @@ -1,18 +0,0 @@ - -- web_search: Search the web for real-time information using all configured search engines. - - Use this for current events, news, prices, weather, public facts, or any question requiring - up-to-date information from the internet. - - This tool dispatches to all configured search engines (SearXNG, Tavily, Linkup, Baidu) in - parallel and merges the results. - - IMPORTANT (REAL-TIME / PUBLIC WEB QUERIES): For questions that require current public web data - (e.g., live exchange rates, stock prices, breaking news, weather, current events), you MUST call - `web_search` instead of answering from memory. - - For these real-time/public web queries, DO NOT answer from memory and DO NOT say you lack internet - access before attempting a web search. - - If the search returns no relevant results, explain that web sources did not return enough - data and ask the user if they want you to retry with a refined query. - - Args: - - query: The search query - use specific, descriptive terms - - top_k: Number of results to retrieve (default: 10, max: 50) - - If search snippets are insufficient for the user's question, use `scrape_webpage` on the most relevant result URL for full content. - - When presenting results, reference sources as markdown links [descriptive text](url) — never bare URLs. diff --git a/surfsense_backend/tests/unit/agents/new_chat/prompts/test_composer.py b/surfsense_backend/tests/unit/agents/new_chat/prompts/test_composer.py deleted file mode 100644 index 0140bd606..000000000 --- a/surfsense_backend/tests/unit/agents/new_chat/prompts/test_composer.py +++ /dev/null @@ -1,296 +0,0 @@ -"""Tests for the prompt fragment composer.""" - -from __future__ import annotations - -from datetime import UTC, datetime - -import pytest - -from app.db import ChatVisibility -from app.prompts.system_prompt_composer.composer import ( - ALL_TOOL_NAMES_ORDERED, - compose_system_prompt, - detect_provider_variant, -) - -pytestmark = pytest.mark.unit - - -@pytest.fixture -def fixed_today() -> datetime: - return datetime(2025, 6, 1, 12, 0, tzinfo=UTC) - - -class TestProviderVariantDetection: - @pytest.mark.parametrize( - "model_name,expected", - [ - # GPT-4 family routes to "classic" (autonomous-persistence style) - ("openai:gpt-4o-mini", "openai_classic"), - ("openai:gpt-4-turbo", "openai_classic"), - # GPT-5 / o-series route to "reasoning" (channel-aware pragmatic) - ("openai:gpt-5", "openai_reasoning"), - ("openai:o1-preview", "openai_reasoning"), - ("openai:o3-mini", "openai_reasoning"), - # Codex family beats reasoning (more specific). Mirrors OpenCode - # ``system.ts`` — ``gpt-*-codex`` gets the code-purist prompt. - ("openai:gpt-5-codex", "openai_codex"), - ("openai:gpt-codex", "openai_codex"), - ("openai:codex-mini", "openai_codex"), - # Anthropic + Google - ("anthropic:claude-3-5-sonnet", "anthropic"), - ("anthropic/claude-opus-4", "anthropic"), - ("google:gemini-2.0-flash", "google"), - ("vertex:gemini-1.5-pro", "google"), - # Newly-covered families - ("moonshot:kimi-k2", "kimi"), - ("openrouter:moonshot/kimi-k2.5", "kimi"), - ("xai:grok-2", "grok"), - ("openrouter:x-ai/grok-3", "grok"), - ("openai:deepseek-v3", "deepseek"), - ("deepseek:deepseek-r1", "deepseek"), - # Unknown families fall back to default (no provider block emitted) - ("groq:mixtral-8x7b", "default"), - ("together:llama-3.1-70b", "default"), - (None, "default"), - ("", "default"), - ], - ) - def test_detection(self, model_name: str | None, expected: str) -> None: - assert detect_provider_variant(model_name) == expected - - def test_codex_takes_precedence_over_reasoning(self) -> None: - """Regression guard: ``gpt-5-codex`` must NOT match the generic - ``gpt-5`` reasoning regex first. Codex is the more specialised - prompt and mirrors OpenCode's dispatch order. - """ - from app.prompts.system_prompt_composer.composer import detect_provider_variant - - assert detect_provider_variant("openai:gpt-5-codex") == "openai_codex" - assert detect_provider_variant("openai:gpt-5") == "openai_reasoning" - - -class TestCompose: - def test_default_prompt_has_required_blocks(self, fixed_today: datetime) -> None: - prompt = compose_system_prompt(today=fixed_today) - # System instruction wrapper - assert "" in prompt - assert "" in prompt - # Date interpolated - assert "2025-06-01" in prompt - # Core policy blocks present - assert "" in prompt - assert "" in prompt - assert "" in prompt - assert "" in prompt - # Tools - assert "" in prompt - assert "" in prompt - # Citations on by default — the [n] / contract - assert "" in prompt - assert "" in prompt - assert "[1][2]" in prompt - - def test_team_visibility_uses_team_variants(self, fixed_today: datetime) -> None: - prompt = compose_system_prompt( - today=fixed_today, - thread_visibility=ChatVisibility.SEARCH_SPACE, - ) - # Team-specific phrasing in the agent block - assert "team space" in prompt - # Memory protocol mentions team - assert "team" in prompt - # Should NOT mention the user-only memory phrasing - assert "personal knowledge base" not in prompt - - def test_private_visibility_uses_private_variants( - self, fixed_today: datetime - ) -> None: - prompt = compose_system_prompt( - today=fixed_today, - thread_visibility=ChatVisibility.PRIVATE, - ) - assert "personal knowledge base" in prompt - # Should NOT mention the team-specific phrasing about prefixed authors - assert "[DisplayName of the author]" not in prompt - - def test_citations_disabled_swaps_block(self, fixed_today: datetime) -> None: - prompt_on = compose_system_prompt(today=fixed_today, citations_enabled=True) - prompt_off = compose_system_prompt(today=fixed_today, citations_enabled=False) - assert "Citation markers are **disabled**" in prompt_off - assert "Citation markers are **disabled**" not in prompt_on - assert "" in prompt_on - - def test_enabled_tool_filter_only_includes_listed_tools( - self, fixed_today: datetime - ) -> None: - prompt = compose_system_prompt( - today=fixed_today, - enabled_tool_names={"web_search", "scrape_webpage"}, - ) - assert "web_search:" in prompt or "- web_search:" in prompt - assert "scrape_webpage:" in prompt or "- scrape_webpage:" in prompt - # Excluded tools should NOT appear in tool listing - assert "generate_podcast:" not in prompt - assert "generate_image:" not in prompt - - def test_disabled_tool_note_is_appended(self, fixed_today: datetime) -> None: - prompt = compose_system_prompt( - today=fixed_today, - enabled_tool_names={"web_search"}, - disabled_tool_names={"generate_image", "generate_podcast"}, - ) - assert "DISABLED TOOLS (by user):" in prompt - assert "Generate Image" in prompt - assert "Generate Podcast" in prompt - - def test_mcp_routing_block_emits_when_provided(self, fixed_today: datetime) -> None: - prompt = compose_system_prompt( - today=fixed_today, - mcp_connector_tools={"My GitLab": ["gitlab_search", "gitlab_create_mr"]}, - ) - assert "" in prompt - assert "My GitLab" in prompt - assert "gitlab_search" in prompt - - def test_mcp_routing_block_absent_when_no_servers( - self, fixed_today: datetime - ) -> None: - prompt = compose_system_prompt(today=fixed_today, mcp_connector_tools={}) - assert "" not in prompt - - def test_provider_block_renders_when_anthropic(self, fixed_today: datetime) -> None: - prompt = compose_system_prompt( - today=fixed_today, model_name="anthropic:claude-3-5-sonnet" - ) - assert "" in prompt - assert "Anthropic" in prompt or "Claude" in prompt - - def test_provider_block_absent_for_default(self, fixed_today: datetime) -> None: - prompt = compose_system_prompt(today=fixed_today, model_name="custom:foo") - assert "" not in prompt - - @pytest.mark.parametrize( - "model_name,expected_marker", - [ - # Each marker is a unique-ish phrase from the corresponding fragment. - # If a fragment is renamed/rewritten such that the marker is gone, - # update both the fragment and this test deliberately. - ("openai:gpt-5-codex", "Codex-class"), - ("openai:gpt-5", "OpenAI reasoning model"), - ("openai:gpt-4o", "classic OpenAI chat model"), - ("anthropic:claude-3-5-sonnet", "Anthropic Claude"), - ("google:gemini-2.0-flash", "Google Gemini"), - ("moonshot:kimi-k2", "Moonshot Kimi"), - ("xai:grok-2", "xAI Grok"), - ("deepseek:deepseek-r1", "DeepSeek"), - ], - ) - def test_each_known_variant_renders_with_its_marker( - self, - fixed_today: datetime, - model_name: str, - expected_marker: str, - ) -> None: - """Every supported variant must produce a ```` block - containing its identifying marker. This pins the dispatch + the - on-disk fragments together so a missing/renamed file is caught - immediately. - """ - prompt = compose_system_prompt(today=fixed_today, model_name=model_name) - assert "" in prompt, ( - f"variant for {model_name!r} did not emit a provider_hints block; " - "the corresponding providers/.md may be missing" - ) - assert expected_marker in prompt, ( - f"variant for {model_name!r} emitted hints but lacked the " - f"expected marker {expected_marker!r} — the fragment may have " - "drifted from the dispatch table" - ) - - def test_provider_blocks_are_byte_stable_across_calls( - self, fixed_today: datetime - ) -> None: - """Cache-stability guard: same model id → byte-identical prompt.""" - a = compose_system_prompt(today=fixed_today, model_name="moonshot:kimi-k2") - b = compose_system_prompt(today=fixed_today, model_name="moonshot:kimi-k2") - assert a == b - - def test_custom_system_instructions_override_default( - self, fixed_today: datetime - ) -> None: - custom = "You are a custom assistant. Today is {resolved_today}." - prompt = compose_system_prompt( - today=fixed_today, custom_system_instructions=custom - ) - assert "You are a custom assistant. Today is 2025-06-01." in prompt - # Default block should NOT be present - assert "" not in prompt - - def test_provider_hints_render_with_custom_system_instructions( - self, fixed_today: datetime - ) -> None: - """Regression guard for the always-append decision: provider hints - append AFTER a custom system prompt. - - Provider hints are stylistic nudges (parallel tool-call rules, - formatting guidance, etc.) that help the model regardless of - what the system instructions say. Suppressing them when a - custom prompt is set would partially defeat the per-family - prompt machinery. - """ - prompt = compose_system_prompt( - today=fixed_today, - custom_system_instructions="You are a custom assistant.", - model_name="anthropic/claude-3-5-sonnet", - ) - assert "You are a custom assistant." in prompt - assert "" in prompt - # The custom prompt must come BEFORE the provider hints so the - # user's framing isn't drowned out by the stylistic nudges. - assert prompt.index("You are a custom assistant.") < prompt.index( - "" - ) - - def test_use_default_false_with_no_custom_yields_no_system_block( - self, fixed_today: datetime - ) -> None: - prompt = compose_system_prompt( - today=fixed_today, - use_default_system_instructions=False, - ) - # No system_instruction wrapper but tools/citations still emitted - assert "" not in prompt - assert "" in prompt - - def test_all_known_tools_have_fragments(self) -> None: - # Soft assertion: verify that every tool in the canonical order - # produces non-empty content for at least one variant. - for tool in ALL_TOOL_NAMES_ORDERED: - prompt = compose_system_prompt( - today=datetime(2025, 1, 1, tzinfo=UTC), - enabled_tool_names={tool}, - ) - assert tool in prompt, f"tool {tool!r} missing from composed prompt" - - -class TestStableOrderingForCacheStability: - """Regression guard: prompt cache hit-rate depends on byte-stable prefix.""" - - def test_composition_is_deterministic_given_same_inputs( - self, fixed_today: datetime - ) -> None: - a = compose_system_prompt( - today=fixed_today, - enabled_tool_names={"web_search", "scrape_webpage"}, - mcp_connector_tools={"X": ["x_a", "x_b"]}, - ) - b = compose_system_prompt( - today=fixed_today, - enabled_tool_names={ - "scrape_webpage", - "web_search", - }, # set order shouldn't matter - mcp_connector_tools={"X": ["x_a", "x_b"]}, - ) - assert a == b