OPAL
The unified command surface for operators, automation, and language models
OPAL (Operator Permission and Access Layer) is the single command surface every operator ā you, an automation rule, or a connected language model ā uses to make Korall Engineering™ do something.
The one-sentence version
Every operation in Korall Engineering™ that does something ā capture a screenshot, pair with a node, list mounted hives, run a diagnostic, flash firmware ā is wrapped as an OPAL Task. The same task runs the same way whether you click it, schedule it, or an LLM invokes it; only the permission check changes.
Why OPAL exists
Korall Engineering™ is built around a hard separation between the embedded layer (real-time motor and sensor control, where mistakes are dangerous) and the strategic layer (high-level intent: "drive to the workshop, pick up the box"). See the Embedded-Strategic Barrier for the full architectural background.
OPAL is the strategic layer's interface to the embedded layer. Two consequences fall out of this:
- LLMs can drive Korall Engineering™ safely. A model never reaches a motor directly; it composes OPAL Tasks, and every task declares the permissions it needs. The operator can grant some scopes and not others, and revoke them at any time.
- Every operator goes through the same door. Clicking a button, a trigger firing at 9am, and a chat message from a model all execute the same
OpalTaskwith the same code path. There is no "automation API" that diverges from "what the UI does."
The four pieces
| Piece | What it is |
|---|---|
| OpalTask | One unit of work. Declares its name, parameters, required permissions, execution hints (timeout, idempotent, can-be-skipped), and the keys of the values it produces. |
| OpalTaskRegistry | The catalog of every task the running app knows about. Tasks are registered by domain (catalog, navigation, screenshot, batch, ā¦). |
| TaskPlan | An ordered list of task invocations, serialisable to JSON. Steps can pass values to subsequent steps via output keys. The unit a plan author / LLM composes. |
| OpalTaskRuntime | The executor. Runs a TaskPlan step by step on a background thread, enforces permissions, reports progress, and records the outcome to the history database. |
A Task is to a button what a TaskPlan is to a workflow.
Three operators, one path
Every task invocation carries an operator type. The runtime checks permissions differently for each, but the task itself doesn't care who called it.
| Operator | Permission check | Example |
|---|---|---|
| Sentient (you) | None ā you're the device's authority | You click "Pair via QR" in the Hub |
| Automation (trigger / directive) | Pre-approved at the time the rule was created | A schedule fires "Capture all widgets" every Monday |
| LLM | Live check against the model's granted scopes; unknown scopes prompt for approval | An LLM chats "list my nodes", which resolves to node.list |
The same node.list task in all three cases. The same code. Different gate.
Permissions and audit
Each task declares the permission scope it needs (e.g. catalog.capture, node.create, filesystem.write). The operator's grants are checked at execution time:
- A Sentient operator always passes ā you are the device's authority.
- An Automation operator passes if the rule was approved when it was authored.
- An LLM operator passes only if the model has been explicitly granted that scope. Unknown scopes pop a confirmation activity ("Allow this model to call
filesystem.write?") that you can accept once or for the whole conversation.
Every execution ā successful, failed, or denied ā lands in OpalHistoryDatabase, visible in the OPAL History activity. So you can always see exactly what was done, by whom, against which Sentient and Node, with what parameters, and what came out.
Composition: TaskPlans
A single task does one thing. A TaskPlan is a JSON document chaining several:
{
"steps": [
{"task": "catalog.list_by_tag", "tag": "trust"},
{"task": "catalog.capture", "id": "$1.results[0].id"},
{"task": "filesystem.copy", "from": "$2.image_path", "to": "/exports/"}
]
}
Outputs of one step are addressable by the next ($1.results[0].id). This is the surface LLMs use to express multi-step intent, and what the app uses internally for batch jobs and triggers.
Tasks come from domains
Each subsystem registers its own tasks into the central registry. The domains that exist today:
| Domain | What it controls |
|---|---|
| Catalog | Browse and capture widgets (the widget catalog system) |
| Navigation | List and open activities in the running app |
| Screenshot | Take a screenshot of the screen or a widget |
| Batch | List, query, and cancel background jobs |
| Filesystem | Sandbox-restricted file operations |
| Directives | Create, list, pause, resume, and delete trigger directives |
| Constellation | Manage your node constellation ā list, create, launch, stop |
| Help | Search, list, outline, read, and open documentation pages |
| Serial | Manage serial connections (list ports, open, close, configure) |
| BanterLab | Drive the BanterLab parameter / codec / testset workflow |
| Shade | Run LLVM-compiled compute shaders |
| Sentient | Create / delete / select Nodes, set Sentient password, export recovery |
| Construct | Edit a Construct scene (add / remove / rename items, set transforms) |
See the OPAL reference for the per-task catalogue.
Where you see OPAL in the UI
| UI surface | What it shows |
|---|---|
| OPAL activity | Direct task explorer ā pick a task, fill its parameters, run it. The "developer's REPL" for the app. |
| OPAL History activity | Audit log: every task that ever ran on this Sentient, parameters, outcome, who invoked it |
| Sentient Permissions activity | Per-LLM grant list ā which scopes each model has, accept/revoke individually |
| BatchActivity | Long-running OPAL Tasks that report progress show up here |
| Directives activity | Trigger rules that fire OPAL Task plans on a schedule or condition |
| LLM chat overlay | Every assistant action is one or more OPAL Tasks ā you see the names in the conversation transcript |
Common questions
Can I write my own task? Yes ā see the internal developer documentation. A task is a C++ class with a name, a parameter list, a permission scope, and an execute() method. Once registered in any of the Register<Domain>Tasks files, it's available to every operator.
Are tasks stable across versions? Task names and parameter shapes are part of the public surface and follow semantic versioning. Renames go through a deprecation window with an alias.
Can a TaskPlan be interrupted? Yes ā the runtime supports cooperative cancellation. Tasks that are interruptible (most I/O-bound work) can be canceled mid-execution; CPU-bound tasks finish their current step before stopping.
Does OPAL run only on the Hub? No ā every node type (Agent, Remote, Hub) has an OPAL runtime. The set of available tasks differs by node: an Agent has access to its own actuators, a Remote can drive a paired Agent, a Hub can introspect any connected node.
Component reference
| Component | Role |
|---|---|
OpalTask |
Base class ā name, params, permissions, hints, output keys, execute() |
OpalTaskRegistry |
Central catalog of registered tasks |
OpalTaskRuntime |
Background executor; permission enforcement; progress; cancellation |
TaskPlan |
Serialisable ordered list of task invocations |
OpalHistoryDatabase |
Audit log of every execution |
OPALActivity |
UI task explorer |
OpalHistoryActivity |
UI for the audit log |
SentientPermissionsActivity |
Per-LLM grant management |
OpalTaskBatchWorker |
Bridges long OPAL Tasks into the Batch system |