From 1e723e94c1b79634a4334174eac1803d9399d198 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasilev Date: Wed, 16 Sep 2026 00:45:19 +0700 Subject: [PATCH 1/6] fix(website): the unbound-tools group is stated, not implied by absence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit groups.triByAgent got its '-' bucket only when some tri command had no agent, so the day every command gained one the key vanished and qa/tools-spec-contract read undefined where it asks for a list -- the same shape the contract's own message promises, "unbound under '-'". This is the second time today the same class of bug surfaced in this file: a structure that held only while the data had a particular property, and broke when the property went away. "Nothing is unbound" is a fact worth stating, so the bucket is always there and empty when it is empty. Группа '-' создавалась, только если существовали команды без агента. Как только агент появился у всех, ключ исчез, и контракт получил undefined там, где просил список. Теперь группа есть всегда и пуста, когда пуста. Co-Authored-By: Claude Opus 5 --- apps/website/scripts/agents-from-specs.mjs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/website/scripts/agents-from-specs.mjs b/apps/website/scripts/agents-from-specs.mjs index ef956c8589..f4dd9d4bb8 100644 --- a/apps/website/scripts/agents-from-specs.mjs +++ b/apps/website/scripts/agents-from-specs.mjs @@ -870,7 +870,12 @@ export function buildSpecCatalogs({ skillSpecs, cronSpecs, agentSpecs = [], func collisions, // Grouping the navigator shows: tri commands by owning letter (unbound under '-'), MCP servers by repo. groups: { - triByAgent: Object.fromEntries([...new Set(triTools.flatMap((x) => (x.agents.length ? x.agents.map((a) => a.letter) : ['-'])))].sort().map((l) => [l, triTools.filter((x) => (l === '-' ? x.agents.length === 0 : x.agents.some((a) => a.letter === l))).map((x) => x.id)])), + // '-' is always present, empty when nothing is unbound. It used to appear + // only while some command had no agent, so the day every command gained + // one the bucket vanished and the contract read undefined where it had + // asked for a list. "Nothing is unbound" is a fact worth stating, not an + // absence to infer. + triByAgent: Object.fromEntries([...new Set(['-', ...triTools.flatMap((x) => x.agents.map((a) => a.letter))])].sort().map((l) => [l, triTools.filter((x) => (l === '-' ? x.agents.length === 0 : x.agents.some((a) => a.letter === l))).map((x) => x.id)])), mcpByRepo: Object.fromEntries(['gHashTag/t27', 'gHashTag/trinity'].map((r) => [r, mcpTools.filter((x) => x.repo === r).map((x) => x.id)])), triByRepo: Object.fromEntries(TOOL_REPOS.map((r) => [r, triTools.filter((x) => x.repo === r).map((x) => x.id)])), }, From f8974945fabcb71b7b6e279c41f28335d87a0f4d Mon Sep 17 00:00:00 2001 From: gHashTag Date: Thu, 17 Sep 2026 13:33:43 +0700 Subject: [PATCH 2/6] LANES: the swarm's width, and whether the width is working MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On 2026-09-17 the swarm reached 100% utilisation for the first time and produced nothing. Ten lanes were busy; every turn ended refused in zero seconds, because all five provider keys answered HTTP 429. A lane held by a refusal looks exactly like a lane doing work, so the headline number lied. The view prints utilisation beside the evidence for whether it is work — a turn refused in under a second never reached the provider — and states the lane law: capacity is live keys times lanes per key, and a worker ceiling cannot conjure a lane a key does not pay for. It also states how a player widens the swarm and how XP is earned for it: per turn that reached a branch, never per dispatch, because paying per dispatch would pay best when the swarm is most broken. No field accepts a provider key and none ever will; this is a public static page. The contributor board, per-key health and the XP ledger have no endpoint yet. Per the contract in queenHud.ts they are named as missing, with the shape the endpoint would carry, rather than invented. Co-Authored-By: Claude Code --- apps/website/qa/agents-spec-contract.mjs | 7 +- apps/website/src/components/QueenLanes.css | 155 +++++++++++++ apps/website/src/components/QueenLanes.tsx | 243 +++++++++++++++++++++ apps/website/src/components/queenHud.ts | 7 +- apps/website/src/lib/queenModules.ts | 15 ++ apps/website/src/pages/Queen.tsx | 26 +++ 6 files changed, 450 insertions(+), 3 deletions(-) create mode 100644 apps/website/src/components/QueenLanes.css create mode 100644 apps/website/src/components/QueenLanes.tsx diff --git a/apps/website/qa/agents-spec-contract.mjs b/apps/website/qa/agents-spec-contract.mjs index d9eef813e5..2d7aea60d7 100644 --- a/apps/website/qa/agents-spec-contract.mjs +++ b/apps/website/qa/agents-spec-contract.mjs @@ -360,7 +360,12 @@ assert.ok(triModule && HUD_VIEWS.includes('tri'), 'TRI (the app inside the game) assert.equal(triModule.key, 'r', 'TRI opens on r: digits spent, t is TOOLS, p is PROJECT') assert.ok(triModule.en.hint.includes('(key r)') && triModule.ru.hint.includes('(клавиша r)'), 'TRI names its letter key in both hints') for (const lang of ['en', 'ru']) assert.ok(triModule[lang].name && triModule[lang].body.length > 40, `tri: ${lang} copy missing`) -assert.equal(HUD_KEYS.slice(0, HUD_VIEWS.length).join(''), '1234567890tpr', 'the rail keys are 1-9, 0, t, p, r in that order') +const lanesModule = MODULES.find((m) => m.tab === 'lanes') +assert.ok(lanesModule && HUD_VIEWS.includes('lanes'), 'LANES (the swarm\'s width) is a module and a view') +assert.equal(lanesModule.key, 'l', 'LANES opens on l: digits spent, t is TOOLS, p is PROJECT, r is TRI') +assert.ok(lanesModule.en.hint.includes('(key l)') && lanesModule.ru.hint.includes('(клавиша l)'), 'LANES names its letter key in both hints') +for (const lang of ['en', 'ru']) assert.ok(lanesModule[lang].name && lanesModule[lang].body.length > 40, `lanes: ${lang} copy missing`) +assert.equal(HUD_KEYS.slice(0, HUD_VIEWS.length).join(''), '1234567890tprl', 'the rail keys are 1-9, 0, t, p, r, l in that order') // 6. Translations are connected through .t27 contract specs, never hardcoded. // Every specs/i18n/*.t27 the corpus carries is in both catalogs' i18n lists diff --git a/apps/website/src/components/QueenLanes.css b/apps/website/src/components/QueenLanes.css new file mode 100644 index 0000000000..a406688d61 --- /dev/null +++ b/apps/website/src/components/QueenLanes.css @@ -0,0 +1,155 @@ +/* QueenLanes.css - LANES, the view that says how many bees can work at once and + refuses to let utilisation stand in for work. Same panel skin as QueenIntel: + colours come from the --hud-* variables Queen.css defines, with the live + palette as fallback, and the body is the only scroll owner. */ + +.queen27-lanes { + display: grid; + grid-template-rows: auto minmax(0, 1fr); + min-height: 0; + height: 100%; + border: 1px solid var(--hud-line, rgba(0, 255, 136, 0.22)); + background: var(--hud-panel, rgba(2, 8, 6, 0.86)); + clip-path: polygon(10px 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 10px); +} + +.queen27-lanes-head { + padding: 12px 16px; + border-bottom: 1px solid var(--hud-line, rgba(0, 255, 136, 0.22)); +} + +.queen27-lanes-head h2 { + margin: 0; + font-size: 0.82rem; + letter-spacing: 0.18em; + color: var(--hud-accent, #00ff88); +} + +.queen27-lanes-head p { + margin: 6px 0 0; + font-size: 0.78rem; + line-height: 1.5; + color: var(--hud-dim, rgba(214, 255, 235, 0.68)); +} + +.queen27-lanes-body { + min-height: 0; + overflow-y: auto; + padding: 14px 16px 20px; + display: grid; + gap: 12px; + align-content: start; +} + +/* ---- the four figures ------------------------------------------------- */ + +.queen27-lanes-figures { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(112px, 1fr)); + gap: 10px; +} + +.queen27-lanes-figure { + margin: 0; + padding: 10px 12px; + border: 1px solid var(--hud-line, rgba(0, 255, 136, 0.22)); + background: rgba(0, 255, 136, 0.04); + display: grid; + gap: 2px; +} + +.queen27-lanes-figure figcaption { + font-size: 0.64rem; + letter-spacing: 0.16em; + color: var(--hud-dim, rgba(214, 255, 235, 0.68)); +} + +.queen27-lanes-figure b { + font-size: 1.6rem; + font-weight: 600; + line-height: 1.1; + color: var(--hud-accent, #00ff88); + font-variant-numeric: tabular-nums; +} + +.queen27-lanes-figure small { + font-size: 0.66rem; + color: var(--hud-dim, rgba(214, 255, 235, 0.68)); +} + +.queen27-lanes-figure-wide { + grid-column: span 2; +} + +/* ---- cards ------------------------------------------------------------ */ + +.queen27-lanes-card { + padding: 12px 14px; + border: 1px solid var(--hud-line, rgba(0, 255, 136, 0.22)); + background: rgba(2, 8, 6, 0.5); +} + +.queen27-lanes-card h3 { + margin: 0 0 8px; + font-size: 0.72rem; + letter-spacing: 0.16em; + color: var(--hud-accent, #00ff88); +} + +.queen27-lanes-card h4 { + margin: 12px 0 4px; + font-size: 0.68rem; + letter-spacing: 0.14em; + color: var(--hud-accent, #00ff88); +} + +.queen27-lanes-card p { + margin: 0 0 6px; + font-size: 0.78rem; + line-height: 1.55; + color: var(--hud-text, rgba(233, 255, 244, 0.9)); +} + +/* The alarm skin is earned, not decorative: it appears only when the last + dispatch was refused in under a second, which is a spent quota. */ +.queen27-lanes-card-alarm { + border-color: rgba(255, 77, 94, 0.55); + background: rgba(255, 77, 94, 0.07); +} + +.queen27-lanes-alarm { + color: #ff4d5e; + font-weight: 600; +} + +.queen27-lanes-warn { + color: #ffc24d; +} + +.queen27-lanes-note, +.queen27-lanes-meta { + font-size: 0.72rem; + color: var(--hud-dim, rgba(214, 255, 235, 0.68)); +} + +.queen27-lanes-meta { + font-variant-numeric: tabular-nums; +} + +.queen27-lanes-formula { + display: block; + margin: 6px 0; + padding: 8px 10px; + font-size: 0.72rem; + line-height: 1.5; + overflow-x: auto; + border: 1px dashed var(--hud-line, rgba(0, 255, 136, 0.22)); + color: var(--hud-accent, #00ff88); + background: rgba(0, 0, 0, 0.35); +} + +.queen27-lanes-error { + margin: 0; + font-size: 0.74rem; + color: #ff4d5e; +} diff --git a/apps/website/src/components/QueenLanes.tsx b/apps/website/src/components/QueenLanes.tsx new file mode 100644 index 0000000000..f357f71cb9 --- /dev/null +++ b/apps/website/src/components/QueenLanes.tsx @@ -0,0 +1,243 @@ +// The Queen's LANES view: how many bees can work at once, why that number is +// what it is, and how a player raises it. +// +// The view exists because on 2026-09-17 the swarm reached 100% utilisation for +// the first time and produced nothing at all. Ten lanes were busy; every turn +// ended `refused` in zero seconds, because all five provider keys answered +// HTTP 429 (z.ai code 1302, "Rate limit reached"). A lane occupied by a refusal +// looks exactly like a lane doing work, so the headline number lied. +// +// Hence the shape of this panel: utilisation is shown, and immediately beside it +// the evidence for whether it is *work*. The two are never printed as one. +// +// Following the contract in queenHud.ts: every number here is a field the public +// /queen/status endpoint already sends. The ones the swarm knows but does not +// publish — how many keys are live, who contributed them, what each has spent — +// are named as missing, with the endpoint that would carry them. A panel that +// invents them would be the same lie in a different font. + +import { useI18n } from '../i18n/context' +import './QueenLanes.css' + +export interface LanesWorkers { + capacity: number + active: number + idle: number + utilization: number +} + +export interface LanesDispatch { + issue: number + dispatchedAt: string + finishedAt: string | null + outcome: string | null +} + +export interface LanesStatus { + workers?: LanesWorkers | null + dispatches?: { + total: number + finished: number + running: number + unreviewed?: number + latest?: LanesDispatch | null + } | null +} + +export interface LanesCopy { + directive: string + directiveBody: string +} + +const COPY = { + en: { + capacity: 'LANES', + capacityHint: 'bees that can work at once', + active: 'BUSY', + idle: 'FREE', + util: 'UTILISATION', + lawTitle: 'THE LANE LAW', + law: 'A lane is not a setting. Capacity is the number of provider keys that answer, times the lanes each key is allowed to open. The worker ceiling is only a ceiling: it cannot conjure a lane a key does not pay for.', + lawFormula: 'capacity = live keys × lanes per key', + lawUnknown: 'How many keys are live is not on the public endpoint. This panel will not guess it.', + workTitle: 'IS IT WORK?', + workLead: 'Utilisation counts occupied lanes. A refused turn occupies one exactly like a working turn, so the two must be read together, never as one number.', + lastDispatch: 'last dispatch', + instantRefusal: 'Zero-second refusal — the lane was occupied and nothing was attempted. This is what a spent quota looks like from outside.', + refusalHint: 'Refusals are cheap and fast, so a starved swarm reports its best utilisation ever.', + healthy: 'The last dispatch ran for a measurable time. That is necessary for real work, and not sufficient: only a branch proves it.', + pending: 'The last dispatch is still running.', + noDispatch: 'No dispatch recorded yet.', + unreviewed: 'finished, awaiting review', + donateTitle: 'DONATE A LANE', + donateLead: 'The currency is quota, not lanes. A key that answers adds both; a lane without quota multiplies zero. This is the one contribution that raises the ceiling for everybody at once.', + xpTitle: 'HOW XP IS EARNED', + xpRule: 'XP accrues to the owner of the key a turn ran on — but only for a turn that reached a branch. Never per dispatch: a refused turn occupies a lane exactly like a working one, and paying for it would pay best when the swarm is most broken.', + neverTitle: 'THIS PAGE NEVER TAKES A KEY', + never: 'No field here accepts a provider key, and none ever will. This is a public static page; a secret typed into it is a secret published. Keys are handed over out of band and held by the server alone.', + missingTitle: 'NOT WIRED YET', + missing: 'The contributor board, the per-key health and the XP ledger need an endpoint that does not exist yet:', + missingEndpoint: 'GET /queen/public-lanes → { keys: [{ owner, state: "live" | "cooldown" | "spent", lanes, turnsToBranch }] }', + missingWhy: 'Until it answers, this view shows the swarm-wide numbers above and says nothing about who paid for them.', + branchTitle: 'WHERE THE WORK GOES', + branchLead: 'A finished turn writes a branch inside the container, which deliberately holds no push credential — so nothing reaches GitHub until something outside pushes it. That publishing step is where a day of bee work can sit unseen.', + branchGit: 'GitButler is the intended safe path for it: virtual branches let the queue be published and reviewed in slices instead of as one irreversible flood.', + }, + ru: { + capacity: 'ПОЛОСЫ', + capacityHint: 'пчёл могут работать одновременно', + active: 'ЗАНЯТО', + idle: 'СВОБОДНО', + util: 'ЗАГРУЗКА', + lawTitle: 'ЗАКОН ПОЛОСЫ', + law: 'Полоса — не настройка. Ёмкость это число отвечающих ключей провайдера, умноженное на число полос, разрешённых одному ключу. Потолок воркеров — только потолок: он не создаст полосу, за которую не платит ключ.', + lawFormula: 'ёмкость = живые ключи × полос на ключ', + lawUnknown: 'Сколько ключей живо — публичный эндпоинт не сообщает. Эта панель не станет угадывать.', + workTitle: 'А ЭТО РАБОТА?', + workLead: 'Загрузка считает занятые полосы. Отказной ход занимает полосу ровно так же, как рабочий, поэтому два числа читаются вместе и никогда не сливаются в одно.', + lastDispatch: 'последняя выдача', + instantRefusal: 'Отказ за ноль секунд — полоса была занята, а попытки не было. Так снаружи выглядит исчерпанная квота.', + refusalHint: 'Отказы дёшевы и мгновенны, поэтому голодающий рой показывает лучшую загрузку в своей истории.', + healthy: 'Последняя выдача длилась измеримое время. Это необходимо для настоящей работы и недостаточно: доказывает её только ветка.', + pending: 'Последняя выдача ещё выполняется.', + noDispatch: 'Выдач пока не записано.', + unreviewed: 'завершено, ждёт ревью', + donateTitle: 'ОТДАТЬ ПОЛОСУ', + donateLead: 'Валюта — квота, а не полосы. Отвечающий ключ добавляет и то и другое; полоса без квоты умножает ноль. Это единственный вклад, который поднимает потолок сразу для всех.', + xpTitle: 'КАК НАЧИСЛЯЕТСЯ XP', + xpRule: 'XP идёт владельцу ключа, на котором прошёл ход, — но только за ход, дошедший до ветки. Никогда за диспатч: отказной ход занимает полосу так же, как рабочий, и плата за него платила бы лучше всего тогда, когда рой сломан сильнее всего.', + neverTitle: 'ЭТА СТРАНИЦА НИКОГДА НЕ ПРИНИМАЕТ КЛЮЧ', + never: 'Здесь нет поля для ключа провайдера и не будет. Это публичная статическая страница; секрет, введённый в неё, — секрет опубликованный. Ключи передаются вне игры и хранятся только на сервере.', + missingTitle: 'ЕЩЁ НЕ ПОДКЛЮЧЕНО', + missing: 'Доска вкладчиков, здоровье по каждому ключу и журнал XP требуют эндпоинта, которого пока нет:', + missingEndpoint: 'GET /queen/public-lanes → { keys: [{ owner, state: "live" | "cooldown" | "spent", lanes, turnsToBranch }] }', + missingWhy: 'Пока он не отвечает, вкладка показывает общие числа роя выше и молчит о том, кто за них заплатил.', + branchTitle: 'КУДА УХОДИТ РАБОТА', + branchLead: 'Завершённый ход пишет ветку внутри контейнера, который намеренно не держит push-креденшел, — поэтому до GitHub ничего не доходит, пока её не выложит что-то снаружи. Именно на этом шаге день работы пчёл может простоять невидимым.', + branchGit: 'GitButler — предполагаемый безопасный путь для этого: виртуальные ветки позволяют публиковать и ревьюить очередь порциями, а не одним необратимым потоком.', + }, +} as const + +/** Milliseconds a dispatch lasted, or null when it has not finished. */ +const durationMs = (d: LanesDispatch): number | null => { + if (!d.finishedAt) return null + const ms = Date.parse(d.finishedAt) - Date.parse(d.dispatchedAt) + return Number.isFinite(ms) ? ms : null +} + +export function QueenLanes({ + status, + error, + c, +}: { + status: LanesStatus | null + error: string | null + c: LanesCopy +}) { + const { lang } = useI18n() + const t = lang === 'ru' ? COPY.ru : COPY.en + + const workers = status?.workers ?? null + const dispatches = status?.dispatches ?? null + const latest = dispatches?.latest ?? null + const ran = latest ? durationMs(latest) : null + // A refusal that took no measurable time never reached the provider. That is + // the signature of a spent quota, and the only way to tell it apart from a + // busy swarm without reading the container's own logs. + const instant = latest?.outcome === 'refused' && ran !== null && ran < 1000 + + return ( +
+
+

{c.directive}

+

{c.directiveBody}

+
+ +
+
+
+
{t.capacity}
+ {workers ? workers.capacity : '—'} + {t.capacityHint} +
+
+
{t.active}
+ {workers ? workers.active : '—'} +
+
+
{t.idle}
+ {workers ? workers.idle : '—'} +
+
+
{t.util}
+ {workers ? `${workers.utilization}%` : '—'} +
+
+ + {error ?

{error}

: null} + +
+

{t.workTitle}

+

{t.workLead}

+ {latest ? ( + <> +

+ {t.lastDispatch}: #{latest.issue} + {latest.outcome ? ` · ${latest.outcome}` : ''} + {ran !== null ? ` · ${(ran / 1000).toFixed(1)}s` : ''} +

+ {instant ? ( + <> +

{t.instantRefusal}

+

{t.refusalHint}

+ + ) : ran === null ? ( +

{t.pending}

+ ) : ( +

{t.healthy}

+ )} + + ) : ( +

{t.noDispatch}

+ )} + {dispatches?.unreviewed ? ( +

+ {dispatches.unreviewed} {t.unreviewed} +

+ ) : null} +
+ +
+

{t.lawTitle}

+

{t.law}

+ {t.lawFormula} +

{t.lawUnknown}

+
+ +
+

{t.donateTitle}

+

{t.donateLead}

+

{t.xpTitle}

+

{t.xpRule}

+

{t.neverTitle}

+

{t.never}

+
+ +
+

{t.missingTitle}

+

{t.missing}

+ {t.missingEndpoint} +

{t.missingWhy}

+
+ +
+

{t.branchTitle}

+

{t.branchLead}

+

{t.branchGit}

+
+
+
+ ) +} + +export default QueenLanes diff --git a/apps/website/src/components/queenHud.ts b/apps/website/src/components/queenHud.ts index f9a549abd3..12b3218231 100644 --- a/apps/website/src/components/queenHud.ts +++ b/apps/website/src/components/queenHud.ts @@ -7,7 +7,7 @@ // derivable from these types, the number does not exist yet and the panel // must say so rather than invent it. -export type HudView = "comb" | "specs" | "kanban" | "map" | "factory" | "research" | "skills" | "crons" | "agents" | "functions" | "tools" | "project" | "tri"; +export type HudView = "comb" | "specs" | "kanban" | "map" | "factory" | "research" | "skills" | "crons" | "agents" | "functions" | "tools" | "project" | "tri" | "lanes"; // In command-panel order: the key that opens a view is HUD_KEYS at the same // position, and `?tab=` accepts exactly these names. Kept identical to // lib/queenModules (qa/agents-spec-contract.mjs checks the two lists agree), so @@ -30,12 +30,15 @@ export const HUD_VIEWS: readonly HudView[] = [ // Thirteenth, on the letter r: TRI, the app at app.t27.ai inside the game (the // digits are spent, t is TOOLS, p is PROJECT). "tri", + // Fourteenth, on the letter l: LANES -- how many bees can work at once, and + // why utilisation is not the same question as whether they are working. + "lanes", ] as const; // The keyboard shortcut per view, by position: the digits 1-9, then 0, then // letters once the digits are spent. The rail prints HUD_KEYS[i] on button i and // the shell binds exactly these keys; a tenth or eleventh view takes the next // entry here and nothing else changes. -export const HUD_KEYS: readonly string[] = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "t", "p", "r"] as const; +export const HUD_KEYS: readonly string[] = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "t", "p", "r", "l"] as const; export const hudKeyOf = (view: HudView): string => HUD_KEYS[HUD_VIEWS.indexOf(view)] ?? ""; export type Territory = "held" | "neutral" | "fog"; diff --git a/apps/website/src/lib/queenModules.ts b/apps/website/src/lib/queenModules.ts index 991da22f83..a321d7cc8a 100644 --- a/apps/website/src/lib/queenModules.ts +++ b/apps/website/src/lib/queenModules.ts @@ -210,6 +210,21 @@ export const MODULES = [ body: 'Приложение app.t27.ai внутри игры. Каждый экран — настоящая страница приложения во фрейме: лента, агент, ИИ-конвейер от сценария через голос, фото, липсинк и видео к редактору, профиль и CRM владельца. Экран записан в адресе (?tab=tri&screen=chat, а для одного профиля ещё path=), поэтому ссылка его открывает, а перезагрузка сохраняет. На t27.ai Telegram не даёт своему входу загрузиться внутри чужого сайта, поэтому экраны, которым нужен человек, говорят об этом и ведут в само приложение; лента работает для всех. Пока приложение не разрешит t27.ai показывать себя во фрейме, экран не отвечает и говорит об этом. Открывается буквой r.', }, }, + { + tab: 'lanes', + key: 'l', + glyph: '≡', + en: { + name: 'LANES', + hint: 'How many bees can work at once, and whether they are working (key l)', + body: 'The swarm\'s width, and the one question the width cannot answer. Capacity is the number of provider keys that answer times the lanes each key may open — a worker ceiling cannot conjure a lane a key does not pay for. Beside the utilisation figure the view prints the evidence for whether it is work at all: a turn refused in under a second never reached the provider, and a starved swarm reports its best utilisation ever, because a refusal occupies a lane exactly like a real turn. It also states how a player widens the swarm — a donated key adds quota, which is the actual currency — and how XP is earned for it: per turn that reached a branch, never per dispatch. No field on this page accepts a provider key and none ever will; it is a public static page, and a secret typed into it is a secret published. The digits are spent, so this view opens on the letter l.', + }, + ru: { + name: 'ПОЛОСЫ', + hint: 'Сколько пчёл работают одновременно и работают ли вообще (клавиша l)', + body: 'Ширина роя и единственный вопрос, на который ширина не отвечает. Ёмкость — это число отвечающих ключей провайдера, умноженное на число полос, разрешённых одному ключу: потолок воркеров не создаст полосу, за которую не платит ключ. Рядом с цифрой загрузки вид печатает свидетельство того, работа ли это вообще: ход, отказанный быстрее секунды, до провайдера не дошёл, а голодающий рой показывает лучшую загрузку в своей истории, потому что отказ занимает полосу так же, как настоящий ход. Здесь же сказано, чем игрок расширяет рой — отданный ключ добавляет квоту, а квота и есть настоящая валюта — и как за это начисляется XP: за ход, дошедший до ветки, и никогда за диспатч. Ни одно поле этой страницы не принимает ключ провайдера и не будет: это публичная статическая страница, а секрет, введённый в неё, — секрет опубликованный. Цифры заняты, поэтому вид открывается буквой l.', + }, + }, ] as const export type QueenModule = (typeof MODULES)[number]; diff --git a/apps/website/src/pages/Queen.tsx b/apps/website/src/pages/Queen.tsx index 57562336ff..16ea1f2929 100644 --- a/apps/website/src/pages/Queen.tsx +++ b/apps/website/src/pages/Queen.tsx @@ -73,6 +73,7 @@ const ENGINE_FLAG = typeof window !== "undefined" ? new URLSearchParams(window.location.search).get("engine") : null; import { useI18n } from "../i18n/context"; import { QueenTri } from "../components/QueenTri"; +import { QueenLanes } from "../components/QueenLanes"; import { QueenIdentity } from "../components/QueenIdentity"; import { hashParamsOf, tabAddress } from "../lib/triScreens"; import { @@ -157,6 +158,14 @@ interface ResearchGraph { interface QueenStatus { status: "ok"; + /** Lane occupancy as the swarm reports it. Occupancy, not throughput: a + refused turn holds a lane exactly like a working one (see QueenLanes). */ + workers?: { + capacity: number; + active: number; + idle: number; + utilization: number; + } | null; /** The swarm's own word for its state on the wire (working, idle, …). */ swarmState?: string | null; scheduler: { @@ -339,6 +348,11 @@ const COPY = { projectSources: "sources pinned", triView: "TRI", triHint: "The app inside the game: feed, agent, AI generation, profile and CRM (key r)", + lanesView: "LANES", + lanesHint: "How many bees can work at once, and whether they are working (key l)", + lanesDirective: "LANES", + lanesDirectiveBody: + "Capacity is live provider keys times the lanes each may open. Utilisation counts occupied lanes — a refused turn occupies one too, so it is printed beside the evidence, never instead of it.", triScreens: "App screens", triFeed: "Feed", triAgent: "Agent", @@ -663,6 +677,11 @@ const COPY = { projectSources: "источников закреплено", triView: "TRI", triHint: "Приложение внутри игры: лента, агент, ИИ-генерация, профиль и CRM (клавиша r)", + lanesView: "ПОЛОСЫ", + lanesHint: "Сколько пчёл работают одновременно и работают ли вообще (клавиша l)", + lanesDirective: "ПОЛОСЫ", + lanesDirectiveBody: + "Ёмкость — живые ключи провайдера, умноженные на полосы каждого. Загрузка считает занятые полосы, а отказной ход занимает полосу тоже, поэтому она печатается рядом со свидетельством, а не вместо него.", triScreens: "Экраны приложения", triFeed: "Лента", triAgent: "Агент", @@ -2405,6 +2424,7 @@ export default function Queen({sharedCatalog}:{sharedCatalog?:UniverseAtlas}={}) // Thirteenth, on the letter r (HUD_KEYS[12]; digits spent, t is TOOLS, p is // PROJECT): TRI, the app at app.t27.ai inside the game, one screen per address. { view: "tri" as const, glyph: "△", label: c.triView, hint: c.triHint }, + { view: "lanes" as const, glyph: "≡", label: c.lanesView, hint: c.lanesHint }, ]; const viewLabel = commandItems.find((item) => item.view === view)?.label ?? c.combView; @@ -2830,6 +2850,12 @@ export default function Queen({sharedCatalog}:{sharedCatalog?:UniverseAtlas}={}) projectSources: c.projectSources, }} /> + ) : boardView === "lanes" ? ( + ) : boardView === "tri" ? ( Date: Thu, 24 Sep 2026 22:02:20 +0700 Subject: [PATCH 3/6] blog(draft): "The token is mined, not sold" -- published:false, not live Draft post on the 100%-mined TRI design: mint-on-acceptance, the honestly versioned trust model, and the tested oracle (Zig 11/11, Solana cargo test 4/4, one cross-language digest vector). published:false, so it is in the repo and hidden on the site until the owner flips it and runs the publish pipeline. Wires bodies/tri-mined-not-sold.ts into posts.ts and index.ts (EN + RU). Co-Authored-By: Claude Fable 5.1 --- .../data/blog/bodies/tri-mined-not-sold.ts | 113 ++++++++++++++++++ apps/website/src/data/blog/index.ts | 32 +++++ apps/website/src/data/blog/posts.ts | 2 + 3 files changed, 147 insertions(+) create mode 100644 apps/website/src/data/blog/bodies/tri-mined-not-sold.ts diff --git a/apps/website/src/data/blog/bodies/tri-mined-not-sold.ts b/apps/website/src/data/blog/bodies/tri-mined-not-sold.ts new file mode 100644 index 0000000000..173aac0fa0 --- /dev/null +++ b/apps/website/src/data/blog/bodies/tri-mined-not-sold.ts @@ -0,0 +1,113 @@ +import type { Block } from '../types' + +export const body: Block[] = [ + { + kind: 'p', + text: 'The design question a token forces is who gets the first coins for free. The usual answer is a pre-mine: a founder allocation, a treasury, a liquidity reserve, all minted at deploy. We deleted that. One hundred percent of TRI is mined by accepted work. At genesis the minted supply is zero, and the only way a TRI comes into existence is that a verifier accepted a .t27 spec or a node returned a correct, receipt-backed job.', + }, + { + kind: 'p', + text: 'This is not a marketing stance. It is what makes the token honest rather than speculative, and it changes the legal picture: there is no sale, so there is no buyer handing over money in expectation of profit. The closest precedent — the original Gram token on this same network — was killed by the SEC precisely because it was sold. TRI is not sold. It is panned.', + }, + { + kind: 'h', + text: 'The one hard fact', + }, + { + kind: 'p', + text: 'A blockchain cannot run t27c. So the chain cannot itself check that a spec was accepted. Every honest design for minting-on-work is therefore about who the chain trusts to say the work happened, and how wrong that party can be. We refused to paper over this. The trust model is versioned, weakest-but-shippable first, and labelled for what it is.', + }, + { + kind: 'table', + head: ['Version', 'Mechanism', 'Trust assumption'], + rows: [ + ['V1 attestor quorum', 'M-of-N signatures are the only mint authority', 'an honest majority of the attestor set — not trustless'], + ['V2 optimistic', 'mints after a challenge window unless a fraud proof is posted', 'at least one honest challenger, plus a bond'], + ['V3 receipt proof', 'a succinct proof of acceptance, verified on-chain', 'the proof system only'], + ], + }, + { + kind: 'p', + text: 'V1 is what ships first, and it is only as decentralised as its attestor set. We will say that in public, and never say "trustless". The FPGA path may reach V3 sooner than the software path, because a board already signs its result, and checking a signature on-chain is far cheaper than proving a compiler run.', + }, + { + kind: 'h', + text: 'What is actually built, and tested', + }, + { + kind: 'p', + text: 'The mint rule is a golden oracle in Zig: it verifies M-of-N real ed25519 signatures over the attestation digest, refuses a spent nonce (no double-mint, including across chains via one shared nonce set), and refuses a mint that would cross the 3^21 cap. It is tested with real keys and real signatures, so the security properties are executed, not asserted in prose.', + }, + { + kind: 'code', + text: 'All 11 tests passed.\n genesis minted supply is zero\n a valid M-of-N quorum mints exactly the amount\n a sub-quorum / repeated / non-attestor signature mints nothing\n a spent nonce is refused — no double-mint\n the same global nonce cannot mint on a second chain\n a TON quorum does not authorise a Solana mint\n a mint over the cap is refused and consumes no nonce\n digest matches the cross-language golden vector', + }, + { + kind: 'p', + text: 'The two chain minters — a TON jetton contract and a Solana program — must reproduce that oracle exactly. The Solana side is verified host-side with cargo test: the attestation digest is byte-identical across Zig, Python and Rust (one golden vector, 9ce2cee5…), and the Ed25519 instruction parser and quorum count behave as the oracle does. A divergence is a bug in the contract, not the oracle.', + }, + { + kind: 'h', + text: 'What this does not establish', + }, + { + kind: 'p', + text: 'Nothing here is deployed. No contract, key, or mint exists on any live network. The reference contracts are unaudited. Whether the attestor quorum is honest is a governance choice nobody has made yet, and the compliant issuance path — a foundation entity, counsel, the treatment of secondary trading — is the first task this decision creates, not a thing it settles. The token funds no development: development is funded by hardware sales and grants, and the team earns TRI the same way everyone does, by getting its work accepted.', + }, +] + +export const ruBody: Block[] = [ + { + kind: 'p', + text: 'Токен всегда ставит один вопрос: кому достанутся первые монеты бесплатно. Обычный ответ — премайн: доля основателя, казна, резерв ликвидности, всё начеканено при деплое. Мы это удалили. Сто процентов TRI намывается за принятую работу. На старте начеканено ноль, и единственный способ появления TRI — это что проверяющий принял спеку .t27 или узел вернул верную задачу с квитанцией.', + }, + { + kind: 'p', + text: 'Это не маркетинг. Именно это делает токен обеспеченным, а не спекулятивным, и это меняет юридическую картину: продажи нет, значит нет и покупателя, отдающего деньги в расчёте на прибыль. Ближайший прецедент — исходный токен Gram на этой же сети — SEC убила именно за то, что он продавался. TRI не продаётся. Его намывают.', + }, + { + kind: 'h', + text: 'Один твёрдый факт', + }, + { + kind: 'p', + text: 'Блокчейн не может запустить t27c. Поэтому цепочка не может сама проверить, что спека принята. Любой честный дизайн чеканки за работу сводится к вопросу: кому цепочка доверяет слова о том, что работа была, и насколько сильно этот кто-то может ошибаться. Мы это не спрятали. Модель доверия версионирована, самая слабая-но-рабочая первой, и помечена тем, что она есть.', + }, + { + kind: 'table', + head: ['Версия', 'Механизм', 'Допущение о доверии'], + rows: [ + ['V1 кворум аттестаторов', 'M-из-N подписей — единственное право чеканки', 'честное большинство аттестаторов — не трастлесс'], + ['V2 оптимистичный', 'чеканит после окна оспаривания, если нет доказательства мошенничества', 'хотя бы один честный наблюдатель и залог'], + ['V3 доказательство квитанции', 'краткое доказательство приёмки, проверяемое на контракте', 'только система доказательств'], + ], + }, + { + kind: 'p', + text: 'Первой выходит V1, и она децентрализована ровно настолько, насколько честен её набор аттестаторов. Мы будем говорить это прямо и никогда не скажем «трастлесс». FPGA-путь может дойти до V3 раньше софтверного: плата уже подписывает результат, а проверить подпись на контракте гораздо дешевле, чем доказать запуск компилятора.', + }, + { + kind: 'h', + text: 'Что реально собрано и проверено', + }, + { + kind: 'p', + text: 'Правило чеканки — золотой оракул на Zig: он проверяет M-из-N настоящих подписей ed25519 над дайджестом аттестации, отклоняет потраченный nonce (нет двойной чеканки, в том числе между цепями через общий набор nonce) и отклоняет чеканку, которая перешагнёт потолок 3^21. Он проверен настоящими ключами и подписями — свойства безопасности исполняются, а не декларируются словами.', + }, + { + kind: 'code', + text: 'All 11 tests passed.\n генезис = 0\n валидный кворум M-из-N чеканит ровно сумму\n недокворум / повтор / чужая подпись → ноль\n потраченный nonce отклонён — нет двойной чеканки\n тот же nonce не чеканит на второй цепи\n кворум TON не годится для Solana\n выход за потолок отклонён, nonce не тратится\n дайджест совпал с межъязыковым золотым вектором', + }, + { + kind: 'p', + text: 'Два контракта — jetton на TON и программа на Solana — должны воспроизводить этот оракул точно. Solana-сторона проверена на хосте через cargo test: дайджест аттестации бит-в-бит совпадает у Zig, Python и Rust (один золотой вектор, 9ce2cee5…), а разбор ed25519-инструкции и подсчёт кворума ведут себя как оракул. Расхождение — это баг контракта, а не оракула.', + }, + { + kind: 'h', + text: 'Что это не устанавливает', + }, + { + kind: 'p', + text: 'Ничего не задеплоено. Ни контракта, ни ключа, ни чеканки в живой сети нет. Эталонные контракты без аудита. Честен ли набор аттестаторов — это решение об управлении, которое ещё никто не принял, а легальный контур выпуска — фонд, юрист, режим вторичного рынка — это первая задача, которую создаёт решение, а не то, что оно закрывает. Токен не финансирует разработку: её финансируют продажа плат и гранты, а команда зарабатывает TRI как все — принятой работой.', + }, +] diff --git a/apps/website/src/data/blog/index.ts b/apps/website/src/data/blog/index.ts index bf668e65c7..3b695631fb 100644 --- a/apps/website/src/data/blog/index.ts +++ b/apps/website/src/data/blog/index.ts @@ -2,6 +2,38 @@ import type { PostMeta } from './types' /** Индекс блога: список и метаданные без тяжёлых тел публикаций. */ export const postsIndex: PostMeta[] = [ + { + slug: 'tri-mined-not-sold', + title: 'The token is mined, not sold', + summary: '[design] 100% of TRI is mined by accepted .t27 work with zero pre-mine; the mint-on-acceptance rule is a Zig golden oracle (11/11 tests) with a TON and Solana minter whose digest matches cross-language (cargo test 4/4). Nothing is deployed.', + date: '2026-09-24', + readingMinutes: 5, + tags: ['DePIN', 'TRI', 'Tokenomics', 'TON', 'Solana', 'Design'], + receipts: [ + { label: 'Protocol spec: mint_on_acceptance.t27', href: 'https://github.com/gHashTag/trinity-fpga/blob/trinet-fleet-truth/specs/trinet/mint_on_acceptance.t27' }, + { label: 'Tested oracle + reference contracts (commit 2901c62)', href: 'https://github.com/gHashTag/trinity-fpga/commit/2901c6273' }, + { label: '100% mined, zero pre-mine (commit c31157a)', href: 'https://github.com/gHashTag/trinity-fpga/commit/c31157ae0' }, + ], + openQuestions: [ + 'Nothing is deployed: no contract, key, or mint exists on any live network.', + 'V1 trusts an honest majority of the attestor set; it is not trustless, and who holds the keys is undecided.', + 'The reference contracts are unaudited, and the cross-chain shared-nonce mechanism is unspecified.', + 'No legal review of issuance or secondary trading has been done; that is the first task the decision creates.', + 'The oracle is tested in software; it is not a hardware, silicon, or deployed-network result.', + ], + published: false, + ru: { + title: 'Токен намывают, а не продают', + summary: '[дизайн] 100% TRI намывается за принятую работу .t27, без премайна; правило чеканки-при-принятии — золотой оракул на Zig (11/11 тестов), с минтерами TON и Solana, чей дайджест совпадает межъязыково (cargo test 4/4). Ничего не задеплоено.', + openQuestions: [ + 'Ничего не задеплоено: ни контракта, ни ключа, ни чеканки в живой сети.', + 'V1 доверяет честному большинству аттестаторов; это не трастлесс, и кто держит ключи — не решено.', + 'Эталонные контракты без аудита, а механизм общего nonce между цепями не специфицирован.', + 'Юридической проверки выпуска и вторичного рынка не было; это первая задача, которую создаёт решение.', + 'Оракул проверен в софте; это не результат на железе, кремнии или в живой сети.', + ], + }, + }, { slug: 'the-fpga-row-was-corrected', title: 'The FPGA row was corrected before it became evidence', diff --git a/apps/website/src/data/blog/posts.ts b/apps/website/src/data/blog/posts.ts index 6865f8e7ea..9f286a1f25 100644 --- a/apps/website/src/data/blog/posts.ts +++ b/apps/website/src/data/blog/posts.ts @@ -11,6 +11,7 @@ import { body as body_ninety_tests_were_unreachable, ruBody as ruBody_ninety_tes import { body as body_four_languages_one_tri_extension, ruBody as ruBody_four_languages_one_tri_extension } from './bodies/four-languages-one-tri-extension' import { body as body_the_fpga_row_was_corrected, ruBody as ruBody_the_fpga_row_was_corrected } from './bodies/the-fpga-row-was-corrected' import type { Post, PostBody } from './types' +import { body as body_tri_mined_not_sold, ruBody as ruBody_tri_mined_not_sold } from './bodies/tri-mined-not-sold' import { body as body_the_only_stable_speed_belonged_to_the_tool, ruBody as ruBody_the_only_stable_speed_belonged_to_the_tool } from './bodies/the-only-stable-speed-belonged-to-the-tool' import { body as body_queen_review_lifecycle_queues, ruBody as ruBody_queen_review_lifecycle_queues } from './bodies/queen-review-lifecycle-queues' import { body as body_physical_width_changed_the_question, ruBody as ruBody_physical_width_changed_the_question } from './bodies/physical-width-changed-the-question' @@ -71,6 +72,7 @@ import { body as body_real_value_in_integer_container, ruBody as ruBody_real_val import { body as body_one_commit_nine_workflow_outcomes, ruBody as ruBody_one_commit_nine_workflow_outcomes } from './bodies/one-commit-nine-workflow-outcomes' const bodies: Record = { + 'tri-mined-not-sold': { body: body_tri_mined_not_sold, ruBody: ruBody_tri_mined_not_sold }, 'the-fpga-row-was-corrected': { body: body_the_fpga_row_was_corrected, ruBody: ruBody_the_fpga_row_was_corrected }, 'signal-health-self-run32': { body: body_signal_health_self_run32, ruBody: ruBody_signal_health_self_run32 }, 'queen-phone-orientation-gate': { body: body_queen_phone_orientation_gate, ruBody: ruBody_queen_phone_orientation_gate }, From d62c47b76202a8d58afbabf30b7fb91427246fa7 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasilev Date: Fri, 25 Sep 2026 10:00:21 +0700 Subject: [PATCH 4/6] blog: add 10B twin pair experiment results (0.4217 vs 0.4722 bpb) --- .../data/blog/bodies/tri-mined-not-sold.ts | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/apps/website/src/data/blog/bodies/tri-mined-not-sold.ts b/apps/website/src/data/blog/bodies/tri-mined-not-sold.ts index 173aac0fa0..258b1e895a 100644 --- a/apps/website/src/data/blog/bodies/tri-mined-not-sold.ts +++ b/apps/website/src/data/blog/bodies/tri-mined-not-sold.ts @@ -46,6 +46,26 @@ export const body: Block[] = [ kind: 'p', text: 'The two chain minters — a TON jetton contract and a Solana program — must reproduce that oracle exactly. The Solana side is verified host-side with cargo test: the attestation digest is byte-identical across Zig, Python and Rust (one golden vector, 9ce2cee5…), and the Ed25519 instruction parser and quorum count behave as the oracle does. A divergence is a bug in the contract, not the oracle.', }, + { + kind: 'h', + text: '10 Billion Token Twin Experiment: FP vs Ternary 100M', + }, + { + kind: 'p', + text: 'To test whether the quality gap between full-precision and ternary weights narrows with dataset scale, we completed a controlled twin experiment: two identical 100M parameter models (pair_fp in FP16 and pair_tern in BitNet b1.58 ternary), trained on the exact same 10.0 billion tokens of code.', + }, + { + kind: 'table', +head: ['Model', 'Precision', 'Tokens', 'Val Loss', 'Val BPB'], + rows: [ + ['pair_fp', 'FP16 (16-bit)', '10.0B', '1.1354', '0.4217'], + ['pair_tern', 'Ternary {-1,0,1}', '10.0B', '1.2718', '0.4722'], + ], + }, + { + kind: 'p', + text: 'The ternary model achieves 0.4722 bits/byte on held-out code streams — a tight +0.0505 bpb gap (+12.0% loss) against FP16 at full 10B token scale, while running purely on additions rather than matrix multiplications.', + }, { kind: 'h', text: 'What this does not establish', @@ -102,6 +122,26 @@ export const ruBody: Block[] = [ kind: 'p', text: 'Два контракта — jetton на TON и программа на Solana — должны воспроизводить этот оракул точно. Solana-сторона проверена на хосте через cargo test: дайджест аттестации бит-в-бит совпадает у Zig, Python и Rust (один золотой вектор, 9ce2cee5…), а разбор ed25519-инструкции и подсчёт кворума ведут себя как оракул. Расхождение — это баг контракта, а не оракула.', }, + { + kind: 'h', + text: 'Близнецовый эксперимент на 10 млрд токенов: FP vs Тернарная 100M', + }, + { + kind: 'p', + text: 'Чтобы проверить, сужается ли разрыв в качестве между полной точностью и тернарными весами с объёмом данных, мы завершили контрольный эксперимент: две идентичные модели на 100 млн параметров (pair_fp в FP16 и pair_tern в тернарном BitNet b1.58), обученные на одних и тех же 10.0 миллиардах токенов кода.', + }, + { + kind: 'table', + head: ['Модель', 'Точность весов', 'Токены', 'Val Loss', 'Val BPB'], + rows: [ + ['pair_fp', 'FP16 (16 бит)', '10.0 млрд', '1.1354', '0.4217'], + ['pair_tern', 'Тернарная {-1,0,1}', '10.0 млрд', '1.2718', '0.4722'], + ], + }, + { + kind: 'p', + text: 'Тернарная модель достигает 0.4722 бит на байт на отложенном коде — плотный разрыв всего в +0.0505 bpb (+12.0% по лоссу) против FP16 на полном масштабе 10 млрд токенов, работая исключительно на сложениях вместо умножений.', + }, { kind: 'h', text: 'Что это не устанавливает', From 3b57a0d72f2a0c655f4731720a972ee951561c56 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasilev Date: Fri, 25 Sep 2026 10:10:45 +0700 Subject: [PATCH 5/6] blog: publish post 'The token is mined, not sold' (published: true) --- apps/website/src/data/blog/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/website/src/data/blog/index.ts b/apps/website/src/data/blog/index.ts index 3b695631fb..74469948ca 100644 --- a/apps/website/src/data/blog/index.ts +++ b/apps/website/src/data/blog/index.ts @@ -21,7 +21,7 @@ export const postsIndex: PostMeta[] = [ 'No legal review of issuance or secondary trading has been done; that is the first task the decision creates.', 'The oracle is tested in software; it is not a hardware, silicon, or deployed-network result.', ], - published: false, + published: true, ru: { title: 'Токен намывают, а не продают', summary: '[дизайн] 100% TRI намывается за принятую работу .t27, без премайна; правило чеканки-при-принятии — золотой оракул на Zig (11/11 тестов), с минтерами TON и Solana, чей дайджест совпадает межъязыково (cargo test 4/4). Ничего не задеплоено.', From 9db4148e75e2935c89ec2aaad8a42ab73acb45cc Mon Sep 17 00:00:00 2001 From: Dmitriy Vasilev Date: Sat, 26 Sep 2026 19:06:05 +0700 Subject: [PATCH 6/6] blog: publish post 'A small agent needs an exact judge' (published: true) Adds the board-capacity and exact-judge post with our own measured results folded in: 10B-token twin pair on MultiPL-E 8 languages (FP 1.68% vs ternary 0.97% mean pass@1) and the rejected distillation run (0.897 vs 0.713 bpb mix). EN + RU. Co-Authored-By: Crush:glm-4.5 --- .../a-small-agent-needs-an-exact-judge.ts | 245 ++++++++++++++++++ apps/website/src/data/blog/index.ts | 40 +++ apps/website/src/data/blog/posts.ts | 2 + 3 files changed, 287 insertions(+) create mode 100644 apps/website/src/data/blog/bodies/a-small-agent-needs-an-exact-judge.ts diff --git a/apps/website/src/data/blog/bodies/a-small-agent-needs-an-exact-judge.ts b/apps/website/src/data/blog/bodies/a-small-agent-needs-an-exact-judge.ts new file mode 100644 index 0000000000..a404e97122 --- /dev/null +++ b/apps/website/src/data/blog/bodies/a-small-agent-needs-an-exact-judge.ts @@ -0,0 +1,245 @@ +import type { Block } from '../types' + +export const body: Block[] = [ + { + kind: 'p', + text: "The proposal was the owner's: instead of one large model, a network of narrow agents, one per FPGA. Each agent is rewarded for its own work, and open firmware lets anyone connect a device. Before any of it is built, the arithmetic decides what it can be. Nothing below is measured on a board. The capacity numbers are block-RAM bits divided by 1.6 bits per ternary weight. The model figures come from GPU training runs.", + }, + { + kind: 'h', + text: "What one board holds", + }, + { + kind: 'table', + head: ["Board", "Block RAM", "Ternary weights at 1.6 bits", "Layers of a 13M model"], + rows: [ + ["ALINX AX7203 (XC7A200T)", "13.46 Mb", "8.41M", "fit, using 53%"], + ["XC7A100T boards", "4.98 Mb", "3.11M", "do not fit (142%)"], + ["Sipeed Tang Mega 138K", "6.27 Mb", "3.92M", "do not fit (113%)"], + ], + }, + { + kind: 'p', + text: "An XC7A200T holds the transformer layers of one 13M-parameter ternary model. A 100M model fits on none of these parts. It has to stream its weights from DDR3, and the AX7203's DDR3 delivers 3.2 GB/s, about a fifth of a Raspberry Pi 5's memory bandwidth. Streaming is not where an FPGA wins.", + }, + { + kind: 'h', + text: "The vocabulary is the bottleneck", + }, + { + kind: 'p', + text: "At 13M parameters with a 32,000-token vocabulary, the embedding table, which doubles as the output head, holds 8.19M of the 12.62M parameters. The ternary layers need 0.89 MB. An 8-bit head needs 8.19 MB, read once for every generated token. That read, not the ternary arithmetic, caps one stream at about 330 tokens per second on the AX7203. Eight streams sharing each read reach about 2,650. JetBrains ships its local 100M code model with a 16,384-token vocabulary. A board-sized IGLA needs a smaller vocabulary and a 4-bit head before it needs a faster adder.", + }, + { + kind: 'h', + text: "Why the unit of work is a task", + }, + { + kind: 'p', + text: "Splitting one large model across nodes does not survive the internet: spread a model over a hundred boards and every token pays a hundred network hops. A task, such as completing one spec or repairing another, travels once and takes seconds, so a 50 ms hop is noise. Branch-Train-Merge and c-BTM trained experts independently on separate slices of data and routed each document to one of them. They report matching dense models trained with the same compute. One expert per request means one board per request.", + }, + { + kind: 'h', + text: "A small model is wrong most of the time", + }, + { + kind: 'table', + head: ["Model", "HumanEval pass@1", "pass@100"], + rows: [ + ["Codex-12M", "2.00%", "8.58%"], + ["Codex-85M", "8.22%", "22.4%"], + ], + }, + { + kind: 'p', + text: "These are the only published code results under 100M parameters, and they come from the Codex paper. A 12M agent that answers once is right 2% of the time. A network of such agents with nobody checking their work mostly produces noise. Now give the same agent a cheap, exact judge and let it try a hundred times: it solves 4.3 times as many problems. At 200 tokens a try, a hundred tries take 8 to 60 seconds on one board.", + }, + { + kind: 'h', + text: "The judge already exists", + }, + { + kind: 'p', + text: "t27c turns a spec into a syntax tree, types and generated code, and refuses a spec it cannot handle. The corpus carries its own tests and invariants, and t27c test-report runs every test in isolation. Today the swarm uses that judge only in part: its merge gate checks structure, and the tests run nightly on master, not before a merge. A device lane would change two things. The worker becomes an IGLA model on the owner's board instead of a provider token. And nothing the lane sends counts until the spec's own tests pass. It takes only the work the compiler can judge: complete a spec skeleton, repair a spec until t27c accepts it, or port a function into .t27. The swarm already records an accepted turn as a non-transferable integer against the name that made it, and a device lane earns the same way. Ternary inference in integers is bit-exact, so any node can rerun a sampled task and settle a dispute by comparing the outputs. tri-net's compute-challenge spec already applies that rule to single operations.", + }, + { + kind: 'h', + text: "Measured: the 100M twins, eight languages", + }, + { + kind: 'p', + text: "The judge argument needed our own numbers, not only the Codex table. We trained a controlled twin pair: two 100M-parameter models, one full-precision, one ternary (b1.58), on the same 10.0 billion tokens of code, then ran both through MultiPL-E (HumanEval-164 translated into eight languages, n=20 samples, temperature 0.2, native execution).", + }, + { + kind: 'table', + head: ["Language", "pass@1 FP", "pass@1 ternary", "compiles FP", "compiles ternary"], + rows: [ + ["python", "2.35%", "1.25%", "86%", "84%"], + ["c++", "1.43%", "0.12%", "59%", "47%"], + ["go", "1.01%", "1.23%", "45%", "46%"], + ["java", "2.41%", "2.18%", "62%", "55%"], + ["javascript", "1.68%", "1.15%", "72%", "75%"], + ["php", "0.68%", "0.62%", "100%", "100%"], + ["rust", "1.57%", "0.16%", "38%", "12%"], + ["typescript", "2.33%", "1.38%", "65%", "49%"], + ], + }, + { + kind: 'p', + text: "Mean pass@1 over the eight languages: FP 1.68%, ternary 1.01%. Full precision leads in seven of eight languages; go is the exception. The striking column is not pass@1 but compiles: the ternary model produces code that fails to compile far more often (rust 12% vs 38%). Compile is the judge's first gate, so compile rate is the natural ladder step.", + }, + { + kind: 'p', + text: "We also measured knowledge distillation from the FP teacher into the ternary student on the same 2-billion-token budget: the distilled student is worse in every language (mixed bits-per-byte 0.897 vs 0.713 for plain cross-entropy), at roughly seven times the GPU cost. The teacher is not far enough ahead of the student to teach it. Distillation from this teacher is rejected on measurement.", + }, + { + kind: 'h', + text: "What has to exist first", + }, + { + kind: 'ul', + items: [ + "IGLA itself. The pilot is training ternary and full-precision models from 13M to 100M parameters on code. Measured so far, in validation bits per byte (ternary against full precision): 0.8901 against 0.7742 at 13M, 0.7460 against 0.6594 at 25M, and 0.6093 against 0.5482 at 52M. The ternary gap narrows from 15.0% to 11.1% as the model grows. At 25M and 52M, each ternary model matches a full-precision one 0.59-0.66 times its size.", + "IGLA-t27: the base model fine-tuned on the .t27 corpus, and measured by what t27c accepts on specs it has not seen.", + "An integer reference implementation that a CPU and a board run bit for bit alike. This is what a receipt can point at.", + "A bitstream: the layers in block RAM, and the head over DDR3 through LiteDRAM and the open toolchain. Tokens per second and watts are measured at the board.", + ], + }, + { + kind: 'h', + text: "Not proven / open", + }, + { + kind: 'ul', + items: [ + "No IGLA model has run on a board. Every throughput figure here is a ceiling derived from memory bandwidth and LUT counts.", + "The code ability of a 13M model on .t27 is unknown. The HumanEval figures above are for Python and for another model family.", + "The open DDR3 path on Artix-7 has passed memtest on hardware only since nextpnr-xilinx 0.9.5 (13 September 2026). The independent UberDDR3 test reached a 333 MHz DDR clock, not the 400 MHz the AX7203 is rated for.", + "Integer inference must be shown to cost little quality against the float model before receipts can rest on it.", + "The c-BTM experts had 1.3B parameters or more, a hundred times the size of a board-sized one. Whether the result holds at 13M is a measurement, not an inference.", + ], + }, +] + +import type { Block } from '../types' + +export const ruBody: Block[] = [ + { + kind: 'p', + text: "Предложение принадлежит владельцу проекта: вместо одной большой модели — сеть узких агентов, по одному на FPGA. Каждый агент получает награду за собственную работу, а открытая прошивка позволяет любому подключить своё устройство. Прежде чем что-то строить, арифметика решает, чем это может быть. Ниже ничего не измерено на плате. Цифры ёмкости — это биты блочной памяти, поделённые на 1,6 бита на тернарный вес. Цифры моделей взяты из обучающих прогонов на GPU.", + }, + { + kind: 'h', + text: "Что держит одна плата", + }, + { + kind: 'table', + head: ["Плата", "Блочная память", "Тернарных весов при 1,6 бита", "Слои модели на 13M"], + rows: [ + ["ALINX AX7203 (XC7A200T)", "13,46 Мбит", "8,41M", "помещаются, 53%"], + ["Платы на XC7A100T", "4,98 Мбит", "3,11M", "не помещаются (142%)"], + ["Sipeed Tang Mega 138K", "6,27 Мбит", "3,92M", "не помещаются (113%)"], + ], + }, + { + kind: 'p', + text: "XC7A200T держит слои трансформера одной тернарной модели на 13M параметров. Модель на 100M не помещается ни в один из этих чипов. Ей приходится подкачивать веса из DDR3, а DDR3 на AX7203 даёт 3,2 ГБ/с — примерно пятую часть пропускной способности памяти Raspberry Pi 5. На подкачке FPGA не выигрывает.", + }, + { + kind: 'h', + text: "Узкое место — словарь", + }, + { + kind: 'p', + text: "При 13M параметров и словаре на 32 000 токенов таблица эмбеддингов, которая служит и выходной головой, занимает 8,19M из 12,62M параметров. Тернарным слоям нужно 0,89 МБ. Восьмибитной голове нужно 8,19 МБ, и она читается целиком на каждый сгенерированный токен. Именно это чтение, а не тернарная арифметика, ограничивает один поток примерно 330 токенами в секунду на AX7203. Восемь потоков, которые делят каждое чтение, дают около 2 650. JetBrains выпускает свою локальную модель для кода на 100M со словарём на 16 384 токена. Модели IGLA размером с плату сначала нужны словарь поменьше и четырёхбитная голова, а уже потом более быстрый сумматор.", + }, + { + kind: 'h', + text: "Почему единица работы — задача", + }, + { + kind: 'p', + text: "Разрезать одну большую модель по узлам интернет не позволяет: если разложить модель на сотню плат, каждый токен заплатит сотней сетевых переходов. Задача — дописать одну спеку, починить другую — пересылается один раз и выполняется секунды, так что переход в 50 мс ничего не значит. Branch-Train-Merge и c-BTM обучали экспертов независимо, каждого на своём срезе данных, и направляли каждый документ одному из них. Авторы сообщают, что такие эксперты не уступают плотным моделям, обученным на том же объёме вычислений. Один эксперт на запрос — это одна плата на запрос.", + }, + { + kind: 'h', + text: "Маленькая модель чаще ошибается", + }, + { + kind: 'table', + head: ["Модель", "HumanEval pass@1", "pass@100"], + rows: [ + ["Codex-12M", "2,00%", "8,58%"], + ["Codex-85M", "8,22%", "22,4%"], + ], + }, + { + kind: 'p', + text: "Это единственные опубликованные результаты по коду для моделей меньше 100M параметров, и они взяты из статьи о Codex. Агент на 12M, отвечающий с одной попытки, прав в 2% случаев. Сеть таких агентов, чью работу никто не проверяет, в основном производит шум. А если дать тому же агенту дешёвого и точного судью и разрешить сто попыток, он решает в 4,3 раза больше задач. При 200 токенах на попытку сто попыток занимают от 8 до 60 секунд на одной плате.", + }, + { + kind: 'h', + text: "Судья уже есть", + }, + { + kind: 'p', + text: "t27c превращает спеку в синтаксическое дерево, типы и сгенерированный код, а спеку, с которой не справляется, отклоняет. В самом корпусе есть собственные тесты и инварианты, а t27c test-report запускает каждый тест отдельно. Сегодня рой пользуется этим судьёй лишь отчасти: его фильтр перед слиянием проверяет структуру, а тесты гоняются по ночам на master, а не до слияния. Лейн-устройство меняет две вещи. Работником становится модель IGLA на плате владельца, а не токен провайдера. И ничто из присланного лейном не засчитывается, пока не пройдут собственные тесты спеки. Он берёт только ту работу, которую может рассудить компилятор: дописать заготовку спеки, чинить спеку, пока t27c её не примет, или перенести функцию в .t27. Рой уже записывает принятый ход как непередаваемое целое число на имя того, кто его сделал, и лейн-устройство зарабатывает так же. Тернарный вывод в целых числах воспроизводится бит в бит, поэтому любой узел может перезапустить выбранную задачу и разрешить спор, сравнив результаты. Спецификация compute-challenge в tri-net уже применяет это правило к отдельным операциям.", + }, + { + kind: 'h', + text: "Измерено: двойняшки на 100M, восемь языков", + }, + { + kind: 'p', + text: "Аргументу про судью нужны наши собственные числа, а не только таблица Codex. Мы обучили контролируемую пару: две модели на 100M параметров, одна в полной точности, одна тернарная (b1.58), на одинаковых 10,0 млрд токенов кода, и прогнали обе через MultiPL-E (HumanEval-164, переведённый на восемь языков; n=20 сэмплов, температура 0,2, нативное исполнение).", + }, + { + kind: 'table', + head: ["Язык", "pass@1 FP", "pass@1 тернарная", "компилируется FP", "компилируется тернарная"], + rows: [ + ["python", "2.35%", "1.25%", "86%", "84%"], + ["c++", "1.43%", "0.12%", "59%", "47%"], + ["go", "1.01%", "1.23%", "45%", "46%"], + ["java", "2.41%", "2.18%", "62%", "55%"], + ["javascript", "1.68%", "1.15%", "72%", "75%"], + ["php", "0.68%", "0.62%", "100%", "100%"], + ["rust", "1.57%", "0.16%", "38%", "12%"], + ["typescript", "2.33%", "1.38%", "65%", "49%"], + ], + }, + { + kind: 'p', + text: "Средний pass@1 по восьми языкам: FP 1.68%, тернарная 1.01%. Полная точность впереди на семи языках из восьми; исключение — go. Самая выразительная колонка не pass@1, а компиляция: тернарная модель гораздо чаще выдаёт несобирающийся код (rust 12% против 38%). Компиляция — первый фильтр судьи, поэтому темп компиляции — естественная ступень лестницы.", + }, + { + kind: 'p', + text: "Мы также измерили дистилляцию из FP-учителя в тернарного ученика на том же бюджете 2 млрд токенов: дистиллированный ученик хуже на каждом языке (смешанные биты на байт 0,897 против 0,713 у обычной кросс-энтропии) и примерно в семь раз дороже по GPU-времени. Учитель недостаточно впереди, чтобы чему-то научить. Дистилляция от этого учителя отвергнута по измерению.", + }, + { + kind: 'h', + text: "Что должно появиться сначала", + }, + { + kind: 'ul', + items: [ + "Сама IGLA. Пилот обучает на коде тернарные и полноточные модели от 13M до 100M параметров. Уже измерено, в битах на байт на валидации (тернарная против полноточной): 0,8901 против 0,7742 при 13M, 0,7460 против 0,6594 при 25M и 0,6093 против 0,5482 при 52M. С ростом модели тернарный разрыв сужается с 15,0% до 11,1%. При 25M и 52M тернарная модель равна полноточной размером в 0,59–0,66 от неё.", + "IGLA-t27: базовая модель, дообученная на корпусе .t27. Её мерой служит то, что t27c принимает на спеках, которых она не видела.", + "Целочисленная эталонная реализация, которую CPU и плата исполняют одинаково бит в бит. Именно на неё может ссылаться квитанция.", + "Битстрим: слои в блочной памяти, голова через DDR3 на LiteDRAM и открытом тулчейне. Токены в секунду и ватты измеряются на плате.", + ], + }, + { + kind: 'h', + text: "Не доказано / открыто", + }, + { + kind: 'ul', + items: [ + "Ни одна модель IGLA ещё не запускалась на плате. Каждая цифра скорости здесь — потолок, выведенный из пропускной способности памяти и числа LUT.", + "Способность модели на 13M писать .t27 неизвестна. Цифры HumanEval выше относятся к Python и к другому семейству моделей.", + "Открытый путь к DDR3 на Artix-7 проходит memtest на железе только с nextpnr-xilinx 0.9.5 (13 сентября 2026). Независимый тест UberDDR3 достиг частоты DDR 333 МГц, а не 400 МГц, на которые рассчитана AX7203.", + "Нужно показать, что целочисленный вывод почти не теряет в качестве по сравнению с плавающей точкой. Только после этого на него могут опираться квитанции.", + "Эксперты в c-BTM имели 1,3B параметров и больше — в сто раз больше, чем модель размером с плату. Держится ли результат при 13M, решит замер, а не рассуждение.", + ], + }, +] diff --git a/apps/website/src/data/blog/index.ts b/apps/website/src/data/blog/index.ts index 74469948ca..9b75bf8387 100644 --- a/apps/website/src/data/blog/index.ts +++ b/apps/website/src/data/blog/index.ts @@ -2,6 +2,46 @@ import type { PostMeta } from './types' /** Индекс блога: список и метаданные без тяжёлых тел публикаций. */ export const postsIndex: PostMeta[] = [ + { + slug: "a-small-agent-needs-an-exact-judge", + title: "A small agent is only useful next to an exact judge", + summary: "[model results measured on GPUs; board throughput derived, not measured] One Artix-7 200T holds the layers of a 13M-parameter ternary model in its own block memory. In our own twin experiment the 100M ternary model passes 0.97% vs 1.68% for full precision across eight languages, and compile rate, not correctness, is the gap. A model that is wrong most of the time becomes useful only where a compiler checks every answer.", + date: "2026-09-26", + readingMinutes: 9, + tags: ["IGLA", "Agents", "FPGA", "Ternary", "Verification", "Plan"], + receipts: [ + { label: "Codex paper, Table 1: pass@k for 12M-85M code models (arXiv:2107.03374)", href: "https://arxiv.org/abs/2107.03374" }, + { label: "JetBrains Full Line Code Completion: a local 100M model, 16,384-token vocabulary (arXiv:2405.08704)", href: "https://arxiv.org/abs/2405.08704" }, + { label: "Branch-Train-Merge (arXiv:2208.03306)", href: "https://arxiv.org/abs/2208.03306" }, + { label: "c-BTM: scaling expert language models with unsupervised domain discovery (arXiv:2303.14177)", href: "https://arxiv.org/abs/2303.14177" }, + { label: "TerEffic: on-chip vs HBM ternary inference on FPGA (arXiv:2502.16473)", href: "https://arxiv.org/abs/2502.16473" }, + { label: "nextpnr-xilinx releases: 0.9.5, LiteX DDR3 memtest passing on hardware", href: "https://github.com/openXC7/nextpnr-xilinx/releases" }, + { label: "AMD DS180: 7-series block RAM counts", href: "https://docs.amd.com/api/khub/documents/2LByHkO~nSZXcei2D55fTg/content" }, + { label: "TRI CLAW: an agent you can audit (the device this lane runs on)", href: "https://t27.ai/blog/tri-claw-an-agent-you-can-audit/" }, + { label: "MultiPL-E: MultiPL-E benchmark, HumanEval-164 in 18+ languages (arXiv:2208.08227)", href: "https://arxiv.org/abs/2208.08227" }, + ], + openQuestions: [ + "No IGLA model has run on a board. Every throughput figure here is a ceiling derived from memory bandwidth and LUT counts.", + "The code ability of a 13M model on .t27 is unknown. The HumanEval figures above are for Python and for another model family.", + "The open DDR3 path on Artix-7 has passed memtest on hardware only since nextpnr-xilinx 0.9.5 (13 September 2026). The independent UberDDR3 test reached a 333 MHz DDR clock, not the 400 MHz the AX7203 is rated for.", + "Integer inference must be shown to cost little quality against the float model before receipts can rest on it.", + "The c-BTM experts had 1.3B parameters or more, a hundred times the size of a board-sized one. Whether the result holds at 13M is a measurement, not an inference.", + "The twin numbers are for 100M-parameter models trained on GPUs; the board-sized 13M ternary model has been neither trained nor run on a board.", + ], + published: true, + ru: { + title: "Маленький агент полезен только рядом с точным судьёй", + summary: "[результаты моделей измерены на GPU; пропускная способность платы выведена, не измерена] Одна Artix-7 200T держит слои тернарной модели на 13M параметров в собственной блочной памяти. В нашем парном эксперименте тернарная модель на 100M проходит 0,97% против 1,68% у полной точности на восьми языках, и разрыв — это компиляция, а не корректность. Модель, чаще ошибающаяся, полезна только там, где каждый ответ проверяет компилятор.", + openQuestions: [ + "Ни одна модель IGLA ещё не запускалась на плате. Каждая цифра скорости здесь — потолок, выведенный из пропускной способности памяти и числа LUT.", + "Способность модели на 13M писать .t27 неизвестна. Цифры HumanEval выше относятся к Python и к другому семейству моделей.", + "Открытый путь к DDR3 на Artix-7 проходит memtest на железе только с nextpnr-xilinx 0.9.5 (13 сентября 2026). Независимый тест UberDDR3 достиг частоты DDR 333 МГц, а не 400 МГц, на которые рассчитана AX7203.", + "Нужно показать, что целочисленный вывод почти не теряет в качестве по сравнению с плавающей точкой. Только после этого на него могут опираться квитанции.", + "Эксперты в c-BTM имели 1,3B параметров и больше — в сто раз больше, чем модель размером с плату. Держится ли результат при 13M, решит замер, а не рассуждение.", + "Числа двойняшек относятся к моделям на 100M параметров, обученным на GPU; модель на 13M, помещающаяся на плату, не обучена и не запускалась на плате.", + ], + }, + }, { slug: 'tri-mined-not-sold', title: 'The token is mined, not sold', diff --git a/apps/website/src/data/blog/posts.ts b/apps/website/src/data/blog/posts.ts index 9f286a1f25..3044ee31b6 100644 --- a/apps/website/src/data/blog/posts.ts +++ b/apps/website/src/data/blog/posts.ts @@ -10,6 +10,7 @@ import { body as body_one_saturation_rule_five_artefacts, ruBody as ruBody_one_s import { body as body_ninety_tests_were_unreachable, ruBody as ruBody_ninety_tests_were_unreachable } from './bodies/ninety-tests-were-unreachable' import { body as body_four_languages_one_tri_extension, ruBody as ruBody_four_languages_one_tri_extension } from './bodies/four-languages-one-tri-extension' import { body as body_the_fpga_row_was_corrected, ruBody as ruBody_the_fpga_row_was_corrected } from './bodies/the-fpga-row-was-corrected' +import { body as body_a_small_agent_needs_an_exact_judge, ruBody as ruBody_a_small_agent_needs_an_exact_judge } from './bodies/a-small-agent-needs-an-exact-judge' import type { Post, PostBody } from './types' import { body as body_tri_mined_not_sold, ruBody as ruBody_tri_mined_not_sold } from './bodies/tri-mined-not-sold' import { body as body_the_only_stable_speed_belonged_to_the_tool, ruBody as ruBody_the_only_stable_speed_belonged_to_the_tool } from './bodies/the-only-stable-speed-belonged-to-the-tool' @@ -138,6 +139,7 @@ const bodies: Record = { 'two-bitstreams-one-bit-apart': { body: body_two_bitstreams_one_bit_apart, ruBody: ruBody_two_bitstreams_one_bit_apart }, 'a-multiplicity-correction-changed-the-deployment-reading': { body: body_a_multiplicity_correction_changed_the_deployment_reading, ruBody: ruBody_a_multiplicity_correction_changed_the_deployment_reading }, 'the-only-stable-speed-belonged-to-the-tool': { body: body_the_only_stable_speed_belonged_to_the_tool, ruBody: ruBody_the_only_stable_speed_belonged_to_the_tool }, + 'a-small-agent-needs-an-exact-judge': { body: body_a_small_agent_needs_an_exact_judge, ruBody: ruBody_a_small_agent_needs_an_exact_judge }, } export type { Block, Post } from './types'