Skip to main content

An application's presence

An application meets the person as itself: its face, its name and its welcome, and one line saying what it is doing — never an agent's id, a robot icon or token counters. This is its presence.

Its face, name and welcome​

An application names an emoji (its face), a name and a welcome in its Appspec. In its chat:

  • the header shows the face beside the name, with the line of status;
  • the empty state shows the face at the page's size, 72px (the largest of the three sizes a face is drawn at, --loop-face-large), the name as its heading, and the welcome under it — the first thing a person reads, before anyone writes — with the application's starters as chips;
  • the token and context counters under the prompt are off: a person using an application is not asking about tokens;
  • the session's controls under the prompt — the agents, the model, the tools and their servers, the skills — are gone: a person using an application is not choosing them, its builder did in its spec. An agent's chat keeps them.

An application without a face of its own shows the product's mark in its place.

The same drawing on every platform​

A face given as an emoji is drawn in Fluent Emoji (Microsoft, MIT, Flat style), not by the system's emoji font, so it is the same face on every device: FaceDrawing (in chat/presence/Presence.tsx) hands the string to FluentEmoji from @datalayer/core/lib/components/emoji, at the size asked — 20px in the header, 72px in the empty state, 20px on the embed's floating button. Each drawing is a module of its own in core's bundle, fetched the first time it is drawn: no request leaves for a third party. Core ships a drawing of every emoji in FACE_EMOJIS (the eyes, every example's, and the ones an application is offered); any other emoji is drawn by the system, as text. A host that passes its own drawing as the face (a React node) keeps it.

The line of status​

One line beside the face says what the application is doing, in plain words (PRESENCE_LINES, in chat/presence/presenceStatus.ts):

StateThe lineWhat it means
idleReadyNo turn is running: the application waits for a message.
thinkingThinking…A turn is running and no tool is open: the model is reading and writing.
workingWorking…A tool has been called and has not returned.
waitingWaiting for youA tool waits on the person's approval. The line is set in bold.
pausedPausedIts owner paused its deployment: it answers nobody until it is resumed.

The state is read from the chat, never from the model's words (presenceState, presenceToolOf): whether a turn is running, and the newest tool call — open until it is complete or failed, and waiting when its result says an approval is pending. Waiting for you is said whether or not the turn is still streaming, since the stream may end while the approval is asked. A tool left open by a stopped turn is not work: the line says Ready.

Paused is not read from the chat: it is the deployment's state, which its host knows and says (presence.paused, AppRenderer's paused). It wins over everything the last turn left behind.

The line is a polite live region (role="status"), so a screen reader hears it change.

The ring around the face​

The face is never redrawn. The state is said by the line and by a quiet motion around the face (PresenceFace, in chat/presence/Presence.tsx):

  • Thinking… and Working…: a soft ring in the accent breathes around the face;
  • Waiting for you: the ring stands, still;
  • Paused: a still, muted dashed circle, and the face dimmed and greyed — dimmed, not redrawn;
  • Ready: no ring.

The ring fades in and out at the theme's status pace (--theme-motion-status, --theme-motion-easing), and takes the accent (--theme-accent), whatever the theme. For a reader whose system asks for reduced motion, the ring does not breathe.

ChatPresence, for a host​

The chat plugin of the Loop workspace takes who is answering as presence (ChatPresence, in apps/plugins/chat):

interface ChatPresence {
/** Its name, in the header and the empty state. */
name: string;
/** An emoji: its face. */
face?: string;
/** What it says before anyone writes. */
welcome?: string;
/** Its deployment is paused: the line says *Paused*, its assistant dozes. */
paused?: boolean;
/** Told what it is doing as it changes: idle, thinking, working, waiting, paused. */
onPresence?: (state: PresenceState) => void;
}

The chat also hands the state to whoever reads its turn (LoopChatTurn's presence signal): the floating assistant acts it out from there.

LoopEmbed passes presence and showTokenUsage through to the plugin. A chat without presence keeps its header as it is: the agent's name and mark, no line of status.

AppRenderer sets all of this from the application itself — its name, its emoji, its welcome (or its description), with the counters off and the conversation in the loop theme. A host only adds onPresence when it wants to know, and paused when the deployment it shows is paused.

onPresence: a host that frames the face itself​

A page that draws the application's face in a frame of its own — the home page's hero does — is told the state as it changes:

import { useState } from 'react';
import { AppRenderer } from '@datalayer/agent-runtimes/lib/apps';
import type { PresenceState } from '@datalayer/agent-runtimes/lib/chat/presence/presenceStatus';
import { PRESENCE_LINES } from '@datalayer/agent-runtimes/lib/chat/presence/presenceStatus';

export function Hero({ app }) {
const [presence, setPresence] = useState<PresenceState>('idle');
return (
<section>
<figure data-presence={presence}>
<span aria-hidden>{app.emoji}</span>
<figcaption>{PRESENCE_LINES[presence]}</figcaption>
</figure>
{/* A state setter is stable: pass it as it is. */}
<AppRenderer app={app} onPresence={setPresence} />
</section>
);
}

onPresence is read once, when the workspace is built: pass a stable function, such as a state setter, not a new arrow on every render.

What is verified, and what is not​

Verified. The state, from a turn and the newest tool call, is unit tested (presence-status.test.ts): ready between turns, thinking during a turn, working while a tool has not returned, waiting while an approval is asked (streaming or not), not working on a tool a stopped turn left open, and paused while its deployment is, whatever the turn left.

Not verified yet. No test renders an application's chat to check the face, name, welcome and line in its header and empty state, or the ring around the face; the ring's contrast in every accent, light and dark, is not measured.

The same state drives the floating assistant's motions.