Skip to main content

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:

ModeConnectionIdentity
Hosted in Paracordproxied through the consoleinherits your Paracord session
Standalonedirect to the brokerthe 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.

  • Node View — the skills and actors your panel can address
  • Ledger — every conversation your panel opens is recorded there, and conversationId links the two