Рабочий процесс локального агента LLM, созданный на основе папок и файлов Markdown
Создайте замкнутую многоэтапную систему работы с локальными агентами, при которой каждый этап представляет собой папку, промпты формируются в формате Markdown с возможностью редактирования, а человек проверяет результат перед выполнением дорогостоящего шага.
Источники: Build Your Own Local LLM Agent Workflow in 400 Lines of Python
Локальный агент может использовать воспроизводимый и проверяемый рабочий процесс без необходимости в каких-либо фреймворках. Здесь реализована многоэтапная задача, при которой каждый этап представляет собой папку, а инструкции для каждого этапа — обычный файл в формате Markdown, который можно прочитать и отредактировать; человек также проверяет результаты между этапами. Сам агент не меняется, изменяется лишь способ хранения его контекста, и в этом заключается суть.
Почему локальному агенту это необходимо
Агент уже умеет выполнять поиск в интернете, читать страницы и действовать через постоянно открытую вкладку браузера. Однако раньше не существовало способа повторно запускать задачи с возможностью проверки человеком на промежуточных этапах.
Причина заключалась в том, что всё, что управляло его действиями, находилось в коде. Один системный запрос формировался при импорте, а список инструментов создавался при запуске и передавался модели при каждом шаге. Чтобы заставить агента играть другую роль на определённом этапе, требовалась правка кода на Python. Чтобы остановиться и посмотреть промежуточный результат перед дорогостоящим шагом, места для остановки не существовало. Это приемлемо для чат-цикла, но неудобно для задач с несколькими этапами.
В итоге получается компактная схема для разработки курсов. Четыре этапа приводят от одного предложения, описывающего курс, до черновика мини-курса; после каждого этапа происходит проверка. Нумерованные папки с обычным форматом Markdown теперь хранят контекст, управляющий действиями агента, так что описание курса можно прочитать и исправить до начала работы над черновиком, при этом код не трогается.
Структура основана на методе под названием Interpretable Context Methodology, или ICM, который заменяет оркестрацию на уровне фреймворка структурой файловой системы.
На диаграмме рабочий процесс представлен как папка с названием workspace. Каждая пронумерованная папка внутри pipeline/ является этапом; файл CONTEXT.md в каждом таком этапе содержит соответствующее задание, а папка proposed/ служит временной зоной, где человек вручную утверждает изменения.
Пронумерованные папки выполняют роль этапов, файлы в формате Markdown хранят информацию о контексте каждого этапа, а локальные скрипты обрабатывают рутинные задачи, не требующие использования моделей. Дизайн вдохновлен Unix-пайплайнами и многопроходными компиляторами, где выход одного шага становится входом для следующего, а каждый промежуточный результат — это обычный файл, который можно открыть.
Что необходимо для работы
Для модели qwen3.5:9b в Ollama используется режим без выполнения логических операций и параметр температуры 0.1. Для просмотра веб-страниц и поиска применяются уже существующие решения: camofox-browser работает на порту 9500, а SearXNG — на порту 8090; оба запускаются в Docker. Перед началом необходимо запустить эти два контейнера вместе с Ollama и моделью, поддерживающей вызовы инструментов.
Процесс сборки делится на пять этапов:
- На первом этапе запускаются camofox-browser и SearXNG в Docker; локальные сервисы будут вызываться на последующих этапах.
- На втором этапе формируется системное сообщение агента на основе файлов в формате markdown, при этом заменяется одна жестко заданная строка.
- На третьем этапе предоставляется драйвер с применением механизмов проверки и ограничений на использование инструментов на каждом этапе.
- На четвертом этапе происходит проверка структуры плана: один этап использует результаты двух предыдущих этапов, после чего человек утверждает план перед началом дорогостоящей операции.
proposed/, предназначенная для последующих расширений.Установка
Каждый этап находится в своем собственном подкаталоге и представлен в виде скрипта консоли через pyproject.toml; поэтому после одной установки вы можете запустить любой этап по его имени. Первым шагом является клонирование репозитория и его установка в режиме редактирования с использованием виртуальной среды:
git clone https://github.com/jfjensen/local-LLM-agent-icm-workflow.git
cd local-LLM-agent-icm-workflow
python -m venv .venv
# Windows PowerShell:
.\.venv\Scripts\Activate.ps1
pip install -e .
В ходе установки добавляются mcp, ollama и httpx, а также регистрируется набор скриптов консоли. На этапах с 2 по 5 создаются соответственно запускачи рабочих процессов (mcp-workflow-stage2 до mcp-workflow-stage5) и агенты (mcp-agent-stage2 до mcp-agent-stage5). Два сервера MCP — mcp-search-part3 и mcp-browser-stage5 — копируются из предыдущих версий, что позволяет хранилищу оставаться самодостаточным.
Конфигурация находится в файле config.toml в корне хранилища, к которому добавляется новый раздел [workflow]. Поскольку каждый этап читает данные из папки workspace/ и записывает свою историю в папку history/, относительно текущей директории, наиболее правильным является запуск этапа изнутри его собственной папки:
cd stage5
mcp-workflow-stage5
Этап 1: локальные сервисы
Здесь не используется Python. На этом этапе запускаются два сервиса, которые агент использует во время поиска: camofox-browser — скрытый браузер, в котором работают инструменты для чтения, и SearXNG — поисковая система, отвечающая за операции в режиме search. Те, кто уже выполнил предыдущую настройку, могут оставить прежний файл docker-compose.yml.
cd stage1
docker compose up -d
docker compose logs -f
Есть несколько деталей, которые стоит знать:
- Camofox прослушивает порт 9500, а SearXNG — порт 8090, которые ожидает файл
config.toml, поэтому агент может обнаружить оба сервиса без дополнительной настройки. - Файл compose осуществляет привязку к
searxng/settings.yml, что включает JSON-API и отключает ограничение скорости, благодаря чему поиск работает с первой попытки. - Изображение Camofox собирается один раз из исходного репозитория. Первая сборка занимает много времени из-за большого размера бинарника Camoufox, а в файле
stage1/README.mdописаны соответствующие шаги.
Чтобы проверить работоспособность, используйте curl для запросов http://localhost:9500/health и http://localhost:8090/search?q=ollama&format=json. Оба запроса должны вернуть ответ в формате JSON.
На первом этапе речь идет только об этих двух сервисах. Как только Camofox начнет отвечать на порту 9500, а SearXNG — на порту 8090, у системы поиска появится к чему обращаться.
Этап 2: контекст из файлов
Это единственное реальное изменение в коде. Раньше агент использовал один системный промпт — строку на уровне модуля, сгенерированную при запуске и отправляемую без изменений на каждом шаге независимо от задачи. Позволение агенту выполнять разные роли на разных этапах означает, что промпт должен поступать из редактируемого источника, и чтение его из файлов является самым простым вариантом.
Таким образом, первое изменение позволяет переопределить системный промпт:
def build_messages_for_model(self) -> list[dict[str, Any]]:
system = self.system_text if self.system_text is not None else SYSTEM_PROMPT
return [{"role": "system", "content": system}] + self.messages
Поведение простое:
- Если параметр
system_textне установлен, агент продолжает использовать тот же зашитый в кодSYSTEM_PROMPT, что и раньше, поэтому интерактивный агентmcp-agent-stage2ведет себя точно так же, как и ранее. - Когда параметр
system_textустановлен, он становится системным промптом для текущей работы. Запускающий механизм рабочего процесса задает его для каждого этапа из файлов, при этом класс агента остается неизменным.
Что касается самих файлов, рабочее пространство ICM — это всего лишь папка. В stage2/workspace/ находится файл CLAUDE.md, описывающий рабочее пространство и его структуру, файл CONTEXT.md, в котором перечислены этапы в порядке их выполнения, каталог _config/ с материалами для справки, остающимися неизменными между запусками, а также каталог pipeline/ с пронумерованными этапами. У каждого каталога этапа есть собственный файл CONTEXT.md в качестве спецификации, плюс каталог output/.
Два верхнего уровня файлов определяют идентификатор и механизм навигации. Файл CLAUDE.md является уровнем 0 — первым элементом, который читает агент:
# Workspace: course-research
You are a single agent operating inside an ICM workspace. This file tells
you where you are and how the workspace is laid out. Read it first.
This workspace turns a one-line course idea into a drafted mini-course,
through four stages that run one at a time with a human review between each.
Layout:
- `CONTEXT.md` (next to this file): the workspace routing. It lists the
stages in order and the shared resources they draw on.
- `_config/`: shared reference material that stays the same across runs
(the audience profile, the writing voice). This is the factory.
- `pipeline/`: the numbered stages. Each stage folder holds a `CONTEXT.md`
contract, an `output/` folder for what that stage produces, and
sometimes a `references/` folder for stage-local reference material.
- `pipeline/proposed/`: a staging area, not a stage. A human moves
improvements from here into `_config/` once they are happy with them.
You only ever work on one stage at a time. The stage's own `CONTEXT.md`
tells you exactly what to read, what to do, and what to write. Do not read
files the stage contract does not list.
Рядом с ним CONTEXT.md представляет собой уровень 1 — маршрутизацию, в которой перечислены этапы:
# Routing
The course-research pipeline runs in four stages, in this order:
1. `01-scope`: turn the one-line course idea and the audience profile into
a short list of scoped learning objectives.
2. `02-research`: for each objective, search the web and read sources, and
write structured notes with links. This is the only stage that uses
tools.
3. `03-outline`: turn the objectives and the notes into a module-by-module
course outline. This is the gate before the expensive stage, so the
outline is worth a careful human read.
4. `04-draft`: draft each module from the approved outline and the notes.
Each stage reads from the stage before it (its `output/` folder) and
writes to its own `output/` folder. A human reviews each output before the
next stage runs.
Shared resources (in `_config/`, the same every run):
- `audience.md`: who the course is for. Every stage should respect it.
- `voice.md`: how the course should read. The draft stage follows it.
Общий материал для справки находится в папке _config/; он настраивается один раз и используется при каждой работе. Ниже приведен файл audience.md — это файл уровня 3, который читают на этапах определения объема работы, исследований и составления плана:
# Audience
The course is for working software developers who are comfortable with
Python and the command line, but who have not run a language model on
their own machine before.
They want practical, hands-on knowledge they can apply the same day, not
a survey of the field. Assume they can install software, edit a config
file, and read a stack trace. Do not assume they know any machine-learning
theory, model formats, or GPU terminology.
Keep examples concrete and runnable. Prefer one worked example over three
abstract options. When a trade-off matters, state it plainly and give a
default.
Контракт 01-scope/CONTEXT.md является основой дизайна. Это полная версия для первого этапа пайплайна:
# Stage: 01-scope
## Inputs
- Layer 4 (working): input.md
- Layer 3 (reference): ../../_config/audience.md
## Tools
none
## Process
Read the one-line course idea and the audience profile. Produce a short
list of scoped learning objectives for the course, four to six of them.
Each objective should be one sentence, start with a verb, and describe
something the learner will be able to do by the end. Keep the scope tight:
this is a short course, so leave out anything that does not fit a learner
going from zero to a working local model. Do not pad the list to hit a
number. Fewer, sharper objectives are better.
Write the objectives as a numbered markdown list, nothing else.
## Outputs
- objectives.md -> output/
Единственный входной файл уровня 4, 01-scope/input.md, является рабочим материалом для выполнения задания — однострочной идеей, которая превращается в курс:
# Course idea
A short, practical course that takes a developer from zero to running a
local large language model on their own machine with Ollama, and calling
it from a small Python script.
Некоторые примечания о том, как читается контракт:
- В таблице «Входные данные» перечислены конкретные файлы, которые читает этап, причем каждый из них помечен уровнем: уровень 3 — для референтных материалов, стабильных при всех запусках (профиль аудитории), уровень 4 — для рабочих материалов текущего запуска (идея в одной строке). В рабочем пространстве загружается ничего больше.
- В поле «Инструменты» указано
none, поскольку для преобразования идеи в цели инструменты не требуются. На этом этап 3 возвращается к исходной точке. - В разделе «Процесс» указаны инструкции, которых следует агент, и они становятся очередью действий пользователя.
- В поле «Выходные данные» указано название файла, который этап записывает в свой собственный папку
output/.
Согласно контракту, загрузчик преобразует таблицу входных данных в реальное содержимое файла и формирует на его основе контекст. Референсные данные и рабочий материал остаются разными, поскольку выполняют разные функции: первые представляют собой набор правил, которым должен следовать модель, а вторые — сырые данные, которые необходимо преобразовать.
# System text: identity + routing + the reference material (Layer 3).
parts = []
if layer0:
parts.append(layer0.strip())
if layer1:
parts.append(layer1.strip())
refs3 = [r for r in inputs if r.layer == 3 and r.exists]
if refs3:
block = ["# Reference material (follow these as constraints)"]
for r in refs3:
block.append(f"\n## {r.rel_path}\n\n{r.text.strip()}")
parts.append("\n".join(block))
system_text = "\n\n".join(parts).strip()
Поведение загрузчика можно описать следующим образом:
- Сначала идут информация об идентичности и маршрутизации (
CLAUDE.md,CONTEXT.md), которые указывают агенту, где он находится и какие существуют этапы. - Затем следуют файлы-ссылки третьего уровня под заголовком, показывающим их как ограничения. Рабочие файлы четвертого уровня собираются отдельно, ниже текста о процессе данного этапа, как действия, которые должен выполнить агент.
- Результатом является системное сообщение для этапа, сформированное из обычных файлов без добавления чего-либо, что не было запрошено этапом.
Прежде чем запускать что-либо на модели, стоит проверить результат работы загрузчика, поскольку эта часть является детерминированной. В репозитории существует небольшой инструмент probe_loader.py, который выводит собранный контекст для этапа вместе с приблизительным количеством токенов и не вызывает модель. Запустите его из папки stage2 для первого этапа:
cd .\stage2\
python ..\probe_loader.py 01-scope
На скриншоте показаны первые строки отчета зондов для этапа с ограниченным контекстом вместе с размером окна контекста. На этом этапе получается примерно 900 токенов, каждый из которых имеет значение, что делает идею ICM понятной: небольшое окно контекста на каждом этапе и отсутствие одного огромного запроса, содержащего всё.
Чтобы запустить этот этап на самом деле, используйте mcp-workflow-stage2. Он парсит контракт, запускает агента один раз и создает файл output/objectives.md. Для примера был разработан краткий практический курс по запуску локальной большой языковой модели с помощью Ollama; в результате получилось пять четко сформулированных целей, которые человек может просмотреть, а затем сохранить или отклонить.
На баннере на скриншоте показан контекст, предсказанный зондом, объемом около 900 токенов, а результатом этапа является файл objectives.md. Цели после одного запуска приведены ниже; при втором запуске они могут быть сформулированы немного иначе:
1. Install the necessary software stack (Ollama) and verify it is successfully pulling and serving a local LLM like `llama3`.
2. Configure environment variables to securely expose the local model's API endpoint for external access.
3. Write a Python script using the `requests` library to send prompts to Ollama and parse JSON responses into readable text.
4. Implement error handling in your script to gracefully manage connection timeouts or out-of-memory errors common with local inference.
5. Build an interactive loop that allows you to chat with the model directly from a terminal-based Python application without using a GUI framework.
Теперь, когда контекст поступает из файлов, остаётся лишь выполнить этапы в определённом порядке и остановиться между ними.
Этап 3: драйвер, шлюзы и определение функционала инструментов
Рабочий процесс представляет собой последовательность этапов с человеческим контрольным пунктом между ними. Поэтому драйвер сканирует пронумерованные папки, запускает первый этап с пустым результатом и останавливается.
async def run_pipeline(workspace_dir: Path, run_all: bool = False) -> None:
stages = stage_dirs(workspace_dir)
for sd in stages:
if stage_is_done(sd):
print(f"[skip] {sd.name}: output already present")
continue
await run_stage(workspace_dir, sd.name)
if run_all:
continue
# The review gate. Stop here so the human can read and edit the
# output before the next stage reads it.
print("\n--- review gate ---")
print(f"Review and edit if needed:\n {sd / 'output'}")
return
Как это работает:
- Состояние хранится на диске. Этап считается завершенным, когда в его папке
output/появляется реальный файл, поэтому отдельного хранилища состояния не требуется. Сам файловая система и является хранилищем состояния. - «Ворота» служат лишь для досрочного возврата. Как только этап завершается, драйвер сообщает, где искать результат, и останавливается. Вы читаете полученный результат, при необходимости его изменяете и снова запускаете ту же команду, чтобы продолжить работу.
--all— это способ пройти весь процесс без остановок, когда вы доверяете результату. По умолчанию используется режим с «воротами», поскольку именно они являются ключевой частью системы.
Чтобы запустить конвейер, используйте mcp-workflow-stage3, прочитайте то, что он записал, затем запустите его снова, чтобы двигаться дальше. Первый запуск обрабатывает 01-scope и останавливается; второй — этап исследований, также останавливается, и так далее.
Каждое вызов обрабатывает один этап, сообщает, где искать, и останавливается на этапе проверки; запуск той же команды возобновляет его.
Определение объема работы инструментов
Исследование — единственный этап, в котором используются инструменты, и именно здесь возникли первые серьезные проблемы. Агент подключается к серверам поиска и браузера, а в более ранней версии все инструменты с каждого сервера объединялись в один плоский список, который предоставлялся при каждом запуске. В этом списке было примерно пятнадцать инструментов, из которых для исследования требовалось около пяти. Остальные — это инструменты для работы с вкладками и действиями на странице, которые исследование никогда не использует.
Поскольку были предоставлены все пятнадцать инструментов, модель 9B переключилась на интерактивные инструменты. Она выбрала open_tab и read_tab вместо функций чтения, причем read_tab возвращает только верхнюю часть страницы. Документация Ollama размещена на Mintlify, поэтому эта верхняя часть одинакова везде: тот же текст навигации. Модель продолжала читать этот меню, открывая одну вкладку за другой, пока лимит количества вызовов не прервал её работу, выведя в качестве результата извинения длиной 143 символа.
Решением является ограничение использования инструментов каждой стадией. В контракте указываются необходимые инструменты, и во время выполнения доступны только они. Вот полный контракт исследования:
# Stage: 02-research
## Inputs
- Layer 4 (working): ../01-scope/output/objectives.md
- Layer 3 (reference): ../../_config/audience.md
- Layer 3 (reference): references/tool-use.md
## Tools
search-server: search
browser-server: summarize, extract, fetch_snippet, fetch_urls
## Process
For each learning objective, find and read one or two good current sources,
then write short, factual notes the outline and draft stages can rely on.
Work objective by objective. For each one: run a web search, then read the
most promising result with `summarize` or `extract`. Use `summarize(url,
question=...)` for an open question about a page, and `extract(url, schema)`
when you can name the fields you want (a version number, a default port).
These read the whole page; do not try to page through a site by hand. Read
each page at most once and do not re-read a page you have already read. Once
you have one or two sources per objective, stop calling tools and write the
notes. Pull out the concrete facts that matter for teaching this objective
(commands, version notes, default ports, common pitfalls). Prefer official
documentation and project sources over blog aggregators.
Write the notes as markdown, grouped under a heading per objective. Under
each heading, a few bullet points of facts, then a "Sources:" line with
the URLs you actually read. Keep it factual. Do not draft course prose here;
that happens later.
## Outputs
- notes.md -> output/
Новые названия раздела Инструменты — поиск на сервере поиска, а также всего четыре инструмента с сервера браузера, причем все они являются читателями. Во время выполнения программа применяет это в качестве фильтра к списку инструментов ещё до того, как модель получит его:
def rebuild_ollama_tools(self, allowed: dict[str, set[str] | None] | None = None):
out = []
self._tool_to_server = {}
for server_name, tools in self.mcp_tools_by_server.items():
allow_set = allowed.get(server_name) if allowed else None
for tool in tools:
if allow_set is not None and tool.name not in allow_set:
continue
prefixed = f"{server_name}_{tool.name}"
self._tool_to_server[prefixed] = server_name
out.append({
"type": "function",
"function": {
"name": prefixed,
"description": tool.description or "",
"parameters": tool.inputSchema,
},
})
self.ollama_tools = out
Фильтр работает следующим образом:
- Карта разрешений определяется для каждого сервера. Значение
Noneозначает, что разрешены все инструменты сервера, а набор имен — только те из них. - Любой инструмент, не входящий в список разрешенных, игнорируется, поэтому модель его не видит. В настоящее время исследования учитывают пять инструментов, в то время как функции scope, outline и draft не видят никаких инструментов.
- Карта пересоздается поэтапно, поэтому скрытый инструмент также не может быть вызван; вызов
open_tab, возникший в результате галлюцинации, просто будет считаться неизвестным инструментом.
Контракт на исследования также включает собственный справочник уровня 3 — references/tool-use.md, предназначенный исключительно для данной стадии, в котором объясняется, как использовать оставшиеся средства чтения:
# Tool use (research stage only)
You have two MCP servers for this stage. Use them like this.
- `search-server_search(query, max_results)`: a web search. Start here for
any lookup. It returns URLs with titles and snippets. After a search,
your next action must be a browser-server call on the most promising URL.
Do not stop after a search and do not answer from the snippets alone.
The browser-server reader tools each return a small slice of a page, not
the raw page:
- `browser-server_fetch_snippet(url)`: the head of a page, for a quick look.
- `browser-server_fetch_urls(url)`: the page's links, to decide what to read next.
- `browser-server_fetch_structure(url)`: the page's heading outline.
- `browser-server_extract(url, schema)`: pull named fields you can list in
advance (a version number, a default port) using a JSON Schema.
- `browser-server_summarize(url, question="")`: a prose summary of a whole
page; pass `question` to focus it on what this objective needs.
Prefer `extract` when you know the field names, `summarize` for an open
question about a long page, and `fetch_snippet` for a quick confirmation.
Prefer official documentation and project sources over blog aggregators.
Снова файловая система выполняет роль фреймворка. В фреймворке набор инструментов агента — это код; здесь же это просто одна строка в формате Markdown, которую может изменить любой. При использовании режима чтения без вкладок модели summarize и extract, которые читают целые страницы с помощью разбитого на фрагменты читателя, а также примечаний, содержали реальные результаты: команда установки, стандартный порт 11434 и инструкции по использованию библиотеки Python, каждый из которых сопровождался ссылкой на источник.
Когда модель не может остановиться
Вторая проблема была более тонкой. Обладая подходящими инструментами, модель читала качественные страницы, но так и не понимала, когда следует заканчивать. В одном из запусков та же страница подвергалась обобщению с одним и тем же вопросом более десяти раз, без какого-либо прогресса до достижения лимита количества вызовов. Полезные результаты хранились в истории сообщений, но поскольку окончательного ответа не было написано, выводом снова стало извинение.
Чтобы с этим справиться, при прерывании процесса обработки каждый раз выполняется дополнительный шаг: последний запрос без использования инструментов, при котором модели указывают написать ответ на основе собранной ранее информации.
async def _finalize(self, stop_reason: str) -> dict:
if not self.synthesize_on_stop:
self.messages.append({"role": "assistant", "content": stop_reason})
return {"role": "assistant", "content": stop_reason}
self.messages.append({
"role": "user",
"content": ("Stop using tools now. Using only the information you have "
"already gathered above, write the final output the task "
"asked for. Do not call any tools."),
})
resp = ollama.chat(model=MODEL_NAME, messages=self.build_messages_for_model(),
options={"temperature": MODEL_TEMPERATURE}, think=MODEL_THINKING)
msg = resp["message"]
content = msg.get("content", "") if isinstance(msg, dict) else getattr(msg, "content", "")
final = {"role": "assistant", "content": content or stop_reason}
self.messages.append(final)
return final
Важны три момента:
- Это добровольный вариант. Интерактивный агент оставляет параметр
synthesize_on_stopвыключенным и просто сообщает о прекращении работы, как раньше, в то время как рабочий процесс его включает, чтобы на каждом этапе всегда формировался реальный результат. - Это помогает сохранить результаты работы. Результаты использования инструментов уже хранятся в истории, поэтому вызов без использования инструментов может записать из них примечания, превращая неуправляемый процесс в пригодный для использования файл
notes.mdвместо извинений. - Защита от застревания в цикле тоже полезна. Теперь один и тот же вызов, выполненный три раза подряд, прерывает процесс заранее, независимо от наличия ошибок, так что модель тратит меньше времени до того, как возьмет управление на себя процесс синтеза.
Чистая процедура исследования заняла около семи минут и принесла чуть более 10 000 символов примечаний, основанных на источниках, для пяти поставленных целей.
На скриншоте показано, как второй запуск инструмента завершается и требуется проверка результата. Благодаря наличию этапов контроля, определения объема работ и анализа результатов исследование становится достаточно надежным для дальнейшей работы, причем следующий этап имеет наибольшее значение.
Этап 4: этап составления плана
Из всех этапов именно составление плана требует наибольшего внимания, поскольку за ним следует самый дорогостоящий этап — разработка проекта. Ошибка, обнаруженная на этапе планирования, легче всего исправить. На этом этапе два предыдущих результата — цели из 01-scope и записи исследований из 02-research — объединяются в план, структурированный по модулям. В контракте сказано:
# Stage: 03-outline
## Inputs
- Layer 4 (working): ../01-scope/output/objectives.md
- Layer 4 (working): ../02-research/output/notes.md
- Layer 3 (reference): ../../_config/audience.md
## Tools
none
## Process
Turn the objectives and the research notes into a module-by-module course
outline. One module per objective is a reasonable default, but merge or
split where the notes suggest it.
For each module, give a short title and three to five bullet points naming
what it covers, drawn from the notes. Order the modules so each one builds
on the one before it. Note any module that depends on something from an
earlier module.
This outline is the last checkpoint before the course is drafted, so make
it easy for a human to read and correct. Write it as markdown headings with
bullet points. Do not draft the module text yet.
## Outputs
- outline.md -> output/
Эти две строки уровня 4 — новшество. Они делают конкретной идею многократной передачи данных из статьи ICM, поскольку выход одного этапа подается на следующий, а данный этап объединяет результаты двух предыдущих этапов. Загрузчик берет оба результата в качестве рабочего материала, а модель формирует общую структуру.
Запустите его с помощью mcp-workflow-stage4. Поскольку этапы планирования и исследований завершены, программа выполняет команду 03-outline, создает файл outline.md и останавливается на контрольном пункте.
Запуск mcp-workflow-stage4 приводит к формированию общей структуры и остановке на контрольном пункте — последнем удобном моменте для корректировки плана перед началом работы над проектом.
Этот инструмент сразу показал свою ценность. Во время одной из процедур сбора данных было получено слишком много информации. Курс предназначен для тех, кто начинает с нуля и стремится создать рабочую модель, однако примечания затронули такие темы, как ключи API облачных сервисов и квантизация KV-кэша — информацию, которую модель нашла в документации и верно записала. В результате структура курса унаследовала этот материал и получила целый модуль под названием «Защита вашего соединения с помощью переменных окружения», полный информации, не нужной начинающим.
В рабочем процессе здесь не возникло никаких проблем. Избыточная информация проявилась в виде читаемого заголовка в обычном файле ещё до того, как был написан любой текст курса. Достаточно удалить этот модуль, изменить его уроки или перенаправить их в другое место, и при подготовке курса на него больше не уделяется внимания. Для исправления достаточно сделать одну правку в файле Markdown, тогда как после завершения подготовки курса потребуется перечитать пять модулей и множество уроков, чтобы найти тот, который не должен там находиться.
Пример структуры доступен для редактирования в VS Code на этапе проверки, где модули или уроки, не входящие в рамки проекта, удаляются ещё до того, как они попадут на этап разработки черновика.
На этапе проверки сократите структуру до необходимых модулей и уроков, затем запустите процесс заново. Количество модулей определяется просто количеством заголовков ## Module в файле, поэтому изменение структуры также влияет на длительность курса.
Этап 5: разработка черновика по модулям
На этапе разработки черновика каждый модуль создаётся на основе утверждённой структуры, при этом факты берутся из заметок. Первая попытка предусматривала создание всего курса за один проход, но она оказалась неудачной: в некоторых случаях формировалось четыре модуля, в других — один, после чего считалось, что задача выполнена. Небольшая модель рассматривает задачу «написать весь курс» как одну огромную задачу и склонна прекращать работу рано.
Поэтому на этапе разработки черновика процесс выполняется по модулям. В его полной схеме присутствует инструкция For each, которая определяет цикл:
# Stage: 04-draft
## Inputs
- Layer 4 (working): ../02-research/output/notes.md
- Layer 3 (reference): ../../_config/voice.md
- Layer 3 (reference): ../../_config/audience.md
## Tools
none
## For each
module in ../03-outline/output/outline.md
## Process
Draft the one module shown below, and only that module. Use the research
notes for the facts (commands, ports, pitfalls) rather than inventing them,
and follow the voice and audience guides. Write a few short paragraphs of
teaching prose plus any commands the learner should run. Do not write the
other modules. Start your output with the module's heading, and output only
the drafted module, nothing else.
This is a first draft for a human to revise, so aim for a clear, correct
scaffold rather than a finished, polished module.
## Outputs
- draft.md -> output/
На этапе разработки читается файл voice.md — заключительный файл уровня 3 в рабочем пространстве, который определяет тон курса:
# Voice
Write plainly, as one developer explaining something to another at the
next desk. Short instructional sentences, followed by a longer one that
explains why.
- Lead with what to do, then explain the reasoning.
- Use concrete names: real tools, real commands, real file paths.
- Hedge honestly when something is a judgement call or can fail.
- No marketing language. No hype. No "unlock", "seamless", "robust".
- One idea per paragraph. Bullets only for listing the parts of a thing.
Строка For each преобразует один неограниченный цикл в цикл с конкретным количеством итераций, причем движок выполнения запускает процесс один раз на каждый модуль:
fe = ctx.for_each
print(f" for each: {len(fe.modules)} {fe.item}(s) from {fe.rel_path}")
pieces = []
for i, (heading, body) in enumerate(fe.modules, 1):
print(f" [{i}/{len(fe.modules)}] {heading}")
agent.messages = []
item_block = (f"# The {fe.item} to write now\n\n"
f"## {heading}\n\n{body}")
agent.messages.append({"role": "user", "content": ctx.user_text + "\n\n" + item_block})
final = await agent.run_turn()
pieces.append((final.get("content", "") or "").strip())
content = "\n\n".join(p for p in pieces if p)
Что делает цикл:
- Структура документа делится на модули по заголовкам
##, и цикл выполняется по одному разу для каждого из них. Количество модулей не фиксировано — четыре модуля означают четыре прохода, а шесть — шесть, в зависимости от того, сколько модулей прошли проверку. - Каждый вызов начинается с чистого листа. Переменная
agent.messagesочищается каждый раз, поэтому модуль формируется на основе имеющихся заметок без учета информации из других модулей, что позволяет сохранять каждый этап простым и самодостаточным. - Результаты объединяются в один файл
draft.md. Ограничение количества вызовов на один модуль вместо одного общего запроса на «написание курса» позволяет модели 9B обработать все модули.
Эта директива является общей и не предусматривает особых правил для формирования проекта. На любом этапе можно использовать For each, хотя на трех остальных этапах такой директивы нет, и они выполняются за один ход. При запуске mcp-workflow-stage5 отображается прогресс от [1/5] до [5/5], причем в примере запуска было сгенерировано чуть более 15 000 символов в пяти модулях.
На этапе разработки проекта выполняется цикл по структуре с одним вызовом модели на каждый модуль. Количество вызовов соответствует количеству оставшихся модулей, поэтому четыре или шесть модулей означают четыре или шесть запусков.
В рабочем пространстве также находится папка proposed/, которая никогда не используется в процессе обработки. Это временное хранилище: когда в процессе выполнения предлагается улучшение, которое должно сохраниться дольше его срока действия — например, более точная цель или правило обработки голоса, которое постоянно нарушается — предложение сохраняется там в виде файла, и человек перемещает его в папку _config/, если соглашается с ним. Здесь важна роль человека. Ничто не изменяет рабочее пространство, пока кто-то вручную не одобрит изменение, что обеспечивает проверку, как это предусмотрено ICM, и даёт возможность последующей автоматизации вступить в действие.
Что работает, а что — нет
Модель размером 9B, создающая модули, формирует пригодную структуру, но не готовый учебный материал. Преимущество заключается в рабочем процессе и его интерпретируемости, тогда как автономная работа локальной модели над курсом не является целью.
Существуют реальные недостатки, замечаемые в процессах выполнения:
- В черновике была создана неисправная единица systemd, в которой строки
[Unit]и[Service]слились в одну, а переменные окружения были вставлены в полеExecStart. - Появилась строка с версией, которой не было нигде в примечаниях, а названия моделей менялись в разных модулях — от
gemma2доgemma3иgemma3:2b. - Поскольку каждый модуль разрабатывался независимо и без учета остальных, модули 1 и 2 значительно перекрывались по функциям, каждый из них затрагивая вопросы переменных окружения и системы systemd.
- Процесс обработки информации занимает примерно семь минут, поскольку каждая страница проходит через модуль суммаризации; одни только вопросы и ответы Ollama заняли 41 000 символов в восьми частях.
- Одним из источников информации была защита от ботов от Cloudflare. Читатель правильно отметил, что она не содержала реального контента, поэтому модель продолжила работу, не выдумывая ничего нового.
Ничто из этого не превращает агента в детерминистическую систему, и тот, кому она нужна, должен написать собственный скрипт. Преимущество заключается в том, что настройки хранятся в файлах, которые человек может прочитать и скорректировать между этапами, так что первоначальный вариант может быть исправлен на определённом этапе, и позже не станет неожиданным сюрпризом. Пункт контракта о «четырёх–шести целях» служит лишь намеком для модели до тех пор, пока файл не будет отредактирован, а после этого становится точным фактом. Разница между тем, что склонна делать модель, и тем, что устанавливает человек, — вот почему существуют эти этапы.
Всё вместе
В итоге для каждой пайплайны существует отдельная папка рабочего пространства рядом с агентом; в ней находится файл CONTEXT.md с описанием параметров каждого этапа, а также папка output/ для хранения результатов. Агент остается тем же многосерверным агентом, что и раньше, а движок его работы — включая загрузчик, механизм управления выполнением, определение набора инструментов и цикл обработки каждого модуля — помещается в один файл объемом около 400 строк.
Кратко о составляющих:
- Рабочее пространство, в котором контекст каждого этапа хранится в пронумерованных папках в формате обычного Markdown; его можно редактировать без изменения кода.
- Загрузчик, который формирует системный запрос для каждого этапа на основе файлов, разделяя ссылки и рабочие материалы, при этом объем текста в окне обработки составляет примерно от 900 до 1 500 токенов на этап.
- Драйвер с механизмами управления, выполняющий один этап, ожидающий действия пользователя и возобновляющий работу, при этом его состояние сохраняется на диске.
То, что рабочее пространство представляет собой просто папку, означает, что для другой задачи требуется лишь другая папка. В репозитории имеется еще одна папка — workspace-rust — для курса по Rust для начинающих. Она копирует первую папку, отличаясь лишь двумя файлами: профилем аудитории и однострочным описанием задачи, в то время как все контракты этапов, файлы маршрутизации и справочники по использованию инструментов остаются полностью идентичными. Выполнение команды mcp-workflow-stage5 --workspace workspace-rust позволяет получить курс по Rust из того же потока обработки. Это и есть концепция ICM по настройке «фабрики», а не самого продукта.
Эти два рабочих пространства отличаются только файлом audience.md и однострочным описанием сути: один и тот же процесс, но разные курсы.
Есть возможности для расширения. Раздел Verify в контракте этапа мог бы заставить его перечитать предыдущий результат и выявить отклонения ещё до того, как к ним присмотрится человек; это описано в статье ICM как межэтапная верификация. Однострочное резюме предыдущих модулей могло бы использоваться при каждом вызове для составления черновика, сокращая дублирование между модулями 1 и 2. А папка proposed/ могла бы автоматически заполняться новыми элементами.
Зачем создавать эту легкую версию вручную, когда существуют CrewAI и LangGraph? Это хорошие инструменты, и только те рабочие процессы, которые требуют одновременной работы агентов, динамического разветвления или реальной развертки, оправдывают их использование. Однако многие задачи имеют последовательную структуру, поддаются проверке и могут выполняться повторно, при этом в них нет необходимости в таких функциях. Для таких случаев на любом компьютере уже существует простейшее решение — папка с файлами. Создание такой структуры вручную также показывает, где более сложные инструменты начинают приносить пользу.