The AgentBase company site. One page, English at / and Korean at /ko.
Its only job is to show how the company operates. This is a company page, not
a product page — customer names, campaign results and category metrics belong
on each product's own site, and tests/no-banned-claims.test.ts fails the
build if they turn up here.
The site argues that this company's operating work is done by agents. A commit history is the cheapest form of evidence for that claim, and a private one proves nothing.
Next.js 16 (App Router; every page static) · React 19 · Tailwind CSS 4 ·
TypeScript · deployed on Vercel, icn1. The one dynamic route is
POST /api/intake, the contact form's endpoint.
There is no app/layout.tsx. Each locale is its own root layout under
app/(en) and app/(ko), so <html lang> is correct without a middleware
negotiating locale on every request.
A world map rendered as a dot matrix, where each square is a unit of campaign work. Move the cursor and scattered work falls into formation.
lib/swarm/layout.ts— deterministic seat generation, shared by the server (which renders a static SVG) and the browser (which renders the canvas), so the cross-fade between them is invisible.lib/swarm/step.ts— one physics tick. No neighbour queries: fluidity comes from per-dot phase noise and cohesion from the cursor field, so the loop is O(n) and needs no spatial index.lib/swarm/draw.ts— two batched Canvas 2D passes.lib/swarm/constants.ts— every tuning constant, in one file.
Four things keep this reading as a control display rather than a particle background, and dropping any one of them undoes it: squares instead of circles, integer-snapped coordinates, a grid that stays legible with no cursor on it, and monospace labels that are always visible.
Without JavaScript the hero is still a finished picture — the server renders
the whole world as a single <path>, about 1 kB gzipped.
Every number on the page comes from data/*.json, committed to this
repository and rendered on the server. Nothing is fetched after load, so a
crawler, a social preview, and a person all read the same figures.
| File | Maintained by |
|---|---|
fleet.json |
CI, from the source repo's agent modules |
quality.json |
CI, from the source repo's test run |
economics.json |
by hand; scopeNote is a required field |
replacements.json |
by hand, quoted verbatim from the source roster |
products.json |
by hand — names and links only |
portfolio.json |
by hand — every entry carries a link a visitor can verify |
competitions.json |
by hand — results re-verified against official winner pages; losses stay in |
people.json |
by hand |
partners.json |
by hand — grants and programs, never styled as backing |
geo.json |
by hand — where work has run, not reachable market |
tests/no-banned-claims.test.ts fails the build on figures this company
cannot evidence, and tests/ssr-numbers.test.ts fails it if a headline figure
is missing from the built HTML.
PostHog, US cloud, in the AgentBase organisation — its own project, separate
from the product ones, because a company page and a product share no funnel.
instrumentation-client.ts boots it, matching kbeauty.market. It earns the
spot twice over here: there is no app/layout.tsx to hang a component off, and
mounting one in each of the two locale layouts would be two chances to forget.
The import is dynamic, which is where this departs from kbeauty.market.
posthog-js is about 240 kB, and importing it statically puts all of it in the
chunk the document loads eagerly, next to the hero that waits two animation
frames precisely so it cannot delay the <h1>. Measuring the page must not be
the thing that slows it down.
The browser talks to /ingest on this origin, rewritten to PostHog in
next.config.ts — a request to a posthog.com host is on every ad blocker's
list, and this site's audience runs them. Two things about that are easy to
break silently, so tests/analytics-proxy.test.ts holds them: the
/ingest/static/* rule has to be matched before the /ingest/* catch-all, and
skipTrailingSlashRedirect has to stay on, because PostHog posts to /e/ and
/flags/ and Next would otherwise answer each event with a 308 first.
Visitors are never identified — there is no sign-in, so person_profiles is
identified_only and every event bills at the anonymous rate. Persistence is
PostHog's default cookie, which is what makes returning visitors countable;
there is no consent banner, so if EU traffic ever matters, that is the decision
to revisit.
Everything runs on the PostHog for Startups credits, which expire around August 2027. That is a reason to raise the data resolution now — replay retention, network payloads — on settings that cash alone would not justify later, and not a reason to spend for its own sake.
Set NEXT_PUBLIC_POSTHOG_KEY (see .env.example) in Vercel for Production and
Preview. It is inlined at build time, so changing it needs a redeploy. A local
checkout has no key and loads no analytics at all.
The contact form is two fields — a reply address and one line — posted to
/api/intake, which mails both founders through Resend's REST API (no SDK).
Set RESEND_API_KEY in Vercel (server-side only, never NEXT_PUBLIC_); the
sending domain has to be verified in Resend first. Without the key the
endpoint answers 503 and the form degrades to the mailto fallback, the same
pattern as the analytics key.
Spam is handled without a CAPTCHA: a honeypot field and a minimum-time check, both answered with an empty 204 so a bot learns nothing from the response.
pnpm dev # http://localhost:3000
pnpm verify # lint · typecheck · test · build
pnpm build && pnpm startRegenerating the land mask (rarely needed — the source data is static):
pnpm tsx scripts/generate-landmask.ts # → data/landmask.json
pnpm tsx scripts/measure-svg.ts # size check for the SVG fallback/llms.txt/index.md,/ko/index.md— rendered from the same content modules as the HTML, so the two cannot drift apart/data/*.json— the raw snapshots
CEO Sanggen Chang — sangguen@agentba.se CTO Sejun Kim — sejun@agentba.se