mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-11 03:13:51 +00:00
10 KiB
10 KiB
REPL (serena.repl)
Alternative interaction paradigm: one tool (serena_repl) executes Python code against entrypoint s,
whose attributes are facades (s.lsp, s.edit, s.fs, s.mem, s.shell, s.jb, s.cfg).
Code runs like a notebook cell (module-level exec in the session namespace); the value of a trailing
expression is the result. No return (a top-level return yields a SyntaxError with a hint).
IMPORTANT: The REPL interface is a BETA feature. If you encounter any issues, please report them but do not submit PRs for it (except for trivial fixes); the implementation is still evolving.
Structure
repl/api/*_api.py:FacadeApiimplementations = the single implementation of each operation. The classic tools are thin adapters delegating to the APIs (via*ApiMixin); tools retain only transport concerns (input sanitisation, diagnostics context, tool-level output shaping).repl/facade.py:Facade= indirection over an API instance;FacadeMethod(enabled flag +FacadeMethodInfo);ApiScope= which facades/methods are enabled.repl/repl.py:SerenaRepl(execution, error formatting),SerenaReplEntrypoint(s,info).repl/representable.py:Representable/Renderer; result objects carry their rendering policy.
Design principles
- Exposure is explicit: a method is exposed iff decorated with
@facade_method(...), which carriesoptional,beta,can_edit,corresponding_tool(mirroring the tool markers; the tool correspondence is recorded for optional derivation of exclusions and prompt conditions, never applied automatically). - Naming: on result objects and non-exposed API helpers, a trailing underscore (
symbols_,to_dict_) marks members that are Serena-public but not LLM-facing. - Facades group by domain, not by read vs. write; mutation is expressed via
can_edit(read-only projects exclude editing methods). Boundaryfs/edit: files as units vs. modifying content within existing files. - Facade descriptions describe the domain only; never list operations (the method list is always shown alongside).
- Output parameters (depth, include_body, max_answer_chars, ...) are passed at retrieval time so that the rendering policy is fixed once and inherited by derived results.
- Results expose data to code (
.symbols,.occurrences,.lines, ...) and render like the classic tool output. - Progressive disclosure: a priori only facade names, descriptions and method names;
s.info("<facade>")/s.info("<facade>.<method>")give signature + docstring together, never a signature alone.info(*items)documents several items at once; unknown items are reported inline. - Disclosure tiers: tier 0 (tool description) = facades, descriptions, method names with navigable return types
(
find_symbol -> LspSymbolCollection); tier 1 (s.info("<facade>")) = all common methods in full,nichemethods (@facade_method(niche=True), rarely needed + long docs) only as summary + pointer, result types by name only; tier 2 = types on request. Types are never pushed (provide_info_with_facadeexists but is set nowhere); the tool description tells the model to request type docs only when processing results in code. - Result types: every user-defined class reachable through annotations (method parameters/returns, and the members
of reachable types, transitively) is automatically documentable (
Facade._discover_referenced_types); builtins, typing constructs and stdlib classes are excluded. ExplicitReferencedTypedeclarations (constructor argtypes=) exist for curation only: an optionalmemberswhitelist (foreign/large classes such asLanguageServerSymbol; listed methods are shown even if undocumented, convention-derived ones only if documented) and flags. Enums render with members/values, TypedDicts with their keys. Types are documented vias.info("<facade>.<Type>")or bares.info("<Type>"); method docs point to their referenced types. Result classes declare attribute annotations at class level (attributes set only in__init__are not discoverable). Annotations are rendered without module paths, so signature names equal lookup names. A type's documentation transitively includes the declared types its members reference; within a session (SerenaSession.described_type_names), a contained type is documented once and afterwards only pointed to (explicit requests always yield full documentation).
Sessions (serena.session)
- MCP provides no reliable session identification (newer protocol versions drop it), and clients keep a stdio
server across conversations. Hence the REPL's session identity is LLM-supplied:
create_system_promptcreates aSerenaSessionand states its id;serena_repltakes a requiredsession_id.SessionRegistrycreates unknown ids on demand (benign: at worst docs are repeated) and evicts LRU and idle (TTL) sessions. Sessions survive REPL rebuilds. - Persistence (notebook semantics):
SerenaSession.repl_namespaceis the globals of the session's executions, which run at module level (statements exec'd, a trailing expression eval'd; line numbers preserved), so all top-level bindings persist across calls.sis re-bound in the namespace before every execution, so persisted functions always use the current entrypoint.s.vars()/s.clear()list/remove persisted items. Data is tied to the session's lifetime (not cleared on REPL rebuild); stored facades/project objects may go stale. Deferred idea if memory becomes an issue: hybrid — implicit items expire after N turns, explicit store (e.g.s.d) for indefinite retention. - Only tools whose use presupposes having read the instructions may require the id.
activate_projectandinitial_instructionsmay be called first and keep the existing MCP-context-derived session handling (prompt provision status); migrating that to LLM-supplied ids is a separate, future change. - APIs must not import
serena.toolsat module level except for tool classes in decorators; tools import APIs locally in_api()(API modules refer to tool classes).
Configuration
agent_interface: tools | REPL(AgentInterface; global config, overridable per project; CLI--agent-interface).None= Serena's default (tools). Fixed for the session. In REPL mode the toolset is fixed (serena_repl,initial_instructions,activate_projectunless 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(referencesfacadeorfacade.method) in global config, context, modes, project config; applied in that order viaApiScope(exclusions first, then inclusions; later definitions win).- Opt-in rule: methods of a facade that is not included (excluded, or optional without explicit inclusion) and
optional methods are enabled only if included explicitly; all other methods are enabled unless excluded.
Facades can be optional (
Facade.from_api(..., is_optional=True), e.g.ext, mirroring optional tools);Facade.is_enabled()is derived: a facade is available iff it has at least one enabled method. - The REPL is rebuilt whenever the active tools are updated (mode switch, project activation).
Availability policy
- Keep as much functionality as possible in the REPL; exclude nothing by default.
- Do not derive API exclusions from tool exclusions automatically (not via the
corresponding_toolcorrespondence, not via an option): contexts exclude tools mostly because the host provides equivalents (read_file,find_file,replace_content, shell). In the REPL those reads are what makes operations composable (read → filter → return a summary), and a host tool cannot participate in REPL code. - The host's better-integrated edit tools (diff view, undo) are a matter of guidance in the context prompt, not of availability: exclusions can only steer the model, never enforce anything.
- For users migrating from tool mode: prefer a startup hint listing the
excluded_apisentries corresponding to their own (global/project, not context/mode) tool exclusions over any automatic derivation.
- Do not derive API exclusions from tool exclusions automatically (not via the
- Python code can always modify the system; the REPL tool is inherently fully privileged, regardless of
facade scope or the project's
read_onlysetting (which only makes Serena's own API refuse edits). A "read-only REPL" is not feasible and must not be promised. - External projects (
s.ext):list_projects(),project_context(name)(awith-able context; not nestable). Within it, the agent's active project is temporarily switched (active_project_context) and the facades are read-only (can_editmethods raise). Methods marked@facade_method(uses_project_server=True)(all oflsp) are executed in the project server via/call_facade_method({facade, method, args, kwargs} as JSON, result pickled; the server is a trusted local process) when the LSP backend is active; with JetBrains they run locally (the IDE serves all projects). Result objects must be self-contained/picklable: renderers hold no agent (only the default length limit), LSP results carry eagerly retrieved info and reference contexts, no lambdas in output params. Replaces the query_project/list_queryable_projects tools in the REPL. - Project activation (activate_project) and initial_instructions stay tool-only (activation rebuilds the REPL);
Serena's configuration/session state (config overview, dashboard; later e.g. modes) lives in the
cfgfacade. Computed conditions (read-only project, dashboard not openable) are applied to the API scope inSerenaAgent.get_replviaexclude_editing()/NamedApiInclusionDefinition, mirroring the tool side.