Что такое Codemode
Год назад Армин Ронахер советовал не грузить в контекст модели пользовательские инструменты и MCP-серверы, а просто писать больше скриптов. Тогда это выглядело почти как ересь: Code Is All You Need и MCP needs code. Сейчас в Pi 1.0 поддержка MCP добавлена — и сделана это через Codemode. Ронахер объясняет, что это за механизм и что он меняет.
Что такое инструменты для модели
Когда harness вроде Pi даёт LLM инструменты, он отдаёт их определения. На стороне сервера они превращаются в структуру из токенов. Модель чаще или реже вызывает тот или иной инструмент — это результат обучения с подкреплением.
Команда Pi сильно полагается на CLI и bash. Причины две. Во-первых, в bash легко комбинировать вызовы. Во-вторых, модель во время обучения выучила, как устроена файловая система. Когда она вызывает echo foo > /tmp/test.txt, она понимает, что после вызова появился файл test.txt в /tmp.
Но у bash есть одно фундаментальное ограничение: он умеет комбинировать только те программы, которые реально запускаются. А некоторые вещи для LLM — не программы, а родные инструменты протокола.
Первый пример — read или view_image. Мультимодальной модели нужно посмотреть картинку, но cat не подходит: harness должен подложить в протокол модели саму картинку.
Второй пример — субагенты. Чтобы их создавать и координировать, трудно обойтись без инструментов от самого harness. Теоретически агент может написать CLI-утилиту, которая говорит с внешним harness через переменные окружения и Unix-сокеты, но это грубо. И есть вторая проблема — в этом месте начинает исполняться код.
Мозг и руки
Дальше важно понять, где физически исполняются все эти куски кода. Обычно задействованы две системы.
Первая — мозг, то есть harness. Он работает на одной машине и считается доверенным. Вторая — руки, то есть среда, где исполняются инструменты. Часто это та же самая машина, но логически это другая сторона. В Pi её называют средой исполнения.
Главное здесь — граница между мозгом harness и средой исполнения, где работают bash и инструменты.
Такое разделение даёт серьёзные последствия. Прежде всего, у этих двух сторон разные файловые системы и разный уровень доверия. Если взять решение для изоляции вроде Gondolin, ваш bash будет работать внутри песочницы как надо. А вот сам harness останется за её пределами.
Что делает Codemode
Вот тут и появляется Codemode. Это способ для LLM выражать и координировать сложные операции на стороне harness, а не среды исполнения. Codemode работает внутри harness, в собственной песочнице. В Pi это QuickJS внутри WASM-окружения с намеренными ограничениями: нет сети, нет файловой системы, нет таймеров, ограничена память. Единственный выход наружу — вызов дополнительных инструментов.
Сам Codemode — это способ вызывать инструменты из кода на каком-нибудь языке. В Pi это JavaScript. Так вызовы инструментов можно комбинировать, не протаскивая их через контекст LLM. Название придумали в Cloudflare.
Представьте обычный вызов bash инструментом. Harness кладёт в контекст только последние 2000 строк. Если агенту нужно больше, он сам читает файл с остатком вывода. Если же тот же вызов сделан через Codemode, то большие объёмы данных приходят на сторону Codemode в структурированном виде.
Главное, что даёт JavaScript: агент может выражать параллельные операции и простые рабочие сценарии. Типовой приём сегодня — сначала посмотреть 5–10 элементов из ответа инструмента, чтобы понять, что он возвращает. Потом написать скрипт в Codemode, который обработает следующие n элементов.
Codemode также позволяет класть состояние в транскрипт. Один запуск может сохранить данные, а следующий вызов в той же сессии — прочитать их. И это происходит на хосте harness, а не в песочнице.
В Pi через Codemode доступны вызовы, которые в обычном интерфейсе выглядят бессмысленно. Например, генерация картинок или классификация текста одноразовой моделью-классификатором. Внутри Codemode доступно много внутренних API AI SDK, а обычными инструментами они не выставлены — там они просто съедали бы контекст.
В Pi Codemode по умолчанию включается только вместе с MCP. Его можно включить вручную через "defaultTools": ["+codemode"] в настройках — можно просто попросить Pi это сделать. Агент начинает применять Codemode сам: либо потому, что задача ему подходит, либо потому, что его об этом попросили.
Как это выглядит
Ниже — код из реальных сессий Пи, просто переформатированный для чтения. Человеком он не написан.
Генерация картинок
const [painter] = await models.getAvailableOfType("image");
const result = await models.generateImages(painter, {
input: [{ type: "text", text: "A cute little puppy sitting on a grassy " +
"lawn, soft natural light, photorealistic" }],
});
if (result.stopReason !== "stop") return result.errorMessage;
for (const block of result.output) {
if (block.type === "image") image(block);
else text(block.text);
}
Вызов image() возвращает картинку в LLM как изображение. На стороне harness она попадает и в агента, и на диск — как временный артефакт, чтобы агент позже мог передать её обратно в bash.
Классификация
Похожая история с моделями-классификаторами вроде Jev. Они тоже не ложатся в обычные инструменты. Вместо отдельного инструмента Codemode просто даёт агенту доступ к AI SDK, и агент вызывает классификатор напрямую. Так Jev массово разбирает задачи GitHub:
const jev = await models.getModelOfType("classifier", "typesafe", "jev-latest");
const r = await tools.bash({
command: "gh issue list --state open --limit 100 " +
"--json number,title,body,comments",
});
const issues = JSON.parse(r.output);
const results = await Promise.all(issues.map(async (issue) => {
const res = await models.classify(jev, {
state: {
title: issue.title,
body: (issue.body || "").slice(0, 4000),
comments: issue.comments.slice(-5).map(c => c.body.slice(0, 800)),
},
questions: {
sentiment: {
type: "choice",
instructions: "What is the overall sentiment of the author towards pi?",
criteria: {
positive: "Appreciative, happy, constructive praise",
neutral: "Matter-of-fact report or request without emotion",
negative: "Frustrated, annoyed, upset, or angry",
},
},
frustration: {
type: "score",
instructions: "How frustrated is the reporter?",
criteria: ["not at all", "mildly", "clearly frustrated", "very angry"],
},
},
});
if (res.stopReason !== "stop") {
return { n: issue.number, title: issue.title, error: res.errorMessage };
}
return { n: issue.number, title: issue.title, ...res.answers };
}));
store("sentiment_results", results);
return results
.filter(r => !r.error)
.sort((a, b) => b.frustration.score - a.frustration.score)
.slice(0, 12)
.map(r => `#${r.n} ${r.frustration.score.toFixed(2)} ${r.title}`);
Обратите внимание на store(). Этот вызов кладёт результат исполнения в транскрипт сессии, и следующий запуск Codemode может его прочитать.
С Promise.all тут всё в порядке: Pi сам ограничивает общее число параллельных вызовов инструментов четырьмя и держит очередь на остаток.
Более смелый пример — управление игровым движком с помощью Jev для отладки. Агент знал про команду tankctl и сам собрал вокруг неё минимальную обвязку: цикл из 30 шагов, где на каждом шаге игра отдаёт текстовый дамп состояния, а Jev решает, что делать дальше.
Вызовы MCP-серверов
Для MCP Codemode подходит особенно хорошо. Сами инструменты MCP в контекст модели не попадают. Вместо этого агент сначала через специальные API делает поиск инструментов внутри Codemode и узнаёт, что он может делать с подключёнными серверами. Такая постепенная разведка и делает MCP рабочим во многих сценариях.
В этом примере агент сразу дёргает Sentry MCP, даже не разведывая инструменты. Скорее всего, он знает про такой сервер ещё с этапа обучения. Но агент узнаёт из системного промпта, что сервер Sentry вообще подключён. Это не гадание.
const orgs = await tools.mcp__sentry__find_organizations({});
const { organizations } = orgs.structuredContent;
const results = await Promise.allSettled(organizations.map(org =>
tools.mcp__sentry__find_projects({
organizationSlug: org.slug,
regionUrl: org.regionUrl,
})
));
return organizations.map((org, i) => {
const r = results[i];
if (r.status !== "fulfilled") return { org: org.slug, error: String(r.reason) };
if (r.value.isError) return { org: org.slug, error: r.value.content };
return {
org: org.slug,
projects: r.value.structuredContent.projects.map(p => p.slug),
};
});
Современный MCP — это борьба
Ронахер не хочет много говорить про MCP, но признаёт: это протокол, которому Codemode очень помогает. Проблема в том, что на практике MCP часто нацелен на harness, которые Codemode ещё не используют. Пока ситуация меняется, выход делают так: встраивают Codemode внутрь самого MCP-сервера, как это сделал Cloudflare. Но тогда получается Codemode внутри Codemode, и это плохо. JSON приходится экранировать дважды, из-за чего небольшие модели там путаются. А ещё код внутри не может вызывать инструменты снаружи.
Если взять MCP-серверы Cloudflare в Pi, агенту приходится писать JavaScript и прокидывать его через ещё один JavaScript. Это явно не оптимально, но и понятно, почему так происходит.
Чего Codemode ждёт от MCP
Насколько хорошо Codemode работает с MCP сегодня? Не очень. Причина в том, что MCP-серверы пока не нацелены на harness с Codemode, хотя сейчас его поддерживает уже большинство harness.
Чтобы всё заработало, есть четыре требования.
Структурированное содержимое. Codemode ждёт от вызовов нормально отформатированный JSON. Значит, сервер должен его возвращать, а многие пока не возвращают. Система outputSchema в MCP для этого подходит.
Стабильные результаты. Неприятный случай — сервер отдаёт данные непоследовательно. Например, он подрезает вывод, чтобы сэкономить токены, и зависит от размера набора. Тогда проба на 5 элементах проходит, а на максимальном размере батча всё ломается.
Большие бинарные данные. MCP пока не умеет передавать их. Из-за этого многие интересные сценарии просто не работают. Приходится идти в обход: сначала загрузить файл обычным способом, а потом передать ссылку.
Компонуемый поиск инструментов. Сервер может лучше клиента понимать, какой инструмент подходит под задачу. Но сейчас нет нормального механизма, который позволил бы harness параллельно искать инструменты сразу в нескольких MCP-серверах. Всё держится на поведении, которое само по себе не масштабируется при нескольких активных серверах.
Что дальше
Возвращаемся к главному: это отказ от совета про CLI, который Ронахер дал год назад? Нет. Экосистема MCP, по его мнению, подхватила ровно то, что год назад сработало в CLI: код. Другое дело, что Codemode идёт дальше MCP — он даёт агенту внутри harness больше свободы.
При этом есть задачи, которые ещё предстоит решить. Например, устойчивость Codemode. Возможно, придётся взять идеи из движков надёжных рабочих процессов и делать снимки запусков. Или взять Starlark как язык композиции — из-за его детерминированности он лучше JavaScript. С картинками, бинарными данными и работой на небольших моделях тоже нужно разобраться. Так что решение не идеальное, но Ронахер считает его полезным и ждёт, что им будут пользоваться чаще.