Галоўная / Артыкулы

Лакальны агентны працэс на LLM з тэчак і Markdown-файлаў

Будуем шматэтапны лакальны канвеер агентаў з кантрольнымі кропкамі: кожны этап — тэчка, промпты — рэдагаваныя Markdown-файлы, а чалавек правярае вынік перад дарагім крокам.

Выдавец: REACTAPP.TOP25 хв чытання5861 слоў

Крыніцы: 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, пры чым заменяецца адна жорсткая закодаваная строчка.
  • На трэцім этапе задаецца драйвер, пры чым на кожны этап прыменяюцца правіла перагляду і ліміты інструментаў.
  • На чэтвёртым этапе адбываецца перагляд плану: адны элемент плану ствараецца на адной з пяці пасляўшых стадыяў на адной з двух пярэдніх выходных даных, а чалавек затым затварае план перад запускам дорогіх крокаў.
  • У кожнай модулі стварыяяся чэрнавікі для 5 стадзіяў, а таксама дадаецца папка 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 выкарыстоўвае метод bind-mount для searxng/settings.yml, што увімкнвае JSON API і выключае лімітар частоты запытоў, таму пошук працюе вядома ў першы раз.
    • Зображэнне Camofox ствараецца аднойчы з яго вялікага рэпазітарыю. Першая наладка вядома медленная, таму што бінарны файл Camoufox вялік, а stage1/README.md пераказвае паследовасць дзеянняў.

    Ёсць можна пераканацца ў стане сістэмы, запытавшыся http://localhost:9500/health і http://localhost:8090/search?q=ollama&format=json за дапамогою curl. Оба запыты должны даць адказ у фармаце 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.
    

    Калькілія працы з дагаворам:

    • У табліцы «Вхідныя даны» пераказаны точныя файлы, якія чытае практыка, і кожны з іх пазначаецца шырокам: Layer 3 — для матэрыялу-саюзніку, стабільнам у всіх запусках (профіль аўдытарыя), Layer 4 — для матэрыялу, які вікорыстоўваецца у даным запуску (ідэя ў адной лініі). Нічога іншага з прастору працы не завантажваецца.
    • У ступені «Інструменты» паказана значэнне none, таму што для ператварэння ідэі ў цялевыя пункты не трэба інструментоў. Практыка 3 вяртаецца да гэтага.
    • У роздзеле «Працэс» знаходзяцца інструкцыя, якую выконвае агент, і гэта стае наступным крокам для карыстальніка.
    • У ступені «Выходныя даны» паказана назва файла, які практыка запісвае ў свойяй папцы output/.

    За дапамою кантракта загрузчык ператварае табліцу Input у рэальны змест файлу і стварае на ўснове яго кантэкст. Кансультатывныя матэрыялы і матэрыялы для работы застаюцца разнымі, адколькі воны выпалююць разныя ролі: першыя — гэта набор правіл, якімі мусi следаваць мадэль, а другія — гэта сырые даныя, якія ёй трэба ператварыць.

    # 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. Для прыкладнай ідэі, практычнага короткага курсу па запуску локальнага LLM з 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/
    

    Новыя названні раздзела Tools — search на серверэ пошуку, а таксама толькі чатыры інструмента з сервера прыгледчыка, усе яны ў фармаце чытальнікаў. Час выканання прымае гэта як фільтр для спісу інструментаў прытаму, перш чым модэль яго отрымае:

    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/, яюяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяя

    • У працэвыканні адвачна была створана несправная едыніца systemd, у якой лініі [Unit] і [Service] злучыліся ў адну, а зменныя сераўіса былі падставлены ў ExecStart.
    • З’явілася строчка з версіяй, якой няма нідзе ў прымітках, а назвы модэляў зменяліся ў разных модулях – gemma2, gemma3 і gemma3:2b.
    • Кожны модуль быў напісан незалежна і не ведаў пра іншыя, таму модулі 1 і 2 значна перакрываліся, кожны з яных адпавядаў за зменныя сераўіса і systemd.
    • Аналіз трывае прыбліжна семь хвілін, адколі кожная стораніца праходзіць праз апарат для падсумоввання; сама FAQ даскіглы Ollama зайняла 41 000 сімваў у восьмі частках.
    • Аднам з джэраў была станова Cloudflare для пераканання ботаў. Чытальнік правільна зазначыў, што ў яй няма рэальнага кантэнту, таму модэль працаваў далей, не выдумваючы нічога.

    Нічыя з гэтых прычын не ператварае агента ў дэтэрміністычны пайплайн, і кожны, хто яго патрэбуе, павінен напісаць скрыпт. Адзінныя прыямы — гэта тое, што інструкціі знаходзяцца ў файлах, якія чалавек чытае і корэгуе между стадыямі, таму первыя версіі можна пасправіць на певным этапе, і ўпарацоўкі не будуць выявлены пазней неспадзевана. Пункт «чатыры-шасць цэлей» у контракце ўсьмохваляе толькі мадэль, пакуль файл не будзе адрэдагаваны, а пасля — стае фактам. Разлік между тым, каго належыць робіць модэлю, і тым, што чалавек узгадвае, — гэта сама прычына існавання такіх этапаў.

    Усё разам

    У канцэ ёсць по аднам папкам рабочага прастору на кожную пайплайн, ляжачымым радом з агентам, пры чым для кожнага этапу існуець файл CONTEXT.md, а папка output/ выконвае функцыю зберагча рэзультатаў. Агент застаецца тым жа багатасерверным агентам, як і раней, а сістэма запуску, якая яго керуе — тыя-то ладчык, драйвер з кантролем, апранаванне інструментаў і цыкл на кожны модуль — умішчаецца ў аднам файле прыблізна з 400 лініямі.

    У канцэ, па суты:

    • Рабочы прастор, дзе кантэкст кожнага этапу знаходзіцца ў папках з номерамі, напісанымі простым маркдаунам; іх можна рэдагаваць без змены коду.
    • Ладчык, які стварае сістэмны запрос кожнага этапа з файлоў, раздзеляючы кансультатывны матэрыял і робочы матэрыял, у вікні прыблізна з 900 да 1 500 токенав на этап.
    • Драйвер з кантролем, які выконвае адзін этап, на час зупіняеся для чалавека і продовжывае работу, захоўвая свой стан на дыску.
  • Адаптаванне інструменту па этапах, пры чым кожны этап обмежваецца там, што пераказана ў яго контракце.
  • Альтэрнатыўны варыант, які запісвае адпаведны адказ на аднойшаныя рэзультаты, калі мадэль продовжае доследжэння, тады выходны даныя ніколі не будуць порожнімі.
  • Разрабатаванне па модулях, пры чым колькісць модулей падабяжаеся да колькасці заглав’ёў у затверджанай структуры.
  • Тое, што рабочая зона — гэта проста папка, значыць, для іншай задачы патрэбна толькі іншая папка. У рэпазітарыі ёсць яшчо адна папка, workspace-rust, для курсу па Rust для пачатківаўцоў. Яна копіюе першую папку і разлічыцца толькі двума файламі: праблематыкай аўдытарыі і однай-лінейным опісам ідэі, тады як усе контракты этапаў, файлы маршрутацыі і справакі па вжыванню інструменту застаюцца абсалютна ідэнтычнымі. Выкананне команды mcp-workflow-stage5 --workspace workspace-rust дае курс па Rust з той самай лініі апрацоўкі. Гэта і ёсць панявленне ICM пра налаштаванні фабрыкі, а не самага продукту.

    Два прасторы для работы разлічаюцца толькі за файлам audience.md і аідэяй у аднай лініі: той самы практычны план, але разны курс.

    Ёсць можлівасць расшырыць гэта. Раздзел Verify у кантракце этапы мог бы змусіць стадыю перачытаць паказаны ранейшы рэзультат і пазначыць адхылэнні прычыну, прычыму людзі не павінны гэта робіць; гэта ў дакладзе ICM апісваецца як межстадыйная перакананне. Адналінейныя падсумкі пакульшых модуляў моглі б суплявацца ў кожны праект, зменшычы перакрыцце між Модулямі 1 і 2. А папка proposed/ могла бы стаць чымось, што сама заполняецца.

    Чаму ствараць такую лёгкую версію вручную, калі існуюць CrewAI і LangGraph? Гэта хорашыя інструменты, і толькі тады, калі працэўны працэс выкалікае патрэбу ў одночасных агентах, дынамічным розгалужэнні чы рэальнай разгрузцы, ўзгоджуецца ўжытак іх вагі. Аднак багато завад являюцца последовнымі, падлежачымі перагляду і можлівым да павтарэння, і не выкалікаюць такой патрэбы. Для такіх случаў на кожным камп’ютары ўжо існуе простая адпаведнасць — папка з файламі. Стварэнне такой папкі спачатку таксама паказвае, дзе болей важкія інструменты пачынаюць даваць практычную карысць.