v0.1 · исходники

Skein — терминальный агент разработки

Он помогает писать код: читает проект, вносит изменения, запускает сборку и тесты. Отличается тем, как устроен его контекст: история хранится в журнале событий, а лента, которую видит модель, собирается из него заново на каждом ходу.

Исходники Установка

Skein есть в npm (skein-swe) и на Docker Hub. Для быстрого запуска достаточно npx -p skein-swe skein.

Быстрый старт

Короткий путь — на примере RouterAI, российского OpenAI-совместимого провайдера.

  1. Зарегистрируйся на routerai.ru и создай API-ключ.
  2. Поставь Skein: npm install -g skein-swe (или запускай без установки — npx -p skein-swe skein). Нужны Node.js 22 или новее и bash; если глобальная установка требует root — см. «Установку».
  3. Сохрани ключ и проверь, что он и модель отвечают:
    skein auth login routerai
    skein doctor
  4. Запусти агента в каталоге проекта:
    skein --dir ~/project

Вместо интерактивного входа можно задать ROUTERAI_API_KEY в окружении. auth login показывает бесплатные и локальные варианты первыми и печатает ссылку на ключ выбранного провайдера.

Введение

Skein — терминальный агент разработки. «Терминальный» здесь означает, что агент работает в терминале: пользователь вводит команды в панели чата, видит, что агент делает, в панели наблюдений и следит за ходом работы в панели задач. «Агент разработки» — потому что он помогает писать программный код: читает проект, вносит изменения, запускает сборку и тесты.

Skein устроен иначе, чем традиционные агенты разработки. Чтобы понять, в чём разница, сначала посмотрим, как обычно работают агенты разработки и где у этой схемы слабые места.

Как обычно работает агент разработки

Обычный агент разработки работает по циклу ReAct: модель рассуждает, выбирает действие, исполнитель запускает инструмент и возвращает результат, после чего модель рассуждает снова. Модель получает системный промпт и задачу пользователя, а на каждом шаге выбирает очередное действие: прочитать файл, найти строку, изменить код, выполнить команду. Результат дописывается в историю, и цикл повторяется, пока модель не скажет «готово» или не закончится бюджет.

Инструментов у такого агента обычно немного: прочитать файл, найти файл или строку по шаблону, отредактировать файл, создать файл. Главный из них — оболочка (shell или bash): она позволяет выполнить в терминале любую команду — собрать проект, прогнать тесты, сделать коммит, запустить скрипт. Часто оболочки хватает почти на всё, а отдельные инструменты нужны лишь для удобства и безопасности типовых действий. У Skein к обычному набору добавлены два инструмента, которых у других агентов обычно нет, — recall и search; они позволяют доставать из журнала то, что ушло с ленты. Подробнее о них ниже.

Контекст модели — это не только история действий. В нём лежат системный промпт, задача, все рассуждения и выводы инструментов; для модели это единственная память. Отдельного состояния у агента нет — цель живёт в тексте этой истории. Когда история перестаёт помещаться в окно модели, её обрезают или сжимают.

Провайдеры моделей не запускают модель заново на уже прочитанном тексте: они хранят её внутреннее состояние у себя и находят его по префиксу промпта — это и есть кэш промпта. Поэтому входные токены, которые уже были в предыдущем вызове, обычно стоят на порядки дешевле новых. Длинная история перестаёт быть дорогой, и цена с задержкой отходят на второй план. Остаются две другие проблемы: внимание и управляемость.

Внимание. Цель конкурирует с килобайтами старых выводов инструментов. Модель переоткрывает уже известное, возвращается к пройденному и постепенно теряет исходную задачу.

Управляемость. У шага нет явной границы, а история при сжатии переписывается. По истории трудно сказать, что модель «знала» в момент решения, и ещё труднее воспроизвести это позже.

Почему сравниваем с opencode

opencode — открытый терминальный агент разработки, который работает по этой же схеме. Он подходит для сравнения не потому, что «плохой», а потому что его можно поставить в те же условия: тот же провайдер и модель, тот же режим рассуждений, те же задачи и контейнеры, запуск без интерфейса (opencode run, --continue). В нашем прогоне fix-ocaml-gc он решил задачу за 54 шага, а его контекст вырос примерно с 3k до 300k символов. Кэш сделал это недорогим, но история осталась историей.

Чем Skein отличается

Skein построен вокруг другой идеи: история — это данные, а не промпт.

Оговоримся о слове «лента». Дальше так называется то, что видит модель на входе: у обычного агента это растущая история, у Skein — проекция журнала. Это один и тот же видимый контекст модели, просто устроен он по-разному.

  • История хранится, а не переписывается. Вместо растущей ленты у Skein типизированный журнал, в который только дописывают. Узлы журнала не переписываются, поэтому история остаётся полной и адресуемой.
  • Промпт собирается заново. На каждом ходу контекст модели строится из журнала заново и детерминированно: одни и те же события дают одну и ту же ленту. Лента немонотонна: когда фокус меняется, закрытые состояния уходят с экрана, но остаются в журнале и доступны через recall и search.
  • Явные состояния. Шаг работы — это состояние с целью (what), условием завершения (Done when) и своим набором инструментов. Переходы между состояниями явные. Запрос пользователя — это ориентир, а не активная цель: активная цель у состояния.
  • Модель предлагает, исполнитель записывает. У модели нет скрытой памяти; отказы и ошибки инструментов — единственные факты, которые добавляет движок.
  • Сессия поверх отдельных задач. Весь диалог пользователя с агентом (сессия) состоит из отдельных единиц работы, у каждой свой журнал. Единицы изолированы, и наружу виден только краткий итог каждой. Непрерывность держится на переформулировке запроса и этих итогах.

recall и search — особые инструменты Skein. recall возвращает конкретный ход или фрагмент прошлой работы, search ищет по журналу. В обычном агенте история либо целиком лежит в контексте, либо её уже сжали, и достать оттуда точный факт нечем.

Что это даёт: цель остаётся заметной в точке решения, у каждого шага есть явная граница, а журнал можно воспроизвести и проверить.

Чего это не даёт: это не оптимизация стоимости. Изоляция может стоить дороже из-за повторного чтения проекта. Критерий проверяется текстом модели, а не движком, поэтому возможна преждевременная остановка.

Одна задача целиком: fix-ocaml-gc

Разберём одну реальную задачу целиком — от первой реплики пользователя до финального ответа. Это иллюстративный пример: он нужен, чтобы показать, как Skein ведёт работу и чем его ход отличается от обычного агента вроде opencode на той же задаче.

Пользователь пишет, что сломал сборщик мусора OCaml, пытаясь сжать свободное место в major heap, и компилятор падает при bootstrap. Он просит разобраться и убедиться, что базовый testsuite проходит.

Движок записывает запрос, осматривает рабочую область и просит модель выбрать стартовое состояние. Вот как выглядит первый ход, схематично:

[system]  базовый промпт: роль и как работать
[human]   запрос
[system]  факты о рабочей области: корень, VCS нет, сеть выключена, есть make/gcc/perl
[human]   выбери стартовое состояние: Patch / Construction / Research

Patch

Модель выбирает Patch и формулирует предмет работы: сломанный run-length-compressed sweep в major GC. Проекция состояния выглядит так:

[system]  Start state Patch
[system]  факты о рабочей области
[human]   Goal: найти дефект в существующем артефакте и внести нужную правку
          Done when: план выполнен — ошибка найдена и исправлена
          Plan: 1) найти файл и причину  2) исправить и остановиться с отчётом

Дальше идёт работа внутри состояния. Схематично:

ДействиеЧто дало
list /appструктура проекта
cat HACKING.adocкак собирать компилятор
find -newermtнедавно изменённые файлы
grep по shared_heap.c подозрительные циклы обхода пула
edit p += Whsize_hd(hd) → p += wh

После 18 ходов модель отвечает текстом — движок записывает Finish state Patch с результатом: причина в pool_sweep, размер свободного блока хранится в заголовке, шаг должен идти на ширину size-class.

Переход

Проекция схлопывается. На ленту попадают только базовый промпт, запрос, Start state Patch, Finish state Patch и вопрос о следующем состоянии:

[system]  Choose exactly one next state:
          Verification — изменение внесено и строки прочитаны обратно
          Research — это ситуация, а не дефект, который надо найти и исправить

На пике Patch контекст был около 116k символов, на переходе — около 4.5k. Это не удаление: журнал цел, на ленту попадает только то, что нужно для решения о следующем шаге.

Verification

Модель переходит в состояние проверки.

[system]  Start state Verification
[human]   Goal: проверить изменённый артефакт против acceptance
          Done when: acceptance проходит, результат выведен из материала
          Plan: выполнить acceptance-команду и прочитать вывод;
                нулевой exit code — не доказательство; артефакт здесь не менять
ДействиеЧто дало
./configureподготовка сборки
make -j16 компилятор собрался, bootstrap прошёл
make -C testsuite one DIR=tests/basic 40 tests passed

Модель отвечает текстом — Finish state Verification.

End

[system]  Start state End
[system]  This is End. Прочитай ленту — запрос, пройденные состояния,
          что каждое сделало, какие файлы изменены, как проверено, —
          и напиши финальный отчёт.

Финальный ответ: дефект в pool_sweep (runtime/shared_heap.c), исправлен на p += wh, сборка проходит, базовый testsuite — 40 tests passed.

Что здесь важно

  • Контекст — не транскрипт. У каждого состояния своя проекция, и она пересобирается заново.
  • Запрос остаётся ориентиром; активная цель — у состояния.
  • Текст модели — граница состояния (stop).
  • Закрытое состояние уходит с ленты, но остаётся в журнале.
  • Контекст всё равно растёт, но кэш делает его дешёвым. Смысл не в экономии, а в том, что модель видит цель, а не всю историю.

Механика

Состояния и переходы

Задача проходит через явные состояния. Состояние — это этап работы, у которого есть цель (что именно нужно сделать), условие завершения (как понять, что этап закончен) и свой набор инструментов. У состояния есть короткий список ходов, и после каждого хода модель видит его заново, а не всю историю задачи.

Состояний пять:

СостояниеЧто делаетКуда может перейти
Research разбирается в ситуации и добывает нужный результат из материала End, Patch
Patch находит дефект в существующем артефакте и исправляет его Verification, Research
Construction строит новый артефакт по спецификации End, Research
Verification проверяет изменение против условия задачи End (прошло), Patch (не прошло)
End завершает запрос финальным отчётом —

Обычный маршрут — Research → Patch → Verification → End. Если проверка не прошла, работа возвращается в Patch и правит дальше. Переход выбирает модель, но список допустимых переходов задаёт движок: из Patch можно в Verification или Research, но не сразу в End.

Состояние закрывается, когда модель отвечает текстом. Движок записывает этот ответ как stop и переносит результат в журнал. Отдельной проверки «достигнута ли цель» нет: условие записано в состоянии, но движок его не исполняет. Поэтому возможна преждевременная остановка — это известное ограничение.

Агенты и единицы работы

Реплики пользователя обрабатывает отдельный агент — агент сессии. Он не работает с файлами напрямую: инструменты рабочей области ему недоступны. Вместо этого на каждую реплику он выбирает одну из шести операций: respond, general, revision, task, plan, write_document.

Операция открывает единицу работы — отдельную задачу со своим журналом событий. Внутри единицы агент читает файлы, правит код и запускает команды, но наружу эти шаги не выходят: агент сессии видит только краткий итог единицы. Поэтому единицы не мешают друг другу, а сессия остаётся связной за счёт переформулировки запроса и этих итогов.

  • task — цикл состояний, описанный выше;
  • general — разбор рабочей области без изменений;
  • revision — изменение, уже описанное в запросе (правки и команды);
  • plan — план работ без изменений;
  • write_document — большой Markdown-документ по секциям;
  • respond — ответ из диалога и прошлой работы.

Лента

Лента — это то, что видит модель на входе. Она собирается из журнала заново на каждом ходу, и порядок блоков всегда один:

  1. базовый промпт — роль агента и общие правила;
  2. запрос пользователя — как ориентир, а не как активная цель;
  3. Start state — с какого состояния начали;
  4. краткая справка о рабочей области;
  5. ходы состояния — действия и наблюдения;
  6. рабочий промпт — цель, условие завершения и план именно этого состояния;
  7. Finish state — результат, когда состояние закрыто.

Порядок и содержимое определяет только журнал. Одни и те же события всегда дают одну и ту же ленту — в этом смысле сборка детерминирована. Но она немонотонна: когда фокус переходит в другое состояние, закрытое состояние уходит с ленты. Это не потеря — журнал цел, и вернуть нужное можно через recall и search.

Сессия и изоляция

Сессия — это весь диалог пользователя с агентом. Каждая реплика превращается в отдельную единицу работы: у неё свой журнал, свой набор состояний и свой итог. Единицы изолированы друг от друга: внутри следующей единицы не видно пошаговой истории предыдущей, только её краткий итог. Поэтому единицы не раздувают друг друга и не тянут за собой чужие выводы инструментов.

Цена изоляции — повторное чтение рабочей области. Если новая задача требует тех же фактов, что и прошлая, агенту придётся прочитать их снова: в ленте новой единицы их нет. Непрерывность сессии держится на двух вещах: переформулировке запроса с учётом прошлых реплик и кратких итогах уже выполненных единиц.

Журнал

Журнал — это история работы в виде типизированных записей: запрос, ходы, действия, наблюдения, переходы между состояниями, остановки. В него только дописывают: узлы не переписываются и не удаляются. Поэтому по журналу можно восстановить, что делал агент, в каком порядке и с каким результатом.

Лента строится из журнала, а не наоборот. Если что-то ушло с ленты при смене фокуса, оно остаётся в журнале. Инструменты recall и search достают оттуда конкретный ход или находят нужное по журналу — так модель возвращает факт, не перечитывая всё заново.

Оценка и ограничения

Мы сравнили Skein с opencode на тринадцати задачах. Каждую задачу решали пять раз, с сетью и без, обоими агентами — всего 250 прогонов, доступных для оценки. Решено 246 (98,4%). По группам: Skein с сетью — 63 из 65, Skein без сети — 60 из 60, opencode с сетью — 65 из 65, opencode без сети — 58 из 60. Вся серия шла на одном провайдере и одной модели — deepseek-v4-flash с низким уровнем рассуждений; это не заявление о передовом результате.

Замеры по токенам (только решённые прогоны, по 123 на агента):

Skeinopencode
вход31,38 млн47,63 млн
из кэша26,88 млн45,22 млн
новых входных4,50 млн2,41 млн
выход1,24 млн0,94 млн
попадание в кэш85,7%94,9%
ходов24471800

Картина такая: opencode тратит больше токенов, но почти всё это попадания в кэш, поэтому по деньгам он дешевле. Skein компактнее по общему объёму, но у него больше новых входных токенов и выхода: единицы не делят один растущий промпт, поэтому чаще читают рабочую область заново.

Отсюда главный вывод: разница не в цене. Кэш делает длинную ленту дешёвой, поэтому сравнивать по деньгам бессмысленно. Смысл в другом — держать цель заметной в точке решения и делать шаг воспроизводимым. Эти свойства сложнее измерить, и автоматически мы их не измеряли.

Ограничения:

  • движок не проверяет условие завершения состояния, поэтому возможна преждевременная остановка;
  • изоляция меняет дрейф на повторную работу и может стоить дороже;
  • вся серия — один провайдер, одна модель и низкий уровень рассуждений;
  • инструмент оболочки требует /bin/bash; на Windows подойдут WSL2, Git Bash/MSYS2 или Docker.

Протокол, все прогоны, токены и стоимость по состояниям лежат в bench/results/campaign-k5/; сырые данные публикуются с релизом.

Установка

Нужен Node.js 22 или новее и bash.

npm install -g skein-swe
skein --version

Если глобальный префикс npm — системный каталог (например, /usr/local), обычный npm install -g упадёт с EACCES. Ставь с --prefix "$HOME/.local" и добавь ~/.local/bin в PATH: sudo не нужен. Если у тебя nvm или volta, глобальная установка и так идёт в домашний каталог. Без установки можно запускать npx -p skein-swe skein.

Из исходников

git clone https://github.com/merfill/skein.git
cd skein
npm install
npm run build
npm pack
npm install -g --prefix "$HOME/.local" ./skein-swe-*.tgz
export PATH="$HOME/.local/bin:$PATH"
skein --version

Дальше нужно указать провайдера, ключ и модель — интерактивно или переменными окружения:

skein auth login
skein doctor

skein doctor проверяет, что ключ и модель отвечают. Если всё в порядке, запускается интерфейс:

skein --dir ~/project

Windows

Агенту нужен /bin/bash, поэтому на Windows надёжный путь — WSL2 или Docker. Нативные PowerShell и cmd без bash не подойдут.

WSL2 (рекомендуется):

  1. В PowerShell от администратора выполни wsl --install -d Ubuntu и перезагрузись.
  2. Внутри Ubuntu поставь Node.js 22:
    curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
    sudo apt-get install -y nodejs
  3. Проекты держи в домашнем каталоге WSL (~/project), а не в /mnt/c: так быстрее и не ломаются права.
  4. Дальше повтори шаги установки для Linux внутри WSL.

Docker Desktop: запусти Linux-образ из блока ниже — Node внутри не нужен.

Git Bash / MSYS2: может сработать, только если /bin/bash доступен из запускающей оболочки, но на практике нативный Windows-Node этот путь часто не находит. Надёжнее WSL2 или Docker.

Docker

Готовый образ можно взять с Docker Hub:

docker pull mefrill/skein-swe:0.1.0
docker run --rm -it \
  -v "$PWD:/workspace" \
  -e ROUTERAI_API_KEY=... \
  -e SKEIN_MODEL=~deepseek/deepseek-v4-flash-latest \
  mefrill/skein-swe:0.1.0

Провайдер задаётся переменными окружения; модель в образ не встроена. Тег latest указывает на последнюю версию. В Windows PowerShell для пути используйте ${PWD}, в cmd — %cd%; --env-file не убирает кавычки из значений.

Собрать из исходников: docker build -t skein:0.1.0 . в корне репозитория.