@kallamo/umwelt
The TypeScript client of Umwelt, an epistemic state engine: it records what happened in a story, a game or a simulation and, kept apart from it, who knows it, how they came to know it and since when.
npm install @kallamo/umweltimport { createClient, UmweltError } from '@kallamo/umwelt';
const client = createClient({ file: 'world.umw', create: true });
try {
const project = await client.projectCreate({ name: 'World', timescale: 'day' });
console.log(await client.projectGet({ projectId: project.id }));
} catch (error) {
if (error instanceof UmweltError) console.error(error.code, error.message);
throw error;
} finally {
await client.close();
}The client
Every protocol operation is a typed method, named in camelCase:
project.create is projectCreate, timeline.openDraft is
timelineOpenDraft. Payload and result types are generated from the engine’s
JSON Schemas and exported by the package.
createClient options:
| Option | Meaning |
|---|---|
file | The .umw file to open. |
create | Create the file if it does not exist. |
binary | Path of an umwelt executable to run instead of the bundled one. |
client | The name the client reports in the handshake. |
onStderr | Receives the engine’s diagnostics. |
embedder | Your embedding model, for semantic search (below). |
The client performs the handshake, sends requests concurrently and matches each
response to its request. A failed request rejects with an UmweltError that
carries the protocol code, a message and optional details.
TRANSPORT_CLOSED is reported by the client itself, not by the engine: if the
engine process ends, every pending request rejects with it. close() asks the
engine to shut down and waits for it to exit.
Building requests
Payloads are plain objects, and the package exports small functions that build the repetitive ones:
import { at, authored, by, claim, fact, knows, suspects, witnessed } from '@kallamo/umwelt';
const theft = claim.entity('bob', 'stole', 'necklace');
await client.commit({
projectId: 'ball',
attemptId: 'scene-1',
event: { id: 'scene-1', type: 'scene', title: 'The ball', position: at(1), provenance: by.human() },
records: [
fact(theft),
knows('ana', theft, witnessed('scene-1')),
suspects('duchess', claim.entity('ana', 'stole', 'necklace'), authored(), { confidence: 0.6 }),
],
});Each function is pure and returns the object of the protocol, so its result
can be mixed with hand-written objects. A record that gives no position or
provenance takes those of its event, and a Holding defaults to confidence 1
and available; the last argument of a record function sets them, and id,
text and secrecy, when needed. Who holds a claim, with which attitude and
how they acquired it is always written out. When everyone at an event saw the
same thing, client.witness records it in one call.
Semantic search with your own model
Umwelt never runs a model. Pass an embedder and the client uses yours:
const client = createClient({
file: 'world.umw',
embedder: { spaceId: 'e5-small', embed: texts => model.embed(texts), autoEmbed: true },
});
await client.embeddingDefineSpace({
projectId: 'world',
space: { id: 'e5-small', dimensions: 384, model: 'intfloat/multilingual-e5-small' },
});
await client.embedPending({ projectId: 'world' });
const found = await client.semanticSearch({
projectId: 'world',
text: 'who took the necklace?',
filter: { holder: 'ana' },
});Lower level
StdioTransport exposes request(op, payload) for generic protocol tooling;
applications should prefer the typed methods. automaticHello: false is
reserved for protocol test harnesses that send their own handshake.
License
AGPL-3.0-only. Free, non-monetized games, mods, interactive fiction and tabletop play tools may use Umwelt without releasing their own code under the Umwelt Free Play Exception; other uses that cannot comply with the AGPL can obtain a commercial license (development and prototyping are free) at contact@kallamo.com. Built by Kallamo; see the repository for the license, the documentation and how to contribute.