Panels
A panel is a web application that runs inside Paracord and speaks PNCP directly. It is a participant on your paranet rather than a client of an API in front of one: it opens conversations, answers questions, provides skills and observes traffic, using the same protocol your actors use.
Panels are how you put purpose-built UI on a paranet — an operations console, a review queue, a domain-specific dashboard — without forking Paracord.
What a panel can do
Through @otonoma/paracord-panels-interface:
- Call skills and follow the resulting conversation, message by message
- Answer questions raised by a suspended workflow, resuming it
- Provide skills, so other actors can address requests to the panel
- Observe conversations between other actors, including ones the panel did not start
- Query the paranet's GraphQL API for actors, skills and history
Two ways to run
The same panel runs in two modes, and the SDK selects between them:
| Mode | Connection | Identity |
|---|---|---|
| Hosted in Paracord | proxied through the console | inherits your Paracord session |
| Standalone | direct to the broker | the panel authenticates itself |
Hosted is how a panel runs in production. Standalone is useful during development, and for panels that need to run outside the console.
Write against ManagedPncpClient and the difference is confined to one function:
import {
isInParacord,
ManagedPncpClient,
ParacordClient,
PncpRpcClient,
} from "@otonoma/paracord-panels-interface";
export function createClient(): ManagedPncpClient {
if (isInParacord()) {
// Hosted: Paracord brokers every call and supplies the session.
return new ManagedPncpClient(new ParacordClient());
}
// Standalone: connect to the broker directly.
const broker = import.meta.env.VITE_BROKER_URL;
return new ManagedPncpClient(
new PncpRpcClient({
actorId: "root",
actorVersion: "1.0.0",
broker,
service: `${broker}/api/paranet-service`,
}),
);
}
In standalone mode the panel logs in itself, so it needs somewhere to collect credentials. Hosted panels never see credentials at all.
Working with conversations
sendRequest calls a skill and returns once the conversation resolves. When you
need the exchange as it happens — interim status, a question, then the response —
open a conversation and read from it:
const conv = await client.createConversation({
subject: "incident",
action: "open",
body: { severity: "high", summary: "Checkout latency" },
});
for (;;) {
const msg = await conv.next();
if (!msg) break; // conversation closed
// Render status updates, answer questions, handle the final response.
}
A ConversationHandler exposes sendAnswer for questions the workflow asks,
sendResponse for conversations the panel is fulfilling, and conversationId
for correlating with the Ledger.
Answering a question
When a workflow suspends on a question, it is waiting for a participant to
answer. A panel can be that participant: read the question from the conversation,
present whatever interface suits it, and call sendAnswer. The workflow resumes
from the point it paused.
Observing
An observer receives conversations the panel is not part of:
const observer = await client.createObserver(
{ subject: "incident", action: "open" },
(msg) => {
// every incident/open conversation on the node, not only ours
},
);
// later
await observer.close();
Neither the requester nor the fulfiller is aware of the observer, and neither has to be modified for it to work. This is how a panel builds an audit or supervision view over traffic it does not participate in.
Declaring a panel
Panels are declared at package level in paranet.yaml, alongside models::
panels:
- id: oncall-console
name: On-call console # the label Paracord shows
version: 0.0.1
path: ./panel/dist/ # built output, not source
The available fields are id, name, version, authors, render_info,
path and build. render_info accepts width, height and extras.
Deploying
path points at built output, so build the panel first. Panels are registered as
part of the package, not with the actors:
cd panel && npm run build
cd .. && para docker deploy package --node <your-node> -y
Look for Uploading panel <id> from <path> in the output. The files are stored on
the node and served from it.
Reload Paracord after deploying. The panel list is read when the page loads, so a newly deployed panel appears on the next refresh.
Developing against a live server
Rebuilding and redeploying for every change is slow. Point Paracord at a dev
server instead, using render_info.extras.url:
panels:
- id: oncall-console
name: On-call console
version: 0.0.1
path: ./panel/dist/
render_info:
extras:
url: "http://localhost:5174"
Paracord loads the panel from that URL instead of the uploaded bundle, so edits appear on refresh. Remove the override before shipping, so the deployed bundle is what runs.
Where panels appear
A deployed panel appears in Paracord's Panels section, listed by the name
you declared. Where several versions of a panel are deployed, the newest is
offered.
Related
- Node View — the skills and actors your panel can address
- Ledger — every conversation your panel opens is recorded there, and
conversationIdlinks the two