Your first app
A Kiki app is a Rust struct annotated with #[kiki::app]. The macro generates the manifest, wires up the agent connection, and validates the app's permissions at compile time. This is the shortest path from nothing to a running app.
Anatomy of an app
use kiki::prelude::*;
#[kiki::app(id = "io.kiki.my-app", type = DesktopApp)]
#[kiki::requires(Capability::AudioOutput)]
struct MyApp {
// your app's state
}id— a reverse-DNS identifier, e.g.io.kiki.my-app. It namespaces the app's storage, schemas, and store entry.type— the kind of app, which decides how strongly the OS isolates it.#[kiki::requires(...)]— the permissions it needs, validated at compile time and enforced at runtime.
App types
The type tells the OS how to run and isolate your app:
| Type | Typical app | Isolation |
|---|---|---|
DesktopApp | A windowed app | Namespaces + Wayland filter |
HeadlessApp | A background worker | Namespaces + network namespace |
CliTool / McpTool | A command or tool server | Namespaces + seccomp |
SystemService | A privileged service | Explicit capabilities |
Agent | Externally-authored agent code | Hardware-isolated MicroVM |
See Capabilities & sandbox for why agent code gets a MicroVM.
Storing data
By default an app reads and writes its own private space — relative paths, never absolute:
storage::write("notes/draft.md", body).await?;For data the user wants across devices, request a scoped vault capability; for raw filesystem access, request a scoped fs capability. The user approves these when they install.
Make it agent-operable
The reason to build on Kiki: your app becomes something the agent can drive. Expose actions as tools:
#[kiki::agent_tools]
impl MyApp {
async fn play(&mut self, track: TrackRef) -> Result<()> { /* ... */ }
}Now "play that track" works whether the user does it or the agent does it as part of a plan.
Ship it
When it builds, package and publish it. Next: Exposing tools.