v1.3.0
  1. ds
  2. Agent
  3. Message row

Message row

A compact transcript turn: one text column for every role, state in words, actions on hover.

Light
Trace the slow checkout requests from this morning and tell me where the time goes.
axonsonnet-4.542 s19.1k in, 1.2k out
Thought for 12 seconds640 tokens
The slow span is the row lock. Check whether the drain runs inside the transaction.

The p95 jump starts at 09:14. The slow span is SELECT … FOR UPDATE, not the network: it holds the row lock for 840 ms while the retry queue drains.

3 tool calls · 2.6 s
grepdrainRetries services/checkoutDone180 ms
read_fileservices/checkout/retry.tsDone9 ms
edit_fileservices/checkout/retry.tsDone340 ms
services/checkout/retry.ts+2−2
@@ -17,4 +17,4 @@ export async function charge(orderId: string) {
1717 const order = await db.lock(orderId)
18 await drainRetries(order)
18 queue.drainInBackground(order.id)
1919 await db.commit()

I moved the drain behind the queue. Two things to check before you merge:

  1. The retry suite, which mocks drainRetries.
  2. The dashboard, for a lock wait above 50 ms.
axonNeeds approval09:15

Run the checkout tests

Medium risk
pnpm test --filter checkout
axonStreamingsonnet-4.5

14 passed, 2 skipped. The lock wait is now 31 ms at p95, under the 50 ms budget

axonFailed09:17
Compacted 64 earlier turns into a 2.1k summary.
YouQueuedsends when the turn ends
Also check whether the webhook relay has the same lock pattern: services/event-relay/internal/deliver/relay_with_a_very_long_file_name_that_does_not_break.go
Dark
Trace the slow checkout requests from this morning and tell me where the time goes.
axonsonnet-4.542 s19.1k in, 1.2k out
Thought for 12 seconds640 tokens
The slow span is the row lock. Check whether the drain runs inside the transaction.

The p95 jump starts at 09:14. The slow span is SELECT … FOR UPDATE, not the network: it holds the row lock for 840 ms while the retry queue drains.

3 tool calls · 2.6 s
grepdrainRetries services/checkoutDone180 ms
read_fileservices/checkout/retry.tsDone9 ms
edit_fileservices/checkout/retry.tsDone340 ms
services/checkout/retry.ts+2−2
@@ -17,4 +17,4 @@ export async function charge(orderId: string) {
1717 const order = await db.lock(orderId)
18 await drainRetries(order)
18 queue.drainInBackground(order.id)
1919 await db.commit()

I moved the drain behind the queue. Two things to check before you merge:

  1. The retry suite, which mocks drainRetries.
  2. The dashboard, for a lock wait above 50 ms.
axonNeeds approval09:15

Run the checkout tests

Medium risk
pnpm test --filter checkout
axonStreamingsonnet-4.5

14 passed, 2 skipped. The lock wait is now 31 ms at p95, under the 50 ms budget

axonFailed09:17
Compacted 64 earlier turns into a 2.1k summary.
YouQueuedsends when the turn ends
Also check whether the webhook relay has the same lock pattern: services/event-relay/internal/deliver/relay_with_a_very_long_file_name_that_does_not_break.go
390px, dark
Trace the slow checkout requests from this morning and tell me where the time goes.
axonsonnet-4.542 s19.1k in, 1.2k out
Thought for 12 seconds640 tokens
The slow span is the row lock. Check whether the drain runs inside the transaction.

The p95 jump starts at 09:14. The slow span is SELECT … FOR UPDATE, not the network: it holds the row lock for 840 ms while the retry queue drains.

3 tool calls · 2.6 s
grepdrainRetries services/checkoutDone180 ms
read_fileservices/checkout/retry.tsDone9 ms
edit_fileservices/checkout/retry.tsDone340 ms
services/checkout/retry.ts+2−2
@@ -17,4 +17,4 @@ export async function charge(orderId: string) {
1717 const order = await db.lock(orderId)
18 await drainRetries(order)
18 queue.drainInBackground(order.id)
1919 await db.commit()

I moved the drain behind the queue. Two things to check before you merge:

  1. The retry suite, which mocks drainRetries.
  2. The dashboard, for a lock wait above 50 ms.
axonNeeds approval09:15

Run the checkout tests

Medium risk
pnpm test --filter checkout
axonStreamingsonnet-4.5

14 passed, 2 skipped. The lock wait is now 31 ms at p95, under the 50 ms budget

axonFailed09:17
Compacted 64 earlier turns into a 2.1k summary.
YouQueuedsends when the turn ends
Also check whether the webhook relay has the same lock pattern: services/event-relay/internal/deliver/relay_with_a_very_long_file_name_that_does_not_break.go
Show code
<div class="mrows" role="log" aria-label="Transcript">
  <article class="mrow" data-role="user" aria-label="You">
    <span class="mrow-ava" aria-hidden="true">R</span>
    <div class="mrow-body">Trace the slow checkout requests from this morning and tell me where the time goes.</div>
    <div class="mrow-actions"><button class="btn icon" type="button" aria-label="Edit and resend"><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"/><path d="m15 5 4 4"/></svg></button><button class="btn icon" type="button" aria-label="Copy"><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg></button></div>
  </article>
  <article class="mrow" data-role="assistant" aria-label="axon">
    <span class="mrow-ava" aria-hidden="true"><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"/><path d="M20 2v4"/><path d="M22 4h-4"/><circle cx="4" cy="20" r="2"/></svg></span>
    <header class="mrow-head"><b class="mrow-name">axon</b><span class="mrow-meta"><time datetime="2026-10-21T09:14:44">09:14</time><span>sonnet-4.5</span><span>42 s</span><span>19.1k in, 1.2k out</span></span></header>
    <div class="mrow-body">
      <details class="reasoning dense"><summary><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 18V5"/><path d="M15 13a4.17 4.17 0 0 1-3-4 4.17 4.17 0 0 1-3 4"/><path d="M17.598 6.5A3 3 0 1 0 12 5a3 3 0 1 0-5.598 1.5"/><path d="M17.997 5.125a4 4 0 0 1 2.526 5.77"/><path d="M18 18a4 4 0 0 0 2-7.464"/><path d="M19.967 17.483A4 4 0 1 1 12 18a4 4 0 1 1-7.967-.517"/><path d="M6 18a4 4 0 0 1-2-7.464"/><path d="M6.003 5.125a4 4 0 0 0-2.526 5.77"/></svg><span class="reasoning-title">Thought for 12 seconds</span><span class="reasoning-meta">640 tokens</span><svg class="i reasoning-chev" viewBox="0 0 24 24" aria-hidden="true"><path d="m9 18 6-6-6-6"/></svg></summary><div class="reasoning-body">The slow span is the row lock. Check whether the drain runs inside the transaction.</div></details>
      <p>The p95 jump starts at 09:14. The slow span is <code>SELECT … FOR UPDATE</code>, not the network: it holds the row lock for <strong>840 ms</strong> while the retry queue drains.</p>
      <details class="tcalls dense" open>
        <summary><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg><span><b>3 tool calls</b> · 2.6 s</span><svg class="i tcall-chev" viewBox="0 0 24 24" aria-hidden="true"><path d="m9 18 6-6-6-6"/></svg></summary>
        <details class="tcall dense" data-state="ok"><summary><span class="tcall-ico"><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="m21 21-4.34-4.34"/><circle cx="11" cy="11" r="8"/></svg></span><span class="tcall-name">grep</span><span class="tcall-args">drainRetries services/checkout</span><span class="tcall-status"><span class="tcall-word">Done</span></span><span class="tcall-time">180 ms</span><svg class="i tcall-chev" viewBox="0 0 24 24" aria-hidden="true"><path d="m9 18 6-6-6-6"/></svg></summary></details>
        <details class="tcall dense" data-state="ok"><summary><span class="tcall-ico"><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/></svg></span><span class="tcall-name">read_file</span><span class="tcall-args">services/checkout/retry.ts</span><span class="tcall-status"><span class="tcall-word">Done</span></span><span class="tcall-time">9 ms</span><svg class="i tcall-chev" viewBox="0 0 24 24" aria-hidden="true"><path d="m9 18 6-6-6-6"/></svg></summary></details>
        <details class="tcall dense" data-state="ok" open><summary><span class="tcall-ico"><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"/><path d="m15 5 4 4"/></svg></span><span class="tcall-name">edit_file</span><span class="tcall-args">services/checkout/retry.ts</span><span class="tcall-status"><span class="tcall-word">Done</span></span><span class="tcall-time">340 ms</span><svg class="i tcall-chev" viewBox="0 0 24 24" aria-hidden="true"><path d="m9 18 6-6-6-6"/></svg></summary><div class="tcall-body"><div class="tcall-diff"><div class="diff dense">
  <div class="diff-file"><span class="path">services/checkout/retry.ts</span><span class="diff-n"><span class="add">+2</span><span class="del">−2</span></span></div>
  <div class="diff-scroll"><table>
    <tr class="hunk"><td colspan="3">@@ -17,4 +17,4 @@ export async function charge(orderId: string) {</td></tr>
    <tr><td class="ln">17</td><td class="ln">17</td><td class="cd">  const order = await db.lock(orderId)</td></tr>
    <tr class="del"><td class="ln del">18</td><td class="ln"></td><td class="cd del">  await drainRetries(order)</td></tr>
    <tr class="add"><td class="ln"></td><td class="ln add">18</td><td class="cd add">  queue.drainInBackground(order.id)</td></tr>
    <tr><td class="ln">19</td><td class="ln">19</td><td class="cd">  await db.commit()</td></tr>
  </table></div>
</div></div></div></details>
      </details>
      <p>I moved the drain behind the queue. Two things to check before you merge:</p>
      <ol><li>The retry suite, which mocks <code>drainRetries</code>.</li><li>The dashboard, for a lock wait above 50 ms.</li></ol>
    </div>
    <div class="mrow-actions"><button class="btn icon" type="button" aria-label="Copy"><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg></button><button class="btn icon" type="button" aria-label="Retry"><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/><path d="M8 16H3v5"/></svg></button><button class="btn icon" type="button" aria-label="Fork from here"><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><line x1="6" x2="6" y1="3" y2="15"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M18 9a9 9 0 0 1-9 9"/></svg></button></div>
  </article>
  <article class="mrow" data-role="assistant" aria-label="axon, needs approval">
    <span class="mrow-ava" aria-hidden="true"><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"/><path d="M20 2v4"/><path d="M22 4h-4"/><circle cx="4" cy="20" r="2"/></svg></span>
    <header class="mrow-head"><b class="mrow-name">axon</b><span class="mrow-meta"><span class="warn">Needs approval</span><span>09:15</span></span></header>
    <div class="mrow-body">
      <section class="approval dense" data-risk="medium" aria-label="Approval: run the checkout tests">
        <div class="approval-head"><span class="approval-ico"><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/></svg></span><h3 class="approval-title">Run the checkout tests</h3><span class="approval-risk">Medium risk</span></div>
        <pre class="approval-cmd"><code>pnpm test --filter checkout</code></pre>
        <div class="approval-actions"><button class="btn primary" type="button">Allow once</button><button class="btn" type="button">Allow for session</button><button class="btn ghost danger" type="button">Deny</button></div>
      </section>
    </div>
  </article>
  <article class="mrow" data-role="assistant" data-state="streaming" aria-label="axon, writing" aria-busy="true">
    <span class="mrow-ava" aria-hidden="true"><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"/><path d="M20 2v4"/><path d="M22 4h-4"/><circle cx="4" cy="20" r="2"/></svg></span>
    <header class="mrow-head"><b class="mrow-name">axon</b><span class="mrow-meta"><span class="accent">Streaming</span><span>sonnet-4.5</span></span></header>
    <div class="mrow-body" aria-live="polite"><p>14 passed, 2 skipped. The lock wait is now 31 ms at p95, under the 50 ms budget<span class="chat-caret" aria-hidden="true"></span></p></div>
  </article>
  <article class="mrow" data-role="assistant" data-state="error" aria-label="axon, failed">
    <span class="mrow-ava" aria-hidden="true"><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"/><path d="M20 2v4"/><path d="M22 4h-4"/><circle cx="4" cy="20" r="2"/></svg></span>
    <header class="mrow-head"><b class="mrow-name">axon</b><span class="mrow-meta"><span class="down">Failed</span><span>09:17</span></span></header>
    <div class="mrow-body"><div class="chat-note down" role="alert"><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg><span>The provider closed the stream after 30 s.</span><button class="btn mini" type="button" style="margin-inline-start:auto">Retry</button></div></div>
  </article>
  <article class="mrow" data-role="system" aria-label="Note">
    <span class="mrow-ava" aria-hidden="true" style="--c:var(--muted);--w:var(--bg-sunk)"><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="M13 13.74a2 2 0 0 1-2 0L2.5 8.87a1 1 0 0 1 0-1.74L11 2.26a2 2 0 0 1 2 0l8.5 4.87a1 1 0 0 1 0 1.74z"/><path d="m20 14.285 1.5.845a1 1 0 0 1 0 1.74L13 21.74a2 2 0 0 1-2 0l-8.5-4.87a1 1 0 0 1 0-1.74l1.5-.845"/></svg></span>
    <div class="mrow-body">Compacted 64 earlier turns into a 2.1k summary.</div>
  </article>
  <article class="mrow" data-role="user" data-state="queued" aria-label="You, queued">
    <span class="mrow-ava" aria-hidden="true">R</span>
    <header class="mrow-head"><b class="mrow-name">You</b><span class="mrow-meta"><span>Queued</span><span>sends when the turn ends</span></span></header>
    <div class="mrow-body">Also check whether the webhook relay has the same lock pattern: services/event-relay/internal/deliver/relay_with_a_very_long_file_name_that_does_not_break.go</div>
  </article>
</div>

A message row is one turn of a dense transcript, the way a coding agent reads: a 16px mark in a gutter, a 16px head with the name and the facts, and the body. Every role shares one text column, so a long session reads as one straight edge. User turns are a filled row; assistant turns are open; system notes are quiet and small.

Prose is 13px on 20px lines and code 11.5px. Reasoning, tool calls, approvals and diffs sit in the body in their dense form. A run of tool calls folds into one .tcalls frame with a summary, so the transcript stays readable after forty calls.

Actions

Light
axonsonnet-4.542 s

The selected row keeps its actions in view.

Dark
axonsonnet-4.542 s

The selected row keeps its actions in view.

Code
<article class="mrow" data-role="assistant" aria-selected="true" aria-label="axon">
  <span class="mrow-ava" aria-hidden="true"><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"/><path d="M20 2v4"/><path d="M22 4h-4"/><circle cx="4" cy="20" r="2"/></svg></span>
  <header class="mrow-head"><b class="mrow-name">axon</b><span class="mrow-meta"><time>09:14</time><span>sonnet-4.5</span><span>42 s</span></span></header>
  <div class="mrow-body"><p>The selected row keeps its actions in view.</p></div>
  <div class="mrow-actions"><button class="btn icon" type="button" aria-label="Copy"><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg></button><button class="btn icon" type="button" aria-label="Retry"><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/><path d="M8 16H3v5"/></svg></button><button class="btn" type="button"><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><line x1="6" x2="6" y1="3" y2="15"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M18 9a9 9 0 0 1-9 9"/></svg>Fork</button></div>
</article>

Copy, retry and fork float in at the top right on hover or focus, on a small surface so they never move the text. A selected row (aria-selected="true") keeps them shown. On touch screens they sit under the body instead, always visible. Buttons inside are 24px.

State

State is a word in the meta, and the row repeats it: data-state="streaming" with a caret, queued dims a user turn that will send when the current one ends, error adds a 2px red rule and an inline note with Retry. Give the meta word a tone class (.ok, .warn, .down) to match.

API

ClassOnWhat it does
.mrowsdivThe transcript column, 4px between rows. Give it role="log".
.mrowarticleOne turn: a 16px gutter and the text column, 6px by 8px padding, 6px radius.
data-role.mrowassistant (open), user (filled, keeps line breaks), system (11px, muted).
.mrow-avaspanThe 16px mark: an icon or one letter. Set --c and --w for its tone.
.mrow-headheaderThe 16px head. Leave it out on user turns unless there is a state to say.
.mrow-namebThe speaker, 12px 600.
.mrow-metaspanFacts in 10.5px mono with middots: time, model, duration, tokens. The last one truncates.
.ok, .warn, .down.mrow-meta > spanTone for the state word.
.mrow-bodydivProse at 13px on 20px. Headings, lists, inline code, code blocks and tables at the dense scale.
.mrow-actionsdivRow actions. Hidden until hover or focus; inline on touch.
data-state.mrowstreaming, queued (dimmed), error (a 2px red rule).
aria-selected, aria-current.mrowA selected or jumped-to row: an accent wash and rule, actions shown.
.tcallsdetails, div, ulA run of tool calls in one frame, with an optional summary that folds them.
Do
  • One text column for every role, whatever the width.
  • The model, the time and the cost in the head, once.
  • Fold a run of tool calls under a count.
Don't
  • Right-aligned bubbles in a workbench. They waste the left half of a wide column.
  • An avatar larger than the head line.
  • Actions that push the text when they appear.

Accessibility

  • The column is a role="log"; each turn is an article with a name. Mark the streaming body aria-live="polite" and the row aria-busy="true" until it ends.
  • The mark and the caret are decorative and hidden. The speaker is the visible name or the article label.
  • Actions are real buttons with labels. They appear on focus as well as hover, so keyboard users reach them in order.
  • Every state has a word. The rule and the colours repeat it.
  • Long paths and URLs wrap anywhere in prose and scroll in code blocks, so nothing overflows at 390px.