CMLabs · Psyclone AIOS

# 9. Data Messages

The DataMessage is Psyclone's universal container: typed header, microsecond timestamps, and user contents from single integers to binary blobs and nested messages.

## Anatomy of a DataMessage

Every message flowing through a Psyclone system is a DataMessage. It has two parts:

- Header — like an email header: the message type, sender, addressee (`to`), creation time, tags and the internal reference used for query replies. PsyProbe shows exactly these fields when you inspect a message.
- User contents — any number of named entries you set yourself: strings, integers, floats, times, binary data, and even other DataMessages.

Timestamps are 64-bit with microsecond resolution and are synchronised across the computers of a distributed system — you can meaningfully compare the creation times of messages produced on different machines, which matters for sensor fusion and offline analysis.

## Message types

The type is a dot-delimited ASCII string: the first part is the root namespace and each further part increases specificity left to right — `input.video.raw`, `input.audio.normalised`, `Robot.Status.Navigate.Done`. Types are what triggers subscribe to (including wildcards like `input.audio.*`); the full story is in Messaging & Signals. A message that matches no subscription is discarded — unless it was posted with a `ttl`, in which case it is kept available for that long.

Setting a type happens in the PsySpec, not in code: the crank posts by post name and the spec decides the type:

    <post name="exampleMsg" type="Robot.Status" />

    DataMessage* msg = new DataMessage();
    msg->setString("RobotStatus", "Almost ready");
    msg->setInt("BatteryLevel", 78);
    api->postOutputMessage("exampleMsg", msg);   // spec maps name -> Robot.Status

    msg = cmsdk.DataMessage()
    msg.setString("RobotStatus", "Almost ready")
    msg.setInt("BatteryLevel", 78)
    api.postOutputMessage("exampleMsg", msg)   # spec maps name -> Robot.Status

## User content entries

Entry types: String, Int, Float, Time, Binary and Message (a nested DataMessage). Each entry is addressed by name:

    msg->setString("Utterance", "hello there");
    msg->setInt("Count", 42);
    msg->setFloat("Confidence", 0.93);
    // binary blob (e.g. an image frame)
    msg->setData("Frame", pixelBuffer, bufferSize);
    // nested message
    DataMessage* inner = new DataMessage();
    inner->setInt("x", 10);
    msg->setMessage("Position", inner);

    msg.setString("Utterance", "hello there")
    msg.setInt("Count", 42)
    msg.setFloat("Confidence", 0.93)
    # binary blob (e.g. an image frame)
    msg.setData("Frame", pixelBuffer)
    # nested message
    inner = cmsdk.DataMessage()
    inner.setInt("x", 10)
    msg.setMessage("Position", inner)

Tip. Binary entries make Psyclone a practical glue layer: attach raw camera frames, audio buffers or serialised third-party structures directly — no re-encoding, and PsyProbe can still show the surrounding header and text fields live.

### Arrays (integer index)

Every entry type also comes in an array form, indexed by integer:

    msg->setInt(1, "myarray", 2);      // myarray[1] = 2
    msg->setInt(3, "myarray", 7);      // myarray[3] = 7
    int64 v = msg->getInt(3, "myarray");

    // whole-array access
    std::map<int64,int64> arr = msg->getIntArray("myarray");
    msg->setIntArray("myarray2", arr);

    msg.setInt(1, "myarray", 2)      # myarray[1] = 2
    msg.setInt(3, "myarray", 7)      # myarray[3] = 7
    v = msg.getInt(3, "myarray")

    # whole-array access (index -> value mapping)
    arr = msg.getIntArray("myarray")
    msg.setIntArray("myarray2", arr)

### Maps (string index)

And in a map form, indexed by string:

    msg->setInt("in",  "mymap", 1);    // mymap["in"]  = 1
    msg->setInt("out", "mymap", 0);
    int64 n = msg->getInt("out", "mymap");

    std::map<std::string,int64> m = msg->getIntMap("mymap");
    msg->setIntMap("mymap2", m);

    msg.setInt("in",  "mymap", 1)    # mymap["in"]  = 1
    msg.setInt("out", "mymap", 0)
    n = msg.getInt("out", "mymap")

    m = msg.getIntMap("mymap")
    msg.setIntMap("mymap2", m)

Array and map forms exist for all entry types — int, float, string, time, binary data and nested messages — with the same naming pattern (`getFloatArray`, `setStringMap`, and so on).

## Memory ownership (C++)

Three simple rules cover who deletes what in a C++ crank:

You got it from… | Owner | Rule
--- | --- | ---
`waitForNewMessage()` (also `waitForNewSignal()`) | Framework | The message is owned by the system — never delete it in the crank.
A message you `new` and pass to `postOutputMessage()` | Framework (after posting) | Posting hands ownership to the framework — do not delete or reuse it after posting (it is consumed even when the post fails or matches no post spec).
`getPrivateDataCopy()` and similar copy-returning calls | You | The buffer is a fresh `new char[]` copy — `delete[]` it when done.

Python cranks can ignore all of this — the bindings manage lifetimes for you.

## A realistic example

A vision module posting a detection to the rest of the system might build:

    DataMessage* out = new DataMessage();
    out->setString("Identity", "Thor");
    out->setFloat("PosX", 2.41);
    out->setFloat("PosY", -0.87);
    out->setFloat("Confidence", 0.88);
    out->setData("FaceCrop", cropBuf, cropSize);        // image/bmp blob
    int posted = api->postOutputMessage("HumanDetected", out);
    if (posted < 0) api->logPrint(1, "post failed: %d", posted);

    out = cmsdk.DataMessage()
    out.setString("Identity", "Thor")
    out.setFloat("PosX", 2.41)
    out.setFloat("PosY", -0.87)
    out.setFloat("Confidence", 0.88)
    out.setData("FaceCrop", cropBuf)        # image/bmp blob
    posted = api.postOutputMessage("HumanDetected", out)
    if posted < 0:
        api.logPrint(1, "post failed: %d" % posted)

Downstream, a CCMCollector-style component can extract `PosX`/`PosY` into world-model observations, PsyProbe operators can hover the message in the Activity tab and see every entry, and a ReplayCatalog can record it — all from the same self-describing container.

Note. `postOutputMessage` returns the number of messages actually posted; negative values are errors: `POST_FAILED` (-1), `POST_NOSPEC` (-2, posted before any trigger arrived), `POST_OUTOFCONTEXT` (-3, the active context changed). See Messaging & Signals.
PreviousCatalogs NextDistributed Systems