Allow language backend changes upon project activation with the REPL interface

* _activate_project: a project's backend override switches the backend in REPL mode (background
  modes, facades, prompt parameters and backend initialisation are recomputed upon activation)
* the tool interface keeps rejecting the mismatch (fixed toolset); the error now mentions the REPL
  as a workaround
This commit is contained in:
Dominik Jain authored and Dominik Jain committed 2026-09-15 12:50:44 +02:00
1 parent 4b9ce9ad44
commit 323e855625
3 files changed
+49 -7

No files matched your search

+3
View File
@@ -76,6 +76,9 @@ expression is the result. No `return` (a top-level `return` yields a SyntaxError
(`serena_repl`, `initial_instructions`, `activate_project` unless single-project); tool inclusion/exclusion
definitions do not apply — each interface has its own configuration vocabulary (tool definitions ↔ tools,
API definitions ↔ REPL). Contexts do not influence the interface.
In REPL mode, the language backend may change upon project activation (a project's backend override is
applied; background modes, facades, prompt params and backend initialisation are recomputed), whereas the
tool interface forbids this (the toolset depends on the backend and is fixed).
Idea (not implemented, considered over-engineered for now): contexts could declare *supported* interfaces
(a capability constraint, e.g. clients that handle the REPL badly), with the user's preference choosing among them.
- `included_apis`/`excluded_apis` (references `facade` or `facade.method`) in global config, context, modes,
+17 -7
View File
@@ -663,7 +663,8 @@ class SerenaAgent:
# determine the effective language backend for this session.
# If a startup project is provided and has a per-project override, use it; otherwise use the global config.
# Since we don't want to change the toolset after startup, the language backend cannot be changed within a running Serena session
# With the tool interface, the backend cannot change within a session (the toolset depends on it and is fixed);
# with the REPL interface, it may change upon project activation (see _activate_project).
self._language_backend = self.serena_config.determine_language_backend(
project_config=registered_project_to_activate.project_config if registered_project_to_activate is not None else None,
log_choice=True,
@@ -1420,15 +1421,24 @@ class SerenaAgent:
self._project_activation_error = None
# check if the project requires a different language backend than the one initialized at startup
# handle the case where the project requires a different language backend than the current one.
# With the tool interface, the backend cannot change, since the set of exposed tools depends on it and is fixed
# for the session. With the REPL interface, the backend can be switched, as all backend-dependent state
# (background modes, REPL facades, prompt parameters, the project's language backend initialisation) is
# recomputed upon activation.
project_backend = project.project_config.language_backend
if project_backend is not None and project_backend != self._language_backend:
raise ValueError(
f"Cannot activate project '{project.project_name}': it requires the {project_backend.value} backend, "
f"but this session was initialized with {self._language_backend.value}. "
f"Workarounds: (1) Use project activation at startup via the --project flag, "
f"(2) Configure one MCP server per backend in your client."
if self._agent_interface.is_tools():
raise ValueError(
f"Cannot activate project '{project.project_name}': it requires the {project_backend.value} backend, "
f"but this session was initialized with {self._language_backend.value}. "
f"Workarounds: (1) Use project activation at startup via the --project flag, "
f"(2) Configure one MCP server per backend in your client, (3) use the REPL interface."
)
log.info(
f"Switching language backend from {self._language_backend.value} to {project_backend.value} for project '{project.project_name}'"
)
self._language_backend = project_backend
# shut down the previously active project to release its language server processes
if self._active_project is not None:
+29
View File
@@ -363,6 +363,35 @@ class TestEffectiveLanguageBackend:
finally:
agent.on_shutdown(timeout=5)
def test_activate_project_switches_backend_with_repl_interface(self):
"""With the REPL interface, post-init activation of a project with a different backend switches the backend."""
config, name = _make_config_with_project("lsp_proj", language_backend=None, global_backend=LanguageBackend.LSP)
config.agent_interface = AgentInterface.REPL
jb_project = Project(
project_root=str(Path(__file__).parent.parent / "resources" / "repos" / "java" / "test_repo"),
project_config=ProjectConfig(
project_name="jb_proj",
language_servers=[LanguageServerId.JAVA],
language_backend=LanguageBackend.JETBRAINS,
),
serena_config=config,
)
config.projects.append(RegisteredProject.from_project_instance(jb_project))
agent = SerenaAgent(project=name, serena_config=config)
try:
assert agent.get_language_backend() == LanguageBackend.LSP
assert "s.lsp" in agent.get_repl().entrypoint.overview()
# the backend and everything depending on it follow the activated project
agent.activate_project_from_path_or_name("jb_proj")
assert agent.get_language_backend() == LanguageBackend.JETBRAINS
overview = agent.get_repl().entrypoint.overview()
assert "s.jb" in overview and "s.lsp" not in overview
assert "jetbrains" in [m.name for m in agent.get_active_modes().get_modes(include_background_base_modes=True)]
finally:
agent.on_shutdown(timeout=5)
def test_activate_project_allows_matching_backend(self):
"""Post-init activation of a project with matching backend succeeds."""
config, name = _make_config_with_project("lsp_proj", language_backend=None, global_backend=LanguageBackend.LSP)