How Kast works
You don't need to read this to use kast
This is a deep dive: modules, request flow, daemon lifecycle, why decisions came out the way they did. It exists for contributors and for anyone hitting a behavior they want to understand from first principles. If you're trying to do something, start with the Quickstart or jump to Recipes.
This page explains the architecture: what each module owns, how a
request flows from your terminal to the K2 engine and back, and why
the system is shaped this way. It also covers how kast runs
either on its own or piggybacks on a warm IDEA session, exposing
the same protocol surface either way.
High-level architecture
kast is a client-daemon design with a shared contract and two
runtime modes. The CLI is a thin control plane. The backend keeps
Kotlin semantic state warm across requests.
flowchart LR
subgraph "Client plane"
CLI["kast CLI<br/>command parsing + lifecycle"]
end
subgraph "Contract and transport"
API["analysis-api<br/>shared models + capabilities"]
SERVER["analysis-server<br/>JSON-RPC dispatch"]
end
subgraph "Runtime plane"
HEADLESS["backend-headless<br/>headless K2 session"]
IDEA["backend-idea<br/>IDE-hosted session"]
SHARED["backend-shared<br/>shared analysis utilities"]
end
CLI --> API
CLI --> SERVER
SERVER --> HEADLESS
SERVER --> IDEA
HEADLESS --> SHARED
IDEA --> SHARED
The core decision: isolate semantic runtime cost in long-lived backends so repeat queries reuse session state instead of rebuilding compiler context on every command.
One protocol, two runtime modes
The JSON-RPC contract stays stable. The runtime that holds
semantic state is the part that swaps. All backends expose the
same method surface, the same capability reporting, the same result
shapes. The practical difference: where the warm Kotlin state
lives. kast lsp --stdio is another client adapter over that
contract: it maps core LSP features to raw/* methods and exposes
experimental kast/* methods for the operator-level symbol/*,
database/*, and system methods.
| Runtime mode | Where semantic state lives | Who keeps it warm | Best fit |
|---|---|---|---|
| IDEA plugin | An already-open IDEA or Android Studio project, reusing the IDE's project model, PSI, and indexes | IDE project lifecycle | Local developer-machine tools |
| Headless | A packaged IDEA backend outside any open IDE | kast workspace lifecycle |
CI, hosted Linux agents, server images |
If IDEA or Android Studio is warm, external tools connect to the plugin backend and inherit that state for free. If a Linux CI runner, hosted agent, or server image needs its own runtime, the headless bundle exposes the same surface independently.
Module ownership
Module ownership table (for contributors)
Each module has a clear boundary. Changes belong in the narrowest module that owns the behavior.
| Module | Owns | Why it exists |
|---|---|---|
analysis-api |
Shared contract, serializable models, capability flags, edit validation | Keeps protocol semantics stable across all consumers |
analysis-server |
JSON-RPC transport, dispatch, descriptor lifecycle | Isolates transport concerns from semantic logic |
backend-headless |
Headless runtime, workspace discovery, K2 session bootstrap | Concentrates stateful analysis in one runtime |
backend-idea |
IDE-hosted runtime, plugin lifecycle, project service | Reuses the IDE project model when IDEA or Android Studio is running |
backend-shared |
Shared analysis helpers for both runtimes | Avoids duplicate semantic utility code |
analysis-api test fixtures |
Contract fixtures and fake backend infrastructure | Pins behavior consistency across implementations without a separate production module |
build-logic |
Gradle conventions, wrapper generation, runtime-lib sync | Keeps build and packaging rules centralized |
End-to-end request flow
This sequence walks one command from terminal to engine and back.
sequenceDiagram
participant User as "You / Agent"
participant CLI as "kast CLI"
participant Server as "analysis-server"
participant Backend as "Backend runtime"
participant K2 as "K2 session"
User->>CLI: kast rpc raw/resolve
CLI->>Server: JSON-RPC request over Unix socket
Server->>Backend: Dispatch typed backend call
Backend->>K2: Resolve symbol in workspace session
K2-->>Backend: Semantic result
Backend-->>Server: Typed response
Server-->>CLI: JSON-RPC response
CLI-->>User: Structured JSON on stdout
Every step returns structured data. No string scraping. No regex parsing. Anywhere.
In headless mode, "Backend runtime" is the headless daemon and its own analysis session. In IDEA mode, it's the plugin service inside the IDE — same transport, but answering from the IDE's warm project state.
For clients that speak LSP, the CLI keeps the same center of gravity.
Standard LSP requests become raw/* JSON-RPC calls. Custom
kast/* LSP requests pass through to the matching Kast RPC method,
with the initialized workspace root supplied for symbol requests when
the client omits it. Rust-owned source-index methods such as
symbol/query and database/metrics still run in the CLI before any
daemon passthrough.
Why a daemon?
Starting a Kotlin analysis session is the expensive part:
discovering the workspace, resolving classpaths, building compiler
indexes. kast pays that cost once per workspace and keeps the
session warm.
- First command is slower — workspace discovery, session startup, initial indexing all happen up front
- Later commands are fast — the backend reuses loaded state
- One long-lived host owns the analysis context — caches and indexes stay with the workspace until the host stops
In headless mode, that host is the kast daemon. In IDEA mode,
it's the IDE itself. The plugin starts the kast server as part of the IDE
lifecycle, so external tools piggyback on the IDE's already-open project model,
indexes, and analysis session instead of bringing up a second warm session.
Why two runtimes?
Same protocol, two operating environments.
- IDEA plugin favors developer machines — lets external tools tap into an IDE session that's already open, indexed, and ready
- Headless favors server independence — lets the same semantic operations run in CI jobs, hosted Linux agents, and server images; the Ubuntu/Debian bundle carries the required runtime without requiring a desktop IDE process
The split keeps the contract stable for clients while letting the semantic state live wherever the workflow already keeps it.
Design decisions
These choices shape everyday behavior. Knowing them helps you
predict what kast will and won't do.
JSON-RPC contract as the stable center
The wire protocol is explicit and capability-gated. Clients check
capabilities before assuming an operation exists. That keeps the
contract honest and stops backends from advertising work they can't
do.
Bounded traversals
Operations like call-hierarchy are bounded on purpose: depth,
fan-out, total edges, time. Every result carries truncation
metadata so callers can tell "the tree is complete" from "kast
stopped on purpose."
Planned mutation over blind rewrite
Rename and edit application use plan-and-apply with SHA-256 file
hashes. You plan, review, apply. If any file changed in between,
kast rejects the apply with a clear conflict error. Stale plans
can't slip through.
Workspace-scoped analysis
One daemon, one workspace root, one analysis session. All results — references, call hierarchy, diagnostics, edits — are scoped to files inside that session. Code outside the root is invisible.
Workspace discovery
How the daemon finds your project depends on what's there.
flowchart TD
A["up"] --> B{"Gradle wrapper present?"}
B -->|Yes| C["Gradle Tooling API<br/>modules, source roots, classpath"]
B -->|No| D["Conventional fallback<br/>src/main/kotlin, src/test/kotlin"]
C --> E["Build K2 analysis session"]
D --> E
E --> F["Index workspace → READY"]
Gradle projects use the Tooling API for full visibility into multi-module layouts. Everything else falls back to conventional source roots.
Command tiers
Every CLI command is documented in the CLI cheat sheet, which also explains the two-tier organization: a primary path of everyday commands and a set of advanced primitives for expert workflows and agent automation.
Next steps
- Backends — pick between the headless daemon and IDEA plugin
- Limits and boundaries — the rules and
limits behind
kastresults - Kast vs LSP — why
kastexists alongside LSP