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

  1. The declaration of types and functions using the cross-language Wasm Interface Type (WIT) language
  2. 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 records, and enum-like types called variants. Users can also define funcs with function signatures.

Interfaces

interfaces define a set of types and functions and, in Kinode, are how a process signals its API.

Worlds

worlds define a set of imports and exports and, in Kinode, correspond to a package's API. They can also include other worlds, copying that worlds imports and exports. 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 imported interface contains funcs as well, a Wasm component is required that exports 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 imports types from interfaces defined in two other WIT files.

Get Help: