惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

W
WeLiveSecurity
博客园 - 【当耐特】
Microsoft Azure Blog
Microsoft Azure Blog
WordPress大学
WordPress大学
Stack Overflow Blog
Stack Overflow Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
IT之家
IT之家
Cloudbric
Cloudbric
The Register - Security
The Register - Security
小众软件
小众软件
PCI Perspectives
PCI Perspectives
G
Google Developers Blog
AI
AI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Google DeepMind News
Google DeepMind News
Google DeepMind News
Google DeepMind News
宝玉的分享
宝玉的分享
Recent Commits to openclaw:main
Recent Commits to openclaw:main
量子位
TaoSecurity Blog
TaoSecurity Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
F
Full Disclosure
N
Netflix TechBlog - Medium
博客园_首页
Last Week in AI
Last Week in AI
A
Arctic Wolf
B
Blog RSS Feed
J
Java Code Geeks
C
Cybersecurity and Infrastructure Security Agency CISA
I
InfoQ
aimingoo的专栏
aimingoo的专栏
云风的 BLOG
云风的 BLOG
NISL@THU
NISL@THU
MyScale Blog
MyScale Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Jina AI
Jina AI
有赞技术团队
有赞技术团队
S
Schneier on Security
L
Lohrmann on Cybersecurity
P
Privacy & Cybersecurity Law Blog
T
Threat Research - Cisco Blogs
P
Palo Alto Networks Blog
S
Security @ Cisco Blogs
Security Archives - TechRepublic
Security Archives - TechRepublic
Security Latest
Security Latest
Vercel News
Vercel News
博客园 - 司徒正美
Webroot Blog
Webroot Blog
Hacker News: Ask HN
Hacker News: Ask HN
A
About on SuperTechFans

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Волшебный Koog. Пишем Kotlin-агент широкого профиля KMP
anioutka (Юз · 2026-04-24 · via Все публикации подряд на Хабре

Уровень сложностиСредний

Время на прочтение13 мин

Охват и читатели432

Туториал

Всем привет! С вами Анна Жаркова, руководитель мобильной практики ГК Юзтех. Что ж, за последние полгода мир разработки и мир ИИ скакнули и ушли далеко вперед. Теперь знания работы с агентами, умение написать не только правильный промт, но и собственные скиллы (навыки) для этих агентов, готовить свои MCP для погружения в контекст задачи, проекта, становятся не только полезными, но и обязательными для разработчиков и IT-специалистов. Уже многие используют как специальные IDE с ИИ-агентами (Claude, Cursor, Windsurf и т.п.), так и встраиваемые в привычные VsCode и AndroidStudio в виде плагинов. Можно не ограничиваться готовым настраиваемым функционалом, а пойти дальше и написать свой собственный агент. И сегодня мы поговорим про такое решение, использование специального фреймворка от JetBrains Koog для разработки своих агентов. С его помощью мы создадим агент для генерации простых KMP приложений и кросс-платформенных задач и подключим к плагину Continue Dev.

Небольшой спойлер: сам агент был написан при участии Cursor, и про нюансы его создания читайте в конце статьи.

А пока мы поговорим:

  • что такое Koog

  • что входит в собственный агент

  • как подготовить tools и MCP

  • как подключить наш MCP к Continue Dev плагину.

Koog — это фреймворк на базе Kotlin, разработанный JetBrains для создания и запуска AI-агентов. Его основная идея заключается в том, чтобы дать разработчикам возможность строить интеллектуальных агентов, используя Kotlin. Это означает, что вы можете создавать сложные AI-решения, оставаясь в привычной и любимой среде Kotlin, без необходимости изучать новые языки или парадигмы для работы с AI. Что мне понравилось: Koog можно использовать как компонент вашего кроссплатформенного KMP приложения. Вы можете в одном проекте создать и MCP сервер, и агента, и клиента для проверки. Также вы можете встроить агента на Koog в свое приложение.

У Koog есть и другие особенности:

  • Интеграция с MCP (Model Context Protocol): позволяет эффективно управлять моделями и их контекстом.

  • Возможности встраивания (Embedding capabilities) для семантического поиска и извлечения знаний, что критически важно для RAG (Retrieval Augmented Generation) систем.

  • Создание пользовательских инструментов, которые могут взаимодействовать с внешними системами и API. Это позволяет агентам выполнять действия в реальном мире.

  • Готовые компоненты: Koog поставляется с набором предварительно созданных решений для общих задач AI-инженерии, что значительно ускоряет разработку.

  • Интеллектуальное сжатие истории.

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

  • Создание графовых стратегий из инструментов для решения задач.

  • Модульная система функций.

  • Масштабируемая архитектура: Koog способен обрабатывать рабочие нагрузки от простых чат-ботов до корпоративных приложений.

Также Koog поддерживает работу с различными провайдерами LLM, как внешними (ChatGPT, Gemini, Claude и т.п.), так и локальными (Ollama).

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

dependencies {
    implementation("ai.koog:koog-agents:0.7.3") 
}

Для определения минимального агента достаточно указать AIAgent, задать ему нужные PromptExecutor (исполнитель промтов, завязанный на провайдер) и LLModel (поддерживаемая модель для исполнителя).

val agent = AIAgent(
    promptExecutor = executor,
    llmModel = model,
    systemPrompt = "You are a helpful assistant."
)

val result = agent.run("Explain Kotlin Multiplatform.")
println(result)

// Для ChatGPT
 val agent = AIAgent(
        executor = simpleOpenAIExecutor(apiKey),
        systemPrompt = "You are a helpful assistant.",
        llmModel = OpenAIModels.Chat.GPT4o 
    )

// Для Ollama
 val agent = AIAgent(
        executor = simpleOllamaAIExecutor(ollamaHost),
        systemPrompt = "You are a helpful assistant.",
        llmModel = LLModel(
            provider = OllamaLLMProvider(),
            id = "qwen3-coder:30b"
)
    )

Также в агенте можно задать:

  • набор инструментов,

  • стратегию (граф с порядком вызова комбинации инструментов),

  • некоторые ограничения.

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

В целом, агенты на Koog в зависимости от предстоящей задачи можно разделить на следующие группы:

  1. Prompt-only агент Минимальный сценарий: один запрос к модели, без tools. Хорошо для быстрых задач, плохо для автоматизации.

  2. Tool-driven агент Модель умеет вызывать функции/инструменты. Хорошо для интеграций и контролируемых действий.

  3. Graph-based агент (workflow) Сценарий разложен на узлы и переходы. Хорошо для многошаговых процессов

  4. Composed / Multi-agent стиль Несколько подзадач или субграфов, каждый со своей ролью/набором tools. Применяется, когда одна “универсальная” модель становится слишком сложной.

Наша задача - создать инструмент, который будет помогать строить приложение на KMP. Поэтому выбираем агент на графе с использованием инструментов.

Инструменты

Будем исходить из того, что нам нужно в нашем приложении:

  • сетевой слой (на Ktor),

  • слой презентации и архитектуры,

  • бизнес-логика.

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

  • для генерации сети и бизнес-логики (NewsAppTooling)

  • для генерации архитектуры (LayerTemplateTools)

  • для цельного рабочего процесса - оркестрации (OrchestratorTools).

В Koog инструмент — это Kotlin-метод, помеченный @Tool. Он входит в класс ToolSet, а затем регистрируется в реестре инструментов, ToolRegistry.

Рассмотрим на примере генератора слоев:

class LayerTemplateTools : ToolSet {

    @Tool
    @LLMDescription("Template for Ktor client, serialization, and NetworkConfig wiring in shared/commonMain.")
    fun generateNetworkLayerTemplate(): String = NewsAppTooling.networkLayerTemplate()
}

На уровне API здесь происходят две вещи:

  • @Tool делает метод доступным модели;

  • @LLMDescription объясняет, зачем и когда его вызывать. Это правило и ключевые слова.

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

Не для каждого шага нужно создание своего инструмента, что-то можно оставить обычной функцией:

  • если модель должна явно решить, нужен этот шаг или нет, это инструмент;

  • если шаг нужен только внутренней реализации, это обычная функция.

инструмент = агентное действие
обычная функция = реализация агентного действия 

Готовый инструмент регистрируем в ToolRegistry, реестре инструментов:

fun createToolRegistry(session: SessionSymbolStore): ToolRegistry = ToolRegistry {
    tools(OrchestratorTools(session).asTools())
    tools(LayerTemplateTools().asTools())
}

Реестр инструментов подключается к AI агенту во время его создания:

AIAgent(
        promptExecutor = executor,
        llmModel = LLModel(
            provider = OllamaLLMProvider(),
            id = "qwen3-coder:30b",
            capabilities = listOf(LLMCapability.Tools),
        ),
        systemPrompt = systemPrompt,
        temperature = 0.2,
        toolRegistry = createToolRegistry(session)
)

Теперь рассмотрим подробнее инструменты, которые мы будем использовать.

Инструменты оркестрации: план и общий язык

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

Два главных метода здесь:

@Tool
fun planKmpNewsApp(brief: String): String

@Tool
fun rememberGenerationSymbol(symbol: String, role: String): String

Первый, planKmpNewsApp, берёт пожелания вроде «сделай новостной клиент» и выдаёт на выходе не общие слова, а чёткую спецификацию: архитектурные слои, порядок их реализации, ограничения (например, по NetworkConfig), список компонентов и даже последовательность следующих вызовов. Получается не импровизация, а понятная инструкция: имея такой план, агент не потеряется даже в длинной цепочке шагов.

Второй, rememberGenerationSymbol, закрывает вечную проблему кодогенерации — несогласованные имена. Если в одном ответе репозиторий уже назвали NewsRepositoryImpl, а use case — GetTopHeadlinesUseCase, этот инструмент запоминает выбор. При следующем шаге не придётся заново придумывать имена и рисковать, что репозиторий вдруг окажется NewsDataRepository. Вся команда (агент и пользователь) продолжает работать в единой системе обозначений.

Оркестратор вынесен отдельно, потому что планирование и фиксация названий не относятся ни к сети, ни к UI, ни к бизнес-логике. Если смешать их с генерацией кода, получится инструмент с размытыми обязанностями: он будет и управлять процессом, и создавать шаблоны одновременно. Разделение даёт простую границу: OrchestratorTools решает «что и в каком порядке делаем», а следующие инструменты — «как именно это будет выглядеть в коде».

Шаблоны слоёв: сеть, логика, интерфейс

Если оркестратор прокладывает маршрут, то LayerTemplateTools — это фабрика заготовок для каждого архитектурного слоя. Внутри три метода:

@Tool
fun generateNetworkLayerTemplate(): String

@Tool
fun generateDomainLayerTemplate(): String

@Tool
fun generatePresentationLayerTemplate(): String

Каждый возвращает не готовое приложение, а типовой каркас:

  • сетевой — настройка Ktor-клиента, сериализации, конфигурация в shared/commonMain;

  • доменный — интерфейсы репозиториев, реализации, use‑cases (всё в shared);

  • презентационный — ViewModel и Compose‑экран (в composeApp или shared).

Мы разделяем генерацию слоев, чтобы, во-первых, не смешивать их внутреннюю логику и инструменты, во-вторых, перезапускать по отдельности, в-третьих, для читабельности. Нет одного огромного метода generateApp(), который валится целиком. Если что-то пошло не так на уровне доменного слоя, это не ломает сетевой или UI. Можно гибко перезапускать или корректировать отдельные шаги.

В итоге OrchestratorTools и LayerTemplateTools работают в паре по принципу «сначала договариваемся, потом делаем». Первый держит фокус на общей картине и терминах, второй — дает готовые архитектурные лекала. Граница между ними и позволяет агенту одновременно быть стратегом и аккуратным исполнителем.

Шаблоны

Чтобы генерация компонента или слоя отвечала нашим ожиданиям, задаем шаблоны и заготовки для примера. В текущем проекте эту роль играет NewsAppTooling.

object NewsAppTooling {
    fun planKmpNewsApp(brief: String, symbols: Map<String, String>): String = """
        |# KMP News (newsapi.org) — implementation plan
        |
        |## Architecture (MVVM + use case, coroutines, Compose, Ktor in common)
        |1. **NetworkConfig** — client sets `NetworkConfig.install(apiKey)` before any request.
        |2. **Data** — Ktor `HttpClient`, JSON DTOs (`kotlinx.serialization`), `NewsRepositoryImpl` calling `v2/top-headlines`.
        |3. **Domain** — `NewsRepository` interface, `GetTopHeadlinesUseCase`.
        |4. **Presentation** — `NewsViewModel`, Compose list screen.
        """.trimMargin()
}

Этот слой решает одну архитектурную проблему: модель не должна каждый раз заново “изобретать” базовую архитектуру проекта.

Рассмотрим некоторые шаблоны:

fun networkLayerTemplate(): String = """
    |// shared/src/commonMain/.../news/NetworkConfig.kt
    |object NetworkConfig {
    |    private var apiKey: String? = null
    |    fun install(apiKey: String) { this.apiKey = apiKey }
    |    fun requireApiKey(): String = apiKey ?: error("Call NetworkConfig.install(apiKey) from Android/iOS entry before network calls")
    |}
    |
    |// Ktor client in commonMain + expect/actual or multiplatform engines
    |// NewsApi: suspend fun topHeadlines(country: String = "us"): HttpResponse or typed DTO
    """.trimMargin()
fun domainLayerTemplate(): String = """
    |interface NewsRepository {
    |    suspend fun topHeadlines(country: String = "us"): Result<List<Article>>
    |}
    |
    |class GetTopHeadlinesUseCase(private val repository: NewsRepository) {
    |    suspend operator fun invoke(country: String = "us") = repository.topHeadlines(country)
    |}
    """.trimMargin()
fun presentationLayerTemplate(): String = """
    |class NewsViewModel(private val useCase: GetTopHeadlinesUseCase) : ViewModel() {
    |    private val _state = MutableStateFlow(NewsUiState(loading = false, articles = emptyList(), error = null))
    |    val state: StateFlow<NewsUiState> = _state.asStateFlow()
    |    fun load(country: String = "us") { viewModelScope.launch { /* invoke useCase, fold Result */ } }
    |}
    |
    |@Composable
    |fun NewsScreen(viewModel: NewsViewModel = viewModel { ... }) { /* LazyColumn, error, loading */ }
    """.trimMargin()

Графы и стратегии

Мы не хотим вручную вызывать все инструменты. Нам нужно ввести промт и получить результат. Как его достичь, наш агент должен понимать сам. Для того, чтобы получить именно пошаговую реализацию, или план и реализацию, нам потребуется создать стратегию на основе графа инструментов.

Минимальная схема мыслится так:

[Start] | [CollectContextNode] --(context)–> [PlanningSubgraphWithTools] --(result)–> [Finish] Что здесь важно:

  • узел делает один смысловой шаг;

  • переходы явно передают преобразованный выход;

  • subgraph позволяет “вложить” этап, где модель активно вызывает tools. :

val strategy = strategy<Input, Output> { val collect by nodeLLMRequest(“collect”, allowToolCalls = false) val plan by subgraphWithTask<Input, Output>(tools = toolRegistry.tools) { input -> “Сделай план и используй tools” }

edge(nodeStart forwardTo collect transformed { input -> "Собери контекст: $input" })
edge(collect forwardTo plan transformed { msg -> msg.content })
edge(plan forwardTo nodeFinish)

}

В нашем проекте граф выглядит немного по-другому, т.к. у нас есть повторные фазы:

fun kmpNewsDevelopmentStrategy() = strategy<String, String>("kmp-news-dev") {
    val nodeCallLLM by nodeLLMRequest("sendInput")
    val nodeExecuteTool by nodeExecuteTool("nodeExecuteTool")
    val nodeSendToolResult by nodeLLMSendToolResult("nodeSendToolResult")

    edge(nodeStart forwardTo nodeCallLLM)
    edge(nodeCallLLM forwardTo nodeExecuteTool onToolCall { true })
    edge(nodeCallLLM forwardTo nodeFinish onAssistantMessage { true })
    edge(nodeExecuteTool forwardTo nodeSendToolResult)
    edge(nodeSendToolResult forwardTo nodeFinish onAssistantMessage { true })
    edge(nodeSendToolResult forwardTo nodeExecuteTool onToolCall { true })
}

Что здесь означает каждый узел

  • nodeLLMRequest — отправляет запрос в модель;

  • nodeExecuteTool — исполняет вызванный инструмент;

  • nodeLLMSendToolResult — возвращает результат инструмента обратно в модель;

  • nodeFinish — завершает работу агента.

Почему переходы соединены именно так

  • nodeStart -> nodeLLMRequest: выполнение всегда начинается с модели;

  • nodeLLMRequest -> nodeExecuteTool по onToolCall: если модель запросила инструмент, управление передается исполнителю;

  • nodeLLMRequest -> nodeFinish по onAssistantMessage: если пришел обычный ответ, работа завершается;

  • nodeExecuteTool -> nodeLLMSendToolResult: после исполнения инструмента его результат обязан вернуться в модель;

  • nodeLLMSendToolResult -> nodeExecuteTool по onToolCall: после получения результата модель может запросить следующий инструмент;

  • nodeLLMSendToolResult -> nodeFinish по onAssistantMessage: если после результата инструмента модель завершила рассуждение, агент заканчивает работу.

Как выглядит выполнение задачи в текущем агенте

Полный сценарий выполнения текущего агента на одной задаче выглядит так.

Пользователь формулирует запрос:

Добавь feature top-headlines для KMP News app

Агент проходит его так:

  1. модель получает запрос;

  2. оркестратор строит план через planKmpNewsApp;

  3. при необходимости фиксируется naming через rememberGenerationSymbol;

  4. последовательно вызываются шаблоны сетевого, доменного и презентационного слоев;

  5. после этого агент либо завершает ответ, либо продолжает следующий ход уже с опорой на детерминированные результаты предыдущих вызовов.

Короткая схема:

user request
 -> planKmpNewsApp
 -> rememberGenerationSymbol (optional)
 -> generateNetworkLayerTemplate
 -> generateDomainLayerTemplate
 -> generatePresentationLayerTemplate
 -> final answer

Подробный смысл каждого вызова в этой последовательности:

  1. planKmpNewsApp Возвращает определенный план реализации. Это первая обязательная точка, потому что без нее агент не фиксирует, какие слои должны появиться, какие компоненты обязательны и в каком порядке двигаться дальше.

  2. rememberGenerationSymbol Вызывается не всегда. Он нужен только тогда, когда в ходе сессии появилось имя, которое нужно закрепить и переиспользовать дальше без дрейфа.

  3. generateNetworkLayerTemplate Возвращает каркас сетевого слоя: NetworkConfig, схему подключения Ktor, базовые ожидания к API и общую форму кода в commonMain.

  4. generateDomainLayerTemplate Возвращает каркас доменного слоя: интерфейс репозитория и use case. Этот шаг отделяет транспорт и сериализацию от доменной модели и сценариев использования.

  5. generatePresentationLayerTemplate Возвращает каркас презентационного слоя: ViewModel, состояние экрана и общую форму Compose-экрана.

Именно поэтому оркестратор должен быть описан отдельно от layer tools. В этой последовательности он не «еще один инструмент», а управляющий уровень, который сначала определяет план и согласованность имен, а уже затем передает работу инструментам прикладных слоев.

Прикрепляем к Continue

Что ж, нам надо, чтобы инструментом можно было свободно пользоваться. Если мы будем запускать модуль агента, как есть, то пользоваться им можно будет только через cli. Превратим нашего агента в MCP-сервер. MCP (Model Context Protocol) — это протокол, через который внешний клиент вызывает инструменты и получает их результаты в стандартной форме. В этой архитектуре он нужен не для внутренней работы Koog-агента, а для публикации возможностей системы наружу. Установим библиотеку: https://github.com/modelcontextprotocol/kotlin-sdk

commonMain {
   dependencies {
       // Works as a common dependency as well as the platform one
       implementation("io.modelcontextprotocol:kotlin-sdk:$mcpVersion")
   }
}

Теперь займемся самим сервером:

 val server = Server(
        serverInfo = Implementation(name = "koog-kmp-planner", version = "1.0.0"),
        options = ServerOptions(
            capabilities = ServerCapabilities(tools = ServerCapabilities.Tools(listChanged = false)),
        ),
    )

Подключим нашего агента к модулю и добавим инструменты серверу:

// Пример подключения инструмента
server.addTool(
        name = "generate_presentation_layer_template",
        description = "ViewModel + Compose screen template.",
        inputSchema = ToolSchema(properties = buildJsonObject { }),
        toolAnnotations = ToolAnnotations(readOnlyHint = true, openWorldHint = true),
    ) {
        CallToolResult(content = listOf(TextContent(NewsAppTooling.presentationLayerTemplate())))
    }

Настроим наш сервер на запуск:

val transport = StdioServerTransport(
        inputStream = System.`in`.asInput(),
        outputStream = System.out.asSink().buffered(),
    )

    runBlocking {
        val session = server.createSession(transport)
        val done = Job()
        session.onClose { done.complete() }
        done.join()
    }

Упакуем в fat JAR:

tasks.build {
    dependsOn(tasks.shadowJar)
}

Теперь нам нужно создать скрипт для подключения MCP-сервера к Continue:

name: Local Agent
version: 1.0.0
schema: v1

models:
  - name: Qwen3-Coder 30b
    provider: ollama
    model: qwen3-coder:30b
    roles: [chat, edit, apply]

mcpServers:
  - name: koog-kmp-planner
    type: stdio
    command: <JAVA_HOME>
    args:
      - -jar
      - <путь к jar>
      - --stdio
    env:
      OLLAMA_BASE_URL: http://localhost:11434
      OLLAMA_MODEL: qwen3-coder:30b
      TARGET_PROJECT_ROOT: <корень проекта для поиска настроек>

Проверим, что инструменты нашлись:

Пример работы нашего генератора:

Теперь поговорим о проблемах. Результат зависит от той LLM, которую вы будете использовать. Будьте готовы к такому:

Или такому:

Генерация агента

Как я анонсировала, агент был сгенерирован с помощью Cursor и такого промта:

 . Раздели на отдельные инструменты, соедини их в стратегии. Мне нужны: 
- инструмент оркестратор для планировки
- агент для разработки
- отдельные инструменты для разработки слоев приложения.

В генерируемом приложении должен быть сетевой слой на Retrofit, архитектура MVVM + usecase, корутины,  UI на Compose. @Android SDK 

Агент должен быть упакован в jar. Mcp сервер подготовь отдельным таргетом. Создай сэмпл таргет для проверки работы агента.

Задавай вопросы прежде, чем сделать

На первый взгляд, все указано для создания. Но на деле, пришлось просить добавить MCP, создать конфигурацию под Continue и многое другое. Все зависит от вашей модели.

Вы можете отдельно настроить rules для вашего плагина, добавить команды и промты. Если сравнивать производительность и корректность работы плагина с собственным MCP и готового агента с настроенными навыками, то все индивидуально. Зависит как от ваших настроек и кода, так и модели, которую вы выбрали. Иногда действительно достаточно взять просто более мощную LLM, чтобы ваши труды оправдались.

Код решения ловите здесь До новых встреч)