Base dialect: CommonMark + GFM (tables, strikethrough, autolinks) Language version: 0.2 License: CC BY-SA 4.0 File extensions:
.cm· Info-string / fence tag:chromamarkThis document is written in ChromaMark. On a plain markdown viewer (e.g. GitHub) the ChromaMark constructs degrade to readable text; for the full experience render it with
@chromamark/rendereror try the playground.
ChromaMark is a strict syntax superset of CommonMark + GFM that adds the three things agents actually need to communicate clearly — colored blocks, colored inline pills, and collapsible sections — plus a small set of opt-in extensions. It costs a fraction of the tokens of equivalent HTML and degrades to readable plain text anywhere it isn't natively rendered.
Agents stream a lot of status: pass/fail, warnings, diffs, long logs. The two existing options both hurt:
PASS/FAIL pill, and
can't hide a 300-line stack trace behind a toggle.<span class="pill pill--ok">PASS</span> burns
~9 tokens for one badge, invites unsafe raw markup, and reads terribly when a
tool doesn't render it.ChromaMark adds exactly those capabilities with the cheapest, least-ambiguous syntax we could find.
ChromaMark extends CommonMark + GFM. Everything in the CommonMark spec and the GFM extensions (tables, strikethrough, autolinks) works unchanged. Raw HTML follows the renderer's policy: the reference convenience renderers disable it for safe handling of untrusted agent output, while the plugins honor their host Markdown renderer's setting. ChromaMark reserves four previously inert lexical niches:
| Sigil | ChromaMark meaning | In plain CommonMark it was… |
|---|---|---|
::: at line start |
container fence | literal text |
[! … ] |
inline pill / text / meter | literal text |
{++, {--, {~~, {== |
CriticMarkup diff | literal text |
↳ / > starting a table's first cell |
row-group depth | literal text |
Because each was previously meaningless, no existing document changes meaning.
Blocks, pills, text, and meters all draw from one vocabulary, so there is a single thing to learn.
| Semantic token | Aliases | Default hue | Meaning |
|---|---|---|---|
success |
ok, pass |
green | good / passed / done |
danger |
error, fail |
red | bad / failed / blocking |
warning |
warn |
amber | caution / non-blocking |
info |
note |
blue | neutral information |
tip |
hint |
teal | advice / suggestion |
muted |
skip |
gray | de-emphasized / skipped |
Prefer semantic tokens — they respect the reader's theme. When a token genuinely
won't do, use the escape hatch color=<name> or color=#RRGGBB
(e.g. color=#6f42c1). Explicit colors are literal and bypass theming.
:::Fenced containers, aligned with the widely-used ::: admonition convention
(Pandoc, markdown-it-container, Docusaurus) so existing tooling gets you partway.
::: <kind> [attrs] [title]
…block content (markdown, may nest)…
:::
note, tip, info, warning,
danger, success, muted), or block for a color-only container, or a
structural kind (details §7, fields §8.1).key=value tokens or flags immediately after the kind.
Recognized: color=… (override hue) and open (details only). Attribute
parsing stops at the first token that is neither key=value nor a known flag.**bold**, `code`, links). Raw HTML in a title is
escaped, not injected.Live examples (these render as real callouts above/below):
::: success Deploy [!ok healthy] · 3/3 replicas
All 247 tests passed.
:::
::: warning Deprecation
`recon.flights` is removed in v3. Use `query_parameters` instead.
:::
::: block color=#6f42c1 Custom hue
Only when a semantic token won't do.
:::
On a plain markdown engine a colored block degrades to its literal ::: lines —
the title and body text remain readable and nothing is hidden.
[!…]Inline badges. The reserved opener [! starts a pill and never collides with
CommonMark (there, [!x] is just literal text — exactly the fallback we want).
[!<token-or-color> <label>]
#hex, or color=<hex|name>.
(Bare CSS color names are only accepted via color= so they can't be mistaken
for a label word.)]. If omitted, the token name is the
label (uppercased): [!pass] → PASS.Build PASS · lint 12 issues · deploy FAILED · coverage 87% · flaky SKIPPED · tag beta
Pills shine in tables:
| Suite | Result | Time |
|---|---|---|
| unit | PASS | 4.1s |
| integration | 3 of 88 | 61.0s |
| e2e | SKIP | — |
Fallback: renders as the literal bracketed text, e.g. [!success PASS], which
still communicates status.
::: detailsA container kind, so it is block-level and streaming-friendly (the summary sits on the opener line, before the hidden body).
::: details [open] [tone] <summary>
…hidden content (markdown, may nest)…
:::
open flag to start expanded.::: details danger ….::: details danger Integration failures [!fail 3 of 88]
FAILED test_recon_merge_precedence — expected config to win
:::
This body is hidden until you expand it. It may contain any markdown, including nested blocks and code fences.
Fallback: renders as a real <details>/<summary> where supported. On a plain
markdown engine it degrades to its literal ::: lines — the summary and body
remain readable, and content is never hidden in a non-supporting renderer
(principle 3).
The inline sigil family is deliberately uniform — one rule to learn:
| Sigil | Construct | Fill? |
|---|---|---|
[!…] |
pill / badge | filled |
[.…] |
colored text | tint, no fill |
[=…] |
progress / meter | bar |
::: fieldsAgents emit key/value status constantly. A fields block renders as a compact
definition list.
::: fields
Region: eastus
Replicas: 3/3
Commit: `25d7426`
Status: [!ok healthy]
:::
[.…]Tinted words with no badge background — for prose emphasis.
The gate is [.success green] but the [.danger critical] path is [.warning slow].
The gate is green but the critical path is slow.
[=…]Coverage [=success 87%] Budget [=warning 72%] Disk [=danger 96%] Tasks [=info 3/10]
The label is the displayed value; a trailing NN% or A/B sets the fill.
diff block + inline CriticMarkupBlock-level diffs use the existing GFM fenced ```diff convention. For inline
change-tracking, ChromaMark adopts CriticMarkup
(© 2013 Gabe Weatherhead & Erik Hess) rather than inventing syntax:
| CriticMarkup | Meaning |
|---|---|
{++insert++} |
insertion |
{--delete--} |
deletion |
{~~old~>new~~} |
substitution |
{==highlight==} |
highlight / mark |
{>>comment<<} |
comment |
The field was flightsquery_parameters and is worth reviewing
before merge.
↳Agent reports are full of parent/child relationships that a flat GFM table cannot express: a summary task and its sub-tasks, a failing suite and its cases, a resource and its dependents. A row group lets rows fold into the row above them.
A row whose first cell begins with a run of ↳ is a child row. It attaches
to the nearest preceding row one level shallower, which becomes the group's
toggle. Child rows are expanded by default, matching the static output, so
a reader sees the whole table until they choose to fold part of it away.
| ID | Diff | Task |
| --- | ---------------- | ----------------------------- |
| 200 | [!warn MEDIUM] | Pickpocket anyone 200 times |
| ↳ 195 | [!success EASY] | Pickpocket from a man or woman |
| ↳↳ 196 | [!success EASY] | …10 times |
| 198 | [!success EASY] | Steal from any stall 10 times |
↳ is depth 1, ↳↳ depth 2, and so on.> is an accepted alias, so > and >> mean exactly what ↳ and
↳↳ mean. Agents emit ASCII more reliably; humans paste ↳. Mixing the two
forms in one run parses by count but is a lint warning.↳ ↳ equals ↳↳.> starting any other
cell is ordinary text, so prose and blockquote-ish content stay unaffected.State. Collapsing a row hides its entire subtree. Each group tracks its own state, so collapsing and reopening a parent restores its direct children in whatever state they already had — a nested group the reader collapsed stays collapsed when its parent reopens.
Malformed depth degrades, never errors. A row deeper than one level below its predecessor clamps to the deepest legal depth, and a child row with no eligible parent becomes an ordinary row. Both emit lint warnings. This matters because the common author of these tables is a language model: a miscounted marker must yield a slightly flatter table, never a dropped or unreachable row.
Fallback: a renderer without row-group support shows every row, in document
order, with its literal ↳ prefix intact — which already reads as indentation.
As with every other construct, nothing is hidden by a renderer that does not
understand it (principle 3).
Containers nest. Use more colons for the outer fence so boundaries are unambiguous:
:::: danger Release blocked
Two gates failed:
::: details Failing gate 1 — tests
FAILED test_recon_merge_precedence
:::
::: details Failing gate 2 — coverage
Coverage 71% < 80% threshold.
:::
::::
Pills, text, and meters nest freely inside any block, list, heading, or table cell.
\::: at the start of a line → a literal :::.\[!, \[., \[= → literal openers.| Construct | ChromaMark | Plain-markdown fallback |
|---|---|---|
| Colored block | ::: warning Title |
literal ::: lines; title + body readable |
| Pill | [!success PASS] |
literal text [!success PASS] |
| Collapsible | ::: details Sum… |
literal ::: lines; summary + body readable |
| Colored text | [.danger x] |
literal text [.danger x] |
| Meter | [=success 87%] |
literal text [=success 87%] |
| Fields | ::: fields |
literal ::: lines of key: value |
| Table row group | | ↳ 195 | … |
all rows shown; literal ↳ reads as indent |
Nothing is ever hidden or lost when unsupported — a non-ChromaMark engine shows the literal source, which stays readable. Renderers MAY present richer fallbacks (e.g. a blockquote), but that is not required.
Because every opener precedes its content and every inline construct is self-delimiting, a renderer can:
::: opener line.] arrives (typically within one or two tokens).No construct requires seeing its end before it can begin styling.
The sketch below is a quick overview. The complete EBNF syntax companion for the
extension layer — kept in sync with the parser by tests — lives in
docs/grammar.ebnf. Use it to build third-party tooling
(editors, validators, tree-sitter grammars) without reimplementing the parser.
document = { gfm-block | container } ;
container = fence-open , newline , { line } , fence-close ;
fence-open = ":" , ":" , { ":" } , ws , kind , [ ws , attrs ] , [ ws , title ] ;
fence-close = ":" , ":" , { ":" } ; (* >= opener's colon count *)
kind = tone | "block" | "details" | "fields" ;
tone = "success" | "danger" | "warning" | "info" | "tip" | "muted" | alias ;
attrs = { attr , ws } ;
attr = "color=" , colorval | "open" ;
colorval = hexcolor | named-color ;
hexcolor = "#" , ( 3 | 6 | 8 ) * hexdig ;
pill = "[" , "!" , spec , [ ws , label ] , "]" ;
text = "[" , "." , spec , ws , label , "]" ;
meter = "[" , "=" , spec , ws , value , "]" ;
spec = tone | "color=" , colorval | hexcolor ;
label = { any-char-except-"]" } ;
value = number , [ "%" ] | number , "/" , number ;
critic = "{++" , text , "++}" | "{--" , text , "--}"
| "{~~" , text , "~>" , text , "~~}" | "{==" , text , "==}"
| "{>>" , text , "<<}" ;
row-marker = ( "↳" | ">" ) , { ws , ( "↳" | ">" ) } ; (* depth = marker count *)
@chromamark/renderer — a markdown-it plugin (packages/renderer). Works in any webpage; ships the CSS theme.chromamark-vscode — reuses the renderer in the built-in markdown preview and adds syntax highlighting (packages/vscode).## Deploy report — `service-recon` @ 25d7426
:::: success Deploy succeeded in 3m12s
::: fields
Region: eastus
Replicas: 3/3 [!ok healthy]
Coverage: [=success 87%]
:::
::::
| Stage | Result | Notes |
| ------ | --------------- | --------------- |
| build | [!pass] | cached |
| unit | [!pass 247] | 4.1s |
| integ | [!fail 3 of 88] | see trace below |
| lint | [!warn 12] | non-blocking |
| deploy | [!pass] | 3m12s |
::: details danger Integration failures (3)
FAILED test_recon_merge_precedence — expected config to win
FAILED test_query_parameters_map — key casing mismatch
FAILED test_seller_ids_removed — legacy field still referenced
:::
Overall: [!warn SHIP WITH CAUTION]