WIT APIs
This document describes how Kinode OS processes use WIT to export or import APIs at a conceptual level. If you are interested in usage examples, see the Package APIs recipe.
High-level Overview
Kinode OS runs processes that are WebAssembly components, as discussed elsewhere. Two key advantages of WebAssembly components are
- The declaration of types and functions using the cross-language Wasm Interface Type (WIT) language
- The composibility of components. See discussion here.
Kinode processes make use of these two advantages.
Processes within a package — a group of processes, also referred to as an app — may define an API in WIT format.
Each process defines a WIT interface
; the package defines a WIT world
.
The API is published alongside the package.
Other packages may then import and depend upon that API, and thus communicate with the processes in that package.
The publication of the API also allows for easy inspection by developers or by machines, e.g., LLM agents.
More than types can be published. Because components are composable, packages may publish, along with the types in their API, library functions that may be of use in interacting with that package. When set as as a dependency, these functions will be composed into new packages. Libraries unassociated with packages can also be published and composed.
WIT for Kinode
The following is a brief discussion of the WIT language for use in writing Kinode package APIs. A more full discussion of the WIT language is here.
Conventions
WIT uses kebab-case
for multi-word variable names.
WIT uses // C-style comments
.
Kinode package APIs must be placed in the top-level api/
directory.
They have a name matching the PackageId
and appended with a version number, e.g.,
$ tree chat
chat
├── api
│ └── chat:template.os-v0.wit
...
What WIT compiles into
WIT compiles into types of your preferred language.
Kinode currently recommends Rust, but also supports Python and Javascript, with plans to support C/C++ and JVM languages like Java, Scala, and Clojure.
You can see the code generated by your WIT file using the wit-bindgen
CLI.
For example, to generate the Rust code for the app_store
API, use, e.g.,
kit b app_store
wit-bindgen rust -w app-store-sys-v0 --generate-unused-types --additional_derive_attribute serde::Deserialize app_store/target/wit
In the case of Rust, kebab-case
WIT variable names become UpperCamelCase
.
Rust derive
macros can be applied to the WIT types in the wit_bindgen::generate!
macro that appears in each process.
A typical macro invocation looks like
#![allow(unused)] fn main() { wit_bindgen::generate!({ path: "target/wit", world: "chat-template-dot-os-v0", generate_unused_types: true, additional_derives: [serde::Deserialize, serde::Serialize], }); }
where the field of interest here is the additional_derives
.
Types
The built-in types of WIT closely mirror Rust's types, with the exception of sets and maps.
Users can define struct
-like types called record
s, and enum
-like types called variant
s.
Users can also define func
s with function signatures.
Interfaces
interface
s define a set of types and functions and, in Kinode, are how a process signals its API.
Worlds
world
s define a set of import
s and export
s and, in Kinode, correspond to a package's API.
They can also include
other world
s, copying that world
s import
s and export
s.
An export
is an interface
that a package defines and makes available, while an import
is an interface
that must be made available to the package.
If an interface
contains only types, the presence of the WIT file is enough to provide that interface: the types can be generated from the WIT file.
However, if an import
ed interface
contains func
s as well, a Wasm component is required that export
s those functions.
For example, consider the chat
template's test/
package (see kit
installation instructions):
kit n chat
cat chat/test/chat_test/api/*
Here, chat-template-dot-os-v0
is the test/
package world
.
It import
s types from interface
s defined in two other WIT files.