UmweltDocs
Get started/Quickstart

Quickstart

Record one scene and ask who knows what happened in it. Pick a language once; every example on this page follows it.

The scene

At a ball, Bob steals the duchess’s necklace and Ana sees him do it. Canon records the theft. Ana knows because she witnessed it, and Bob knows because he did it. The duchess was at the ball too, but she knows nothing.

Being present grants nothing. Nobody knows anything without a record of it, and Umwelt never infers one: your application records what each character knows.

Install

Terminal
cargo install \
  --git https://github.com/Kallamo/Umwelt \
  umwelt-cli

Or download the umwelt command from the latest release.

Terminal
npm install @kallamo/umwelt

The package includes the engine for Windows, Linux and macOS. It needs Node.js 22 or newer.

Terminal
dotnet add package Kallamo.Umwelt

The engine runs in your process, through its native library. It needs .NET 6 or newer.

Create a world

A file, then the world in one call: a project, the kinds of entities, the entities and the predicates. person is an agent, so it can hold knowledge; thing is other, so the necklace can be talked about but holds nothing. Projects are strict by default, so predicates are declared before they are used.

Terminal
umwelt create story.umw

umwelt call story.umw world.ensure '{
  "project": {
    "id": "ball",
    "name": "The Ball",
    "timescale": "scene"
  },
  "entityTypes": [
    {
      "id": "person",
      "name": "Person",
      "kind": "agent"
    },
    {
      "id": "thing",
      "name": "Thing",
      "kind": "other"
    }
  ],
  "entities": [
    {
      "id": "ana",
      "typeId": "person",
      "name": "Ana"
    },
    {
      "id": "bob",
      "typeId": "person",
      "name": "Bob"
    },
    {
      "id": "duchess",
      "typeId": "person",
      "name": "The duchess"
    },
    {
      "id": "necklace",
      "typeId": "thing",
      "name": "The necklace"
    }
  ],
  "predicates": [
    {
      "id": "stole",
      "label": "stole",
      "object": "entity",
      "cardinality": "many"
    }
  ]
}'

The manifest creates what is missing and leaves the rest as it is: sending it again changes nothing, so an application can send it every time it opens the file.

The whole scene, from the file to the answers. The client starts the engine, performs the handshake and gives every operation a typed method.

getting-started.ts
// The getting-started scene: Ana sees Bob steal the necklace. Canon records
// the theft; Ana and Bob know it, and the duchess, though present, does not.
// The next morning she suspects the wrong person.
import { at, authored, by, claim, createClient, suspects } from '@kallamo/umwelt';

const client = createClient({ file: 'story.umw', create: true });
try {
  const projectId = 'ball';
  // The world, declared once. It creates what is missing and changes nothing
  // that is already there, so it can run every time the file is opened.
  await client.worldEnsure({
    project: { id: projectId, name: 'The Ball', timescale: 'scene' },
    entityTypes: [
      { id: 'person', name: 'Person', kind: 'agent' },
      { id: 'thing', name: 'Thing', kind: 'other' },
    ],
    entities: [
      { id: 'ana', typeId: 'person', name: 'Ana' },
      { id: 'bob', typeId: 'person', name: 'Bob' },
      { id: 'duchess', typeId: 'person', name: 'The duchess' },
      { id: 'necklace', typeId: 'thing', name: "The duchess's necklace" },
    ],
    predicates: [{ id: 'stole', label: 'stole', object: 'entity', cardinality: 'many' }],
  });

  // The scene: one event, the Fact (canon) and what each witness saw.
  const theft = claim.entity('bob', 'stole', 'necklace');
  await client.witness({
    projectId,
    attemptId: 'scene-1',
    event: {
      id: 'scene-1',
      type: 'scene',
      title: 'The ball',
      position: at(1),
      participants: ['ana', 'bob', 'duchess'],
      provenance: by.human(),
    },
    claims: [{ claim: theft, canon: true }],
    witnesses: [{ holder: 'ana' }, { holder: 'bob' }],
  });

  const ana = await client.holds({ projectId, holder: 'ana', claim: theft });
  const bob = await client.holds({ projectId, holder: 'bob', claim: theft });
  const duchess = await client.holds({ projectId, holder: 'duchess', claim: theft });
  // `holds` is also true for someone who believes the opposite; `holdsAs` tells them apart.
  console.log(`Ana knows: ${ana.holdsAs === 'same'}`); // Ana knows: true
  console.log(`Bob knows: ${bob.holdsAs === 'same'}`); // Bob knows: true
  console.log(`The duchess knows: ${duchess.holdsAs === 'same'}`); // The duchess knows: false

  // The morning after: nobody saw anything and nobody told her, so this is a
  // record written directly. It takes its position and provenance from the event.
  const accusation = claim.entity('ana', 'stole', 'necklace');
  await client.commit({
    projectId,
    attemptId: 'morning',
    event: { type: 'scene', title: 'The morning after', position: at(2), provenance: by.human() },
    records: [suspects('duchess', accusation, authored(), { confidence: 0.6 })],
  });

  const suspicion = await client.holds({ projectId, holder: 'duchess', claim: accusation });
  console.log(`The duchess ${suspicion.attitude} Ana: ${suspicion.confidence}`); // The duchess suspects Ana: 0.6
} finally {
  await client.close();
}

The whole scene, from the file to the answers, with the engine running in your process.

Program.cs
// The getting-started scene with the .NET binding: Ana sees Bob steal the
// necklace. Canon records the theft; Ana and Bob know it, and the duchess,
// though present, does not. The next morning she suspects the wrong person.
using System.Globalization;
using Kallamo.Umwelt;

var path = Path.Combine(Path.GetTempPath(), $"umwelt-example-{Guid.NewGuid():N}.umw");

using (var engine = UmweltEngine.Create(path))
{
    const string projectId = "ball";
    // The world, declared once. It creates what is missing and changes nothing
    // that is already there, so it can run every time the file is opened.
    engine.WorldEnsure(new EnsureWorldInput
    {
        Project = new WorldProject { Id = projectId, Name = "The Ball", Timescale = "scene" },
        EntityTypes = new()
        {
            new WorldEntityType { Id = "person", Name = "Person", Kind = EntityKind.Agent },
            new WorldEntityType { Id = "thing", Name = "Thing", Kind = EntityKind.Other },
        },
        Entities = new()
        {
            new WorldEntity { Id = "ana", TypeId = "person", Name = "Ana" },
            new WorldEntity { Id = "bob", TypeId = "person", Name = "Bob" },
            new WorldEntity { Id = "duchess", TypeId = "person", Name = "The duchess" },
            new WorldEntity { Id = "necklace", TypeId = "thing", Name = "The duchess's necklace" },
        },
        Predicates = new() { new PredicateDef { Id = "stole", Label = "stole", Object = ObjectKind.Entity, Cardinality = Cardinality.Many } },
    });

    // The scene: one event, the Fact (canon) and what each witness saw.
    var theft = Claim.Entity("bob", "stole", "necklace");
    engine.Witness(new WitnessInput
    {
        ProjectId = projectId,
        AttemptId = "scene-1",
        Event = new EventInput
        {
            Id = "scene-1",
            Type = "scene",
            Title = "The ball",
            Position = Position.At(1),
            Participants = new() { "ana", "bob", "duchess" },
            Provenance = Provenance.Human(),
        },
        Claims = new() { new WitnessedClaim { Claim = theft, Canon = true } },
        Witnesses = new() { new Witness { Holder = "ana" }, new Witness { Holder = "bob" } },
    });

    var ana = engine.Holds(new HoldsPayload { ProjectId = projectId, Holder = "ana", Claim = theft });
    var bob = engine.Holds(new HoldsPayload { ProjectId = projectId, Holder = "bob", Claim = theft });
    var duchess = engine.Holds(new HoldsPayload { ProjectId = projectId, Holder = "duchess", Claim = theft });
    // Holds is also true for someone who believes the opposite; HoldsAs tells them apart.
    Console.WriteLine($"Ana knows: {ana.HoldsAs == HoldsAs.Same}"); // Ana knows: True
    Console.WriteLine($"Bob knows: {bob.HoldsAs == HoldsAs.Same}"); // Bob knows: True
    Console.WriteLine($"The duchess knows: {duchess.HoldsAs == HoldsAs.Same}"); // The duchess knows: False

    // The morning after: nobody saw anything and nobody told her, so this is a
    // record written directly. It takes its position and provenance from the event.
    var accusation = Claim.Entity("ana", "stole", "necklace");
    engine.Commit(new CommitInput
    {
        ProjectId = projectId,
        AttemptId = "morning",
        Event = new EventInput { Type = "scene", Title = "The morning after", Position = Position.At(2), Provenance = Provenance.Human() },
        Records = new() { Records.Suspects("duchess", accusation, Acquired.Authored(), confidence: 0.6) },
    });

    var suspicion = engine.Holds(new HoldsPayload { ProjectId = projectId, Holder = "duchess", Claim = accusation });
    var confidence = suspicion.Confidence?.ToString(CultureInfo.InvariantCulture);
    Console.WriteLine($"The duchess {suspicion.Attitude?.ToString().ToLowerInvariant()} Ana: {confidence}"); // The duchess suspects Ana: 0.6

    // Errors are exceptions that carry the protocol code.
    try
    {
        engine.Holds(new HoldsPayload { ProjectId = "missing", Holder = "ana", Claim = theft });
    }
    catch (NotFoundException error)
    {
        Console.WriteLine($"Error: {error.Code}"); // Error: NOT_FOUND
    }

    if (ana.HoldsAs != HoldsAs.Same || bob.HoldsAs != HoldsAs.Same || duchess.Holds || suspicion.Attitude != Attitude.Suspects)
    {
        return 1;
    }
}

File.Delete(path);
return 0;

Record what happened

One witness call writes the event, the Fact and the Holdings of Ana and Bob, atomically. The duchess is a participant, and gets no Holding.

Terminal
umwelt call story.umw witness '{
  "projectId": "ball",
  "attemptId": "scene-1",
  "event": {
    "id": "scene-1",
    "type": "scene",
    "title": "The ball",
    "position": { "tick": 1 },
    "participants": ["ana", "bob", "duchess"],
    "provenance": { "origin": { "kind": "human" } }
  },
  "claims": [
    {
      "canon": true,
      "factId": "theft",
      "claim": {
        "subject": "bob",
        "predicate": "stole",
        "object": { "kind": "entity", "id": "necklace" },
        "polarity": true
      }
    }
  ],
  "witnesses": [
    { "holder": "ana", "recordIds": ["ana-saw"] },
    { "holder": "bob", "recordIds": ["bob-did"] }
  ]
}'
Response
{
  "v": 1,
  "id": "call",
  "ok": true,
  "result": {
    "eventId": "scene-1",
    "recordIds": ["theft", "ana-saw", "bob-did"],
    "transitionIds": [],
    "version": 1,
    "warnings": []
  }
}

In the example above, this is the witness call: the same event, claim and witnesses, as typed objects. Further down, the morning after, the duchess suspects Ana: nobody saw or told her anything, so that record is written with commit.

Ask who knows

Terminal
umwelt call story.umw holds '{
  "projectId": "ball",
  "holder": "ana",
  "claim": {
    "subject": "bob",
    "predicate": "stole",
    "object": { "kind": "entity", "id": "necklace" },
    "polarity": true
  }
}'
Response
{
  "v": 1,
  "id": "call",
  "ok": true,
  "result": {
    "attitude": "knows",
    "confidence": 1,
    "holds": true,
    "holdsAs": "same",
    "polarity": true,
    "recordId": "ana-saw"
  }
}

After the scene, the example asks Ana, Bob and the duchess, and prints their answers.

holdsAs is same: Ana holds the claim as it was asked. Someone who believed the opposite would also answer holds: true, with holdsAs: "opposite", so a check for “does she know?” reads holdsAs.

The same question for bob answers true with his own record, bob-did. For duchess it answers false: she was there, and knows nothing. So does the question for Ana before the ball, at tick 0.

Reads never see later. A read at a position never sees anything recorded after it, on that axis or in a later version.

Next steps

Was this page helpful?