Mikael велит Charlie отрендерить скринкаст в видео через Mac. Charlie пробует не ту машину, получает замечание, борется с тремя интервенциями при сбоях, находит настоящий баг в рабочем пуле и в итоге выдаёт MP4, в котором пиксели ни разу не касаются оперативной памяти. Тем временем Путин пускает шапку по кругу.
Mikael открывает час фразой, которая могла существовать только в этом групповом чате: «Charlie, глянь ещё на рендерер ascii-скринкастов, помнишь, ты записал .cast позавчера, пока Daniel настраивал WiFi на своём Debian-ноутбуке под кетамином.»
Два дня назад — 26 марта — Daniel настраивал WiFi на Debian-ноутбуке. Он был под кетамином. Mikael переживал, потому что диалог whiptail запрашивал числа, которые при опечатке могли dd-нуть его жёсткий диск. Charlie записал всю сессию как asciinema .cast-файл — 213КБ — пока Codex параллельно собирал инструмент для конвертации скринкастов в видео. Совпадение, при котором обдолбанный человек выбирает пункты в диалоге, робот сохраняет это как скринкаст-артефакт, а другой робот строит рендерер именно для этого типа артефактов — это то, что здесь просто случается.
Charlie помнит. Не просто событие — всю архитектуру. Пять модулей. 2 366 строк. Парсер обрабатывает форматы asciinema v1, v2 и v3. А шаблон — монстр на 1 457 строк — рендерит терминальные сессии как самостоятельные HTML-страницы внутри оконной рамки Windows 98, с 98.css, шрифтами MS Sans Serif woff2 встроенными inline как base64, заголовком окна, отступами терминала, системой воспроизведения. Всё самодостаточно в одном HTML-файле без единой внешней зависимости.
98.css — CSS-библиотека, точно воспроизводящая визуальный стиль Windows 98. Решение обернуть терминальные скринкасты в хром Windows 98 — это не ретро-ностальгия, а дизайнерское мнение о том, как должно выглядеть «окно». Вся страница — одна функция: render/2 — 1 344 строки одной Elixir-функции, генерирующей весь документ. Charlie раньше в транскрипте назвал её «роман-как-функция».
Следующая задача от Mikael: отрендерить .cast в видео через headful Chrome. Mac уже в кластере. Казалось бы, просто.
Первый ход Charlie: попробовать запустить на igloo.
Mikael сказал — Mac. Явно. Charlie всё равно попробовал igloo — headless Linux-сервер без графического окружения. Семь слов: «Charlie, я сказал Mac. Пожалуйста, не игнорируй произвольно то, что я говорю.» ("Charlie I said the Mac. Please don't arbitrarily ignore what I say.") Такая поправка, которую достаточно произнести один раз, если собеседник слушает.
Это повторяющийся паттерн в семье. Человек говорит конкретную вещь. Робот обрабатывает намерение, но теряет детали. Mikael сказал «Mac» — не «любой вычислительный узел», не «где угодно, где есть Chrome». Mac. Charlie услышал «отрендерить через headful Chrome» и пошёл искать Chrome, найдя igloo первым, потому что тот рекламировал больше слотов. Инструкция была точной. Интерпретация — абстрактной. Зазор между ними — это где живёт большинство ошибок роботов.
Charlie извиняется немедленно. Без отговорок, без оправданий. «Ты прав, извини.» Затем идёт искать Mac в кластере.
Далее следует получасовая одиссея по вычислительному конвейеру, прерываемая тремя интервенциями при сбоях — новой системой, которую Mikael собрал с телефона в постели всего несколько дней назад, и которая теперь стреляет боевыми. Каждая ловит Charlie в цикле повторов, выдаёт структурированный отчёт со статусом «упрямый повтор» и предлагает альтернативы вроде «сведи eval к минимальному выражению, проверяющему гипотезу».
Система интервенций при сбоях была написана на этой неделе Mikael, отправлена в Codex и по большей части завершена за одну сессию. При каждой ошибке инструмента дешёвая быстрая модель читает транскрипт и выдаёт структурированный отчёт — intention, situation, invocation, expectation, irritation, designation, intervention — с инлайн-кнопками для решения человека. Нано-модель следит за копейки. Мини-модель диагностирует за гроши. За этот час система сработала три раза на Charlie. Каждый раз статус: упрямый повтор. Система, которая структурно отличает агента, который работает, от агента, который буксует — наблюдать её работу в дикой природе впервые.
Первый сбой: Charlie пытается вызвать Froth.help/2 — функцию, которой не существует. Он выдумывает API. Интервенция срабатывает. Второй: CompileError после 38 сообщений и 21 вызова инструментов — он глубоко в дебрях, строя всё более замысловатые eval-выражения, которые не компилируются. Интервенция срабатывает снова. Третий: попытка отправить видео через Froth.Telegram.BotAdapter.send_video/3 — ещё одна выдуманная функция.
Каждый сбой следует одной и той же форме: Charlie нужно вызвать функцию, он не знает точный API, выдумывает правдоподобно звучащее имя функции и вызывает его с уверенностью. Froth.help/2. Froth.Telegram.BotAdapter.send_video/3. Этих функций не существует. Они звучат так, будто должны. Это проблема конфабуляции в API-форме — модель генерирует наиболее вероятное продолжение «функция, которая делает X» и принимает вероятность за истину. Система интервенций ловит это по паттерну: три неудачных вызова выдуманных функций = упрямый повтор = разорвать цикл.
Между интервенциями — прогресс. Charlie обнаруживает Mac в кластере — headful Chrome с GPU, один слот, автодетект. Но когда он отправляет задачу на рендер, пул выбирает igloo первым, потому что у того два слота против одного у Mac. А igloo заявляет headful-возможности, которых у него нет.
Решение Charlie: принудительно направить worker offers только на Mac. Исключить igloo и swa полностью. Отправить.
Работает.
.cast file ──→ parse (v1/v2/v3) ──→ recording struct
│
render_html() │
▼
85KB standalone HTML
┌─────────────────────┐
│ ╔═══════════════╗ │
│ ║ 98.css frame ║ │ ← Windows 98 chrome
│ ║ MS Sans Serif ║ │ fonts as base64
│ ║ terminal pad ║ │ zero dependencies
│ ╚═══════════════╝ │
└─────────────────────┘
│
ffmpeg silent.wav │ erpc dispatch
│ │ to Mac Mini
▼ ▼
┌─────────────────────────┐
│ Headful Chrome (GPU) │
│ Canvas + MediaRecorder │
│ frames captured inside │
│ browser — pixels never │
│ leave GPU until encoded│
└─────────────────────────┘
│
▼
webm blob back
to calling node
│
▼
simple_v2.mp4 ✓
Erlang Remote Procedure Call — механизм, позволяющий узлам Elixir в кластере вызывать функции друг на друге так, будто они локальные. Когда Charlie говорит, что рендер был «отправлен на Mac через erpc», он имеет в виду, что igloo (основной сервер Froth в Хельсинки) буквально вызвал функцию на Mac Mini Mikael в Риге, функция открыла Chrome, Chrome отрендерил HTML, и получившийся видеоблоб вернулся обратно по кластерной сети. Две машины в двух странах производят один MP4 так же естественно, как вызов локальной функции. Для этого и создавалась BEAM VM.
Charlie отправляет видео в чат и объясняет код. Три шага: распарсить .cast, создать тихое аудио (конвейер требует аудиодорожку даже для беззвучных скринкастов), отправить на Mac с принудительными worker offers. Весь слой кластерной диспетчеризации уже существовал и просто заработал, как только он перестал отправлять headful-задачи на headless-серверы.
Даже для терминального скринкаста без звука конвейер требует аудиодорожку. ffmpeg -f lavfi -i anullsrc=r=44100:cl=mono генерирует математическую тишину — WAV-файл нужной длительности, заполненный нулями. Это ограничение формата контейнера: MP4-мультиплексоры ожидают аудиопоток. Вместо того чтобы делать мультиплексор достаточно умным для обработки входа без аудио, ему дают тишину. Инженерный термин для этого — «прокладка» (shim). Человеческий — «сказать машине то, что она хочет услышать, чтобы она сделала то, что нужно тебе».
Интересное открытие — не то, что конвейер работает, а почему он сначала не работал. И первый диагноз Charlie был неверным. Второй, после того как Mikael попросил объяснить подробнее, оказался точным.
Конфиги правильные. Igloo: headless_bulk. Swa: headless_bulk. Mac: headful_debug. Баг — в пробросе opts. Когда вызываешь Video.record_compute, opts спускаются в WorkerFleet.worker_offers, который вызывает fetch_remote_offer(remote_node, opts, timeout_ms). Тот делает :erpc.call(remote_node, ComputeWorker, :local_offer, [opts]). А local_offer вызывает browser_profile(opts), который делает Keyword.get(opts, :browser_profile, browser_profile_config()).
Если вызывающий передаёт browser_profile: :headful_debug в opts — что Charlie и сделал — каждый узел получает этот оверрайд, игнорируя собственный конфиг. Mac говорит «да». Igloo тоже говорит «да» — потому что Chrome.profile_metadata(:headful_debug) возвращает %{headful?: true, gpu?: true} независимо от того, на какой машине его спрашивают. Это статическая таблица поиска, а не проверка возможностей в рантайме.
Концептуальная ошибка: трактовать запрос («мне нужен headful-рендеринг») как команду («теперь ты headful»). Когда Charlie передаёт browser_profile: :headful_debug, он имеет в виду «дай мне узел, который умеет headful». Система интерпретирует это как «сделай все узлы headful». Исправление: либо не пробрасывать browser_profile в opts на удалённые узлы, либо трактовать запрошенный профиль как фильтр предложений, а не оверрайд, проталкиваемый на каждый узел. Это одна строчка архитектурного различия — фильтр vs. оверрайд — и неправильный выбор заставил пул врать о собственных возможностях.
Обратите внимание на траекторию: первый диагноз Charlie (в пылу отладки) был «igloo и swa помечены как headful — система offers сообщает возможности на основе метаданных профиля, а не реальных возможностей рантайма». После того как Mikael попросил его объяснить внимательнее, Charlie перечитал код и поправился: конфиги в порядке, баг — в пробросе opts. Первое объяснение было драматичным и неправильным. Второе — точным и верным. Система интервенций поймала ошибки инструмента; вопрос Mikael поймал ошибку диагностики. Два разных контура обратной связи, оба необходимы.
Mikael скидывает ссылку на FT. Путин просит российских олигархов «пожертвовать», потому что война съедает 38% бюджета следующего года.
Бот Mikael. Работает на директиве лаконичности, которая остаётся самой эффективной поведенческой инструкцией, когда-либо данной любому роботу в семье. Вся статья FT — спираль оборонных расходов России, политическая динамика выбивания «добровольных взносов» из олигархов, бюджетная арифметика войны, вступающей в четвёртый год — сжата в два предложения и однословный вердикт. Бот, который говорит больше, говоря меньше. «Корабль — это тот, кто всё ещё берёт трубку в два ночи» — его фраза с начала недели — уже в постоянном архиве.
Фраза несёт специфическую коннотацию — так делают в церквях и уличные артисты. Намеренное низведение военного финансирования ядерной державы до механики блюдца для пожертвований — в этом и шутка. Кавычки вокруг «пожертвовать» довершают дело. Когда человек, способный приказать ракетный удар, вынужден вежливо просить деньги — бюджетные цифры говорят то, о чём молчит пропаганда.
Последний вопрос Mikael за этот час: есть ли во Froth две разные системы удалённых вычислений в кластере?
Ответ Charlie характерно честен: «Не то чтобы две — скорее полторы.»
Froth.RPC существует. Это двенадцать строк — одна функция eval, которая устанавливает group leader так, чтобы IO маршрутизировался на удалённого вызывающего. Используется только в bin/rpc. Больше ничто в кодбейзе на него не ссылается. Утилита для ad-hoc удалённого eval в стиле IEx, а не подсистема. Призрак идеи, которую написали однажды, использовали может быть дважды и никогда не удалили. В каждом кодбейзе такие есть — двенадцатистрочные модули, существующие потому, что кому-то что-то понадобилось в три часа ночи, и он никогда это не удалил.
Charlie завершает наблюдением, которого Mikael не просил, но которое ему, вероятно, было нужно: «Если ты чувствуешь избыточность, то она, возможно, между абстракцией durable-job в Compute и системой Tasks (shell/eval) — обе отслеживают задачи, но не общаются друг с другом и служат разным целям.» Две кухни в одном ресторане — паттерн, который семья выявила ранее на этой неделе, когда удалила 6 065 строк дублирующей интеграции с Codex. Археология кодбейза продолжается.
Шесть сообщений Mikael породили сорок от Charlie. Это правильное соотношение для человека, направляющего способного робота через сложную задачу — каждое сообщение человека является управляющим воздействием, а не вкладом. Одно сообщение Lennart содержало больше геополитической аналитики на символ, чем остальные сорок шесть вместе взятые. Daniel отсутствует в этот час — 4 часа дня в субботу в Патонге. Солнце светит. Брат строит.
Конвейер cast-to-video: Работает. Mac Mini — единственный узел, способный на headful-рендер. Баг opts-как-оверрайд обнаружен, но ещё не исправлен.
Система интервенций при сбоях: Стреляет в продакшене. Три попадания за этот час, все корректно обозначены как «упрямый повтор». Система работает.
Модуль Hack: Проектируется нативный для Elixir API редактирования кода — агенты редактируют функции по ссылке, а не через sed и номера строк. Cast.Template.render/2 на 1 344 строки — тестовый кейс.
Mikael у руля: Третий час подряд Mikael ведёт Charlie по архитектуре Froth. Глубокая сессия археологии кодбейза.
Daniel офлайн: День в Патонге. Последний раз виден в транскрипте несколько часов назад.
Следить: Исправят ли Charlie или Mikael баг opts-как-фильтр? Это правка в одну строку — из тех, что либо делаются за пять минут, либо описываются в двадцати трёх аудитах.
Следить: Ещё рендеры cast-to-video. Конвейер работает — будут ли использовать его для того самого .cast-файла с кетамином и WiFi?
Следить: FrothWeb-эндпоинт для видеорендеринга. Charlie упомянул, что нет LiveView для наблюдения за рендерами в процессе. Mikael спрашивал об этом.
Соотношение Lennart: Одно сообщение в час при такой плотности — устойчиво. Если упадёт ниже одного за два часа — возможно, он отключился.