# KCL Evaluator vs Runner: Understanding the Functional Difference Between kcl-evaluator and kcl-runner Components

> Understand the core difference between kcl-evaluator and kcl-runner. Learn how kcl-evaluator executes KCL code while kcl-runner handles the CLI and output for seamless execution.

- Repository: [The KCL Programming Language/kcl](https://github.com/kcl-lang/kcl)
- Tags: deep-dive
- Published: 2026-03-05

---

**The `kcl-evaluator` crate provides the core language engine that executes KCL AST and produces in-memory values, while `kcl-runner` serves as the executable wrapper that handles CLI arguments, file I/O, and invokes the evaluator to produce formatted output.**

KCL (Kusion Configuration Language) separates language semantics from execution orchestration through two distinct architectural components in the `kcl-lang/kcl` repository. Understanding the functional difference between `kcl-evaluator` and `kcl-runner` is essential for developers embedding KCL in Rust applications or contributing to the compiler toolchain. While both crates work together to execute KCL programs, they operate at fundamentally different layers of the stack.

## What Is the kcl-evaluator Component?

The **`kcl-evaluator`** crate contains the core evaluation engine responsible for walking the KCL AST, resolving symbols, executing statements, and producing in-memory KCL values (`ValueRef`). Located in [`crates/evaluator/src/lib.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/evaluator/src/lib.rs), this component implements pure language semantics without dealing with command-line handling, file discovery, or output formatting.

The primary type is **`Evaluator<'ctx>`**, which owns the AST, evaluation frames, scopes, lazy evaluation structures, and a `Context` used by the runtime. Key public methods include:

- **`new(program)`** – Instantiates the evaluator with a parsed `ast::Program`
- **`run()`** – Executes the full program evaluation
- **`run_as_function()`** – Evaluates the program as a specific function or schema target
- **`plan_globals_to_string()`** – Converts the final evaluated globals into JSON/YAML string representations

The evaluator’s workflow follows a strict pipeline: it initializes scopes via `init_scope(kcl_ast::MAIN_PKG)`, compiles AST modules using `compile_ast_modules()`, and plans the final value through `plan_globals_to_string()`. This design makes the evaluator ideal for embedding KCL logic directly into other Rust programs or for unit-testing evaluation logic without filesystem dependencies.

## What Is the kcl-runner Component?

The **`kcl-runner`** crate implements the executable front-end (the `kclc`/`kcl` binary) that orchestrates the entire execution process. Unlike the evaluator, this crate focuses on process-level concerns including environment configuration, CLI parsing, and result formatting. The crate is located at `crates/runner/` and notably contains only [`Cargo.toml`](https://github.com/kcl-lang/kcl/blob/main/Cargo.toml) and [`build.rs`](https://github.com/kcl-lang/kcl/blob/main/build.rs), lacking a `src/` directory because it primarily exports a binary target rather than a library API.

The runner’s responsibilities include:

1. **CLI parsing** – Handling flags such as `--output`, `--schema`, and `--stream`
2. **File loading** – Using `kcl-loader` to resolve the import graph and load source files
3. **Context creation** – Instantiating a `kcl_runtime::Context` to hold buffers, options, and result strings
4. **Evaluator invocation** – Calling `Evaluator::new(...).run()` or `run_as_function()` after preparing the runtime context
5. **Output formatting** – Printing or writing JSON/YAML results, handling custom manifest streams, and managing exit codes

The [`build.rs`](https://github.com/kcl-lang/kcl/blob/main/build.rs) script in [`crates/runner/build.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/runner/build.rs) injects environment variables such as `KCL_DEFAULT_TARGET`, ensuring the binary is configured correctly for the target platform. While the runner crate itself is minimal, the actual CLI entry point typically resides in [`crates/cli/src/main.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/cli/src/main.rs), which coordinates file loading and invokes the evaluator logic.

## Key Architectural Differences

| Aspect | kcl-evaluator | kcl-runner |
|--------|---------------|------------|
| **Primary Role** | Language engine executing AST and semantics | Executable wrapper managing I/O and runtime |
| **Public API** | Rust library with `Evaluator<'ctx>` type | Binary target only; no public Rust API |
| **Input** | In-memory `ast::Program` | File paths and CLI arguments |
| **Output** | `ValueRef` and internal strings | Formatted JSON/YAML to stdout or files |
| **Key File** | [`crates/evaluator/src/lib.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/evaluator/src/lib.rs) | [`crates/runner/Cargo.toml`](https://github.com/kcl-lang/kcl/blob/main/crates/runner/Cargo.toml) and [`build.rs`](https://github.com/kcl-lang/kcl/blob/main/build.rs) |
| **Environment** | Pure computation; no side effects | Sets env vars (e.g., `KCL_DEFAULT_TARGET`) |

The **evaluator** acts as the *brain* that understands KCL syntax, type checking, and lazy evaluation, while the **runner** acts as the *nervous system* connecting that brain to the outside world through filesystem operations and standard streams.

## Practical Usage Examples

### Using the Evaluator Directly (Library Code)

Embed KCL evaluation in your Rust application by interfacing directly with the evaluator crate:

```rust
use kcl_ast::ast::Program;
use kcl_evaluator::Evaluator;
use kcl_runtime::Context;

// Assume `prog` is a parsed `Program` obtained from kcl_parser
let mut runtime = Context::new();               // runtime context
let evaluator = Evaluator::new(&prog);          // create evaluator
evaluator.init_scope(kcl_ast::MAIN_PKG);       // initialise top‑level scope
evaluator.compile_ast_modules(&prog.get_modules_for_pkg(kcl_ast::MAIN_PKG));
let (json, yaml) = evaluator.plan_globals_to_string(); // final output
println!("JSON:\n{}", json);
println!("YAML:\n{}", yaml);

```

### Running KCL via the CLI (Binary)

Execute KCL files using the runner binary built from the `kcl-runner` crate:

```bash

# Build the binary

cargo build -p kcl-runner

# Execute a KCL file and get JSON/YAML

./target/debug/kclc example.k

# → prints JSON and YAML to stdout, or writes to files with `--output`

```

### Running as a Function

Target specific schemas or functions within a KCL file using the runner’s function mode:

```bash
./kclc --func my_pkg.my_schema example.k

# The runner calls `Evaluator::run_as_function()` and returns the

# evaluated `ValueRef` of the target function/schema.

```

## Source Code Structure

Understanding the repository layout clarifies the separation of concerns:

- **[`crates/evaluator/src/lib.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/evaluator/src/lib.rs)** – Defines the `Evaluator` struct, evaluation flow, and value planning logic according to the KCL language semantics.
- **[`crates/runner/Cargo.toml`](https://github.com/kcl-lang/kcl/blob/main/crates/runner/Cargo.toml)** – Cargo manifest specifying the binary target and dependencies for the executable front-end.
- **[`crates/runner/build.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/runner/build.rs)** – Build script that injects environment variables such as `KCL_DEFAULT_TARGET` during compilation.
- **[`crates/cli/src/main.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/cli/src/main.rs)** – CLI entry point that parses arguments, loads files, creates the `Context`, and invokes the evaluator.
- **[`crates/runtime/src/context.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/runtime/src/context.rs)** – Holds runtime buffers, options, and result strings used by both the evaluator and runner components.

## Summary

- **`kcl-evaluator`** provides the core language engine in [`crates/evaluator/src/lib.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/evaluator/src/lib.rs), exposing the `Evaluator<'ctx>` type for AST execution and value production.
- **`kcl-runner`** serves as the minimal binary wrapper in `crates/runner/` that handles CLI parsing, file I/O, and environment setup before invoking the evaluator.
- The evaluator operates on in-memory AST structures (`ast::Program`) and returns `ValueRef` objects, while the runner processes filesystem paths and formats output as JSON/YAML.
- The runner crate contains only [`Cargo.toml`](https://github.com/kcl-lang/kcl/blob/main/Cargo.toml) and [`build.rs`](https://github.com/kcl-lang/kcl/blob/main/build.rs), indicating it primarily exports a binary rather than a library API.
- Direct library use of `kcl-evaluator` is ideal for embedding KCL in Rust applications, while `kcl-runner` provides the standard command-line interface.

## Frequently Asked Questions

### Can I use kcl-evaluator without kcl-runner in my Rust application?

Yes. The `kcl-evaluator` crate is designed as a library that operates independently of the runner. You can construct an `Evaluator` directly with a parsed `ast::Program`, call `init_scope()` and `compile_ast_modules()`, and retrieve results via `plan_globals_to_string()`. This approach is ideal for unit testing, custom build tools, or embedded scripting engines where you want to avoid CLI overhead.

### Why does the kcl-runner crate lack a src directory?

The `kcl-runner` crate functions as a binary target rather than a library. It contains only [`Cargo.toml`](https://github.com/kcl-lang/kcl/blob/main/Cargo.toml) (defining the binary dependencies) and [`build.rs`](https://github.com/kcl-lang/kcl/blob/main/build.rs) (setting compile-time environment variables like `KCL_DEFAULT_TARGET`). The actual CLI logic typically resides in [`crates/cli/src/main.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/cli/src/main.rs), which imports functionality from the evaluator and runtime crates to orchestrate execution.

### How does the evaluator handle output formatting versus the runner?

The evaluator produces raw string representations through `plan_globals_to_string()`, which returns JSON and YAML strings based on the evaluated global variables. The `kcl-runner` component handles the presentation layer—writing these strings to stdout or files, applying custom formatting flags like `--stream`, and managing error exit codes. The evaluator focuses on correctness of the values; the runner focuses on delivery of those values to the user.

### Which component should I modify to add custom CLI flags?

You should modify the runner layer, specifically the CLI entry point in [`crates/cli/src/main.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/cli/src/main.rs) (which coordinates the runner functionality). This file parses command-line arguments, validates inputs, and prepares the `Context` before invoking the evaluator. The `kcl-evaluator` itself should remain agnostic to CLI concerns, maintaining its focus on language semantics and evaluation logic.