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.
<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()andstd::string contextToText(PsyContext).
shouldContinue() == false as a normal, frequent event — save state, exit, and trust the platform to run the right crank when the context returns.