Lanser-CLI: Language Server CLI Empowers Language Agents with Process Rewards 🛠️🏆
Repo: https://github.com/yifanzhang-pro/lanser-cli
Project Page: https://yifanzhang-pro.github.io/lanser-cli
TL;DR (a.k.a. the elevator pitch with bass)
LLMs guess. Language servers check. Lanser-CLI is the official implementation of Language Server CLI Empowers Language Agents with Process Rewards — a CLI-first bridge between your coding agent and an LSP (e.g., Pyright). It turns IDE-grade facts (defs, refs, types, diagnostics, safe edits) into deterministic, replayable artifacts and a process reward that tells your agent it’s actually getting somewhere.
Less “trust me bro,” more “here are the receipts.”
Why this exists (and why your agent will thank you)
- Hallucinated APIs and mislocalized edits are the jump-scares of agent coding.
- Language servers already compute verifiable answers.
- Agents need those answers in a form that’s deterministic, auditable, and safe to apply—and they need shaped feedback (XP!) on every step, not just the final diff.
Lanser-CLI is the orchestration layer that:
- Pins and speaks LSP like a pro
- Emits byte-stable, hash-addressed Analysis Bundles
- Provides robust selectors that survive edits
- Wraps edits in a safety envelope
- Computes a process reward so your agent stops flailing and starts leveling up
Meme-level overview
Language Agent: “I renamed it.”
Language Server: “No you didn’t.”
Lanser-CLI: “Here’s the diff, the diagnostics delta, the safety check, and the hash. You’re welcome.”
The features you actually want
1) Robust Selectors (goodbye file:line:col fragility)
Address code by intent, not just coordinates:
- Symbolic:
py://pkg.mod#Class.method:body - AST path:
ast://[module=pkg.mod]/[class=Class]/[def=method] - Content anchor:
anchor://src/app.py#"def load_data("?ctx=24
They relocate deterministically as code changes. No more off-by-one chaos.
2) Analysis Bundles (the auditable artifact)
Every command emits a JSON bundle that includes:
- Resolved target + facts (defs/refs/hover/diags)
- Environment capture (server version, position encoding, interpreter, config digest)
- Stable
bundleId(hash over canonicalized JSON) - Optional trace for offline replay and CI audits
3) Safety Envelope (because prod still matters)
- Preview-first for mutations (rename/code actions)
- Workspace jail prevents writes outside project root
- Git-aware: clean tree required unless overridden
- Transactional apply (atomic writes) + conflict reporting
4) Process Rewards (XP for agents)
When your agent takes a step, it gets a score shaped by:
- Diagnostics going down
- Safety checks passing
- Selector ambiguity shrinking
That’s feedback your planner can actually use—online and replayable offline.
60-second tour (copy/paste these)
# 0) Clone & explore
git clone https://github.com/yifanzhang-pro/lanser-cli
uv venv --python 3.12 .venv && source .venv/bin/activate
uv pip install -e ".[dev]"
lanser --help
# Inspect the environment and verify the orchestrator is healthy
lanser doctor
# Resolve a symbol definition via the selector DSL
lanser def py://lanser.cli#definition:def --json
# Run document diagnostics with deterministic bundles
lanser diag src/lanser/cli.py@L1:C1 --json
# Preview a rename before applying workspace edits
lanser rename py://lanser.cli#definition:def new_definition --json
Pro tip: Everything above emits deterministic bundles you can stash, diff, replay, and score.
What “deterministic” actually means (plain-English edition)
When you freeze the workspace + server version + config, the same query yields the same bundle with the same hash. Lists are sorted deterministically; JSON is canonicalized before hashing. That’s why CI can trust it, and why offline replay works without re-running the server.
Under the hood (the short nerd bit)
- LSP lifecycle orchestration: start/stop, capability negotiation, restarts with backoff
- Single-flight cache: dedup identical in-flight queries
- Position encoding sanity: negotiates UTF-16 per LSP, handles UTF-8/“codepoint” IO correctly
- Trace & replay: optional JSON-RPC frame capture; regenerate byte-stable bundles offline
- Currently instantiated with Pyright for Python; design is LSP-agnostic
Who this is for
- Agent developers who want planning on facts (not “hopefully correct” heuristics)
- Infra/CI owners who want reproducible static checks and safe automated edits
- Refactor wranglers doing large-scale code moves with auditability
A tiny before/after to vibe-check the reward
Before: Agent renames function, diagnostics unchanged, selector ambiguous → low/negative reward.
After: Diagnostics drop, prepareRename passes, ambiguity shrinks → positive reward.
The signal is simple on purpose: it’s shaping, not a research paper in disguise. Your trainer/evaluator will thank you.
FAQ (fast and slightly feral)
Is this an IDE? No. This is a CLI-first orchestrator that makes language-server power usable by agents and CI.
Will it break my repo? Mutations are preview-by-default, jailed, git-aware, and transactional. You’d have to try to make it spicy.
Does it only do Python? Today’s demo rides with Pyright. The design is LSP-agnostic. Bring your favorite server via PRs.
Do I need to publish anything to use process rewards? Nope. Rewards are computed from bundles you already produced. They’re replayable offline for evals.
Roadmap-ish (community-flavored)
- More LSP servers + language flavors
- Richer selectors and ambiguity evidence
- Expanded batch SDK for high-throughput agent farms
- First-class eval harness using replayed bundles
Want a thing? Open an issue. Even better: open a PR.
Try it on your codebase today
- Clone the repo
- Install Lanser-CLI
- Point Lanser-CLI at a project
- Run
lanser def,lanser refs,lanser diag - Preview a
renameand watch the bundle + reward kick in
👉 Repo: https://github.com/yifanzhang-pro/lanser-cli
⭐ Star it so your agent gets gains.
🙌 Contribute memes, ideas, and code.
Final punchline: Tool-use shouldn’t be theatrical. With Lanser-CLI, language servers become a ground truth API, edits come with guardrails, and agents get a scoreboard for every step they take. Ship confidently; leave the guesswork to the ghosts.