CMLabs · Psyclone AIOS

6. Contexts

Contexts let modules behave differently per situation — and let one message reroute the dataflow of the whole system — without any in-code state management.

Why contexts?

Suppose a vision module should use one algorithm in a bright scene and another in a dark one. You could manage that inside the code — or you could give the module a separate set of inputs and outputs per context and let an external signal switch between them. With contexts, the active context plus the PsySpec make module behaviour fully observable: look at the spec and the current context, and you know exactly which triggers, cranks and posts are live.

The context tree

Contexts are hierarchical, dot-notation trees, e.g. SoB.[Alive, Dead] and SoB.Alive.[Awake, Asleep]. Several context roots can co-exist (e.g. SoB.* and System.Ok). If no context is specified, everything lives in the default context Psyclone.Ready, which is always active while the system runs.

Declare per-context behaviour with <context> blocks inside a module:

<module name="Looking">
  <context name="SoB.Alive.Awake">
    <trigger name="t1" type="bla1" />
    <crank name="c1" function="lib::function" />
    <post name="p1" type="bla4" />
  </context>
</module>

One module can hold multiple <context> blocks with different cranks; all blocks share the module's private data.

Switching contexts

Contexts are switched by a flag on a post:

<post name="done" context="SoB.Alive.Asleep" />

Posting SoB.Alive.Asleep deactivates the sibling branches of the same root — SoB.Dead, SoB.Alive.Awake and deeper branches like SoB.Alive.Asleep.Snoring — while the ancestors (SoB, SoB.Alive) stay active. The switch only happens when a current branch is actually replaced; posting an already-active ancestor does nothing.

Context triggers

Modules can react to a context change itself:

<trigger name="t1" context="SoB.Alive.Asleep" />

This fires when the system switches to that context — useful for initialisation, cleanup or announcing state.

Global reroute: one graph, many behaviours

Because each module's live subscriptions depend on the active context, a single context-change post effectively reroutes the dataflow of the whole system in near real time — switching algorithms, muting pipelines, or waking entire subsystems at once.

Context: Scene.Bright Context: Scene.Dark Camera FastTrackeractive IRTrackerinactive Planner Camera FastTrackerinactive IRTrackeractive Planner post context= Scene.Dark
The same modules under two contexts: one <post context="Scene.Dark"> flips which trigger sets are live, rerouting the whole pipeline — no code changes, no restarts.

Being out of context

When the context switches away while a crank is working:

  • api->shouldContinue() returns false — continuous cranks should exit their loop cleanly.
  • Posting from an out-of-context crank returns POST_OUTOFCONTEXT (-3).
  • Internal modules automatically use the crank belonging to the active context.
  • External modules must check for themselves: PsyContext ctx = getCurrentTriggerContext() and std::string contextToText(PsyContext).
Tip. Design cranks to treat shouldContinue() == false as a normal, frequent event — save state, exit, and trust the platform to run the right crank when the context returns.
Psyclone AIOS · CMLabs