CMLabs · Psyclone AIOS

# 13. PsySpec XML Reference

Every PsySpec element and attribute, with an honest status badge for each: what ships, what is parsed but inert, and what is still roadmap.

## How to read this chapter

This is the lookup chapter, extracted from the current parser code. Each element carries a badge:

- Shipped — parsed and functionally wired.
- Stub — parsed into internal structures but not (or only partially) acted on.
- Roadmap — documented/reserved syntax; no real parsing or behaviour yet.

## Status summary

Element | Status | Notes
--- | --- | ---
`<psyspec>` | Shipped | Root tag; new SSL policy attributes
`<include>` | Shipped | XML file inclusion
`<variable>` | Shipped | `%var%` substitution + new cmdline override
`<node>` | Shipped | Multi-node topology; per-node `<library>`
`<library>` | Shipped | DLL/.so registration
`<space>` | Shipped | Only `type="external"` handled at top level
`<interface>` | Shipped | Auth/encryption children stubbed; new SSL attrs
`<service>` | Stub | Partial: custom services have a NULL receiver
`<psyprobe>` | Shipped | Port / location / alias / post
`<module>` | Shipped | Per-module verbose/debug/logfile now wired
`<whiteboard>` | Shipped | `root=` persistence is parsed-only
`<catalog>` | Shipped | File/Data/Replay + CCM/TimeSeries families
`<stream>` | Stub | Component created, backing function empty
`<feed>` | Roadmap | Log-only branch, never becomes a component
`<supervisor>` | Roadmap | Log-only branch
`<monitoring>` / `<performance>` | Roadmap | “Not yet implemented” log
`<group>` | Roadmap | Log-only branch
`<trigger>` | Shipped | Filters, interval, context triggers
`<filter>` | Shipped | 7 comparison types + maxage
`<triggergroup>` | Shipped | Declarative multi-trigger join (all/N-of-M, time windows, snapshot, etc.)
`<triggers>` | Stub | Recognised, silently does nothing
`<retrieve>` | Shipped | Whiteboard/catalog retrieval spec
`<query>` | Shipped | Incl. remote host/port queries
`<retrieves>` / `<posts>` / `<signals>` | Stub | Empty branches
`<crank>` | Shipped | Function / script / inline / external
`<post>` | Shipped |
`<signal>` | Shipped | Minimal: name + type only
`<context>` (in module) | Shipped | Groups triggers under a named context
`<parameter>` | Shipped | Typed values incl. ranges/lists/random
`<setup>` / `<key>` / `<customview>` | Shipped |
`<recording>` / `<playback>` | Stub | Parameters stored, not acted on
`<executable>` | Stub | Cmdline stored, never launched
`<alias>` (in psyprobe) | Shipped |
`<authentication>` (in interface) | Stub | Parser body empty
`<phase>` | Roadmap | No parsing code at all
`system=` trigger attribute | Roadmap | Use `context=` instead

## <psyspec> (root) Shipped

The root element of every spec. Besides `port=` (main system port, see System Guide), it now carries the global SSL client-verification policy:

Attribute | Meaning | Default
--- | --- | ---
`allowselfsigned` | New. `yes`/`true`/`1` loosens verification (accepts self-signed certificates) for all SSL client connections. Per-interface value overrides either way. | secure — verify peer
`cafile` | New. Path to a CA PEM file used for SSL verification instead of the OS trust store. | OS trust store
`capath` | New. Directory of CA certificates, same mechanism as `cafile`. | OS trust store

Note. Top-level `verbose`/`debug`/`logfile` on the root element are Roadmap — only per-component logging attributes are wired. System-wide levels are set on the command line.

Children: `<include>`, `<variable>`, `<node>`, `<library>`, `<space>`, `<interface>`, `<service>`, `<psyprobe>`, `<whiteboard>`, `<catalog>`, `<module>`, `<stream>`, plus the roadmap tags listed above.

## <include> Shipped

`<include file="system.xml" />` splices the referenced file's contents into the spec (like a C include) and reparses; errors in the expanded spec report line/column. `file` is required. Includes and variables are processed before anything else.

## <variable> Shipped

`<variable name="SystemID" value="2" />` defines a literal `%SystemID%` text substitution applied over the whole spec before XML parsing — usable in names, cranks, triggers, anywhere, as long as the result stays valid XML. Both `name` and `value` are required.

Tip. New: a `name=value` argument on the Psyclone command line overrides the spec default — `Psyclone spec=robot.xml SystemID=3`. Reserved parameters (`spec=`, `port=`, `html=`, `verbose=`, `debug=`, per-topic variants) are respected and not treated as variables.

## <node> Shipped

Attribute | Meaning | Default
--- | --- | ---
`name` | Node name; `Main` (or no/local address) reconfigures the local node | —
`addr` / `address` | IP or hostname (`addr` preferred, `address` accepted) | local
`type` | Node type string (`adhoc`/`java` types not implemented Roadmap) | —
`port` | Port; for the local node a differing port adds Main/PsyProbe/Console interfaces on it | 0
`timeout` | Connect timeout (ms) | 0

Child: `<library name library path>` — libraries loaded only on that node.

## <library> Shipped

`<library name="MyExamples" library="Examples" path="../libs" />`. `name` is how cranks refer to it (`MyExamples::Ping`); `library` is the file name, resolved as `Examples.dll` (Windows) / `libExamples.so` (UNIX) — debug builds first try the `...Debug` variants; `path` is an optional directory; `node` restricts to a node.

## <space> Shipped

`<space name="GUISpace" type="external" />`. At the top level only `type="external"` is handled (creates a process-map entry waiting for an external program to connect). Regular spaces are created implicitly by the `space=` attribute on components (default `Root`). `node` restricts to a node. See Distributed Systems.

## <interface> Shipped

    <interface name="MyWebInterface" node="Node0" port="1234" protocol="HTTP"
               service="MyWebService" timeout="3000" default="yes" />

Attribute | Meaning | Default
--- | --- | ---
`name` | Interface name (required; parse fails without) | —
`service` | Target service name (required) | —
`node` | Node the interface lives on | any/local
`port` | TCP/UDP port (required) | —
`protocol` | `Message` | `HTTP` | `Telnet` | `RAW` (anything else = parse failure) | unset
`ip` | `UDP` selects UDP; otherwise TCP | TCP
`timeout` | Timeout ms | unset
`default` | `yes` = default interface on a shared port | no
`allowselfsigned` | New. Per-interface SSL verification override; wins over the global setting either way | inherit global
`cafile` / `capath` | New. Per-interface CA file/directory override | inherit global

Child `<authentication>`: Stub — parsed by an empty function body; interface encryption attributes are likewise stubbed.

## <service> Stub (partial)

Attributes: `name`, `rootdir`, `subdir`, `timeout`, `node`. The service is registered and a component is created for its subscription, so `<trigger>`/`<post>`/`<crank>` children parse — but custom services register a NULL receiver, so inbound requests are discarded. Built-in PsyProbe/Console services work. See Services & Interfaces.

## <psyprobe> Shipped

Attribute | Meaning | Default
--- | --- | ---
`defaultport` | Any value other than `no` removes PsyProbe from the main port | shares main port
`port` | Adds a dedicated PsyProbe HTTP interface on this port | —
`location` | Web root directory for PsyProbe | built-in

Children: `<alias name location default>` (URL alias to a local directory with optional default file) and `<post>` (same attributes as a module post plus XML content, registered as a clickable manual post in PsyProbe).

## Components: <module> / <whiteboard> / <catalog> / <stream>

All component tags share one parser; the tag name selects the type. `<module type="external">` declares an external process component (connects via a space). `<module count=N>` tag-instancing is Roadmap. Common attributes:

Attribute | Meaning | Default
--- | --- | ---
`name` | Component name (required) | —
`tag` | Appends `_tag` to the name; `*` uses the component's own name as tag | none
`node` | Node assignment; modules also accept `*` = every node | config node
`space` | Process space; Python-crank modules auto-get their own space | Root
`selftrigger` | `allow` | `warn` | anything-else = none | warn
`logfile` | New — now wired. Per-module log file | node log
`verbose` / `debug` | New — now wired. Per-module verbosity/debug levels (0–9) | node levels
`migration` | `yes` flags the component movable; runtime migration itself is parsed-only Stub | no
`priority` | uint8 stored; scheduling effect parsed-only Stub | 0

Extra attributes for whiteboard/catalog/stream/service:

Attribute | Meaning | Default
--- | --- | ---
`function` / `type` | Backing crank `Lib::Func`; bare names get the `PsySystem::` prefix. Defaults: whiteboard→`PsySystem::Whiteboard`, stream→`PsySystem::Stream`; a catalog/service with neither is an error. Catalog `type=` selects the family: File/Data/Replay/RequestStore and the CCM & TimeSeries families are Shipped; the `Media` and `Google` catalog types are Stub — do not rely on them. | see left
`root` | Storage root directory (whiteboard `root=` persistence is parsed-only Stub) | —
`subdir` / `ext` | Subdirectory / file extension | —
`operation` | Default operation | —
`key` + `keytype` | Adds an index key (repeatable via `<key>` children) | time index
`timeout` / `interval` | ms | 0
`maxcount` / `maxsize` | Max entries / max bytes | 0
`rotate` | `yes`/`no` rotation (replay looping) | no

### Component children

Child | Status | Meaning
--- | --- | ---
`<recording root filesize overwrite>` | Stub | Stores recording parameters; not acted on per-module
`<playback root interval>` | Stub | Stores playback parameters; not acted on
`<executable consoleoutput autorestart>cmdline</executable>` | Stub | Command line stored but never launched (roadmap: auto-start external processes)
`<customview name template>` | Shipped | Registers a PsyProbe custom view; also accepts an inline XML/text body instead of a `template=` file
`<setup>` | Shipped | Free-form XML/text blob, read via `getParameterString("componentsetup")`; `file=` variant supported
`<key name type>` | Shipped | Extra index keys
`<parameter>` | Shipped | See below
`<context name>` | Shipped | See below

## <parameter> Shipped

`<parameter name=".." type="String|Integer|Float" value=".." interval=".." />` (type defaults to String). Rich value syntax:

Form | Example | Meaning
--- | --- | ---
Plain value | `value="10"` | Fixed initial value
Range | `value="[0:110]"` | Bounded range, initial = lowest
Stepped range | `value="[0:110:2]"` | Step 2; step can also come from `interval="2"`
Range + initial | `value="[0:110]=55"` | Initial value 55
Random initial | `value="[0:110]=*"` | Random initial within the range
Lists | `value="[-2.5;1.5;1.8]"`, `value="['a';'b';'c']"` | Enumerated numeric or string choices

All parameters are queryable, settable and tweakable at runtime (`getParameterString/Int`, `setParameter`, `tweakParameter`, `resetParameter`) — see Modules.

## <context> (in a component) Shipped

`<context name="SoB.Alive.Awake">...</context>` groups child trigger/post/retrieve/query/crank/signal elements under a named context instead of the default `Psyclone.Ready`. See Contexts.

## <trigger> Shipped

Subscribes the component to messages; a match dispatches its cranks. Valid if it has a `name` plus at least one of `type`, `from`, `to`; duplicate names are an error.

Attribute | Meaning | Default
--- | --- | ---
`name` | Trigger name (required, unique) | —
`type` | Message type to match (wildcards allowed: `input.audio.*`) | —
`from` / `to` | Only messages from this component / addressed to this component | any
`interval` | ms — makes it a timer trigger (needs one real message first to start) | 0
`delay` / `after` | ms delay before firing (`after` is an alias) | 0
`context` | Fire on context change to this context (forces type `CTRL_CONTEXT_CHANGE`) | —
`maxage` | ms — becomes a MAXAGE filter | ∞
`tag` | Match tag; `*` = component's own name | any

Trigger `priority`/`history` and the `system=` attribute are Roadmap — use `context=` to react to context changes.

### <filter> (child of trigger) Shipped

    <filter type="haskey" key="SomeKey" />                      <!-- entry exists (any type) -->
    <filter type="equals" key="camera" value="front-left" />    <!-- string ==, exact, case-sensitive -->
    <filter type="notequals" key="state" value="disabled" />    <!-- string != -->
    <filter type="equalsnumeric" key="mode" value="3" />        <!-- float == -->
    <filter type="notequalsnumeric" key="channel" value="0" />  <!-- float != -->
    <filter type="greaterthan" key="temp" value="80" />         <!-- float > (≥ at the epsilon boundary) -->
    <filter type="lessthan" key="pressure" value="2.5" />       <!-- float < (≤ at the epsilon boundary) -->

`type` is one of `equals` (exact, case-sensitive, no wildcards), `notequals`, `equalsnumeric`, `notequalsnumeric`, `greaterthan`, `lessthan` (all take key+value; numeric values are parsed as float64 with an epsilon compare, so exactly-equal values also pass greaterthan/lessthan) and `haskey` (key only, matches any entry type). The eighth filter operation, `maxage`, is written as a trigger attribute (milliseconds) rather than a `<filter>` child. Unrecognised types are silently ignored.

## <retrieve> Shipped

On-demand fetch from a whiteboard/catalog when the owning trigger fires (see Whiteboards).

Attribute | Meaning | Default
--- | --- | ---
`name` | Required, unique | —
`source` | Component to retrieve from (required) | —
`type` / `from` / `to` | Type / original sender / addressee filters | any
`maxcount` | Max messages (0 = all) | 0
`maxage` | Age window, stored ×1000 (exact wall-clock unit under review — see Whiteboards) | ∞
`tag` | Tag filter; `*` = own name | any
`key` / `keytype` | Index key; keytype `string`|`integer`|`float`|`time` (invalid drops the spec) | `time`
`start` / `end` | Range bounds, interpreted per keytype | open

## <query> Shipped

Free-form query to a catalog/component; catalogs dispatch on the Query/Operation fields. Attributes: `name` (required, unique), `source` (required), `type` (string), `subdir`, `ext`, `binary` (`yes`), `maxcount`, `maxage` (stored ×1000; unit under review), `key`, `value`, `operation`, and for remote queries `host` (DNS-resolved at parse time) + `port`.

## <crank> Shipped

The processing function attached to triggers. Four forms:

    <crank name="c1" function="lib::function" />            <!-- C/C++ library crank -->
    <crank name="c2" language="python" script="file.py" />  <!-- external script file -->
    <crank name="c3" language="python">inline code</crank>  <!-- inline script -->
    <crank name="c4" />                                     <!-- external crank slot -->

A function without `lib::` is an internal (built-in) crank. Duplicate crank names across components are rejected. Modules with no crank get the built-in passthrough (`Internal::Simple`) default crank. Java cranks are Roadmap.

## <post> Shipped

Message posted when the owning trigger fires. Valid if it has a `name` and (`type` or `context`).

Attribute | Meaning | Default
--- | --- | ---
`name` | Required, unique | —
`type` | Type of the posted message | —
`context` | Post a context change to this context | —
`to` | Address to a specific component (also still delivered to subscribers) | broadcast
`ttl` | Seconds (stored ×1000 ms); 0 = no expiry | 0
`guaranteed` | `no` marks the message non-guaranteed | guaranteed
`tag` | `*` = own name | none

Message content comes from child XML elements; the older `contentkey`/`content` attribute form is no longer parsed. `time="trigger"` is Roadmap.

## <signal> Shipped (minimal)

`<signal name="Output" type="My.Signal.1" />` — both attributes required; no others are parsed. See Messaging & Signals.

## <triggergroup> declarative join Shipped

Joins several triggers into one crank activation: member messages sharing the same tag are buffered on the node until the group completes, then delivered together as one set (wakeups ≈ joins, not one per member). Untagged messages group under the literal empty-string tag `""` and never mix with real tags. This is the primitive for multi-modal fusion (audio + text + vision joined before a decision crank runs).

    <module name="Simple" node="Node0">
      <triggergroup name="ts" count="2" maxage="2000">
        <trigger name="t1" type="bla1" />
        <trigger name="t2" type="bla2" />
        <trigger name="t3" type="bla3" optional="true" maxage="200" />
      </triggergroup>
      <crank name="c1" function="lib::function" />
      <post name="p1" type="bla4" to="WB/Stream/Cat" />
    </module>

Attribute | Meaning | Default
--- | --- | ---
`name` | Required, unique within the module. A duplicate group name discards the extra group with a level-1 warning; a group missing `name` or `maxage` is skipped with a warning. | —
`maxage` | ms — required, no default. A partial set living longer than this is evicted. | —
`count` | Fire when any N members are present (`"all"` = every named member). For N-of-M, any N complete the set; members arriving after the set closes are discarded. | `all`
`min` | Fire at N members, then re-fire as more of the same tag-set arrive (growing set). | —
`within` | ms — all required members present and their post times (send time) within the window. | —
`time` | ms — as `within` but on content times (created time). `time="0"` = identical content time. | —
`mode` | `snapshot` — fire on the primary trigger (first child, or `primary="true"`), attaching the latest value of each other member: no wait, no tag match. | —
`slide` | Sliding-window correlation (with `within=`): re-emit a fresh joined set each time a member updates while the window still holds. | off
`debounce` | ms — coalesce fast member streams: fire at most once per N ms with the latest set. | —
`dup` | On a duplicate member before completion: `latest` (latest-wins) or `keep` (queue all). | `latest`
`order` | `strict` — only fire if members arrived in declared order (pipeline-stage joins). | off
`timeout` / `ontimeout` | On `timeout="<ms>"` (or `maxage`) expiry with an incomplete set: `ontimeout="fire"` partial-fires (with a level-1 warning) or `"discard"` drops the set. | `fire`

Child `<trigger>` elements accept all their normal attributes (they still route individually) plus:

Attribute | Meaning
--- | ---
`optional="true"` | Member is not required for completion; included in the delivered map if present.
`maxage` (per member) | A member older than its own freshness bound does not count toward completion.
`primary="true"` | Marks the primary trigger for `mode="snapshot"` (defaults to the first child).

SDK. A completed (or partial-fired) set arrives as one grouped activation. The crank calls `waitForNewMessageGroup(ms, groupName)`, which returns a `std::map<std::string, DataMessage*>` keyed by trigger name — optional/absent members are simply not in the map (check with `.count("t3")`). `getCurrentTriggerName()` returns the group name. Messages are owned by the API for the activation and freed on the next `waitForNewMessageGroup()`/`waitForNewMessage()` call. Existing single-message cranks are untouched.

## Stub grouping elements Stub

`<triggers>`, `<retrieves>`, `<posts>` and `<signals>` are recognised by the parser but their handling bodies are empty — they silently do nothing.

## Roadmap-only elements Roadmap

`<feed>`, `<supervisor>`, `<monitoring>`, `<performance>` and `<group>` are logged and ignored; `<phase>` has no parsing code at all. None of these produce components or behaviour in the current build — do not rely on them.

## Cross-cutting rules

- Config-node scoping: elements without `node=` run on the config node; with `node=` only on the named node.
- Expansion order: variables and includes are processed before all other parsing; the spec is reparsed after each expansion.
- Code escaping: inside the spec, message content and setup blobs must remain valid XML.

PreviousPsyProbe NextTutorials