Skip to content

Implement generic SymbolDictGrouper #1036

Open
opcode81 wants to merge 15 commits intomainfrom
generic-symbol-output-grouping
Open

Implement generic SymbolDictGrouper #1036
opcode81 wants to merge 15 commits intomainfrom
generic-symbol-output-grouping

Conversation

@opcode81
Copy link
Contributor

@opcode81 opcode81 commented Feb 13, 2026

... with specialisations for LS & JB

It represents a reasonable compromise between pragmatism and type-safety. Soundness is checked at runtime, but if the grouper is instantiated statically, then we immediately detect misspecification.

Apply the grouper to the relevant tools.

@opcode81 opcode81 marked this pull request as draft February 13, 2026 17:21
@opcode81
Copy link
Contributor Author

opcode81 commented Feb 13, 2026

@MischaPanch take a look.

Things yet to be done:

  • We need to get rid of the hacky _sanitize_symbol_dict function
  • We might want to reify another concept, which is applied in existing compaction implementations: A dictionary simplifier, which, for example, collapses an object with a single key to that key's value.
  • We need to apply it in all places where we want this.

@opcode81 opcode81 force-pushed the generic-symbol-output-grouping branch from a7e216e to 53d6a41 Compare February 13, 2026 17:34
It represents a reasonable compromise between pragmatism and type-safety.
Soundness is checked at runtime, but if the grouper is instantiated statically,
then we immediately detect misspecification.

One example application is implemented in GetSymbolsOverviewTool

Provides basis for #1021
@opcode81 opcode81 force-pushed the generic-symbol-output-grouping branch from 53d6a41 to 798a31b Compare February 13, 2026 17:36
@opcode81 opcode81 force-pushed the generic-symbol-output-grouping branch from 09e3d69 to 6814cf1 Compare February 13, 2026 20:33
@opcode81
Copy link
Contributor Author

We need to get rid of the hacky _sanitize_symbol_dict function

@MischaPanch I removed it already and found that the old implementation was actually flawed, because the function was not applied recursively to children, causing children to contain a lot of superfluous stuff.
Now it is all explicit and clean.

This fixes a bug: _sanitize_symbol_dict was not recursively applied
to children, causing children to contain superfluous entries
(full location, etc.)

Additional change: Improve factorisation of symbol overview computation
@opcode81 opcode81 force-pushed the generic-symbol-output-grouping branch from 6814cf1 to a1f9590 Compare February 13, 2026 20:37
@MischaPanch
Copy link
Contributor

A dictionary simplifier

I think it makes sense. Since it can only be applied to leaves, we could call someting like LeafDictSimplifier

…ctGrouper,

adding support for the necessary transformations of the final dictionaries
@opcode81
Copy link
Contributor Author

A dictionary simplifier

I think it makes sense. Since it can only be applied to leaves, we could call someting like LeafDictSimplifier

I implemented it; see d41b9ec

@opcode81
Copy link
Contributor Author

opcode81 commented Feb 13, 2026

cbff50b implements #1021.

Here are the results:

JetBrains before
[{
	"name_path": "DQN",
	"relative_path": "examples/atari/atari_dqn.py",
	"type": "Py:IMPORT_ELEMENT"
},
{
	"name_path": "DQN",
	"relative_path": "examples/box2d/lunarlander_dqn.py",
	"type": "Py:IMPORT_ELEMENT"
},
{
	"name_path": "DQN",
	"relative_path": "test/pettingzoo/tic_tac_toe.py",
	"type": "Py:IMPORT_ELEMENT"
},
{
	"name_path": "test_dqn",
	"relative_path": "examples/box2d/lunarlander_dqn.py",
	"type": "Py:FUNCTION_DECLARATION"
},
{
	"name_path": "get_agents",
	"relative_path": "test/pettingzoo/tic_tac_toe.py",
	"type": "Py:FUNCTION_DECLARATION"
},
{
	"name_path": "main",
	"relative_path": "examples/atari/atari_dqn.py",
	"type": "Py:FUNCTION_DECLARATION"
},
{
	"name_path": "test_dqn",
	"relative_path": "examples/box2d/lunarlander_dqn.py",
	"type": "Py:FUNCTION_DECLARATION"
},
{
	"name_path": "main",
	"relative_path": "examples/atari/atari_dqn.py",
	"type": "Py:FUNCTION_DECLARATION"
},
{
	"name_path": "DQN",
	"relative_path": "tianshou/highlevel/trainer.py",
	"type": "Py:IMPORT_ELEMENT"
},
{
	"name_path": "DQN",
	"relative_path": "examples/box2d/acrobot_dualdqn.py",
	"type": "Py:IMPORT_ELEMENT"
},
{
	"name_path": "test_dqn",
	"relative_path": "examples/box2d/acrobot_dualdqn.py",
	"type": "Py:FUNCTION_DECLARATION"
},
{
	"name_path": "test_dqn/to",
	"relative_path": "examples/box2d/acrobot_dualdqn.py",
	"type": "Py:REFERENCE_EXPRESSION"
},
{
	"name_path": "EpochTrainCallbackDQNSetEps/callback",
	"relative_path": "tianshou/highlevel/trainer.py",
	"type": "Py:FUNCTION_DECLARATION"
},
{
	"name_path": "EpochTrainCallbackDQNEpsLinearDecay/callback",
	"relative_path": "tianshou/highlevel/trainer.py",
	"type": "Py:FUNCTION_DECLARATION"
},
{
	"name_path": "EpochTestCallbackDQNSetEps/callback",
	"relative_path": "tianshou/highlevel/trainer.py",
	"type": "Py:FUNCTION_DECLARATION"
},
{
	"name_path": "DQN",
	"relative_path": "tianshou/highlevel/algorithm.py",
	"type": "Py:IMPORT_ELEMENT"
},
{
	"name_path": "DQNAlgorithmFactory",
	"relative_path": "tianshou/highlevel/algorithm.py",
	"type": "Py:CLASS_DECLARATION"
},
{
	"name_path": "DQN",
	"relative_path": "test/modelbased/test_dqn_icm.py",
	"type": "Py:IMPORT_ELEMENT"
},
{
	"name_path": "test_dqn_icm",
	"relative_path": "test/modelbased/test_dqn_icm.py",
	"type": "Py:FUNCTION_DECLARATION"
},
{
	"name_path": "DQNAlgorithmFactory/_get_algorithm_class",
	"relative_path": "tianshou/highlevel/algorithm.py",
	"type": "Py:FUNCTION_DECLARATION"
},
{
	"name_path": "test_dqn_icm",
	"relative_path": "test/modelbased/test_dqn_icm.py",
	"type": "Py:FUNCTION_DECLARATION"
},
{
	"name_path": "DQNAlgorithmFactory/_get_algorithm_class",
	"relative_path": "tianshou/highlevel/algorithm.py",
	"type": "Py:FUNCTION_DECLARATION"
},
{
	"name_path": "DQN",
	"relative_path": "test/discrete/test_dqn.py",
	"type": "Py:IMPORT_ELEMENT"
},
{
	"name_path": "test_dqn",
	"relative_path": "test/discrete/test_dqn.py",
	"type": "Py:FUNCTION_DECLARATION"
},
{
	"name_path": "test_dqn",
	"relative_path": "test/discrete/test_dqn.py",
	"type": "Py:FUNCTION_DECLARATION"
},
{
	"name_path": "DQN",
	"relative_path": "test/discrete/test_drqn.py",
	"type": "Py:IMPORT_ELEMENT"
},
{
	"name_path": "test_drqn",
	"relative_path": "test/discrete/test_drqn.py",
	"type": "Py:FUNCTION_DECLARATION"
},
{
	"name_path": "test_drqn",
	"relative_path": "test/discrete/test_drqn.py",
	"type": "Py:FUNCTION_DECLARATION"
},
{
	"name_path": "DQN",
	"relative_path": "test/pettingzoo/pistonball.py",
	"type": "Py:IMPORT_ELEMENT"
},
{
	"name_path": "get_agents",
	"relative_path": "test/pettingzoo/pistonball.py",
	"type": "Py:FUNCTION_DECLARATION"
},
{
	"name_path": "get_agents",
	"relative_path": "test/pettingzoo/pistonball.py",
	"type": "Py:FUNCTION_DECLARATION"
},
{
	"name_path": "DQN",
	"relative_path": "tianshou/algorithm/__init__.py",
	"type": "Py:IMPORT_ELEMENT"
},
{
	"name_path": "01_apis.md",
	"relative_path": "",
	"type": "FILE"
},
{
	"name_path": "main",
	"relative_path": "examples/discrete/discrete_dqn.py",
	"type": "Py:FUNCTION_DECLARATION"
}]
JetBrains after
{
	"tianshou/algorithm/__init__.py": {
		"Py:IMPORT_ELEMENT": ["DQN"]
	},
	"examples/atari/atari_dqn.py": {
		"Py:IMPORT_ELEMENT": ["DQN"],
		"Py:FUNCTION_DECLARATION": ["main",
		"main"]
	},
	"test/pettingzoo/tic_tac_toe.py": {
		"Py:IMPORT_ELEMENT": ["DQN"],
		"Py:FUNCTION_DECLARATION": ["get_agents"]
	},
	"examples/box2d/lunarlander_dqn.py": {
		"Py:IMPORT_ELEMENT": ["DQN"],
		"Py:FUNCTION_DECLARATION": ["test_dqn",
		"test_dqn"]
	},
	"tianshou/highlevel/trainer.py": {
		"Py:IMPORT_ELEMENT": ["DQN"],
		"Py:FUNCTION_DECLARATION": ["EpochTrainCallbackDQNSetEps/callback",
		"EpochTrainCallbackDQNEpsLinearDecay/callback",
		"EpochTestCallbackDQNSetEps/callback"]
	},
	"examples/box2d/acrobot_dualdqn.py": {
		"Py:IMPORT_ELEMENT": ["DQN"],
		"Py:FUNCTION_DECLARATION": ["test_dqn"],
		"Py:REFERENCE_EXPRESSION": ["test_dqn/to"]
	},
	"test/discrete/test_dqn.py": {
		"Py:IMPORT_ELEMENT": ["DQN"],
		"Py:FUNCTION_DECLARATION": ["test_dqn",
		"test_dqn"]
	},
	"test/discrete/test_drqn.py": {
		"Py:IMPORT_ELEMENT": ["DQN"],
		"Py:FUNCTION_DECLARATION": ["test_drqn",
		"test_drqn"]
	},
	"test/pettingzoo/pistonball.py": {
		"Py:IMPORT_ELEMENT": ["DQN"],
		"Py:FUNCTION_DECLARATION": ["get_agents",
		"get_agents"]
	},
	"tianshou/highlevel/algorithm.py": {
		"Py:IMPORT_ELEMENT": ["DQN"],
		"Py:CLASS_DECLARATION": ["DQNAlgorithmFactory"],
		"Py:FUNCTION_DECLARATION": ["DQNAlgorithmFactory/_get_algorithm_class",
		"DQNAlgorithmFactory/_get_algorithm_class"]
	},
	"test/modelbased/test_dqn_icm.py": {
		"Py:IMPORT_ELEMENT": ["DQN"],
		"Py:FUNCTION_DECLARATION": ["test_dqn_icm",
		"test_dqn_icm"]
	},
	"": {
		"FILE": ["01_apis.md"]
	},
	"examples/discrete/discrete_dqn.py": {
		"Py:FUNCTION_DECLARATION": ["main"]
	}
}
LS before
[{
	"name_path": "demo_run_tools",
	"kind": "File",
	"relative_path": "scripts\\demo_run_tools.py",
	"body_location": {
		"start_line": 0,
		"end_line": 37
	},
	"content_around_reference": "...   7:\n  >   8:from serena.agent import SerenaAgent\n...   9:from serena.config.serena_config import SerenaConfig"
},
{
	"name_path": "agent",
	"kind": "Variable",
	"relative_path": "scripts\\demo_run_tools.py",
	"body_location": {
		"start_line": 22,
		"end_line": 22
	},
	"content_around_reference": "...  21:    serena_config.web_dashboard = False\n  >  22:    agent = SerenaAgent(project=REPO_ROOT, serena_config=serena_config)\n...  23:"
},
{
	"name_path": "profile_tool_call",
	"kind": "File",
	"relative_path": "scripts\\profile_tool_call.py",
	"body_location": {
		"start_line": 0,
		"end_line": 53
	},
	"content_around_reference": "...   7:\n  >   8:from serena.agent import SerenaAgent\n...   9:from serena.config.serena_config import SerenaConfig"
},
{
	"name_path": "agent",
	"kind": "Variable",
	"relative_path": "scripts\\profile_tool_call.py",
	"body_location": {
		"start_line": 30,
		"end_line": 30
	},
	"content_around_reference": "...  29:\n  >  30:    agent = SerenaAgent(str(project_path), serena_config=serena_config)\n...  31:"
},
{
	"name_path": "agno",
	"kind": "File",
	"relative_path": "src\\serena\\agno.py",
	"body_location": {
		"start_line": 0,
		"end_line": 144
	},
	"content_around_reference": "...  15:\n  >  16:from serena.agent import SerenaAgent, Tool\n...  17:from serena.config.context_mode import SerenaAgentContext"
},
{
	"name_path": "SerenaAgnoToolkit/__init__",
	"kind": "Method",
	"relative_path": "src\\serena\\agno.py",
	"body_location": {
		"start_line": 25,
		"end_line": 29
	},
	"content_around_reference": "...  24:class SerenaAgnoToolkit(Toolkit):\n  >  25:    def __init__(self, serena_agent: SerenaAgent):\n...  26:        super().__init__(\"Serena\")"
},
{
	"name_path": "SerenaAgnoAgentProvider/get_agent/serena_agent",
	"kind": "Variable",
	"relative_path": "src\\serena\\agno.py",
	"body_location": {
		"start_line": 107,
		"end_line": 107
	},
	"content_around_reference": "... 106:                try:\n  > 107:                    serena_agent = SerenaAgent(project_file, context=SerenaAgentContext.load(\"agent\"))\n... 108:                except Exception as e:"
},
{
	"name_path": "cli",
	"kind": "File",
	"relative_path": "src\\serena\\cli.py",
	"body_location": {
		"start_line": 0,
		"end_line": 1055
	},
	"content_around_reference": "...  17:\n  >  18:from serena.agent import SerenaAgent\n...  19:from serena.config.context_mode import SerenaAgentContext, SerenaAgentMode"
},
{
	"name_path": "TopLevelCommands/print_system_prompt/agent",
	"kind": "Variable",
	"relative_path": "src\\serena\\cli.py",
	"body_location": {
		"start_line": 338,
		"end_line": 338
	},
	"content_around_reference": "... 337:            modes_selection_def = ModeSelectionDefinition(default_modes=modes)\n  > 338:        agent = SerenaAgent(\n... 339:            project=os.path.abspath(project),"
},
{
	"name_path": "ProjectCommands/health_check/agent",
	"kind": "Variable",
	"relative_path": "src\\serena\\cli.py",
	"body_location": {
		"start_line": 768,
		"end_line": 768
	},
	"content_around_reference": "... 767:                serena_config.web_dashboard = False\n  > 768:                agent = SerenaAgent(project=project_path, serena_config=serena_config)\n... 769:                log.info(\"SerenaAgent created successfully\")"
},
{
	"name_path": "ToolCommands/description/agent",
	"kind": "Variable",
	"relative_path": "src\\serena\\cli.py",
	"body_location": {
		"start_line": 929,
		"end_line": 929
	},
	"content_around_reference": "... 928:\n  > 929:        agent = SerenaAgent(\n... 930:            project=None,"
},
{
	"name_path": "code_editor",
	"kind": "File",
	"relative_path": "src\\serena\\code_editor.py",
	"body_location": {
		"start_line": 0,
		"end_line": 440
	},
	"content_around_reference": "...  17:if TYPE_CHECKING:\n  >  18:    from .agent import SerenaAgent\n...  19:"
},
{
	"name_path": "CodeEditor/__init__",
	"kind": "Method",
	"relative_path": "src\\serena\\code_editor.py",
	"body_location": {
		"start_line": 26,
		"end_line": 36
	},
	"content_around_reference": "...  25:class CodeEditor(Generic[TSymbol], ABC):\n  >  26:    def __init__(self, project_root: str, agent: Optional[\"SerenaAgent\"] = None) -> None:\n...  27:        self.project_root = project_root"
},
{
	"name_path": "LanguageServerCodeEditor/__init__",
	"kind": "Method",
	"relative_path": "src\\serena\\code_editor.py",
	"body_location": {
		"start_line": 246,
		"end_line": 248
	},
	"content_around_reference": "... 245:class LanguageServerCodeEditor(CodeEditor[LanguageServerSymbol]):\n  > 246:    def __init__(self, symbol_retriever: LanguageServerSymbolRetriever, agent: Optional[\"SerenaAgent\"] = None):\n... 247:        super().__init__(project_root=symbol_retriever.get_root_path(), agent=agent)"
},
{
	"name_path": "JetBrainsCodeEditor/__init__",
	"kind": "Method",
	"relative_path": "src\\serena\\code_editor.py",
	"body_location": {
		"start_line": 381,
		"end_line": 383
	},
	"content_around_reference": "... 380:class JetBrainsCodeEditor(CodeEditor[JetBrainsSymbol]):\n  > 381:    def __init__(self, project: Project, agent: Optional[\"SerenaAgent\"] = None) -> None:\n... 382:        self._project = project"
},
{
	"name_path": "dashboard",
	"kind": "File",
	"relative_path": "src\\serena\\dashboard.py",
	"body_location": {
		"start_line": 0,
		"end_line": 641
	},
	"content_around_reference": "...  17:if TYPE_CHECKING:\n  >  18:    from serena.agent import SerenaAgent\n...  19:"
},
{
	"name_path": "SerenaDashboardAPI/__init__/agent",
	"kind": "Variable",
	"relative_path": "src\\serena\\dashboard.py",
	"body_location": {
		"start_line": 128,
		"end_line": 128
	},
	"content_around_reference": "... 127:        tool_names: list[str],\n  > 128:        agent: \"SerenaAgent\",\n... 129:        shutdown_callback: Callable[[], None] | None = None,"
},
{
	"name_path": "mcp",
	"kind": "File",
	"relative_path": "src\\serena\\mcp.py",
	"body_location": {
		"start_line": 0,
		"end_line": 350
	},
	"content_around_reference": "...  19:from serena.agent import (\n  >  20:    SerenaAgent,\n...  21:    SerenaConfig,"
},
{
	"name_path": "SerenaMCPRequestContext/agent",
	"kind": "Variable",
	"relative_path": "src\\serena\\mcp.py",
	"body_location": {
		"start_line": 46,
		"end_line": 46
	},
	"content_around_reference": "...  45:class SerenaMCPRequestContext:\n  >  46:    agent: SerenaAgent\n...  47:"
},
{
	"name_path": "SerenaMCPFactory/agent",
	"kind": "Variable",
	"relative_path": "src\\serena\\mcp.py",
	"body_location": {
		"start_line": 64,
		"end_line": 64
	},
	"content_around_reference": "...  63:        self.project = project\n  >  64:        self.agent: SerenaAgent | None = None\n...  65:        self.memory_log_handler = memory_log_handler"
},
{
	"name_path": "SerenaMCPFactory/_create_serena_agent",
	"kind": "Method",
	"relative_path": "src\\serena\\mcp.py",
	"body_location": {
		"start_line": 262,
		"end_line": 265
	},
	"content_around_reference": "... 261:\n  > 262:    def _create_serena_agent(self, serena_config: SerenaConfig, modes: ModeSelectionDefinition | None = None) -> SerenaAgent:\n... 263:        return SerenaAgent("
},
{
	"name_path": "SerenaMCPFactory/_create_serena_agent",
	"kind": "Method",
	"relative_path": "src\\serena\\mcp.py",
	"body_location": {
		"start_line": 262,
		"end_line": 265
	},
	"content_around_reference": "... 262:    def _create_serena_agent(self, serena_config: SerenaConfig, modes: ModeSelectionDefinition | None = None) -> SerenaAgent:\n  > 263:        return SerenaAgent(\n... 264:            project=self.project, serena_config=serena_config, context=self.context, modes=modes, memory_log_handler=self.memory_log_handler"
},
{
	"name_path": "symbol",
	"kind": "File",
	"relative_path": "src\\serena\\symbol.py",
	"body_location": {
		"start_line": 0,
		"end_line": 865
	},
	"content_around_reference": "...  18:if TYPE_CHECKING:\n  >  19:    from .agent import SerenaAgent\n...  20:"
},
{
	"name_path": "LanguageServerSymbolRetriever/__init__",
	"kind": "Method",
	"relative_path": "src\\serena\\symbol.py",
	"body_location": {
		"start_line": 533,
		"end_line": 545
	},
	"content_around_reference": "... 532:class LanguageServerSymbolRetriever:\n  > 533:    def __init__(self, ls: SolidLanguageServer | LanguageServerManager, agent: Union[\"SerenaAgent\", None] = None) -> None:\n... 534:        \"\"\""
},
{
	"name_path": "tools_base",
	"kind": "File",
	"relative_path": "src\\serena\\tools\\tools_base.py",
	"body_location": {
		"start_line": 0,
		"end_line": 453
	},
	"content_around_reference": "...  20:if TYPE_CHECKING:\n  >  21:    from serena.agent import SerenaAgent\n...  22:    from serena.code_editor import CodeEditor"
},
{
	"name_path": "Component/__init__",
	"kind": "Method",
	"relative_path": "src\\serena\\tools\\tools_base.py",
	"body_location": {
		"start_line": 31,
		"end_line": 32
	},
	"content_around_reference": "...  30:class Component(ABC):\n  >  31:    def __init__(self, agent: \"SerenaAgent\"):\n...  32:        self.agent = agent"
},
{
	"name_path": "test_serena_agent",
	"kind": "File",
	"relative_path": "test\\serena\\test_serena_agent.py",
	"body_location": {
		"start_line": 0,
		"end_line": 487
	},
	"content_around_reference": "...  10:\n  >  11:from serena.agent import SerenaAgent\n...  12:from serena.config.serena_config import ProjectConfig, RegisteredProject, SerenaConfig"
},
{
	"name_path": "project_file_modification_context/serena_agent",
	"kind": "Variable",
	"relative_path": "test\\serena\\test_serena_agent.py",
	"body_location": {
		"start_line": 71,
		"end_line": 71
	},
	"content_around_reference": "...  70:@contextmanager\n  >  71:def project_file_modification_context(serena_agent: SerenaAgent, relative_path: str) -> Iterator[None]:\n...  72:    \"\"\"Context manager to modify a project file and revert the changes after use.\"\"\""
},
{
	"name_path": "serena_agent/request",
	"kind": "Variable",
	"relative_path": "test\\serena\\test_serena_agent.py",
	"body_location": {
		"start_line": 88,
		"end_line": 88
	},
	"content_around_reference": "...  87:@pytest.fixture\n  >  88:def serena_agent(request: pytest.FixtureRequest, serena_config) -> Iterator[SerenaAgent]:\n...  89:    language = Language(request.param)"
},
{
	"name_path": "serena_agent/agent",
	"kind": "Variable",
	"relative_path": "test\\serena\\test_serena_agent.py",
	"body_location": {
		"start_line": 95,
		"end_line": 95
	},
	"content_around_reference": "...  94:\n  >  95:    agent = SerenaAgent(project=project_name, serena_config=serena_config)\n...  96:"
},
{
	"name_path": "TestSerenaAgent/test_find_symbol/serena_agent",
	"kind": "Variable",
	"relative_path": "test\\serena\\test_serena_agent.py",
	"body_location": {
		"start_line": 125,
		"end_line": 125
	},
	"content_around_reference": "... 124:    )\n  > 125:    def test_find_symbol(self, serena_agent: SerenaAgent, symbol_name: str, expected_kind: str, expected_file: str):\n... 126:        agent = serena_agent"
},
{
	"name_path": "TestSerenaAgent/test_find_symbol_references/serena_agent",
	"kind": "Variable",
	"relative_path": "test\\serena\\test_serena_agent.py",
	"body_location": {
		"start_line": 194,
		"end_line": 194
	},
	"content_around_reference": "... 193:    )\n  > 194:    def test_find_symbol_references(self, serena_agent: SerenaAgent, symbol_name: str, def_file: str, ref_file: str) -> None:\n... 195:        agent = serena_agent"
},
{
	"name_path": "TestSerenaAgent/test_find_symbol_overloaded_function/serena_agent",
	"kind": "Variable",
	"relative_path": "test\\serena\\test_serena_agent.py",
	"body_location": {
		"start_line": 359,
		"end_line": 359
	},
	"content_around_reference": "... 358:    )\n  > 359:    def test_find_symbol_overloaded_function(self, serena_agent: SerenaAgent, name_path: str, num_expected: int):\n... 360:        \"\"\""
},
{
	"name_path": "TestSerenaAgent/test_non_unique_symbol_reference_error/serena_agent",
	"kind": "Variable",
	"relative_path": "test\\serena\\test_serena_agent.py",
	"body_location": {
		"start_line": 391,
		"end_line": 391
	},
	"content_around_reference": "... 390:    )\n  > 391:    def test_non_unique_symbol_reference_error(self, serena_agent: SerenaAgent, name_path: str, relative_path: str):\n... 392:        \"\"\""
},
{
	"name_path": "TestSerenaAgent/test_replace_content_regex_with_wildcard_ok/serena_agent",
	"kind": "Variable",
	"relative_path": "test\\serena\\test_serena_agent.py",
	"body_location": {
		"start_line": 416,
		"end_line": 416
	},
	"content_around_reference": "... 415:    )\n  > 416:    def test_replace_content_regex_with_wildcard_ok(self, serena_agent: SerenaAgent):\n... 417:        \"\"\""
},
{
	"name_path": "TestSerenaAgent/test_replace_content_with_backslashes/serena_agent",
	"kind": "Variable",
	"relative_path": "test\\serena\\test_serena_agent.py",
	"body_location": {
		"start_line": 442,
		"end_line": 442
	},
	"content_around_reference": "... 441:    @pytest.mark.parametrize(\"mode\", [\"literal\", \"regex\"])\n  > 442:    def test_replace_content_with_backslashes(self, serena_agent: SerenaAgent, mode: Literal[\"literal\", \"regex\"]):\n... 443:        \"\"\""
},
{
	"name_path": "TestSerenaAgent/test_replace_content_regex_with_wildcard_ambiguous/serena_agent",
	"kind": "Variable",
	"relative_path": "test\\serena\\test_serena_agent.py",
	"body_location": {
		"start_line": 473,
		"end_line": 473
	},
	"content_around_reference": "... 472:    )\n  > 473:    def test_replace_content_regex_with_wildcard_ambiguous(self, serena_agent: SerenaAgent):\n... 474:        \"\"\""
},
{
	"name_path": "test_set_modes",
	"kind": "File",
	"relative_path": "test\\serena\\test_set_modes.py",
	"body_location": {
		"start_line": 0,
		"end_line": 76
	},
	"content_around_reference": "...   3:\n  >   4:from serena.agent import SerenaAgent\n...   5:from serena.config.serena_config import ModeSelectionDefinition, SerenaConfig"
},
{
	"name_path": "TestSetModes/_create_agent",
	"kind": "Method",
	"relative_path": "test\\serena\\test_set_modes.py",
	"body_location": {
		"start_line": 11,
		"end_line": 13
	},
	"content_around_reference": "...  10:\n  >  11:    def _create_agent(self, modes: ModeSelectionDefinition | None = None) -> SerenaAgent:\n...  12:        config = SerenaConfig(gui_log_window=False, web_dashboard=False, log_level=logging.ERROR)"
},
{
	"name_path": "TestSetModes/_create_agent",
	"kind": "Method",
	"relative_path": "test\\serena\\test_set_modes.py",
	"body_location": {
		"start_line": 11,
		"end_line": 13
	},
	"content_around_reference": "...  12:        config = SerenaConfig(gui_log_window=False, web_dashboard=False, log_level=logging.ERROR)\n  >  13:        return SerenaAgent(serena_config=config, modes=modes)\n...  14:"
},
{
	"name_path": "TestSetModes/test_set_modes_overrides_config_defaults/agent",
	"kind": "Variable",
	"relative_path": "test\\serena\\test_set_modes.py",
	"body_location": {
		"start_line": 35,
		"end_line": 35
	},
	"content_around_reference": "...  34:        config.default_modes = [\"editing\", \"interactive\"]\n  >  35:        agent = SerenaAgent(serena_config=config)\n...  36:"
}]
LS after
{
	"scripts\\demo_run_tools.py": {
		"File": [{
			"name_path": "demo_run_tools",
			"body_location": {
				"start_line": 0,
				"end_line": 37
			},
			"content_around_reference": "...   7:\n  >   8:from serena.agent import SerenaAgent\n...   9:from serena.config.serena_config import SerenaConfig"
		}],
		"Variable": [{
			"name_path": "agent",
			"body_location": {
				"start_line": 22,
				"end_line": 22
			},
			"content_around_reference": "...  21:    serena_config.web_dashboard = False\n  >  22:    agent = SerenaAgent(project=REPO_ROOT, serena_config=serena_config)\n...  23:"
		}]
	},
	"scripts\\profile_tool_call.py": {
		"File": [{
			"name_path": "profile_tool_call",
			"body_location": {
				"start_line": 0,
				"end_line": 53
			},
			"content_around_reference": "...   7:\n  >   8:from serena.agent import SerenaAgent\n...   9:from serena.config.serena_config import SerenaConfig"
		}],
		"Variable": [{
			"name_path": "agent",
			"body_location": {
				"start_line": 30,
				"end_line": 30
			},
			"content_around_reference": "...  29:\n  >  30:    agent = SerenaAgent(str(project_path), serena_config=serena_config)\n...  31:"
		}]
	},
	"src\\serena\\agno.py": {
		"File": [{
			"name_path": "agno",
			"body_location": {
				"start_line": 0,
				"end_line": 144
			},
			"content_around_reference": "...  15:\n  >  16:from serena.agent import SerenaAgent, Tool\n...  17:from serena.config.context_mode import SerenaAgentContext"
		}],
		"Method": [{
			"name_path": "SerenaAgnoToolkit/__init__",
			"body_location": {
				"start_line": 25,
				"end_line": 29
			},
			"content_around_reference": "...  24:class SerenaAgnoToolkit(Toolkit):\n  >  25:    def __init__(self, serena_agent: SerenaAgent):\n...  26:        super().__init__(\"Serena\")"
		}],
		"Variable": [{
			"name_path": "SerenaAgnoAgentProvider/get_agent/serena_agent",
			"body_location": {
				"start_line": 107,
				"end_line": 107
			},
			"content_around_reference": "... 106:                try:\n  > 107:                    serena_agent = SerenaAgent(project_file, context=SerenaAgentContext.load(\"agent\"))\n... 108:                except Exception as e:"
		}]
	},
	"src\\serena\\cli.py": {
		"File": [{
			"name_path": "cli",
			"body_location": {
				"start_line": 0,
				"end_line": 1055
			},
			"content_around_reference": "...  17:\n  >  18:from serena.agent import SerenaAgent\n...  19:from serena.config.context_mode import SerenaAgentContext, SerenaAgentMode"
		}],
		"Variable": [{
			"name_path": "TopLevelCommands/print_system_prompt/agent",
			"body_location": {
				"start_line": 338,
				"end_line": 338
			},
			"content_around_reference": "... 337:            modes_selection_def = ModeSelectionDefinition(default_modes=modes)\n  > 338:        agent = SerenaAgent(\n... 339:            project=os.path.abspath(project),"
		},
		{
			"name_path": "ProjectCommands/health_check/agent",
			"body_location": {
				"start_line": 768,
				"end_line": 768
			},
			"content_around_reference": "... 767:                serena_config.web_dashboard = False\n  > 768:                agent = SerenaAgent(project=project_path, serena_config=serena_config)\n... 769:                log.info(\"SerenaAgent created successfully\")"
		},
		{
			"name_path": "ToolCommands/description/agent",
			"body_location": {
				"start_line": 929,
				"end_line": 929
			},
			"content_around_reference": "... 928:\n  > 929:        agent = SerenaAgent(\n... 930:            project=None,"
		}]
	},
	"src\\serena\\code_editor.py": {
		"File": [{
			"name_path": "code_editor",
			"body_location": {
				"start_line": 0,
				"end_line": 440
			},
			"content_around_reference": "...  17:if TYPE_CHECKING:\n  >  18:    from .agent import SerenaAgent\n...  19:"
		}],
		"Method": [{
			"name_path": "CodeEditor/__init__",
			"body_location": {
				"start_line": 26,
				"end_line": 36
			},
			"content_around_reference": "...  25:class CodeEditor(Generic[TSymbol], ABC):\n  >  26:    def __init__(self, project_root: str, agent: Optional[\"SerenaAgent\"] = None) -> None:\n...  27:        self.project_root = project_root"
		},
		{
			"name_path": "LanguageServerCodeEditor/__init__",
			"body_location": {
				"start_line": 246,
				"end_line": 248
			},
			"content_around_reference": "... 245:class LanguageServerCodeEditor(CodeEditor[LanguageServerSymbol]):\n  > 246:    def __init__(self, symbol_retriever: LanguageServerSymbolRetriever, agent: Optional[\"SerenaAgent\"] = None):\n... 247:        super().__init__(project_root=symbol_retriever.get_root_path(), agent=agent)"
		},
		{
			"name_path": "JetBrainsCodeEditor/__init__",
			"body_location": {
				"start_line": 381,
				"end_line": 383
			},
			"content_around_reference": "... 380:class JetBrainsCodeEditor(CodeEditor[JetBrainsSymbol]):\n  > 381:    def __init__(self, project: Project, agent: Optional[\"SerenaAgent\"] = None) -> None:\n... 382:        self._project = project"
		}]
	},
	"src\\serena\\dashboard.py": {
		"File": [{
			"name_path": "dashboard",
			"body_location": {
				"start_line": 0,
				"end_line": 641
			},
			"content_around_reference": "...  17:if TYPE_CHECKING:\n  >  18:    from serena.agent import SerenaAgent\n...  19:"
		}],
		"Variable": [{
			"name_path": "SerenaDashboardAPI/__init__/agent",
			"body_location": {
				"start_line": 128,
				"end_line": 128
			},
			"content_around_reference": "... 127:        tool_names: list[str],\n  > 128:        agent: \"SerenaAgent\",\n... 129:        shutdown_callback: Callable[[], None] | None = None,"
		}]
	},
	"src\\serena\\mcp.py": {
		"File": [{
			"name_path": "mcp",
			"body_location": {
				"start_line": 0,
				"end_line": 350
			},
			"content_around_reference": "...  19:from serena.agent import (\n  >  20:    SerenaAgent,\n...  21:    SerenaConfig,"
		}],
		"Variable": [{
			"name_path": "SerenaMCPRequestContext/agent",
			"body_location": {
				"start_line": 46,
				"end_line": 46
			},
			"content_around_reference": "...  45:class SerenaMCPRequestContext:\n  >  46:    agent: SerenaAgent\n...  47:"
		},
		{
			"name_path": "SerenaMCPFactory/agent",
			"body_location": {
				"start_line": 64,
				"end_line": 64
			},
			"content_around_reference": "...  63:        self.project = project\n  >  64:        self.agent: SerenaAgent | None = None\n...  65:        self.memory_log_handler = memory_log_handler"
		}],
		"Method": [{
			"name_path": "SerenaMCPFactory/_create_serena_agent",
			"body_location": {
				"start_line": 262,
				"end_line": 265
			},
			"content_around_reference": "... 261:\n  > 262:    def _create_serena_agent(self, serena_config: SerenaConfig, modes: ModeSelectionDefinition | None = None) -> SerenaAgent:\n... 263:        return SerenaAgent("
		},
		{
			"name_path": "SerenaMCPFactory/_create_serena_agent",
			"body_location": {
				"start_line": 262,
				"end_line": 265
			},
			"content_around_reference": "... 262:    def _create_serena_agent(self, serena_config: SerenaConfig, modes: ModeSelectionDefinition | None = None) -> SerenaAgent:\n  > 263:        return SerenaAgent(\n... 264:            project=self.project, serena_config=serena_config, context=self.context, modes=modes, memory_log_handler=self.memory_log_handler"
		}]
	},
	"src\\serena\\symbol.py": {
		"File": [{
			"name_path": "symbol",
			"body_location": {
				"start_line": 0,
				"end_line": 915
			},
			"content_around_reference": "...  18:if TYPE_CHECKING:\n  >  19:    from .agent import SerenaAgent\n...  20:"
		}],
		"Method": [{
			"name_path": "LanguageServerSymbolRetriever/__init__",
			"body_location": {
				"start_line": 533,
				"end_line": 545
			},
			"content_around_reference": "... 532:class LanguageServerSymbolRetriever:\n  > 533:    def __init__(self, ls: SolidLanguageServer | LanguageServerManager, agent: Union[\"SerenaAgent\", None] = None) -> None:\n... 534:        \"\"\""
		}]
	},
	"src\\serena\\tools\\tools_base.py": {
		"File": [{
			"name_path": "tools_base",
			"body_location": {
				"start_line": 0,
				"end_line": 453
			},
			"content_around_reference": "...  20:if TYPE_CHECKING:\n  >  21:    from serena.agent import SerenaAgent\n...  22:    from serena.code_editor import CodeEditor"
		}],
		"Method": [{
			"name_path": "Component/__init__",
			"body_location": {
				"start_line": 31,
				"end_line": 32
			},
			"content_around_reference": "...  30:class Component(ABC):\n  >  31:    def __init__(self, agent: \"SerenaAgent\"):\n...  32:        self.agent = agent"
		}]
	},
	"test\\serena\\test_serena_agent.py": {
		"File": [{
			"name_path": "test_serena_agent",
			"body_location": {
				"start_line": 0,
				"end_line": 487
			},
			"content_around_reference": "...  10:\n  >  11:from serena.agent import SerenaAgent\n...  12:from serena.config.serena_config import ProjectConfig, RegisteredProject, SerenaConfig"
		}],
		"Variable": [{
			"name_path": "project_file_modification_context/serena_agent",
			"body_location": {
				"start_line": 71,
				"end_line": 71
			},
			"content_around_reference": "...  70:@contextmanager\n  >  71:def project_file_modification_context(serena_agent: SerenaAgent, relative_path: str) -> Iterator[None]:\n...  72:    \"\"\"Context manager to modify a project file and revert the changes after use.\"\"\""
		},
		{
			"name_path": "serena_agent/request",
			"body_location": {
				"start_line": 88,
				"end_line": 88
			},
			"content_around_reference": "...  87:@pytest.fixture\n  >  88:def serena_agent(request: pytest.FixtureRequest, serena_config) -> Iterator[SerenaAgent]:\n...  89:    language = Language(request.param)"
		},
		{
			"name_path": "serena_agent/agent",
			"body_location": {
				"start_line": 95,
				"end_line": 95
			},
			"content_around_reference": "...  94:\n  >  95:    agent = SerenaAgent(project=project_name, serena_config=serena_config)\n...  96:"
		},
		{
			"name_path": "TestSerenaAgent/test_find_symbol/serena_agent",
			"body_location": {
				"start_line": 125,
				"end_line": 125
			},
			"content_around_reference": "... 124:    )\n  > 125:    def test_find_symbol(self, serena_agent: SerenaAgent, symbol_name: str, expected_kind: str, expected_file: str):\n... 126:        agent = serena_agent"
		},
		{
			"name_path": "TestSerenaAgent/test_find_symbol_references/serena_agent",
			"body_location": {
				"start_line": 194,
				"end_line": 194
			},
			"content_around_reference": "... 193:    )\n  > 194:    def test_find_symbol_references(self, serena_agent: SerenaAgent, symbol_name: str, def_file: str, ref_file: str) -> None:\n... 195:        agent = serena_agent"
		},
		{
			"name_path": "TestSerenaAgent/test_find_symbol_overloaded_function/serena_agent",
			"body_location": {
				"start_line": 359,
				"end_line": 359
			},
			"content_around_reference": "... 358:    )\n  > 359:    def test_find_symbol_overloaded_function(self, serena_agent: SerenaAgent, name_path: str, num_expected: int):\n... 360:        \"\"\""
		},
		{
			"name_path": "TestSerenaAgent/test_non_unique_symbol_reference_error/serena_agent",
			"body_location": {
				"start_line": 391,
				"end_line": 391
			},
			"content_around_reference": "... 390:    )\n  > 391:    def test_non_unique_symbol_reference_error(self, serena_agent: SerenaAgent, name_path: str, relative_path: str):\n... 392:        \"\"\""
		},
		{
			"name_path": "TestSerenaAgent/test_replace_content_regex_with_wildcard_ok/serena_agent",
			"body_location": {
				"start_line": 416,
				"end_line": 416
			},
			"content_around_reference": "... 415:    )\n  > 416:    def test_replace_content_regex_with_wildcard_ok(self, serena_agent: SerenaAgent):\n... 417:        \"\"\""
		},
		{
			"name_path": "TestSerenaAgent/test_replace_content_with_backslashes/serena_agent",
			"body_location": {
				"start_line": 442,
				"end_line": 442
			},
			"content_around_reference": "... 441:    @pytest.mark.parametrize(\"mode\", [\"literal\", \"regex\"])\n  > 442:    def test_replace_content_with_backslashes(self, serena_agent: SerenaAgent, mode: Literal[\"literal\", \"regex\"]):\n... 443:        \"\"\""
		},
		{
			"name_path": "TestSerenaAgent/test_replace_content_regex_with_wildcard_ambiguous/serena_agent",
			"body_location": {
				"start_line": 473,
				"end_line": 473
			},
			"content_around_reference": "... 472:    )\n  > 473:    def test_replace_content_regex_with_wildcard_ambiguous(self, serena_agent: SerenaAgent):\n... 474:        \"\"\""
		}]
	},
	"test\\serena\\test_set_modes.py": {
		"File": [{
			"name_path": "test_set_modes",
			"body_location": {
				"start_line": 0,
				"end_line": 76
			},
			"content_around_reference": "...   3:\n  >   4:from serena.agent import SerenaAgent\n...   5:from serena.config.serena_config import ModeSelectionDefinition, SerenaConfig"
		}],
		"Method": [{
			"name_path": "TestSetModes/_create_agent",
			"body_location": {
				"start_line": 11,
				"end_line": 13
			},
			"content_around_reference": "...  10:\n  >  11:    def _create_agent(self, modes: ModeSelectionDefinition | None = None) -> SerenaAgent:\n...  12:        config = SerenaConfig(gui_log_window=False, web_dashboard=False, log_level=logging.ERROR)"
		},
		{
			"name_path": "TestSetModes/_create_agent",
			"body_location": {
				"start_line": 11,
				"end_line": 13
			},
			"content_around_reference": "...  12:        config = SerenaConfig(gui_log_window=False, web_dashboard=False, log_level=logging.ERROR)\n  >  13:        return SerenaAgent(serena_config=config, modes=modes)\n...  14:"
		}],
		"Variable": [{
			"name_path": "TestSetModes/test_set_modes_overrides_config_defaults/agent",
			"body_location": {
				"start_line": 35,
				"end_line": 35
			},
			"content_around_reference": "...  34:        config.default_modes = [\"editing\", \"interactive\"]\n  >  35:        agent = SerenaAgent(serena_config=config)\n...  36:"
		}]
	}
}

The results for the other tools are the same as before (except for the bugfix that children previously weren't converted).

MischaPanch
MischaPanch previously approved these changes Feb 13, 2026
@opcode81 opcode81 force-pushed the generic-symbol-output-grouping branch from 87ee48e to 499abf1 Compare February 13, 2026 23:48
@opcode81 opcode81 force-pushed the generic-symbol-output-grouping branch from 499abf1 to 2139552 Compare February 13, 2026 23:48
@opcode81 opcode81 marked this pull request as ready for review February 13, 2026 23:49
@opcode81
Copy link
Contributor Author

Tests that still assume the old output structure need to be adjusted.

@opcode81
Copy link
Contributor Author

opcode81 commented Feb 14, 2026

@MischaPanch, feel free to have a look at the tests if you want. I probably won't have time today.

@MischaPanch
Copy link
Contributor

Ok, I'll then finalize and merge this

@opcode81 opcode81 dismissed MischaPanch’s stale review February 14, 2026 18:51

Tests not passing

@opcode81
Copy link
Contributor Author

I adapted the tests.

@opcode81 opcode81 force-pushed the generic-symbol-output-grouping branch from 4761d42 to 070b0ae Compare February 14, 2026 22:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants