Skip to content

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