From 0968bae6ebbb18acd6105631c78c475cf881fc36 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Sat, 8 Aug 2026 13:52:09 +0200 Subject: [PATCH 1/3] Enclose sub-prompts in tags --- CHANGELOG.md | 1 + src/serena/agent.py | 22 ++++++++++++------- .../config/prompt_templates/system_prompt.yml | 2 -- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 256dd973..c89dea24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ Status of the `main` branch. Changes prior to the next official version change w failed `--project-from-cwd` auto-detection (#1773). - ProjectServer: Configure trusted hosts (local hosts only) when listening on localhost - SerenaDashboardTrayManager: Configure trusted hosts (local hosts only) + - Enclose sub-prompts in XML-like tags to make scopes explicit * CLI: - Fix: `start-mcp-server` help text for `--project-from-cwd` falsely promised a fallback to the CWD, which was diff --git a/src/serena/agent.py b/src/serena/agent.py index 2b70389a..6dd9954c 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -956,14 +956,21 @@ class SerenaAgent: result[tool_class.get_name_from_cls()] = new_tool_class.get_name_from_cls() return result - def _format_prompt(self, prompt_template: str) -> str: + def _format_prompt(self, prompt_template: str, tag: str | None = None, name : str | None = None) -> str: template = JinjaTemplate(prompt_template) - return template.render( + text = template.render( available_tools=self._exposed_tools.tool_names, available_markers=self._exposed_tools.tool_marker_names, tool_names=self._prompt_tool_names_mapping, ) + if tag is not None: + open_tag = f"<{tag}" + (f' name="{name}"' if name is not None else "") + ">" + close_tag = f"" + text = f"{open_tag}{text}{close_tag}" + + return text + def create_connection_prompt(self) -> str: """ Returns the bootstrap prompt to be sent at MCP connection time. @@ -997,8 +1004,8 @@ class SerenaAgent: self._project_prompt_status.mark_mode_prompts_as_provided(session_id) system_prompt = self.prompt_factory.create_system_prompt( - context_system_prompt=self._format_prompt(self._context.prompt), - mode_system_prompts=[self._format_prompt(mode.prompt) for mode in relevant_modes], + context_system_prompt=self._format_prompt(self._context.prompt, tag="context"), + mode_system_prompts=[self._format_prompt(mode.prompt, tag="mode", name=mode.name) for mode in relevant_modes], available_tools=available_tools.tool_names, available_markers=available_markers, global_memories_list=global_memories_str, @@ -1007,7 +1014,7 @@ class SerenaAgent: # provide the project activation message if it hasn't yet been provided if self._active_project is not None and not self._project_prompt_status.is_project_activation_message_already_provided(session_id): - system_prompt += "\n\n" + self.get_project_activation_message(session_id) + system_prompt += "\n\n" + self.get_project_activation_message(session_id) + "" elif self._project_activation_error: system_prompt += f"\n\nNo project is active ({self._project_activation_error})." @@ -1054,14 +1061,13 @@ class SerenaAgent: # add prompts for modes that were dynamically activated by the project modes_with_prompts = self._project_prompt_status.get_modes_with_prompts_to_be_provided_for_project_activation(session_id) if modes_with_prompts: - msg += "\nNewly applicable mode instructions:" for mode in modes_with_prompts: - msg += f"\n{mode.prompt}" + msg += f'\n{mode.prompt}' self._project_prompt_status.mark_mode_prompts_as_provided(session_id) # add project-specific prompt if proj.project_config.initial_prompt: - msg += f"\nProject-specific instructions:\n {proj.project_config.initial_prompt}" + msg += f"\n{proj.project_config.initial_prompt}" self._project_prompt_status.mark_project_activation_message_as_provided(session_id) diff --git a/src/serena/resources/config/prompt_templates/system_prompt.yml b/src/serena/resources/config/prompt_templates/system_prompt.yml index 78a7ad18..99604bf1 100644 --- a/src/serena/resources/config/prompt_templates/system_prompt.yml +++ b/src/serena/resources/config/prompt_templates/system_prompt.yml @@ -49,10 +49,8 @@ prompts: The context and modes of operation are described below. These determine how to interact with your user and which kinds of interactions are expected of you. - Context description: {{ context_system_prompt }} - Modes descriptions: {% for prompt in mode_system_prompts %} {{ prompt }} {% endfor %} From 548aea28a620a181a28ae2283cfdf4278d4d205c Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Sat, 8 Aug 2026 13:56:19 +0200 Subject: [PATCH 2/3] Allow project initial prompt & newly activated dynamic mode prompts to use templating/variables Add helper method _format_prompt_tag --- CHANGELOG.md | 1 + src/serena/agent.py | 21 ++++++++++++--------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c89dea24..a5b0966e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ Status of the `main` branch. Changes prior to the next official version change w - ProjectServer: Configure trusted hosts (local hosts only) when listening on localhost - SerenaDashboardTrayManager: Configure trusted hosts (local hosts only) - Enclose sub-prompts in XML-like tags to make scopes explicit + - Allow initial project prompts and project-specific newly activated modes to use templating * CLI: - Fix: `start-mcp-server` help text for `--project-from-cwd` falsely promised a fallback to the CWD, which was diff --git a/src/serena/agent.py b/src/serena/agent.py index 6dd9954c..545bc599 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -956,7 +956,12 @@ class SerenaAgent: result[tool_class.get_name_from_cls()] = new_tool_class.get_name_from_cls() return result - def _format_prompt(self, prompt_template: str, tag: str | None = None, name : str | None = None) -> str: + def _format_prompt_tag(self, text: str, tag: str, tag_name_attr: str | None = None) -> str: + open_tag = f"<{tag}" + (f' name="{tag_name_attr}"' if tag_name_attr is not None else "") + ">" + close_tag = f"" + return f"{open_tag}\n{text.strip()}\n{close_tag}" + + def _format_prompt(self, prompt_template: str, tag: str | None = None, tag_name_attr: str | None = None) -> str: template = JinjaTemplate(prompt_template) text = template.render( available_tools=self._exposed_tools.tool_names, @@ -965,9 +970,7 @@ class SerenaAgent: ) if tag is not None: - open_tag = f"<{tag}" + (f' name="{name}"' if name is not None else "") + ">" - close_tag = f"" - text = f"{open_tag}{text}{close_tag}" + text = self._format_prompt_tag(text, tag=tag, tag_name_attr=tag_name_attr) return text @@ -1005,7 +1008,7 @@ class SerenaAgent: system_prompt = self.prompt_factory.create_system_prompt( context_system_prompt=self._format_prompt(self._context.prompt, tag="context"), - mode_system_prompts=[self._format_prompt(mode.prompt, tag="mode", name=mode.name) for mode in relevant_modes], + mode_system_prompts=[self._format_prompt(mode.prompt, tag="mode", tag_name_attr=mode.name) for mode in relevant_modes], available_tools=available_tools.tool_names, available_markers=available_markers, global_memories_list=global_memories_str, @@ -1014,11 +1017,11 @@ class SerenaAgent: # provide the project activation message if it hasn't yet been provided if self._active_project is not None and not self._project_prompt_status.is_project_activation_message_already_provided(session_id): - system_prompt += "\n\n" + self.get_project_activation_message(session_id) + "" + system_prompt += "\n\n" + self._format_prompt_tag(self.get_project_activation_message(session_id), tag="active-project") elif self._project_activation_error: system_prompt += f"\n\nNo project is active ({self._project_activation_error})." - return system_prompt + return self._format_prompt_tag(system_prompt, tag="serena") def get_project_activation_message(self, session_id: str) -> str: """ @@ -1062,12 +1065,12 @@ class SerenaAgent: modes_with_prompts = self._project_prompt_status.get_modes_with_prompts_to_be_provided_for_project_activation(session_id) if modes_with_prompts: for mode in modes_with_prompts: - msg += f'\n{mode.prompt}' + msg += self._format_prompt(mode.prompt, tag="mode", tag_name_attr=mode.name) + "\n" self._project_prompt_status.mark_mode_prompts_as_provided(session_id) # add project-specific prompt if proj.project_config.initial_prompt: - msg += f"\n{proj.project_config.initial_prompt}" + msg += "\n" + self._format_prompt(proj.project_config.initial_prompt, tag="project-instructions") self._project_prompt_status.mark_project_activation_message_as_provided(session_id) From d3a8e9b5658a9721596142cbc7f2dedabb585859 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Sat, 8 Aug 2026 14:11:41 +0200 Subject: [PATCH 3/3] Improve project activation message --- src/serena/agent.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/serena/agent.py b/src/serena/agent.py index 545bc599..b9649fcf 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -1041,13 +1041,13 @@ class SerenaAgent: # provide basic project information (name, location, languages, encoding) if proj.is_newly_created: - msg = f"Created and activated a new project with name '{proj.project_name}' at {proj.project_root}. " + msg = f"Created and activated a new project with name '{proj.project_name}' at {proj.project_root}.\n" else: - msg = f"The project with name '{proj.project_name}' at {proj.project_root} is activated." + msg = f"The project with name '{proj.project_name}' at {proj.project_root} is activated.\n" if self._language_backend == LanguageBackend.LSP: - languages_str = ", ".join([lang.value for lang in proj.project_config.language_servers]) - msg += f"\nProgramming languages: {languages_str}." - msg += f"File encoding: {proj.project_config.encoding}." + language_servers_str = ", ".join([ls.value for ls in proj.project_config.language_servers]) + msg += f"Active language servers: {language_servers_str}.\n" + msg += f"File encoding: {proj.project_config.encoding}.\n" # add list of memories (if memories are enabled) include_memories = self._active_tools.contains_tool_class(ReadMemoryTool) @@ -1055,11 +1055,11 @@ class SerenaAgent: project_memories = proj.memory_manager.list_project_memories() if project_memories: msg += ( - f"\n{json.dumps(project_memories.to_dict())}\n" - + "Use the `read_memory` tool to read these memories later if they are relevant to the task." + f"{json.dumps(project_memories.to_dict())}\n" + + f"Use the `{ReadMemoryTool.get_name_from_cls()}` tool to read these memories later if they are relevant to the task.\n" ) elif self._active_tools.contains_tool_class(OnboardingTool): - msg += "Onboarding has not been performed yet, you should call Serena's `onboarding` tool now to set up project memories." + msg += f"Onboarding has not been performed yet. Ask the user whether to perform onboarding via the `{OnboardingTool.get_name_from_cls()}` tool.\n" # add prompts for modes that were dynamically activated by the project modes_with_prompts = self._project_prompt_status.get_modes_with_prompts_to_be_provided_for_project_activation(session_id)