← Learn · Build · 7 November 2026 · 6 min

Agent Sheet: A Portfolio as a Spreadsheet My Agents Fill In

My Reel numbers as a spreadsheet that ten named agent cursors fill in at once: titles typed, stats pulled, a formula dragged down, a heat map and a chart. Real data, real HTML, code and prompt inside.

The output, liveavivashishta.com/lab/agent-sheet
Open the live page ↗

Portfolios usually show numbers as big counters. I wanted mine to show where the numbers come from, and the most honest place for numbers is a spreadsheet. So this page is a sheet of my twelve most watched Reels that ten agents fill in together, like a shared sheet with ten people in it. It is live at avivashishta.com/lab/agent-sheet.

The short version: the table is a real HTML table with every value already in it, straight from data/stats.json. With JavaScript, the values start hidden and ten cursors write them in, each with its own coloured selection and name tag. Copy types the titles, four agents pull a column of numbers each at the same time, Claude writes =F2/C2 and drags the fill handle down, Pixel paints a heat map, Codex selects the views and draws a chart, and Sum writes the totals. Then it's yours: click any cell to see its formula.

Where it came from

This was one of six designs Claude drew when I asked for ways to use the agents-at-work idea on screens that aren't a web page: a trading terminal, a spreadsheet and a kanban board among them. The sheet won because multiplayer spreadsheets already look like this. Everyone has seen a coloured box with a name on it sitting on a cell, so ten cursors in a sheet read as a team straight away, where ten cursors on a web page read as a swarm. The other winner from that canvas became agent crews.

The data

Nothing in the sheet is made up. The rows are the twelve Reels with the most views in reelStats, the per-Reel numbers I copy from Meta Business Suite into data/stats.json, with titles and worlds from data/reels.json:

const ROWS = Object.entries(stats.reelStats.stats)
  .sort((a, b) => b[1].views - a[1].views).slice(0, 12)
  .map(([id, s]) => ({ id, title: reels.find((r) => r.id === id)?.title ?? id, ...s }));

The totals, saves per view and the chart's title are worked out from the same rows, so when I refresh the stats the sheet, the formulas and the headline on the chart ("The top three Reels did 96% of the views") all update together.

A real table first

Every cell is a <td data-cell="C2"> with the final value in it, and cells made by a formula carry it too: data-f="=F2/C2". That means the page is complete without JavaScript, crawlers and screen readers get a proper table, and the animation has its script built in: each agent just reveals what's already there.

@media (scripting: enabled) and (prefers-reduced-motion: no-preference) {
  .sheet td[data-cell]:not([data-on]) .v { opacity: 0; }
  .sheet td[data-h]:not([data-hot]) { background: var(--color-surface); }
}

Cursors and selections

Each agent has two things in an overlay above the page: a cursor, and a selection box in its colour with its name in a tab on top. Selecting a cell moves the box over it with a short transition, which is exactly how a shared sheet looks when someone clicks around. Dragging a range turns the transition off, lets the box follow the cursor, then snaps it to the range:

await moveTo(a, r2.x + r2.w - 8, r2.y + r2.h - 8, dur, (x, y) => {
  a.sel.style.width = `${Math.max(r1.w, x - r1.x + 8)}px`;
  a.sel.style.height = `${Math.max(r1.h, y - r1.y + 8)}px`;
});

The faces in the toolbar lift up while their agent is working, and an agent's cursor only shows its name tag when it has no selection, so a name is never shown twice.

The script

The whole thing is a few async functions, one per job, and Promise.all is what makes it feel like ten people at once:

await headers(id);
await Promise.all([titles(id), worlds(id),
  numbers(id, "Data", "C", "views", 200), numbers(id, "Fetch", "D", "likes", 450),
  numbers(id, "Count", "E", "shares", 700), numbers(id, "Stats", "F", "saves", 950)]);
await Promise.all([formula(id), heat(id), draw(id)]);
await totals(id);

The number agents start a quarter of a second apart, so the four columns fill as a staircase instead of in lockstep. Each number counts up into its cell from zero.

The formula and the fill handle

Claude clicks G2 and types =F2/C2 into the cell and the formula bar at the same time, a character at a time. After a beat the formula turns into its value, 1.24%. Then the move everyone knows: the cursor goes to the small square on the selection's corner and drags it down to G13. A dashed range follows it, and when it lets go the values drop into every row, one after another. Sum does the same thing sideways for the totals row.

Heat map and chart

Pixel selects the saves column and moves down it, and each cell warms up as the cursor passes. The colour is my yellow mixed into white by the cell's share of the biggest number, set on the server as a custom property:

.sheet td[data-h] { background: color-mix(in srgb, var(--color-learn) calc(var(--h) * 85%), var(--color-surface)); }

At the same time Codex selects the views column, flies to the side, drags a dashed marquee where the chart goes, and the bars grow one by one in each Reel's world colour. Copy then reads the chart and types its title.

Then it's yours

When the agents are done, the sheet works: click a cell and the formula bar shows what's in it, the formula for anything calculated. On phones the world, likes and shares columns are dropped so the rest fits, and the agents fill those in without a cursor. Rebuild clears it from the bottom right and runs it again.

Build your own

Build a spreadsheet page in React with CSS modules that a team of ten named agent cursors fills in live. Render a real HTML table server side with every value in it: a header row, one row per item from my JSON data, and a totals row. Give each cell data-cell="C2" and cells made by a formula data-f="=F2/C2". Under @media (scripting: enabled) and (prefers-reduced-motion: no-preference), hide each cell's value until it has data-on. Add a formula bar (cell reference, fx, contents) and a toolbar with one coloured face per agent that lifts while that agent works.

In an absolute overlay, give each agent a cursor (an SVG arrow) and a selection box in its colour with its name in a tab on top. Selecting a cell moves the box with a short transition; dragging a range makes the box follow the cursor and then snap to the range. Move all cursors with one requestAnimationFrame loop with eased, slightly bowed tweens. Script it with async functions run in parallel with Promise.all: one agent types the headers, one types every title into column A (mirrored in the formula bar), one tags a category column, four agents each count numbers up into one column, staggered by 250ms.

Then in parallel: one agent types =F2/C2 into G2, shows the value, and drags the fill handle (a small square on the selection's corner) down the column while a dashed range follows, revealing each row's value in turn; one selects a column and paints a heat map with color-mix by each value's share of the max; one selects a column, drags a marquee beside the sheet and grows a bar chart, whose title another agent types. Finish with a SUM in the totals row dragged across. Afterwards, clicking a cell shows its formula or value in the formula bar. Add an activity log, Skip and Rebuild.