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.

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.
PreviousMessaging & Signals NextWhiteboards