Skip to main content

Interactive charts in chat

Ask "which region grew fastest?" and get a chart you can hover, not a table you have to read. The agent writes a fenced block of plain JSON; the web app draws it.

Task

Give a personality (the directory of files that decides an agent's tools, memory, and model) the charts skill (a markdown file that teaches the agent one procedure), so its answers can render as interactive charts in the web app.

Result

The personality's character sheet names the renderer it may use, and a ```echarts fence in its replies draws a chart in the web app. Everywhere else — the CLI, Telegram, Discord, email — the same message shows the fence as a code block.

Prereqs

  • The web app running (ethos serve). Charts render in the SPA; no other surface draws them.
  • A personality you can edit, or one of the built-ins.

1. Give the personality the charts skill

The skill lives in the bundled library at skills/document/charts/SKILL.md. Copy it into the personality's own skills/ directory:

mkdir -p ~/.ethos/personalities/<personality-id>/skills
cp skills/document/charts/SKILL.md ~/.ethos/personalities/<personality-id>/skills/charts.md

No restart is needed — personality directories are re-read on the next turn.

2. Write the fence (or let the agent)

The skill is the source of truth for the format — read skills/document/charts/SKILL.md rather than a copy of it here. In short: one fence tagged echarts, body is a single JSON object, no JavaScript anywhere.

```echarts
{ "title": { "text": "Errors by service" },
"xAxis": { "type": "category", "data": ["auth", "api"] },
"yAxis": { "type": "value" },
"series": [{ "type": "bar", "data": [12, 31] }] }
```

Most of the time you write nothing: the agent has read the skill and emits the fence itself when a comparison, trend, or distribution is easier to see than to read.

Verify

ethos personality show <personality-id>

The Capabilities section gains one line:

## Capabilities
- Renders: echarts@1 (interactive charts — via charts skill)

That line and the web app's renderer read the same derivation, so if the line is present the chart will draw. If it is absent, the skill is not in this personality's resolved skill set — check the path and the file's ethos.renders block.

Then ask for a chart in the web app:

you > chart last quarter's revenue by region

The reply renders as a chart in a bordered container. In ethos chat the same reply prints the JSON.

What echarts@1 actually versions

echarts@1 names the Ethos spec version — a documented subset of ECharts options — not the npm package version. The two move independently:

LevelOwned byChanges when
Spec version (echarts@1)The charts skillA new subset is taught. It is echarts@2, never echarts@1.1 — spec versions are small integers, and the grammar rejects semver on purpose.
Library version (echarts 6.x)The web appThe app upgrades its pinned dependency. Skills never notice; the spec-1 subset keeps rendering.

This is the answer to why did my chart stop rendering. An npm upgrade does not break your specs. A spec bump does — and when it happens the skill declares both versions during migration (renders: ['echarts@1', 'echarts@2']), and a surface that maps only spec 1 names the mismatch in a one-line notice instead of drawing the wrong picture.

Why a skill cannot ship a renderer

Skills declare a capability; they never ship the code that provides it. ethos.renders: ['echarts@1'] is a claim the surface checks against its own hardcoded registry of first-party, version-pinned, code-split renderers. No JavaScript from any skill directory ever executes in a surface, and nothing in ~/.ethos/ can register a renderer.

The fence body is data, not a program. It is JSON.parsed, run through an allowlist filter that strips function-shaped strings, remote references, and markup-bearing values, then handed to the charting library — never evaluated. A renderer name the surface does not map is simply not drawn.

So the worst a hostile skill can do is emit a fence that falls back to a code block. That is the whole blast radius, and it is why installing a third-party skill does not widen your attack surface.

The fallbacks are the design

Every one of these shows the reader the fence text — the same thing a CLI user sees:

SituationWhat renders
Personality lacks the charts skillCode block
The surface has no chart renderer (CLI, Telegram, Discord, email)Code block
Declared spec version the surface does not map (echarts@2 today)Code block plus a one-line notice naming both versions
Body is not valid JSON, or nothing survives the allowlist filterCode block
The message is still streaming (fence not yet closed)Code block, upgraded once the fence closes

None of these is a failure state. A spec written the way the skill teaches — small, titled, with named series — reads as legible content in every one of them. That is why the skill insists on title.text.

When to use make-chart instead

Use make-chart when the recipient needs an artifact rather than a view: a PNG to attach, an image in an email or a Telegram message, a file on disk for a report. charts is for interactive chat surfaces and produces no file. The two skills are complementary; a personality can hold both.

Troubleshoot

SymptomCauseFix
No Renders: line on the character sheetThe skill is not in this personality's resolved skill setConfirm the file is at ~/.ethos/personalities/<personality-id>/skills/charts.md and its front-matter has renders: ['echarts@1'] under ethos:
Character sheet shows the line, chat shows a code blockNot the web app, or the message predates the skill being addedCharts render only in the SPA; already-rendered messages are not re-rendered when a personality gains the skill
Chart draws but colours look wrongThe spec set its own colorOmit color and let the design tokens theme it; the chart follows light/dark and the active skin
Heatmap draws monochromevisualMap is missingA heatmap needs visualMap with min and max — it is the only series type that requires a companion key
Chart briefly appears as a code block, then drawsExpected — the fence upgrades once it closesNothing to fix

See also