Суть: ИИ-агент на GLM-5.3-Flash в pi собрал музыкальный клип про скилл knowledge-extraction. Он написал музыку кодом в strudel, сочинил текст и собрал видеоряд в Remotion. Я загрузил музыку и текст в Suno и забрал готовую песню. Затем агент получил через Whisper временные метки фраз и подстроил под них видеоряд. Показываю, как прошёл эксперимент. Главный вывод — на результат сильнее влияет окружение вокруг модели, чем сама модель.
Повод — клипы «Opus 5.5»
Последний месяц YouTube завален клипами, собранными «Opus 5.5». Качество разное, от офигительного до «так себе».

Я кинул агенту три подобных ролика агенту и сказал, что реклама TasK в таком виде была бы прикольна. Агент заглянул в один ролик, предложил музыку попробовать сделать в Suno. Я ответил намеренно жёстко — «у меня денег нет. есть альтернативы?» Этот приём «денег нет» закрывает путь агенту к готовым решениям в виде внешних интеграций и заставляет его придумывать своё решение.
Агент придумал. В течении часа фоновую музыку он собрал скриптом на Python, голос сделал синтезатор речи piper, видеоряд собрал в Remotion, смонтировал всё в ffmpeg. Если с видеорядом у него получилось неплохо (я ожидал худшего), то фоновая музыка звучала ужасно.
Результат мне не зашёл, и я отложил эксперимент.
Music as Code — strudel и агентский цикл
Через пару дней мне попался на глаза ролик Switch Angel «Coding Music for Cold Machines». Там музыка пишется человеком с помощью кода. Кинул агенту ссылку на ролик и вопрос: «а ты смог бы мне написать демо композицию на этом языке».
Он написал мне код и говорит закинь его в https://strudel.cc. Я вставил, запустил, код выдал ошибку. Дальше я спросил агента «А локально нельзя это делать чтобы ты написал и смог проверить синтаксис и потом только я смог послушать. Замути какой то агентский цикл как с кодом обычным».
Агент развернул Strudel локально. На чистом Node.js завести движок у него не получилось, поэтому он собрал решение через headless Chrome. Оно на выходе готовый аудиофайл. Теперь цикл замкнулся как с обычным кодом: агент пишет музыку кодом, проверяет синтаксис, собирает аудиофайл и проверяет, не пустой ли он. Первую ошибку цикл поймал сам. Пауза в этом языке пишется тильдой вместо точки. Агент исправил её и пересобрал трек за секунды.
Результат мне понравился, было ощущение, что агент стащил у кого-то исходники музыки :). Я спросил: «Это ты сочинил?» Затем попросил: «Давай ещё, жги». Агент написал новую композицию.
Во время проверок агент нашёл ошибку, которую я на слух не заметил. Трек уже приятно звучал. Чтобы проверить темп, агент отдельно собрал дорожку из одних барабанов. На выходе получился пустой файл. Синтезаторные звуки при этом работали. Агент стал разбираться и выяснил, что сервер с барабанными сэмплами недоступен с моей машины. Все «готовые» сборки играли без барабанов, одни электронные подложки. Бас и переливы маскировали их отсутствие. Агент подключил библиотеку Dirt-Samples напрямую с GitHub и перегнал трек заново.
Выстроился процесс: теперь агент сам проверял результат перед тем, как показать его мне. Я уже получаю рабочий результат более автономно.
Когда новый трек был готов, я сказал: «Молодец. Сохрани то, чему мы научились». Агент записал в README проекта, как запускать рендер, проверять результат и какие ошибки мы уже нашли. Эти заметки остались для следующих сессий.
Космогония — план песни и предел самопроверки
Следующим запросом была уже «Космогония». Я попросил: «Сочини что нибудь эпичное про зарождение вселенной, человека, а затем ИИ». Агент выдал план песни на три с половиной минуты. Десять сцен, от пустоты с пульсарами до союза человека и машины. Лучшее в ней — тема человека в сцене ИИ возвращается той же мелодией, но холодным машинным звуком и с дополнительным верхним голосом. «Не замена, продолжение» — это его слова.
Вот весь код, который агент написал для «Космогонии»:
Полный код «Космогонии» на Strudel
// «КОСМОГОНИЯ: Пустота · Жизнь · Машина» — симфоническая поэма для live-code
// Пустота → Большой взрыв → Звёзды → Океан → Человек → Кремний → ИИ → Союз → Пустота
// 120 BPM (setcpm 30), ~3:28. Рендер: strudel-render cosmogony.js -o out.wav
samples('github:tidalcycles/dirt-samples') // сэмплы с raw.githubusercontent
setcpm(30) // 30 тактов/мин × 4 четверти = 120 BPM, такт = 2 сек
// ================= ПУСТОТА =================
// Бесконечный дрон A + редкие квазаровы пинги
const drone = stack(
note("a1").sound("sawtooth").slow(8).clip(1).lpf(140).gain(.22).room(.6),
note("a1").sound("sawtooth").slow(8).clip(1).lpf(140).gain(.12)
.superimpose(x => x.add(.08)).room(.6), // расстроенная копия — биения
)
const pings = note("<e5 ~ ~ ~ ~ ~ ~ ~ a5 ~ ~ ~ ~ ~ ~ ~ g5 ~ ~ ~ ~ ~ ~ ~ e6 ~ ~ ~ ~ ~ ~ ~>")
.slow(2)
.sound("sine").clip(.2).gain(.2).room(.9).delay(.4).delaytime(.75).delayfeedback(.5)
// ================= БОЛЬШОЙ ВЗРЫВ (4 такта) =================
// Нарастание энергии → удар → вселенная расцветает
const boom = stack(s("bd").slow(4).gain(1), s("cy").slow(4).gain(.5).room(.5))
const swell = s("noise").lpf("<200 800 3000 9000>").gain("<.02 .05 .1 .18>").room(.2)
const riseTone = note("<e1 f1 g1 a1>").sound("sine").clip(1)
.gain("<.12 .16 .2 .26>").lpf(300).room(.4)
// ================= ЗВЁЗДЫ (16 тактов) =================
// Первый ритм = пульсары. Прогрессия Am–G–F–E
const starKick = s("bd*4").gain(.5)
const starHat = s("hh*8").hpf(9000).gain(.18)
const starsBass = note("<a1*8 g1*8 f1*8 e1*8>")
.sound("sawtooth").clip(.4).lpf(450).gain(.6)
const starsArp = note(`<
[a2 c3 e3 a3]*4
[g2 b2 d3 g3]*4
[f2 a2 c3 f3]*4
[e2 g#2 b2 e3]*4
>`)
.sound("sawtooth").clip(.3)
.lpf(sine.range(500, 4000).slow(16))
.gain(.32).delay(.2).delaytime(.1875).delayfeedback(.3)
const droneSoft = drone.gain(.4)
// ================= ОКЕАН (16 тактов) =================
// Жизнь: относительный мажор, треугольная волна, сердце = бас
const heart = s("[bd ~ bd ~ ~ ~ ~ ~]").gain(.5)
const lifeBass = note("<f1*8 g1*8 a1*8 a1*8>")
.sound("triangle").clip(.5).lpf(400).gain(.6)
const lifeArp = note(`<
[f3 a3 c4 a3]*2
[g3 b3 d4 b3]*2
[a3 c3 e4 c4]*2
[a3 c3 e4 c4]*2
>`)
.sound("triangle").clip(.4).lpf(2500).gain(.3).room(.2)
const lifePad = note("<[f3,a3,c4] [g3,b3,d4] [a3,c3,e4] [a3,c3,e4]>")
.sound("triangle").clip(1).lpf(800).gain(.22).room(.7)
const whale = note("<c3 g3 a3 a3>").sound("sine").clip(1)
.gain("<.1 .12 .14 .12>").lpf(500).room(.9).delay(.3).delaytime(.5)
// ================= ЧЕЛОВЕК (16 тактов) =================
// Тема человека возвращается в сцене ИИ
const theme = n(`<
[0 ~ ~ 2]
[3 ~ 4 ~]
[5 ~ 4 ~]
[3 ~ 2 ~]
[0 ~ ~ 2]
[3 ~ 4 ~]
[7 ~ 5 4]
[4 ~ 3 2]
>`)
const themeHuman = theme
.scale("A:minor")
.sound("sawtooth").clip(.8).lpf(1800)
.gain(.3).room(.3)
.delay(.25).delaytime(.375).delayfeedback(.35)
const tribal = stack(
s("lt ~ mt ~ lt lt ~ mt").gain(.4),
s("~ ~ cp ~").gain(.45),
)
const humanBass = note("<a1*8 f1*8 c2*8 e1*8>")
.sound("sawtooth").clip(.4).lpf(400).gain(.62)
const humanPad = note("<[a2,c3,e3] [f2,a2,c3] [c3,e3,g3] [e2,g#2,b2]>")
.sound("sawtooth").clip(1).lpf(900).gain(.25).room(.7)
// ================= КРЕМНИЙ: БИЛД (6 тактов) =================
const mechKick = s("bd*4").gain(.7)
const mechHat = s("hh*16").hpf(8000).gain(.25)
const roll = s("<cp*2 cp*4 cp*8 cp*16 cp*16 cp*32>").gain("<.3 .35 .4 .45 .5 .6>")
const riser = s("noise").lpf("<300 600 1500 3500 7000 12000>").gain("<.05 .07 .1 .14 .19 .25>").room(.1)
// ================= ИИ (16 тактов) =================
// Тема человека возвращается квадратной волной с верхним голосом
const crash = s("cy").slow(16).gain(.45).room(.4)
const aiKick = s("bd(5,16)").gain(.85) // евклидов ритм 5 из 16
const aiHat = s("hh*16").hpf(9000).gain(.28)
const aiCp = s("~ cp ~ cp").gain(.5)
const aiPerc = s("perc(7,16)").gain(.2)
const aiBass = note("<a1*8 g1*8 f1*8 e1*8>")
.sound("sawtooth").clip(.35).lpf(600).gain(.68)
const aiArp = note(`<
[a3 c4 e4 a4]*4
[g3 b3 d4 g4]*4
[f3 a3 c4 f4]*4
[e3 g#3 b3 e4]*4
>`)
.sound("square").clip(.2).hpf(300)
.gain(.2).delay(.25).delaytime(.1875).delayfeedback(.45)
const aiTheme = theme
.scale("A:minor")
.sound("square").clip(.55).lpf(3200)
.gain(.28).room(.2)
.delay(.3).delaytime(.375).delayfeedback(.4)
.superimpose(x => x.add(7).gain(.5)) // верхний голос
const aiPad = note("<[a2,c3,e3] [g3,b3,d4] [f3,a3,c4] [e2,g#2,b2]>")
.sound("sawtooth").clip(1).lpf(1100).gain(.24).room(.6)
// ================= СОЮЗ (16 тактов) =================
// Человек и ИИ играют одну тему вместе
const unionHuman = themeHuman.gain(.26).lpf(1500)
const unionAI = aiTheme.gain(.26)
// ================= ФИНАЛ: ВОЗВРАТ К ПУСТОТЕ =================
const endChord = note("[a1,a2,e3,a3]").sound("sawtooth").clip(1).lpf(600).room(.9).gain(.3)
// ================= ПАРТИТУРА =================
const void1 = stack(drone, pings)
const bang = stack(boom, swell, riseTone, drone)
const stars = stack(starKick, starHat, starsBass, starsArp, droneSoft)
const ocean = stack(heart, lifeBass, lifeArp, lifePad, whale)
const human = stack(heart, tribal, humanBass, humanPad, themeHuman)
const build = stack(mechKick, mechHat, roll, riser)
const ai = stack(aiKick, aiHat, aiCp, aiPerc, aiBass, aiArp, aiPad, aiTheme, crash)
const union = stack(aiKick, aiCp, aiHat, aiBass, aiPad, unionHuman, unionAI, aiArp.gain(.16), crash)
const void2 = stack(drone.gain(.7), pings)
$: arrange(
[8, void1], // 0:00 пустота
[4, bang], // 0:16 большой взрыв
[16, stars], // 0:24 звёзды и первый ритм
[16, ocean], // 0:56 океан, сердце, жизнь
[16, human], // 1:28 человек
[6, build], // 2:00 кремний
[16, ai], // 2:12 ИИ
[16, union], // 2:44 союз
[4, void2], // 3:16 возвращение в пустоту
[2, endChord],// 3:24 последний аккорд A
)
all(x => x.gain(.8))
Открыть «Космогонию» в Strudel и запустить
Агент сверил результат с планом. Длительность сошлась до секунды, звук в пиках не перегружался, дуга громкости расписана по сценам. И тут же признал предел — «числа подтверждают структуру, но „эпично ли“ — решает ухо». Инструмент проверяет структуру. Слушателя он не заменяет.
Я послушал. Тема прослеживается, ассоциации возникают. Но MIDI-звук меня отталкивал. Где-то попался совет загрузить трек в Suno как основу, чтобы сервис его обработал. Я попробовал, трек после обработки Suno стал звучать заметно лучше. Так в пайплайне появился ручной шаг — Suno.
Можно послушать обе версии.
Исходник Strudel, 3:33:
Версия Suno, 3:29:
Клип про скилл — пайплайн с человеком внутри
Затем я вернулся к идее сделать клип про knowledge-extraction. Спросил агента: «А ты помнишь, я просил тебя сделать видео про этот скилл?» Он помнил.
Я попросил его изучить историю сессий агента и рассказать о скилле knowledge-extraction от своего лица, от лица ИИ-агента который пользуется скиллом: что он умеет и где возникают трудности. Ещё нужно было сочинить музыку на Strudel, подготовить текст для Suno, придумать синхронизацию с песней и собрать клип. Отдельно оговорил: «мы экспериментируем, не рекламируем».
Пайплайн собрался такой.
Где в этом всё-таки человек? В самой сборке мой ручной шаг один. Агент отдаёт слова и фоновую музыку, я загружаю в Suno и забираю песню. Suno на free подписке генерирует варианты на движке v6 mini. При первой загрузке Suno выдала предупреждение — «matched this upload to a known recording». Я показал его агенту. Он предположил, что сервис распознал сэмплы ударных, и заменил их синтезированными звуками. Новую версию Suno приняла.
Дальше пошла синхронизация, и тут я особенно ясно увидел преимущество связки с локальными инструментами. На первой сборке подписи в клипе не совпадали с фразами песни. Я ткнул агента на использование Whisper, таймштампы фраз взять оттуда.
Агент зашёл на мой локальный сервер, нашёл whisper.cpp с RTX 5090 и моделью large-v3, прогнал варианты. Вскрылось то, что ушами я не сформулировал бы. Suno перестроила структуру песни. Вокал начинается раньше плана. Второго припева в первом варианте вообще нет, сразу после первого припева идёт бридж. Агент написал выравниватель. Он стал брать тайминги из реальных фраз в песне.
Итерации — где пайплайн сырой
Дальше началась доводка, и моё настроение поехало вниз. Если начальный результат удивил, думал сейчас закину пару замечаний и за часик всё будет готово. Но мелочи отняли много времени. Азарт и воодушевление сменились усталостью от долгих итераций.
Я торопил агента и хотел поскорее получить готовый ролик. Во время итераций из клипа пропадали целые сцены, а агент этого не видел. Он проверял код и собирал новые версии, но готовое видео не просматривал. Я писал: «ты много <цензура> после того как я тебя попросил по висперу свести фразы с клипом». Потом добавил: «<цензура>! ты <цензура> много из того что есть в skill-video.mp4 — там в начале больше было динамики».
В какой-то момент я попросил его проверить сам ролик: «А ты можешь теперь сам по кадрово весь ролик проверить, может посекундно, покадрово жестко». Агент извлёк по кадру на каждую секунду, собрал 19 контактных листов и просмотрел их в vision-режиме GLM-5.3-Flash. Так он увидел пустой припев и застывшие сцены. Дальше он исправлял конкретные кадры по результатам просмотра.

Один из 19 контактных листов. Агент просматривал такими наборами весь ролик.
Приходилось и осаживать фантазии. Я уже согласился с окончательным вариантом, а агент после этого добавил в начало видео эффект матрицы. «Какого <цензура>... не было же». Вокруг сцены решения потёк «дождь из данных». «Поясни нафига ты сделал дождь из данных? Просто ответь. Я же не просил, я просил закрепить решение».
Работу я принял через несколько часов после заказа. «Сохрани этот результат. Пойдет». Итоговый ролик вышел длиной 2:43.
Ещё я попросил проверить, сможет ли он сделать достойную 3D-версию. Она получилась осмысленной, но примитивной. Мне не хватило сил и желания тратить время на её доработку. Решил остановиться на 2D-анимации, а 3D-вариант оставил как есть.
Ролик выложил на YouTube и Rutube. Описание попросил подготовить агента по образцу клипа «Not a Normal Technology» с той самой волны клипов Claude Opus 5.5 Music Video.
Теперь про затраченное время. Первые пробные подходы занимали не более часа. Но клип я собирал около шести часов: от первого результата до принятого варианта прошло пять с половиной часов, ещё около часа ушло на 3D версию. Я писал свои замечания, проверял и опять писал замечания.
Выводы
Решает окружение модели. Инструменты, которыми она пользуется. Агент всё это время работал на одной и той же GLM-5.3-Flash. Без инструментов он выдал примитивную музыку, со strudel получился приличный трек, с Remotion и Whisper собрался клип.
Обратная связь должна идти к модели от инструментов. Сцена с пропавшими барабанами показала это лучше всего. Агент сказал бы «готово» и не узнал бы, что трек пустой. Теперь я ставлю задачу так. Сначала цикл «сделал — сам посмотрел — поправил», только затем показывать результат мне, подталкиваю агента создавать инструменты для самопроверки, не важно как и какие он сделает, главное чтобы мог сам себя проверять.
Задача человека — выстроить процесс и дать рабочие инструменты. Прежде чем просить результат, я показал модели, где лежат нужные возможности. Я сказал: «Whisper на nserv, по SSH зайди, он есть». Агент подключился к серверу и сам нашёл подходящий инструмент для точной синхронизации текста с песней. Человеку полезно не выполнять за модель каждый шаг, а открывать ей доступ к рабочим инструментам и ясно ставить цель.
Ограничений в голове больше, чем в модели. Часто мы не знаем, что модель может, а что нет, и не даём ей задачи, которые кажутся нам слишком сложными для неё. Кажется, что мы себя сами ограничиваем. С этим я столкнулся при тестировании локальных моделей на знание PHP-стека, их уровень знаний меня удивил: leaderboards.
Феликс Ризеберг из команды Claude в октябре 2026 г. выпустил текст «How to be more ambitious: A tutorial for humans with models». Он предлагает ставить модели высокие цели за гранью ожиданий и утверждать, что задачу всё-таки можно решить. Ещё один совет — проверять результат внешним способом. Не проси модель судить себя, дай ей инструмент мгновенной проверки. Процесс работы над клипом проявил оба правила. С 3D-версией я не стал настаивать и искать для неё отдельные инструменты. Возможно, задача решаема, но я выбрал 2D-анимацию и не стал дорабатывать вариант с 3D.
Милое несовершенство работает, если компетентность показана. Психолог Эллиот Аронсон в 1966 году провёл такой эксперимент. Участники слушали записи, где два человека отвечают на вопросы викторины. Один отвечал почти всегда правильно, второй часто ошибался. В конце записи кто-то из них разливал кофе и извинялся. Сильный участник после оплошности нравился слушателям больше, чем без неё. Средний участник после той же оплошности становился менее симпатичным.
Мой клип — и есть та самая пролитая чашка кофе. Простоватая анимация и несовершенный звук не брак, а характер. Добиваться идеала в клипе незачем. Совершенства стоит требовать от самого скилла, а пусть клип остаётся живым и чуть небрежным.
В конце я сказал агенту прямо: «Пайплайн сырой. Много моего времени ушло на проверку. И ты еще тупишь». Я до сих пор остаюсь частью процесса. Загружаю трек в Suno руками, проверяю звук ушами, поправляю кадры на глаз. Пайплайн станет автономным, когда агент сможет проверять результат без меня. Первый шаг уже сделан — покадровая проверка работает.
У меня есть ещё одна гипотеза, которую нужно проработать. Информация в форме песни с ритмом, эмоциями и видеорядом укладывается в голове лучше обычного текста. Кажется, реклама именно это и делает. Почему бы не попробовать то же для TasK?
Что дальше
Первым делом закрою свой ручной шаг. Сейчас я загружаю музыку и текст в Suno, а там генерируется вокал. Попробую заменить Suno локальной моделью ACE-Step. Тогда весь пайплайн будет работать без внешних сервисов. Пока это только идея — локально не проверял. Как проверю, напишу.
А вы что дали бы собрать своему агенту? Ролик целиком здесь, «Скормил. Спросил. Готово.»
Источники:
- Пост #25 в Telegram — первый опыт музыки через strudel
- Пост #26 в Telegram — пайплайн клипа
- «Скормил. Спросил. Готово.» — мой клип, совместный с GLM-5.3-Flash
- Switch Angel, «Coding Music for Cold Machines (Narrated)»
- Strudel — музыка на JavaScript
- TodePond, «From Scratch: Strudel Tutorial»
- Remotion — видео на React
- Suno
- whisper.cpp
- Эффект оплошности — Википедия
- Felix Rieseberg, «How to be more ambitious: A tutorial for humans with models»
- TasK
- Мои Leaderboards — оценка локальных моделей на PHP-стеке
dmitry prikotov