Getting started with the Kio tooling
Start here when you want to run the Kio development loop on a real package. You will create a tiny greeter package, edit it, format it, typecheck it, test it, build it, and record its contract surface with kio sig.
Every command below is meant to be run from the package root as you read. The tutorial assumes only the language used in the examples; for a fuller language tour, read the language tutorial. For exact command-line behavior, see specs/cli.md.
The whole loop uses one program, kio, with a subcommand per step.
Chapter 1 — Scaffold a package with kio init
A Kio program is a package: a directory holding a package file (the package's contract with its host, plus its build settings) and one or more modules (the code). kio init writes the smallest working package for you.
mkdir greeter
cd greeter
kio init
With no argument, kio init names the package after the current directory; pass a name to override it (kio init greeter). The package name must be a lowercase identifier containing at least one ASCII letter: _a and _1a are valid, but _ and _1 are not. Invalid names are rejected before any files are created. The command writes two files at the package root and prints what it created:
created Kio package `greeter`
greeter.pkg.kio
main.kio
So the package starts as:
greeter/
├── greeter.pkg.kio
└── main.kio
kio init refuses to overwrite an existing package file, so it is safe to run in an empty directory and loud in one that already looks like a package.
What the package file declares
greeter.pkg.kio is the package file. Its filename stem (greeter) is the package name.
package greeter;
build {
cache "out/.kio-cache/";
target js {
out "out/js/";
baseline es2020
}
}
bridge {
main
}
Two blocks do the work:
build { ... }names a compilation target (js, writing underout/js/) and a cache directory. This is whatkio buildreads.bridge { ... }lists the modules whose public surface forms the package's contract with its host. Here it admitsmain, the one module we have.
baseline es2020 pins the generated JavaScript compatibility contract. If the field is omitted or written baseline (), kio fmt writes the newest concrete baseline supported by that Kio version. baseline latest instead keeps following the compiler's newest supported baseline, even after formatting. See Build blocks for the build-file guide.
The starting module
main.kio is the package's root module — just a module <name>; file at the package root. The generated one declares a string type and a print capability it asks the host to supply, then exports a main that prints a greeting:
module main;
host type String role(str);
host fn print(p0: String);
pub fn main() { print("hello from Kio\n"(String)) }
A host type / host fn is a capability the host provides: the package declares the shape it needs and the host supplies the implementation when it loads the package. pub marks main as part of the package's exported surface. That is all the language you need to follow the rest of this tutorial.
Chapter 2 — Write a little code
Let's add one function and call it from main: a greeting function that builds a string, with main printing its result.
First, the new function — deliberately typed on one cramped line so we have something for the formatter to fix in the next step:
fn greeting(name: String) -> String {string_concat("hello, "(String),name)}
greeting concatenates a literal prefix with its argument. That call needs one more host capability — string concatenation — so add its declaration alongside the existing host lines in main.kio:
host fn string_concat(p0: String, p1: String) -> String;
Finally, rewrite main to call greeting:
fn greeting(name: String) -> String { string_concat("hello, "(String), name) }
pub fn main() { print(greeting("Kio"(String))) }
The module now reads, top to bottom: a module main; header, three host declarations (String, print, string_concat), then greeting and main.
Chapter 3 — Format with kio fmt
Kio has exactly one canonical style and no knobs to configure it. kio fmt rewrites your source to that style in place:
kio fmt
It prints the path of every file it changed; silence means everything was already canonical. Because we wrote greeting on one cramped line, kio fmt reports:
main.kio
Open the file again and the body has been spaced to the house style (the greeting line above already shows the canonical form). Writes are atomic, so an interrupted kio fmt never leaves a half-written file.
In CI you want the check-only mode, which writes nothing and fails if any file is off-style:
kio fmt --check
--check exits 60 when at least one file would change (and lists those paths), 0 when everything is already canonical. Wire that into your pipeline so unformatted code can't merge.
Chapter 4 — Typecheck with kio check
kio check runs the whole front end — parse, resolve, typecheck — and reports any errors. It writes no output files and touches no network; it is the fast command to run after every edit.
kio check
A clean run is silent and exits 0. If you have a type error, kio check prints it and exits with a code identifying the category of error (parse, type, name resolution, …) so scripts can branch on the kind of failure without scraping messages; the table is in specs/exit-codes.md.
Try breaking it on purpose: pass greeting an integer instead of a string and re-run kio check. You will get a type error pointing at the offending call. Fix it back, re-run, and you get silence again.
Carets mark the error in a source excerpt; secondary underlines show related context such as the annotation requiring a String. Read that requirement alongside the offending call before changing the source. For help deciding where an annotation belongs, see Understanding typechecking.
An editor connected to kio lsp reports errors as you edit and offers quick fixes for supported mistakes, including misspelled names and type/value punctuation. Applying a fix rechecks the source. The editor can also warn about unused bindings, imports, and private declarations. Review the warning before removing anything; prefix an intentionally unused name with _.
The editor setup guide explains how to connect VS Code to the compiler. The recursion guide and declarations guide explain repairs for recursive code. Exact diagnostic and code-action behavior is in the language-server reference.
Chapter 5 — Test with kio test
Kio's built-in testing primitive is the equiv declaration: a claim that two or more expressions reduce to the same value. kio test runs the same front end as kio check, then evaluates every equiv in the package and reports which ones hold.
Add a claim to main.kio that pins down greeting's behavior:
equiv greeting_kio() {
greeting("Kio"(String));
string_concat("hello, "(String), "Kio"(String))
}
The claim says "calling greeting with "Kio" is the same as concatenating the prefix with "Kio" directly." Run the tests:
kio test
Each equiv prints a pass or fail line, and the run ends with a summary:
pass equiv `greeting_kio` in greeter/main
result: 1/1 equiv block passed
A package with no equiv blocks prints no equiv blocks found and exits 0 — having no tests is not a failure. A genuine equivalence failure exits 50. For more on writing claims, see Testing with equiv.
Chapter 6 — Build with kio build
kio build reads the build { ... } block from the package file, hands each declared target to its backend, and writes output under that target's out directory. It typechecks first, so a package that fails kio check never reaches codegen.
kio build # build every target in the build block
kio build js # build only the target named "js"
With no argument every target is emitted; giving target names builds only those (an unknown name is an error). Our package declares one js target, so both commands here do the same thing. The emitted JavaScript lands under out/js/. What the artifact looks like, and how to call into it, is backend-specific — see the host guide for your target: JavaScript, TypeScript, Python, Java, Rust, Go, Swift, or Haskell.
Chapter 7 — Seal the contract surface with kio sig
The package's contract surface is everything its bridge block exposes: the host capabilities it requires and the pub items it offers back. When that surface changes, consumers that loaded the package care. kio sig records each version of the surface in a changelog file (greeter.sig.kio) and gates changes on backward compatibility. The full contract is in specs/versioning.md; here we walk the everyday path.
Run it bare first. With no changelog yet, kio sig is non-mutating and points you at the first step:
kio sig
kio sig: `greeter` has no compatibility changelog yet — `kio sig commit` seals the first version
Stage, then commit
A version is a mutable draft until you seal it. kio sig stage records the current surface as a compatible delta into that draft; kio sig commit seals it and bumps the version number.
kio sig stage
kio sig commit -m "Initial contract: String, print, string_concat, main."
stage writes the draft (the host type String, the host fns print and string_concat, and the export main). commit seals it as v(1); the -m message is stored with the version and shown by the log. A bare kio sig stage only ever records a compatible change — if your edit broke the last sealed surface, it errors until you acknowledge the break with kio sig stage --force.
Review the history
In that history, add introduces a declaration, modify changes one that is still present, and remove drops one. Bringing back a removed declaration is another add, not a modify, even though its earlier declaration stays in the history.
kio sig log pretty-prints the changelog, each version with its commit message:
kio sig log
It takes read-only display filters: kio sig log --breaking shows only versions that carry a breaking change, and kio sig log --since 1 scopes the output to versions after v(1). (When the history grows unwieldy, kio sig compact <version> collapses the additive history before a version into a single boundary block — a detail for later.)
The CI gate
kio sig status is the command CI runs to enforce the changelog. It compares the live surface against the last sealed version and exits:
kio sig status
0— the changelog is up to date with the source;81— there is an unrecorded but compatible drift (runkio sig stage);82— a break is recorded but not yet sealed (runkio sig commit);80— the source breaks the sealed contract and the break is unrecorded (acknowledge withkio sig stage --force, or reconcile the source).
Right after our stage / commit, kio sig status is clean and exits 0. Add a new pub fn later and it reports 81; kio sig stage then records it and clears the gate again. Bare kio sig prints the same status summary without changing anything, so it is the safe command to run when you just want to look.
Chapter 8 — The loop, end to end
That is one full lap. Day to day you'll spend most of your time in the first two steps:
- Edit a module.
kio check— catch type errors fast.kio fmt— canonicalize before committing (kio fmt --checkin CI).kio test— confirm theequivclaims still hold.kio build— emit the artifact when you're ready to run it in a host.kio sig stage→kio sig commitwhen the contract surface changes;kio sig statusas the CI gate.
For a rename, use the editor's rename action to update the declaration and its references together, then continue the same checking and testing loop.
Chapter 9 — Where to go next
- The Kio language — a guided tour of the language itself: types, functions, sums, polymorphism, and structural glue.
specs/cli.md— the exact command-line contract.- Exploring a package with
kio repl— load a package interactively and query its types, docs, and source. - Host guides — run a built package from JavaScript, TypeScript, Python, Java, Rust, Go, Swift, or Haskell.