Что такое Codemode

· 3 мин чтения
mcp tools harness context-engineering opinion
Что такое 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. С картинками, бинарными данными и работой на небольших моделях тоже нужно разобраться. Так что решение не идеальное, но Ронахер считает его полезным и ждёт, что им будут пользоваться чаще.

Источник: https://lucumr.pocoo.org/2026/10/6/codemode/