2026-10-11 17:15 UTC

Sébastien Burel claims KaozKit's released Swift runtime embeds capability-confined JavaScript agents whose heaps can be checkpointed and restored across process restarts, reducing bespoke state-persistence plumbing for resident macOS agents.

state: watchingheat: lowuncertainty: highconvergesscott: mediumagent-harnesses agent-memory local-inferenceSébastien BurelKaozKitModdable

What is this?

The case describes KaozKit as a released Swift runtime for JavaScript LLM agents on Apple Silicon macOS, embedding Moddable’s XS engine, and attributes the announcement to Sébastien Burel. Its claimed distinction is capability-confined execution with JavaScript heaps that can be checkpointed and restored after process restarts, potentially reducing custom persistence code. None of the supplied web results directly covers KaozKit or verifies its release, authorship, confinement guarantees, or heap-restoration behavior; those details remain case claims. The snippets establish the broader engineering problem of durable agent state, while noting that checkpointing alone does not resolve duplicate tool side effects or stale external state.

Why it matters to Scott

KaozKit’s claimed restartable, host-confined runtime converges with Scott’s separation of agent continuity from execution authority and offers a concrete persistence mechanism to evaluate against Proposal Compiler’s bespoke job-recovery plumbing—not evidence that restored heaps replace external completion records or safe handling of tool side effects. The release and guarantees remain unverified in the supplied material; the radar tracks related continuation recovery in Trigora and atomic REPL restoration in Prime Agent, but not this KaozKit development.
ip:framework.prompt-interrupt-architectureip:framework.long-running-agentsdev:project.proposaldev:concept.padded-cell-agent-architectureradar:trigora-continuation-recoveryradar:prime-agent-090-atomic-repl-stateradar:concept.durable-executionradar:concept.agent-sandboxing
queries asked of Scott's wikis
  • agent harness runtime-managed persistence versus explicit state serialization
  • resident desktop agents Swift macOS local inference
  • agent memory process restart recovery checkpoints
  • capability confinement sandboxing host-mediated agent tools
  • durable execution tool side effects replay stale workspace state

Measured heat

now 0 pts/hpeak 0 pts/hcomments 0/hpeers p14momentum: steady2 platformsage 626h
points/hour across evidence · reading as of 2026-10-12 02:59:37.977291+11:00 · deterministic, not a model opinion

How the heat travelled

09-15 14:29 (minted)⭐ origin echo-reconstructedKaozKit embeds Moddable's XS engine in Swift on macOS 26+ Apple Silicon, with host-mediated tools, local and cloud model providers, and resi
Sébastien Burel on github (echo) · attributed from hn.story.49712640 · published time unknown
—
09-15 14:00first on hacker news · published · lag ?Show HN: KaozKit – JavaScript LLM agents on an engine built for microcontrollers
sebastienburel
—
09-15 14:00amplified on hacker news 👑hn.story.49712640
sebastienburel
peak 3 · 3 comments · 101% of case engagement
09-15 14:21our radar first saw it · lag ?discovery anchor: hn.story.49712640—
pace: p42 vs 1032 stories at the 336h mark (now 626h old) — ahead of agent-memory-add-search-evaluation (1.2x), behind agenticos-self-hosted-governance (0.9x)

Evidence (2) — ⭐ canonical anchor

sourceobjectauthorscorecomments
🟧 hnShow HN: KaozKit – JavaScript LLM agents on an engine built for microcontrollers
Retrieved article excerpt

Open article · Retrieved 2026-09-15T14:22:33.199214+00:00

# KaozKit

**Autonomous LLM agents, written in JavaScript, running inside your Swift app.**

KaozKit embeds the [XS engine](https://github.com/Moddable-OpenSource/moddable) (Moddable) in a Swift package. An agent is a small JS module — `export function run(input)` — that drives a language model, calls tools, and reads/writes memory. The JS heap can be **snapshotted to disk and restored in a fresh process**, so resident agents survive app restarts with their full state.

[Platform](https://camo.githubusercontent.com/4ad041380715a7b5624a5c78450756e1f70d29abc657bf0c1e14b6716c52059c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f706c6174666f726d2d6d61634f532532303236253242253230284170706c6525323053696c69636f6e292d626c7565)
[Swift](https://camo.githubusercontent.com/ebf94692d5d4523ab75b3a6604717cc2c7c5dfe54f162719ba8cc51a2cebfcd1/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f53776966742d362d6f72616e6765)
[License](https://camo.githubusercontent.com/f8df3091bbe1149f398a5369b2c39e896766f9f6efba3477c63e9b4aa940ef14/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e)

```
// demo/weather.js — runs inside the engine
export async function run(input) {
  const reply = await host.llm.chat(
    [{ role: "user", content: input.question }],
    { tools: ["current_datetime", "web_search"] }   // web_search needs BRAVE_API_KEY
  );
  await host.memory.save("last question", input.question);
  return { answer: reply };
}
```

```
export ANTHROPIC_API_KEY=…
swift run -c release kaoz demo/weather.js --provider anthropic --model claude-opus-4-8 \
    --input '{"question":"what day is it?"}'
```

[The kaoz CLI running the same agent twice — first on Anthropic, then fully on-device with Apple Intelligence](https://github.com/sebastien-burel/KaozKit/blob/main/demo/kaoz-demo.gif)

*(`kaoz` here is `.build/release/kaoz` on the PATH — see [Install the kaoz CLI](https://github.com/sebastien-burel/KaozKit#install-the-kaoz-cli).)*

## Quick start

macOS 26+, Apple Silicon, Xcode 26 (Swift 6). Apple Intelligence must be enabled in System Settings for `--provider apple`. From an empty folder:

```
# 1. XS engine sources (not vendored, LGPL — see License)
git clone --depth 1 https://github.com/Moddable-OpenSource/moddable.git
export MODDABLE="$PWD/moddable"

# 2. KaozKit
git clone https://github.com/sebastien-burel/KaozKit.git
cd KaozKit
./scripts/link-moddable.sh
swift build -c release

# 3. Your first agent — fully on-device, no API key needed
swift run -c release kaoz demo/hello.js --provider apple
```

**With a cloud provider:** `export ANTHROPIC_API_KEY=…` and run the same agent with `--provider anthropic --model claude-opus-4-8`.

**Optional tools:** `web_search` needs `BRAVE_API_KEY`, `news_search` needs `NEWS_API_KEY`, `send_email`/`read_email` need `--email` plus an SMTP account (see [CLI](https://github.com/sebastien-burel/KaozKit#cli--kaoz)). Without them the tool is simply unavailable — an agent that asks for it gets a warning on stderr and carries on with the rest.

## Why XS, and not JavaScriptCore?

Fair question — JSC ships with the OS. XS earns its place with capabilities JSC doesn't have:

- **Heap snapshots.** `writeSnapshot()` serializes the entire JS heap; `init(snapshot:)` restores it in a new process. A resident agent's state, conversation, and scheduled work survive relaunches — no serialization layer to write.
- **Multi-machine services.** Agents spawn sub-agents (`new Thread` + `new Service`) as isolated XS machines with alien-marshalled calls between them.
- **Confinement by construction.** Module resolution is restricted to registered roots; the `host.*` surface is the *only* capability an agent has. Secrets never enter JS — providers are resolved and keys injected on the Swift side.
- **Small engine, tiny memory footprint.** XS was built for embedded systems, where every byte counts — a full ES2026 engine that runs in a fraction of the memory JSC needs. That's what makes *one machine per agent* a reasonable architecture: spinning up sub-agents, or keeping several resident agents alive side by side, costs very little.
- **Built to be embedded.** XS runs happily on a private thread with a clean C API, and `await` continuations settle correctly across the JS↔Swift boundary.

If you only need to evaluate scripts, JSC is fine. If you want **stateful, restartable, confined agents**, that's what KaozKit is for.

## One package, layered products

KaozKit is a single SwiftPM package that vends products in layers. A project embedding **only JavaScript** depends on `KaozJS`; an **agent** project depends on `KaozKit`, which pulls the engine in.

```
KaozJSCore (C)  — XS engine + the xsService* async-settle bridge
KaozJS          — Swift XSEngine (dedicated thread + CFRunLoop, snapshot, module roots)
KaozHostC (C)   — the agent's XS host functions (host.llm/tool/memory/schedule)
KaozKit         — agent runtime: providers, tools, memory, channels, persona
KaozMLX         — MLX local-inference providers (heavy deps, opt-in)
kaoz            — headless CLI / resident daemon
```

`import KaozKit` for the agent runtime; `import KaozJS` (+ `KaozJSCore`) for the bare JS↔Swift engine. **macOS 26+, Apple Silicon.**

```
.package(path: "../KaozKit"),
// agent runtime:
.target(name: "YourApp", dependencies: [
    .product(name: "KaozKit", package: "KaozKit"),
    .product(name: "KaozMLX", package: "KaozKit"),   // optional: on-device MLX
]),
// …or just the JS engine:
.target(name: "YourEngine", dependencies: [
    .product(name: "KaozJS", package: "KaozKit"),
    .product(name: "KaozJSCore", package: "KaozKit"),   // flat C settle functions
]),
```

## Writing an agent (JS)

An agent module exports `run(input)` (or `default`). Its return value comes back to Swift as JSON. The `host` global (installed by `KaozHostC`) is the whole capability surface:

| `host.*` | Role |
| --- | --- |
| `host.llm.chat(messages, { tools }, onToken?)` | One LLM turn on the run's default provider. Runs the tool-call loop internally (the model calls a tool → Swift executes it → the model continues), resolves with the final assistant text. `onToken` streams text deltas. |
| `host.provider(id, { model, … }).chat(…)` | Same, on a specific provider from the catalog. Secrets stay in Swift — never passed from JS. |
| `host.providers()` | The provider ids/names the host exposes. |
| `host.tool.list()` / `host.tool.call(name, args)` | Enumerate / invoke a registered tool directly. |
| `host.memory.save(title, content)` / `.read(id)` / `.list()` / `.search(query, limit?)` | Persistent notes; `search` ranks by embedding similarity. |
| `host.schedule(ms, payload?)` / `host.every(ms, payload?)` / `host.cancel(handle)` | Self-scheduling: deliver a `tick` to the agent's `onTick` after / every `ms` (resident mode). |
| `host.usage()` | Cumulative `{ promptTokens, completionTokens, chatCalls }` for the run. |
| `host.snapshot(reason?)` | Ask for a checkpoint of the whole heap. Returns `false` if this host doesn't persist. |
| `host.log(…args)` | Log to the host. |

`messages` are `{ role, content }` objects (`role`: `system` / `user` / `assistant`); `tools` is an array of registered tool **names**. A **resident** agent instead exports an object of handlers — `{ onMessage, onEvent, onTick, onRestore }` — and its JS heap (state, conversation) survives across deliveries.

### Checkpoint and restore (resident)

A snapshot cannot be taken *during* a delivery: the JS stack is live and host calls may be in flight. `host.snapshot()` is therefore a **request** — the host writes as soon as the current delivery has settled. Ask for it at a point you'd be happy to wake up at:

```
onTick() { this.work(); host.snapshot("cycle done"); }
```

Coming back is the mirror image. The heap returns intact, but **timers do not**: they live in Swift and die with the process, and no module body re-evaluates to notice. So the host delivers one `restore` event to the revived agent, which re-arms from its own state — never from a stored handle, which now names a dead timer:

```
export default {
  onRestore({ count, at }) { this.armFrom(this.nextRunAt); },   // optional
};
```

`restored()` (from `kaoz/host`) returns `{ count, at }` at any time, and `lastSnapshot()` reports the last checkpoint's outcome. `kaoz --state-auto` checkpoints after *every* delivery for agents that would rather not ask. See [`demo/resident-checkpoint.js`](https://github.com/sebastien-burel/KaozKit/blob/main/demo/resident-checkpoint.js).

The same capabilities are also **importable as ES modules**, so an agent can name what it uses instead of reaching for an ambient global — both forms work, and resolve to one implementation:

```
import { llm, tool, memory } from "kaoz/host";     // the surface above
import { Thread, Service } from "kaoz/thread";     // sub-agent spawn
```

Agents can compose providers freely — for example, one model writes a prompt and another renders it:

```
export async function run(input) {
  const prompt = await host.provider("anthropic").chat(
    [{ role: "user", content: `Write an image prompt for: ${input.idea}` }]);
  const image = await host.provider("comfyui").chat(
    [{ role: "user", content: prompt }]);
  return { prompt, image };
}
```

An agent may also spawn **sub-agents** from the script: `new Thread(name)` + `new Service(thread, "sub-agent")`, then `await svc.method(args)` (see the engine layer below).

## Running an agent (Swift)

Two entry points in `KaozKit`:

**`AgentRuntime`** — one-shot. One engine per run, torn down when `run` finishes.

```
import KaozKit

let runtime = AgentRuntime(
    makeProvider: { AnthropicProvider(apiKey: key, model: "claude-opus-4-8") },
    tools: ToolRegistry(tools: [SaveMemoryTool(store: memory), /* … */]),
    memory: memory,                    // any MemoryStoring (e.g. SemanticMemoryStore)
    persona: "You are Kaoz, terse and precise.")

let json = try await runtime.run(script: source, input: ["question": "…"], timeout: 30)
// …or Moddable-style, importing the agent + its modules from disk by bare name:
let json2 = try await runtime.runRooted(
    entryModule: "agent", roots: [("", agentDir.path)], input: nil, timeout: 30)
```

**`AgentHost`** — resident. One engine kept alive across many `deliver(kind:payload:)` calls; the JS heap persists, and the whole heap can be snapshotted to disk and restored in a fresh process.

```
let agent = AgentHost(entryModule: "agent", roots: [("", dir.path)],
                      makeProvider: …, tools: registry, memory: memory,
                      installThreads: false)          // false ⇒ snapshot-capable
let out  = try await agent.deliver(kind: "message", payload: ["text": "hi"])
let bytes = try agent.writeSnapshot()                 // persist state
// …later, fresh process:
let restored = AgentHost(snapshot: bytes, roots: […], makeProvider: …, tools: …, memory: …)
```

Both take a `makeProvider` (the run default), an optional `resolveProvider(id, options)` (for JS-selected providers, secrets injected in Swift), a `ProviderDescriptor` catalog, a `ToolRegistry`, a `MemoryStoring`, and optional `tokenBudget` / `persona`. `Sources/kaoz/main.swift` is the canonical worked example of wiring all of them.

## Providers

Every provider conforms to `LLMProvider` (a streaming `chat(messages:tools:)`).

- **Native (Swift):** `AnthropicProvider`, `GoogleProvider` (Gemini), `OllamaProvider`, `OpenAIProvider`, and OpenAI-compatible wrappers `LocalOpenAIProvider` (LM Studio / llama.cpp), `DeepSeekProvider`, `MistralProvider`, `QwenProvider`, `ZAIProvider`; `AppleIntelligenceProvider` (on-device Foundation Models); `ComfyUIProvider` (image generation).
- **JS-defined** (`JSProvider`, backed by `Resources/js/*.js` over the native `__http` primitive): `JSProviders.anthropic` / `.openai` / `.openaiCompatible` / `.ollama` / `.kimi` / `.google`.
- **Embeddings** (`EmbeddingProvider`): `HashingEmbeddingProvider` (dependency-free, 
sebastienburel33
🟧 echo.github ⭐KaozKit embeds Moddable's XS engine in Swift on macOS 26+ Apple Silicon, with host-mediated tools, local and cloud model providers, and resiSébastien Burel——

Interpretation history

Decision trace