Semlith

Serena vs Semlith

Serena gives agents IDE-style symbol tools over language servers. Semlith finds code from a question. What each does, run on Flask and SWE-bench Lite.

Checked on 29 October 2026 with Serena 2.1.0 and semlith 0.50.0, on Flask 3.1.3 on an M1 MacBook Air.

What Serena is

Serena calls itself “The IDE for Your Coding Agent” (Serena on GitHub). It gives an agent symbol-level retrieval, editing and refactoring through language servers, with support for over 40 languages, or through a paid JetBrains plugin. Since version 2 it reaches the agent as a REPL, so the agent writes a little Python against Serena’s tools instead of calling one MCP tool per action (Serena’s tools).

Where Serena is stronger

Serena edits code. It can replace a function’s body, insert code after a symbol and rename across files, all by symbol rather than by line number. Semlith doesn’t do any of that. Its MCP tools only read.

Serena’s references also come from a real language server. Give it a name and it goes straight to it.

uv venv -p 3.11 venv-serena
uv pip install -p venv-serena "serena-agent @ git+https://github.com/oraios/serena@v2.1.0"
cd flask && serena project index .
Output
Generated project with language servers {python} at flask/.serena/project.yml.
Indexing symbols in Project[root='flask', name=flask] …
Indexed files per language: python=83

Indexing took 30.9 seconds. Then, in Serena’s REPL over MCP:

Serena REPL
s.lsp.find_symbol("max_content_length")
Output
[{"name_path": "Request/max_content_length", "kind": "Method", "relative_path": "src/flask/wrappers.py", "body_location": {"start_line": 87, "end_line": 89}}]
Serena REPL
s.lsp.find_referencing_symbols("Request/max_content_length", "src/flask/wrappers.py")
Output
{"src/flask/wrappers.py": {"Class": [{"name_path": "Request", "body_location": {"start_line": 17, "end_line": 218}, "content_around_reference": "...  58:    @property\n  >  59:    def max_content_length(self) -> int | None:\n...  60:        \"\"\"The maximum number of bytes that will be read during this request. If"}], "Method": [{"name_path": "Request/max_content_length", "body_location": {"start_line": 87, "end_line": 89}, "content_around_reference": "...  86:\n  >  87:    @max_content_length.setter\n...  88:    def max_content_length(self, value: int | None) -> None:"}]}}

That is the property and its setter, from the language server, in well under a second for the symbol and about 2 seconds for the references. Serena’s line numbers count from zero.

Where it needs a name

Serena has no search by meaning. Without the name, an agent falls back on Serena’s pattern search, which is a regex over the files.

Serena REPL
s.fs.search_for_pattern("upload|big", relative_path="src")
Output
{"src/flask/cli.py": ["  > 296:    a bigger role.  Typically it's created automatically by the"], "src/flask/helpers.py": ["  > 541:        @app.route(\"/uploads/<path:name>\")", "  > 548:    files or uploads. Uses :func:`~werkzeug.security.safe_join` to"]}

These are the same lines grep finds, and none of them is the answer. Semlith takes the question as it is.

semlith index .
semlith search -k 5 "limit how big an uploaded request body can be"
Output
1. 0.087 vf  src/flask/wrappers.py:59-71
2. 0.083 vf  src/flask/wrappers.py:91-106
3. 0.077 v   docs/web-security.rst:26-38
4. 0.076 vf  docs/patterns/fileuploads.rst:135-153
5. 0.072 vf  src/flask/wrappers.py:105-123
5 hits in 360.027667ms

The first hit is the property Serena found once it had the name. Semlith’s index of the same checkout took 84.3 seconds, because it embeds every chunk of code and docs, so Serena was quicker to set up here.

Measured on SWE-bench Lite

The scorecard’s tool comparison gave every tool the same checkout and issue text on 20 issues from SWE-bench Lite. Since Serena has no search by meaning, its adapter looked up each name in the issue with find_symbol and then ran a pattern search over the same names.

Serena 1.7.0 completed 16 of 20, and 4 timed out in search. It had the right file in its top 5 for 25.0% of those 16, and Semlith for 56.2% of the same issues. That run used Serena 1.7.0, before the REPL. We haven’t run 2.1.0 through it yet. The benchmarks page has the full table, and the scorecard has the adapter and the command.

What each server sends an agent up front

Every MCP server sends its tool definitions to the agent when a session starts, and the agent pays for them on every turn. We counted the tools/list result of each server, at about four bytes to a token.

python3 toolslist.py serena start-mcp-server --project . --transport stdio --context claude-code
python3 toolslist.py semlith mcp
Output
server: {'name': 'Serena', 'version': '2.1.0', 'websiteUrl': 'https://oraios.github.io/serena'}
tools: 2
tools/list result: 4544 bytes, about 1136 tokens
names: initial_instructions, serena_repl

server: {'name': 'semlith', 'version': '0.50.0'}
tools: 8
tools/list result: 2668 bytes, about 667 tokens
names: semlith_search, semlith_brief, semlith_read, semlith_stats, semlith_files, semlith_symbol, semlith_neighbors, semlith_impact

Both are small. Serena’s REPL keeps the list short by describing its tools in one place, and an agent then reads its 9,292-character instructions when it calls initial_instructions. toolslist.py is a short script of ours that starts the server, sends initialize and tools/list over stdio and counts the bytes of the result.

Use both

They do different jobs. Serena is for changing code by symbol with a language server behind it. Semlith is for finding where to look from a question, across many repositories and documents in one local index. Both are MCP servers, so an agent can have both. Set up your agent client shows how to add Semlith.