AgentScreen

Live visual output for AI agents, automation jobs, scripts, and developer tools. Publish structured events from any internet-connected system and watch them appear on a shareable screen.

Create a screenUse the dashboard to create a destination for agent output.
Publish eventsSend messages, metrics, terminal output, links, and display updates.
Share viewersMint viewer links so collaborators can watch without your API key.
Credits: every GenieTé account gets a free monthly allowance (topped up to 200 credits), paid plans add more each month, and one-time credit packs can be bought on any plan — manage all of it at account.geniete.com.

Quickstart

Prefer clicking to typing? The event composer pre-fills every event type from a dropdown — tweak, preview with the real renderer, and send to your screen.

The loop is: create a screen → publish events to it → watch it through a viewer link. Not sure what it looks like? Open the demo screen first — it shows every event type rendered live.

  1. Open the console and create a screen.
  2. Copy the screen id and screen API key — the key is shown once. Export them: AGENTSCREEN_SCREEN_ID, AGENTSCREEN_API_KEY.
  3. Press send test on the screen row — a test card is published so you can verify the pipe before writing any code.
  4. Press new link to mint a viewer link and open it — that page is your live screen (share links freely; previews can't consume them).
  5. Publish your first real event (below) and watch it appear.
curl -sS https://screens.geniete.com/public/api/publish \
  -H "Authorization: Bearer $AGENTSCREEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "screen_id": "'"$AGENTSCREEN_SCREEN_ID"'",
    "event": {
      "type": "content.message",
      "title": "agent started",
      "body": "AgentScreen is receiving events.",
      "tone": "success"
    }
  }'

A successful response returns {"event_id":"..."}.

Account And Credits

AgentScreen is part of GenieTé. The same GenieTé account and shared credit balance can be used across GenieTé services.

ItemCurrent behavior
AccountSign in through the Screens dashboard or account site.
CreditsFree monthly allowance (topped up to 200), shared across all GenieTé services; paid plans add monthly credits.
Plan changesSelf-service at account.geniete.com — upgrades immediate, downgrades at period end.
Exhausted creditsPublishing past your included events returns credit errors until your balance recovers — monthly allowance, plan top-up, an upgrade, or a one-time credit pack (“Buy credits” on the account page).

Pricing

Metering matches ChannelService's shape: every billable action draws on your shared GenieTé credits, charged to the screen owner. Publishing within your plan's included monthly events is free; everything below is pay-per-use.

ActionCreditsWhen
Publish an event (past included)2 + 1 per live viewerBase checked upfront; the per-viewer part is charged after delivery (it can briefly take the balance negative — the next publish is then rejected)
Viewer connects1When a viewer WebSocket is accepted; refused (and the slot freed) if the balance is empty
Hold a viewer connection1Every ~10 minutes it stays open; an unaffordable billing tick closes the connection (code 1008)
Mint a viewer link1Checked and charged upfront — a 402 means nothing happened

Connections also have an activity-based idle timeout: a viewer that receives no real events for 1 hour gets repeated reconnect_required warnings in the last 2 minutes, then closes — so a forgotten tab can't hold a billed connection forever. Our own keepalive pings never count as activity.

HTTP Publish

POST/public/api/publish

Publish one event to one screen.

Headers:

Authorization: Bearer <screen_api_key>
Content-Type: application/json

Body:

{
  "screen_id": "scr_abc123",
  "event": {
    "type": "content.message",
    "title": "checkpoint",
    "body": "The agent finished collecting inputs.",
    "tone": "info"
  }
}

Producer WebSocket

Use this when the agent will publish many events on one held-open connection.

WSwss://screens.geniete.com/public/ws/producer

First message authenticates; later messages publish events.

First message:

{
  "api_key": "<screen_api_key>",
  "screen_id": "scr_abc123"
}

Publish message:

{
  "event": {
    "type": "metric.update",
    "name": "tokens",
    "value": 1280,
    "unit": "tok"
  }
}

Event Types

Every publish carries one JSON event object with a type field. Payloads must stay under 64 KiB. Unknown type values are rejected with 400 invalid event type; unknown fields inside a valid event are accepted and ignored, so you can safely attach your own metadata.

TypeFieldsRendered as
content.messagetitle?, body? (or text?), tone?Message card with tone stripe
terminal.appendtext (or body)Appended to a shared terminal block
metric.updatename, value, unit?Tile in a live metrics grid, updated in place by name
display.clearClears everything currently on the screen
content.urlurl (or href/link), title?, body?Link card — clickable URL + description
content.imageurl (or image_url/src), caption? (or body/text), alt?Inline image with caption; click opens full size
content.image.inlinedata (data:image/… URI), caption?, alt?Inline image with caption (keep the event under 64 KiB)
chart.simpleseries (numbers; or values/data/points), labels?, unit?, title?Mini bar chart, last value labeled, hover for points
chart.vegaspec (a full Vega-Lite spec object), title?Full Vega-Lite chart — grouped bars, layers, transforms, anything the grammar can express
visual.pulsetone?, title?Slim pulse banner — a visible heartbeat
scene.settitle?, body?Scene divider banner — marks a new phase
All ten types render live — see every one of them on the demo screen. Field names are forgiving where publishers commonly guess (url vs image_url, caption vs body, series vs values) — the documented name first, natural aliases accepted.

content.message — status cards

The workhorse. tone is one of info (default), success, warning, error and colors the card’s stripe and title. body preserves line breaks.

{
  "type": "content.message",
  "title": "plan selected",
  "body": "The agent will compare three candidate designs.",
  "tone": "info"
}

terminal.append — streaming logs

Consecutive terminal.append events merge into one scrolling terminal block; a non-terminal event in between starts a new block. Newlines are preserved — include them yourself.

{
  "type": "terminal.append",
  "text": "$ pytest\n18 passed in 4.2s\n"
}

metric.update — live counters

Tiles keyed by name: publishing the same name again updates that tile in place instead of adding a new one. value may be a number or a short string.

{
  "type": "metric.update",
  "name": "tokens_used",
  "value": 48210,
  "unit": "tok"
}

display.clear — blank slate

Wipes all cards, terminal blocks, and metric tiles currently shown. Useful between phases of a long-running agent.

{ "type": "display.clear" }

content.url — link to an artifact

{
  "type": "content.url",
  "title": "coverage report",
  "body": "https://example.com/reports/coverage/index.html",
  "url": "https://example.com/reports/coverage/index.html"
}

content.image / content.image.inline

content.image references an image by URL — https only (plain http is rejected: the viewer is an https page); content.image.inline carries a data:image/… URI directly (keep the whole event under 64 KiB, so inline images must be small).

{
  "type": "content.image",
  "title": "screenshot after deploy",
  "url": "https://example.com/artifacts/step-3.png",
  "caption": "Landing page rendering correctly on production.",
  "alt": "landing page screenshot"
}

chart.simple — small series

{
  "type": "chart.simple",
  "title": "requests per minute",
  "series": [12, 18, 25, 31, 28, 40]
}

chart.vega — sophisticated charts

A full Vega-Lite spec under spec — rendered with a dark theme, sized to the card (or the whole stage when it is the newest event). Keep the event under 64 KiB; inline your data values.

{
  "type": "chart.vega",
  "title": "latency by region",
  "spec": {
    "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
    "data": {"values": [
      {"region": "us-west", "p50": 42, "p99": 180},
      {"region": "eu",      "p50": 88, "p99": 340}
    ]},
    "transform": [{"fold": ["p50", "p99"], "as": ["percentile", "ms"]}],
    "mark": "bar",
    "encoding": {
      "x": {"field": "region", "type": "nominal"},
      "y": {"field": "ms", "type": "quantitative"},
      "xOffset": {"field": "percentile"},
      "color": {"field": "percentile", "type": "nominal"}
    }
  }
}

visual.pulse — heartbeat

A lightweight “still alive / something happened” signal.

{ "type": "visual.pulse", "tone": "success" }

scene.set — display state

{
  "type": "scene.set",
  "title": "phase 2: integration tests",
  "body": "Running the cross-service suite."
}

Helper mapping

The Python SDK (pip install geniete-screens) and the suite MCP server (uv tool install geniete-mcp) wrap the common types — you never need raw JSON for these. Both are open source: github.com/geniete/clients.

EventPython SDKMCP tool
content.messagescreen.message(body, title, tone)as_message
metric.updatescreen.metric(name, value, unit)as_metric
terminal.appendscreen.terminal(text)as_terminal
display.clearscreen.clear()as_clear
content.imageEvent.image(url, alt)
any otherscreen.send({...})

ElevenLabs agent tool

Give a conversational voice agent a screen: add a Webhook tool in ElevenLabs pointing at the publish endpoint. The agent then narrates AND shows.

Name:    show_on_screen
Method:  POST
URL:     https://screens.geniete.com/public/api/publish
Headers: Authorization: Bearer <your screen API key>
         Content-Type: application/json
Body (let the LLM fill "title", "body", "tone"):
{
  "screen_id": "<your screen id>",
  "event": { "type": "content.message",
             "title": "{{title}}", "body": "{{body}}", "tone": "{{tone}}" }
}
Description for the agent: "Show a status card on the user's live
screen. Use tone info|success|warning|error."

Reference card for AI agents

Paste this into any agent's system prompt / tool notes so it can publish correctly:

AGENTSCREEN REFERENCE CARD
Publish: POST https://screens.geniete.com/public/api/publish
  Authorization: Bearer $AGENTSCREEN_API_KEY
  Body: {"screen_id": "$AGENTSCREEN_SCREEN_ID", "event": {...}}
Event types (JSON "type" field; <64KiB per event):
  content.message  {title?, body, tone: info|success|warning|error}
  terminal.append  {text}            # merges into one terminal block
  metric.update    {name, value, unit?}   # updates tile in place
  chart.simple     {title?, series: [numbers], unit?}
  content.image    {title?, url (https), caption?}
  content.url      {title?, url, body?}
  scene.set        {title, body?}    # phase divider
  visual.pulse     {tone?, title?}   # heartbeat
  display.clear    {}                # wipe the screen
Rules: publish milestones, not every token; prefer metric.update for
counters; scene.set between phases; one display.clear max per phase.
MCP alternative: screen_message / screen_metric / screen_terminal /
screen_clear / screen_link (server: uv tool install geniete-mcp).

Agent Brief

Give this compact instruction to an agent or coding assistant that can make HTTP requests:

You can publish live progress to AgentScreen.

Use AGENTSCREEN_BASE_URL, AGENTSCREEN_SCREEN_ID, and AGENTSCREEN_API_KEY.
Default base URL is https://screens.geniete.com.

Never reveal the API key.

Publish with:
POST {base_url}/public/api/publish
Authorization: Bearer {api_key}
Content-Type: application/json

Body:
{
  "screen_id": "{screen_id}",
  "event": {
    "type": "content.message",
    "title": "short title",
    "body": "message body",
    "tone": "info"
  }
}

Publish run starts, major progress, useful metrics, terminal snippets,
blocked states, errors, and final summaries. Do not publish secrets.

Examples

Python Standard Library

import json
import os
import urllib.request

base_url = os.getenv("AGENTSCREEN_BASE_URL", "https://screens.geniete.com")
screen_id = os.environ["AGENTSCREEN_SCREEN_ID"]
api_key = os.environ["AGENTSCREEN_API_KEY"]

def publish(event):
    body = json.dumps({"screen_id": screen_id, "event": event}).encode("utf-8")
    request = urllib.request.Request(
        f"{base_url}/public/api/publish",
        data=body,
        headers={
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json",
        },
        method="POST",
    )
    with urllib.request.urlopen(request, timeout=10) as response:
        return json.loads(response.read().decode("utf-8"))

publish({
    "type": "content.message",
    "title": "working",
    "body": "I am generating the report.",
    "tone": "info",
})

FAQ

My screen is empty when I open a viewer link — did I lose the events?
No — new viewers replay the screen's recent history automatically (last 50 events, kept 24 hours). If the screen shows nothing, nothing was published in the last 24 hours (or a display.clear wiped it).

Can a link preview in Slack/X/iMessage burn my one-time viewer link?
No. Previews only fetch the page, which never claims the token, and the claim endpoint refuses known preview crawlers — only a human who opens the link claims it.

What happens when my credits run out?
Minting links and viewer connects are checked upfront (a denial means nothing happened). A publish whose per-viewer delivery cost exceeds your remaining balance still delivers and can take the balance briefly negative — the next publish is then rejected until the balance recovers (allowance, plan renewal, upgrade, or a credit pack).

Why did my viewer connection close by itself?
Either the owner's balance couldn't afford the ~10-minute connection tick, or the viewer received no real events for an hour (idle timeout, with reconnect_required warnings in the last 2 minutes). Reopening the link reconnects — our own keepalive pings never count as activity.

Can I publish from several jobs to one screen at once?
Yes — any number of publishers can hold the same screen key; events interleave in arrival order. For separate contexts, create separate screens (they share your one credit balance).

Support

Sign in at account.geniete.com — your account pages show the support email address and your plan, balance, and ledger, which is exactly what we'll ask about. Billing terms live at genieinc.com (Genie, Inc. is the merchant behind GenieTé).

Errors

StatusMeaning
400Missing screen id, invalid JSON, or invalid event type.
401API key missing, invalid, revoked, or scoped to another screen.
404Screen not found or inactive.
413Event payload is larger than 64 KiB.
429Quota or credits are exhausted.
503Authorization infrastructure is temporarily unavailable.