peer-conversation
Многотуровый диалог писателя (Claude) с одним или несколькими напарниками (любой набор из kimi/codex/hermes/claude-headless) по задаче пилота (DP.SC.154). Ведёт turn-loop (2 участника) или round-loop (3+, WP-509), обнаруживает CONSENSUS/ESCALATE, после консенсуса — Decision Gate
Install
npx skills add https://github.com/TserenTserenov/FMT-exocortex-template/tree/main/.claude/skills/peer-conversation
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install tserentserenov-fmt-exocortex-template@llmmart
git clone https://github.com/TserenTserenov/FMT-exocortex-template.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole tserentserenov/fmt-exocortex-template collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Peer Conversation (DP.SC.154)
Задача: $ARGUMENTS
Архитектура: я (Claude) = писатель всегда (Skill tool доступен только мне в этой сессии). Напарник(и) — параметр
--peer(defaultkimi), список из одного или нескольких зарегистрированных вендоров (§0в). При 2+ напарниках (N>2 участников целиком) — расширение WP-509, см. §0в и §3р. Каждый напарник вызывается через свой<vendor>-peer-adapter.shнапрямую — Bash tool, stdin pipe. Контракт одинаковый у всех: stdin = промпт, stdout = реплика, exit 0-5 (см. §0в). Напарнику запрещено писать файлы своими инструментами внутриSESSION_DIR— только stdout (гонка file-write vs stdout-capture портит журнал, найдено WP-509 2026-07-30).list_peer_statuses(Local Gateway) — координация файлов, не проверка доступности напарника CLI. Gateway offline ≠ напарник недоступен.
When to use
Многотуровый диалог писателя (Claude) с одним или несколькими напарниками (любой набор зарегистрированных вендоров — Kimi, Codex, Hermes, второй Claude-инстанс headless) по задаче пилота (DP.SC.154). При одном напарнике — turn-loop (пара реплик). При двух и более напарниках — round-loop (WP-509): каждый раунд высказывается каждый участник, с явной role-discovery фазой (раунды 0–2), валидацией реплик по 4 критериям, лимитом повторных вызовов и классификацией результата agreed|partial|escalated|not-agreed. Обнаруживает CONSENSUS/ESCALATE, после консенсуса — Decision Gate (зафиксировать vs реализовать → ревью → проверить → задеплоить), синтезирует report.md через Agent tool.
Scope boundary — не подменяет решения, зарезервированные за пилотом (найдено 2026-07-07)
Peer-сессия пригодна для: технических решений, дизайна, code review, поиска компромисса между подходами, подготовки кандидатов к решению. НЕ пригодна для решений, которые процесс явно закрепляет за человеком (например, R15 Валидатор в
/apply-captures— accept/reject/defer кандидатов знания; R1 Стратег — приоритеты месяца). Согласие двух агентов между собой — не решение пилота, даже единогласное и хорошо обоснованное.Если задача внутри пир-сессии требует такого решения — писатель обязан остановиться и спросить пилота напрямую в текущем чате (не через turn-файл), прежде чем фиксировать результат. См.
.claude/skills/apply-captures/SKILL.mdраздел «R15 = живой пилот, не агент» и${IWE_GOVERNANCE_REPO:-DS-strategy}/inbox/bugs/bug-2026-07-07-r15-decisions-bypassed-pilot.md— прецедент, из-за которого добавлено это ограничение.
Шаг 0. Режим
Определить режим из $ARGUMENTS:
--list→ прочитать${IWE_GOVERNANCE_REPO:-DS-strategy}/sessions/00-index.md, вывести таблицу. Файла нет → явно сообщить «индекс сессий ещё не создан (его создаст первая пир-сессия, шаг 1.3); прошлые сессии смотри в дереве sessions/YYYY-MM/DD/» — не молчать (issue #568). Стоп.--interrupt <id>→ перейти к Шагу 5 (interrupt-режим). Стоп после.--finalize <id>→ перейти к Шагу 6 (finalize-режим). Стоп после.- Иначе → новая сессия, продолжать к Шагу 0в.
Шаг 0в. Выбор напарника(ов) (--peer)
Добавлено 2026-07-29 (АрхГейт: вариант «параметризовать» против «отдельный скилл на вендора» — эволюционируемость заблокировала копипаст, см.
sessions/2026-07/2026-07-29-*-wp401-peer-vendor.md). Расширено 2026-07-30 (WP-509): список из N>1 напарников вместо одного.
Извлечь --peer <vendor1,vendor2,...> из $ARGUMENTS (через запятую, без пробелов). Нет флага → PEER_VENDORS=(kimi) (обратная совместимость с версией до 1.4.0). Один vendor без запятой → PEER_VENDORS=(<vendor>), поведение идентично версии 1.4.0 — отдельного code path для одного напарника не заводить, PEER_COUNT=${#PEER_VENDORS[@]} управляет веткой (turn loop §3 при PEER_COUNT==1, round loop §3р при PEER_COUNT>=2).
Реестр вендоров (единственное место маппинга — OwnerIntegrity; новый вендор добавляется только здесь):
PEER_VENDOR |
Адаптер | peer_agent (meta.yaml) |
Поддерживает --add-dir |
Поддерживает --model |
Особый флаг |
|---|---|---|---|---|---|
kimi |
scripts/kimi-peer-adapter.sh |
kimi-headless |
да | да | — |
codex |
scripts/codex-peer-adapter.sh |
codex |
да | да | — |
hermes |
scripts/hermes-peer-adapter.sh |
hermes |
нет | нет | --session-id <id> вместо |
claude |
scripts/claude-peer-adapter.sh |
claude-code-headless |
нет | да | text-only; контекст в stdin |
Каждый элемент PEER_VENDORS валидировать отдельно. Неизвестный PEER_VENDOR в списке → СТОП на этом элементе, не на всём списке: сообщить пилоту, какой конкретно vendor не распознан («Напарник <vendor> не зарегистрирован. Известные: kimi, codex, hermes, claude.»), предложить продолжить с оставшимися распознанными или прервать целиком. Добавление нового вендора — правка таблицы выше + написание <vendor>-peer-adapter.sh по контракту §0в.1.
§0в.1 Общий контракт адаптера (для добавления нового вендора): stdin = полный промпт хода (Bash pipe, не inline echo — inline попадает в командную строку и хук B7.7c может ложно заблокировать повторные вызовы); stdout = одна реплика с frontmatter; exit 0 = OK, 1 = general error, 2 = content-filter/PII violation, 3 = PII hard block, 5 = уже идёт сессия (pidfile lock). Для claude действует усиленный контракт WP-458: --add-dir запрещён, peer получает только минимальную текстовую проекцию в stdin и не имеет файловых или shell-инструментов. Коды 2-5 — не обязательны для нового адаптера, но 0/1 обязательны (Шаг 3.1/3р.1 проверяет exit ≠ 0 как «напарник не ответил»).
Адаптеры лежат в репозитории шаблона ($IWE_TEMPLATE), не в личном governance-репо пользователя — governance-репо хранит только стратегические данные пилота и своих скриптов-адаптеров не содержит. Построить для каждого vendor в PEER_VENDORS: ADAPTER_PATH[$vendor]="${IWE_TEMPLATE:-$HOME/IWE/FMT-exocortex-template}/<адаптер из таблицы>" и PEER_AGENT_ID[$vendor]="<peer_agent из таблицы>" (bash associative arrays; порядок вызова = порядок PEER_VENDORS). Используются везде ниже вместо хардкода kimi-peer-adapter.sh/kimi-headless. (issue #830)
§0в.2 Проверка доступности vendor'ов (capability-check, WP-509 Ф5). После построения ADAPTER_PATH, до анонса напарников:
- Дубликаты в списке (
--peer codex,codex) → отклонить: N участников не подменяется повторными вызовами одного напарника; сообщить пилоту, стоп до исправления списка. - Проверка каждого
vendor(накопить доступных вremaining):
Живой probe-вызов CLI НЕ делать: ошибки всплывут при вызове по контракту адаптера (exit ≠ 0, Шаг 3.1/3р.1); vendor-specific знания сверхremaining=() for vendor in "${PEER_VENDORS[@]}"; do ok=true if [[ ! -x "${ADAPTER_PATH[$vendor]}" ]]; then ok=false; fi # адаптер отсутствует/не исполняем if [[ "$vendor" == hermes ]] && ! command -v hermes >/dev/null 2>&1; then ok=false; fi # CLI hermes недоступен $ok && remaining+=("$vendor") || : # недоступный — кандидат на исключение (п. 3) donehermesв скилл не добавлять (иначе Шаг 0в расходится с адаптерами). - Недоступный vendor → сообщить пилоту какой именно и почему (нет адаптера / нет CLI), предложить продолжить с оставшимися или прервать — тот же UX, что для незарегистрированного vendor (стоп на элементе, не на списке).
- Нормализация после исключений (атомарно):
Режим выбирается заново:PEER_VENDORS=("${remaining[@]}") PEER_COUNT=${#PEER_VENDORS[@]} round_order=("${PEER_VENDORS[@]}")PEER_COUNT>=2→ round-loop (§3р),==1→ turn-loop (§3 — сценарий «запрошены codex,hermes, остался codex» идёт классическим диалогом вдвоём, не усечённым кругом),==0→ безусловный стоп с сообщением пилоту.
Анонсировать пилоту:
Напарник: <PEER_VENDOR> (<peer_agent>) # PEER_COUNT == 1, как раньше
или при PEER_COUNT >= 2:
Напарники (<PEER_COUNT>):
1. kimi (kimi-headless)
2. codex (codex)
Шаг 0б. Открытие (WP Gate — только для новой сессии)
Найти WP по задаче: прочитать ${IWE_GOVERNANCE_REPO:-DS-strategy}/WP-REGISTRY.md (grep по ключевым словам) и ${IWE_GOVERNANCE_REPO:-DS-strategy}/current/WeekPlan W{N}.md.
Анонс пилоту:
Открываю peer-сессию (DP.SC.154)
Роль: Писатель (Claude) | Напарник(и): <PEER_VENDOR (PEER_AGENT_ID)>[, ...]
Задача: <задача>
РП: WP-NNN «<название>» | или: не найден в плане
Метод: turn-loop ≤10 ходов (PEER_COUNT==1) | round-loop ≤rounds_limit раундов (PEER_COUNT>=2, WP-509)
Если РП не найден в плане недели → полный WP Gate Ритуал (memory/protocol-open.md §Сессия):
объявить артефакт + дождаться подтверждения пилота → только после «да» переходить к Шагу 1.
Если РП найден → продолжать без ожидания.
Определение рекомендуемой модели писателя (WP-394 Ф4.6):
# informational — pilot selects model at Claude Code startup, not auto-applied here
verification_class = из WP контекста или из описания задачи
WRITER_MODEL_RECOMMENDED = "sonnet" # default: закрытые задачи с тестами/чёткой проверкой
if verification_class in ("open-loop", "problem-framing"):
WRITER_MODEL_RECOMMENDED = "opus"
Анонсировать пилоту:
Модель писателя (рекомендуется): <WRITER_MODEL_RECOMMENDED>
(Класс задачи: <verification_class>. Выбери модель при запуске Claude Code.)
Подсказка маршрутизации агента (WP-383, информационная — не enforcement). По классу работы есть рекомендуемый инициатор/агент. Это подсказка пилоту, не блокировка:
| Класс работы | Рекомендуемый агент |
|---|---|
| Уборка / форматирование / триаж | Kimi (дёшево, быстро) |
| Верификация shallow (формат/чеклист/drift) | Kimi |
| Верификация deep (cross-file invariant) | Claude (statefulness) |
| Реализация multi-file / tight-loop | Claude (держит состояние сессии) |
| Дизайн / scope / планирование | сильная модель (Claude/Opus или Kimi) |
Trigger эскалации (лог, не блок): если пилот 2 раза подряд выбирает агента вопреки подсказке — записать сигнал «routing-таблица устарела или классификация неверна» в inbox/WP-383/routing-drift.log (создать при первом срабатывании). Не блокировать выбор пилота.
Источник таблицы:
${IWE_GOVERNANCE_REPO:-DS-strategy}/inbox/WP-383/routing-design-v1.md §3. Statefulness-пробел Kimi закрыт автопередачей git-diff вkimi-peer-adapter.sh(§8).
Шаг 1. Инициализация
SESSIONS_DIR="$HOME/IWE/${IWE_GOVERNANCE_REPO:-DS-strategy}/sessions"
TODAY=$(date +%Y-%m-%d)
MONTH=$(date +%Y-%m)
DAY=$(date +%d)
DAY_DIR="$SESSIONS_DIR/$MONTH/$DAY"
mkdir -p "$DAY_DIR"
NUM=$(printf "%02d" $(( $(find "$DAY_DIR" -maxdepth 1 -type d -name "${TODAY}-[0-9][0-9]-*" 2>/dev/null | wc -l | tr -d ' ') + 1 )))
Slug = первые 4 латинских слова из задачи строчными буквами через дефис (не-латиница и дата убираются). Никакой даты в slug — она уже в SESSION_ID. Если латиницы нет → session.
SESSION_ID="${TODAY}-${NUM}-${SLUG}"
SESSION_DIR="${DAY_DIR}/${SESSION_ID}"
1.0 Session-guard open (WP-398, обязательно, ДО любых Write/Edit в сессии). Синхронизирует пир-сессию с session-guard.sh Scope gate — без этого коммит на Шаге 4.5 будет заблокирован pre-commit хуком (mtime файлов сессии старше семафора). WP берётся из Шага 0б (найденный или «day-close»/«unknown», если РП не назначен):
IWE_AGENT=claude-code bash "${IWE_SCRIPTS:-$HOME/IWE/scripts}/session-guard.sh" open \
--wp "<WP-NNN из Шага 0б>" --agent claude-code --close-path peer-session \
--task "<задача одной строкой>" --slug "$SESSION_ID"
Не хардкодить
~/IWE/scripts/session-guard.sh. Пир-сессия 2026-07-31-16-wp484-new-order-cutover сначала внесла такой хардкод по образцуday-close/SKILL.md, но холодный ревью нашёл: у обычного пользователя шаблонаsetup.shНЕ копирует корневуюscripts/— существует только каталог скриптов внутри шаблона, и${IWE_SCRIPTS:-...}резолвится именно туда намеренно (issue #266, commit835d5ea— тот же хардкод уже один раз чинили этим фоллбэком). Хардкод вday-close/SKILL.md— недокументированный долг, ждущий той же поломки при промоции, не образец для копирования. Для author-mode расхождение реальное (FMT-копияsession-guard.shотстаёт от корневой на фиксы WP-484 Нить1) — но лечится синкомtemplate-sync.sh(с отдельного разрешения пилота, S-33) или личной правкой~/.iwe-paths, не хардкодом в файле, который промотируется всем пользователям шаблона.
Если команда упала (exit ≠ 0) — не блокировать сессию: сообщить пилоту одной строкой «session-guard open не сработал (<причина>), продолжаю без семафора — на Шаге 4.5 возможна ручная разблокировка через touch/note-file» и идти дальше. Semaphore-файл session-guard создаёт СВОЙ ORZ-скаффолд-заготовку по пути sessions/<MONTH>/<TODAY>-<CLEAN_SLUG>.md — тот же путь, что закрывающий файл пир-сессии из Шага 4.4/4.5.0 (до 2026-08-03 эти два места ошибочно считали разные пути, см. пометку на Шаге 4.4); Шаг 4.5.0 дописывает в этот же файл финальное содержимое, а не создаёт новый.
1.1 Создать папку:
mkdir -p "$SESSION_DIR"
1.2 Записать meta.yaml (Write):
task_id: ""
date: "<TODAY>"
session_id: "<SESSION_ID>"
start_time: "<ISO-8601 UTC>"
end_time: ""
writer_agent: "claude-code"
personality: "<unassigned|UUID>" # WP-510 Патч 4, слой 3: writer_agent = конструктивная реализация; personality = какая ИИ-личность (если есть авторитетная запись в `current/AI Personalities Registry.md` для текущего хоста/раннера) вела сессию. Ищи по хосту/раннеру в реестре — не выдумывай; нет однозначного совпадения → "unassigned". Маршрутизирующая метка, не допуск к памяти.
peer_agent: "<первый PEER_AGENT_ID из §0в>" # backward-compat (PEER_COUNT==1: единственный); полный список — peer_agents
peer_cmd: "<первый PEER_VENDOR>-peer-adapter" # backward-compat; полный список — peer_cmds
peer_agents: ["<PEER_AGENT_ID>", "..."] # WP-509: в порядке PEER_VENDORS, длина 1 при PEER_COUNT==1 (дублирует peer_agent); §4.2 читает ТОЛЬКО это поле для report.md peer: при PEER_COUNT>=2
peer_cmds: ["<vendor>-peer-adapter", "..."] # WP-509: та же длина/порядок, что peer_agents
round_order: ["<vendor>", "..."] # WP-509: фиксированный порядок раунда (= PEER_VENDORS), только при PEER_COUNT>=2
write_token_holder: "writer_agent" # WP-509: держатель write token сейчас; default = writer_agent, меняется только через подтверждённый ACCEPT_HANDOFF (см. §3р.3)
peer_model: ""
status: "started"
turns_count: 0
turns_limit: 10 # PEER_COUNT==1: лимит реплик, без изменений с v4
rounds_limit: 6 # WP-509: лимит раундов, применяется только при PEER_COUNT>=2, НЕ переопределяет turns_limit
round_skips: {} # WP-509: {round_NN: [vendor, ...]} — кто не ответил/пропустил раунд; исключаются из требования "консенсус у каждого" в §3р.3
participant_status: {} # WP-509: {vendor: active|failed} — финальный статус участника после исчерпания попыток
max_peer_attempts: 2 # WP-509: лимит повторных вызовов одного peer подряд
peer_attempts: {} # WP-509: {vendor: N} — счётчик вызовов в текущем раунде
peer_failures: [] # WP-509: [{round, vendor, reason}] — зафиксированные отказы участников
handoff_history: [] # WP-509: [{round, from, to, reason}] — журнал OFFER_HANDOFF/ACCEPT_HANDOFF (write token, DP.SC.154 «Write token ≠ process_position»)
escalations_count: 0
extensions: []
result_path: ""
task_description: "<задача>"
implementation_pipeline: false
review_iterations: 0
verify_status: ""
deploy_shas: {}
writer_model_recommended: "sonnet" # informational — pilot selects model at startup, not auto-applied; opus only for open-loop|problem-framing
# Sequential role-discovery (WP-367) — заполняется во время Opening:
# Если пилот задал явно — сразу заполни `roles`.
# Если не задал — initiator в ход 0 заполняет `proposed_roles` в frontmatter 00-writer.md;
# после согласования (ход 2) переноси в `roles`.
roles: {} # финальное: {agent_id: [DP.ROLE.NNN, ...]} после consensus
discovery_turns: 0 # сколько ходов ушло на role-discovery (не считается в turns_limit)
# Двухосная модель (WP-367 Ф5, DP.SC.154 v4):
ad_hoc_roles: {} # {role_name: {agent_id, rationale, first_used_turn}} — для каскада audit
swap_history: [] # [{turn, from, to, reason}] — журнал SWAP_WRITER переходов
Если пилот не назначил роли при запуске сессии — initiator в ход/раунд 0 предлагает свою роль и роль каждого напарника (см. DP.SC.154 раздел «Opening сессии: Sequential role-discovery», при PEER_COUNT>=2 — раздел «Role-discovery для N>2»). Discovery-ходы/раунды (0-2) не входят в turns_limit/rounds_limit.
In-session ad-hoc role signal (DP.SC.154 v4, каскад Pack-расширения уровень 1). При использовании ad-hoc роли (нет в Pack DP.ROLE.NNN/MIM.R.NNN/VR.R.NNN) агент обязан сразу объявить пилоту — формат:
Беру ad-hoc роль «<имя>». В каталоге Pack такой нет.
Обязанности: <одной строкой>. Метод: <одной строкой>.
Предлагаю создать РП на формализацию (~30 мин: passport + scenarios + templates).
Выбери:
А. Создать сейчас → отдельный РП «pack-gap-<имя>» через create-wp.sh.
Б. Отложить → продолжу как ad-hoc, сторож напомнит при Week Close.
Запись в meta.yaml.ad_hoc_roles идёт независимо от выбора (для back-up на уровне 2 — Week Close audit). Если пилот выбрал А — после сессии писатель открывает отдельный РП и делает формализацию.
1.3 Добавить строку в sessions/00-index.md сверху таблицы (первая строка таблицы после |---|). Колонка «Агенты» — при PEER_COUNT>=2 напарники соединяются через +:
Файла нет — создать его сейчас (issue #568: установка индекс не создаёт, и до этой правки шаг молча не исполнялся — восемь сессий подряд без единой записи). Временная мера до РП-526 (семейство MC переведёт индекс на snapshot-механику — при миграции этот блок удалить). Создаваемый файл обязан честно объявлять свою неполноту прямо в себе (не в stdout — вывод теряется, а файл читают через недели):
IDX="${IWE_GOVERNANCE_REPO:-DS-strategy}/sessions/00-index.md" if [ ! -f "$IDX" ]; then mkdir -p "$(dirname "$IDX")" { echo "# Индекс пир-сессий" echo "" echo "<!-- index regenerated on $(date +%Y-%m-%d): created empty by peer-conversation step 1.3 (issue #568) — sessions before this date exist on disk but are NOT backfilled here; the folder tree sessions/YYYY-MM/DD/ is the authoritative record -->" echo "" echo "| Дата | Сессия | Задача | Агенты | Ходы | Эскалации | Статус | Отчёт |" echo "|------|--------|--------|--------|------|-----------|--------|-------|" } > "$IDX" fi
| <TODAY> | <SESSION_ID> | <задача ≤50 симв> | claude-code / <PEER_VENDOR1>[+<PEER_VENDOR2>...] | 0 | 0 | started | — |
Шаг 2. Реплика писателя 00-writer.md
Записать ${SESSION_DIR}/00-writer.md (Write):
---
turn: 0
role: writer
agent_id: claude-code
timestamp: <ISO-8601 UTC>
consensus: none
---
<Моя начальная позиция — анализ задачи, тезисы, конкретные вопросы к напарнику(ам).
При `PEER_COUNT>=2` — предложить содержательную роль каждому напарнику отдельно (не только «роль для группы»), с обоснованием на каждого (DP.SC.154 «Role-discovery для N>2»).
НЕ пересказ задачи. Позиция с аргументами.>
Показать пилоту краткое резюме: что написал в 00-writer.md.
Шаг 2.5. Role-discovery для N>2 (PEER_COUNT >= 2, WP-509)
Пропустить, если PEER_COUNT == 1. Для двух участников discovery сводится к предложению ролей в 00-writer.md и согласованию в turn-loop.
После 00-writer.md запустить раунды 0–2, которые не входят в rounds_limit.
2.5.1 Раунд 0 — писатель предлагает роли
В 00-writer.md (Шаг 2) писатель уже предлагает содержательную роль каждому напарнику. Дополнительно записать в frontmatter 00-writer.md:
proposed_roles: { "<PEER_AGENT_ID>": "<role_name>", ... }
suggested_initiator_role: "<role_name>"
2.5.2 Раунд 1 — каждый peer подтверждает или спорит роль
Вызвать каждого напарника по порядку round_order с промптом, аналогичным §3р.1, но с единственной задачей:
- прочитать 00-writer.md;
- согласиться с предложенной ролью, предложить правку или запросить уточнение у пилота;
- явно подтвердить, что понимает ограничение «ответ только в stdout, никаких файловых операций в SESSION_DIR».
Формат реплики:
---
turn: 0
role: peer
agent_id: <PEER_AGENT_ID>
content_role: <согласованная/предложенная роль>
process_position: peer
timestamp: <ISO-8601 UTC>
consensus: none
role_accepted: true | false | clarify
---
<Обоснование принятия роли или запрос уточнения>
Если role_accepted: clarify — писатель уточняет у пилота и повторяет раунд 1 только для этого участника (не считается отдельным раундом discovery).
2.5.3 Раунд 2 — фиксация agreed_roles
Писатель записывает 01-writer.md (turn: 0, role: writer) с итоговой таблицей ролей:
---
turn: 0
role: writer
agent_id: claude-code
consensus: none
agreed_roles: { "<PEER_AGENT_ID>": "<role_name>", ... }
writer_role: "<role_name>"
discovery_turns: 1 # сколько дополнительных проходов раунда 1 потребовалось
---
Обновить meta.yaml:
roles: { "claude-code": ["<writer_role>"], "<PEER_AGENT_ID>": ["<role_name>"], ... }
discovery_turns: <N>
Только после этого переходить к Шагу 3р (ROUND=1) с содержательными раундами.
Шаг 3. Turn loop (PEER_COUNT == 1)
При
PEER_COUNT >= 2— пропустить этот шаг целиком, перейти к Шагу 3р (round loop, WP-509). Этот шаг не менялся с версии 1.4.0 — один напарник, поведение идентично.
Переменные: TURN=1, ESCALATIONS=0, DONE=false.
3.1 Вызов напарника
Прочитать все предыдущие реплики из SESSION_DIR в порядке нумерации.
Составить промпт:
Ты — напарник (peer agent) в диалоговой сессии (DP.SC.154).
Сессия: <SESSION_ID>
Ход: <TURN> из 10
Задача: <задача>
Контекстная проекция ниже — единственный источник о сессии. Не читай файлы и не используй инструменты:
<минимальная текстовая проекция предыдущих реплик и проверяемых фактов>
Напиши реплику в stdout с frontmatter:
---
turn: <TURN>
role: peer
agent_id: <PEER_AGENT_ID>
timestamp: <ISO-8601 UTC>
consensus: none | proposed | reached | escalate
---
<Твой ответ>
Правило критика: найди ХОТЯ БЫ ОДИН тезис или допущение писателя, с которым не согласен. Не сдавайся после первого возражения — держись аргументированно. Если всё действительно ОК — объясни почему конкретно, не просто «согласен».
Маркеры (строго в начале строки):
CONSENSUS: <резюме> — если считаешь что договорились
ESCALATE_TO_USER: <причина> — если писатель игнорирует существенное возражение
Вызов напарника через Bash — флаги зависят от вендора (таблица §0в):
PEER_FILE="${SESSION_DIR}/$(printf '%02d' $TURN)-peer.md"
if [ "$PEER_VENDOR" = "hermes" ]; then
# hermes-peer-adapter.sh не принимает --add-dir/--model — свой --session-id.
# НЕ путать с $SESSION_ID пир-сессии: адаптер всегда шлёт переданный --session-id
# как `--resume <id>` в hermes CLI — на самом первом вызове этой пир-сессии
# у Hermes ещё не существует диалога с ID пир-сессии (это два разных
# пространства идентификаторов), `hermes chat --resume <несуществующий>`
# падает молча (set -euo pipefail в адаптере гасит его же диагностику до того,
# как она успевает напечататься) — найдено живьём 2026-07-31/08-01, WP-484/WP-509.
# Родной session_id Hermes читаем С ДИСКА (последний уже записанный NN-peer.md),
# не из shell-переменной — агент делает каждый вызов отдельным Bash-тулом, и
# переменные не переживают границу между вызовами (найдено code review 01.08).
HERMES_LAST_ID=$(grep -h "^session_id: " "${SESSION_DIR}"/[0-9][0-9]-peer.md 2>/dev/null | tail -1 | sed 's/^session_id: //')
if [ -z "$HERMES_LAST_ID" ]; then
echo "<промпт>" | bash "$ADAPTER_PATH" > "$PEER_FILE" 2>/dev/null
else
echo "<промпт>" | bash "$ADAPTER_PATH" --session-id "$HERMES_LAST_ID" > "$PEER_FILE" 2>/dev/null
fi
else
printf '%s\n' "<промпт с минимальной текстовой проекцией>" | bash "$ADAPTER_PATH" \
> "$PEER_FILE" 2> "${PEER_FILE%.md}.err"
fi
Если файл пустой или exit ≠ 0 → сообщить пилоту: «<PEER_VENDOR> не ответил. Повторить или прервать?»
3.2 Показать пилоту
Прочитать $PEER_FILE. Вывести ключевые тезисы напарника (не всю реплику дословно — краткое резюме + цитаты ключевых позиций).
3.3 Проверить маркеры
ESCALATE_TO_USER:
Если grep -q "^ESCALATE_TO_USER:" "$PEER_FILE":
- Извлечь причину:
grep "^ESCALATE_TO_USER:" "$PEER_FILE" | sed 's/^ESCALATE_TO_USER: //' ESCALATIONS += 1- Записать
${SESSION_DIR}/escalation-$(printf '%02d' $((ESCALATIONS-1))).md:
---
escalation_number: <N>
turn: <TURN>
timestamp: <now>
reason: "<причина>"
pilot_response: ""
---
# Эскалация <N> (ход <TURN>)
**Причина:** <причина>
**Реплика напарника:** <PEER_FILE>
**Ответ пилота:** (ввести ниже)
- Сообщить пилоту: «<PEER_VENDOR> эскалирует: <причина>. Нужно твоё решение.»
- Дождаться ответа пилота, записать в
pilot_responseв escalation-файл. - Обновить
meta.yaml:escalations_count: <ESCALATIONS>(Bash sed).
CONSENSUS:
Если grep -q "^CONSENSUS:" "$PEER_FILE":
- Извлечь резюме.
DONE=true, перейти к Шагу 3.5 (Decision Gate) — НЕ к Шагу 4 напрямую.
3.4 Реплика писателя
Если TURN >= 10 → DONE=true, перейти к Шагу 4.
Написать $(printf '%02d' $((TURN+1)))-writer.md (Write):
---
turn: <TURN+1>
role: writer
agent_id: claude-code
timestamp: <now>
consensus: none
---
<Моя реплика: ответ на аргументы напарника + учёт направления пилота>
TURN += 1 → вернуться к 3.1.
Шаг 3р. Round loop (PEER_COUNT >= 2, WP-509)
Пропустить, если
PEER_COUNT == 1— там действует классический Шаг 3.
Переменные: ROUND=1, ESCALATIONS=0, DONE=false. Порядок раунда = PEER_VENDORS в порядке --peer (записан в meta.yaml.round_order на Шаге 1.2).
3р.1 Вызов каждого напарника по порядку раунда
WRITE_TOKEN_HOLDER — читать из meta.yaml.write_token_holder (default, записанный на Шаге 1.2, = writer_agent: писатель держит write token, пока не было ни одного ACCEPT_HANDOFF). Значение передаётся в промпт явно — напарники не обязаны сами читать meta.yaml в поисках держателя.
Для vendor в round_order (по очереди, не параллельно — каждый следующий видит реплики предыдущих этого же раунда) составить промпт:
Ты — напарник (peer agent) в диалоговой сессии (DP.SC.154, N>2 участников).
Сессия: <SESSION_ID>
Раунд: <ROUND> из <rounds_limit>
Задача: <задача>
Текущий держатель write token: <WRITE_TOKEN_HOLDER>
Для Claude: передай в его stdin минимальную текстовую проекцию всех нужных реплик и фактов этого раунда. Он не читает журнал сессии и не использует инструменты. Для остальных напарников применяй их собственный контракт.
ВАЖНО: ответ только в stdout. Не используй свои файловые инструменты ни для одного
файла в этой папке — это создаёт гонку с перехватом stdout и портит журнал сессии.
Напиши реплику в stdout с frontmatter:
---
turn: <ROUND>
role: peer
agent_id: <PEER_AGENT_ID>
content_role: <согласованная роль или предложение, если ещё не согласована>
process_position: peer
timestamp: <ISO-8601 UTC>
consensus: none | proposed | reached | escalate
---
<Твой ответ>
Правило критика: найди ХОТЯ БЫ ОДИН тезис, с которым не согласен — свой или другого напарника.
Маркеры (строго в начале строки):
PASS — если нечего добавить в этом раунде (не ошибка, явное право промолчать)
CONSENSUS: <резюме> — если согласен с итогом
ESCALATE_TO_USER: <причина> — если есть проигнорированное существенное возражение
OFFER_HANDOFF: <agent_id> — <причина> — если хочешь предложить свой write token другому (только если ты сейчас держатель)
ACCEPT_HANDOFF — если принимаешь предложенный тебе OFFER_HANDOFF
REQUEST_HANDOFF: <причина> — необязывающая просьба к держателю
Вызов напарника через Bash (промпт — во временный файл, не inline echo — тот же B7.7c-риск, что в §0в.1):
PEER_FILE="${SESSION_DIR}/$(printf '%02d' $ROUND)-peer-${vendor}.md"
PROMPT_FILE=$(mktemp)
# записать промпт (см. шаблон выше) в $PROMPT_FILE
if [ "$vendor" = "hermes" ]; then
# Тот же контракт и та же ловушка, что в Шаге 3.1 (turn-loop) — hermes не берёт
# --add-dir, и переданный --session-id всегда трактуется как --resume. Родной
# hermes session_id читаем С ДИСКА (последний уже записанный NN-peer-hermes.md),
# не из shell-переменной — агент делает каждый вызов отдельным Bash-тулом, и
# переменные не переживают границу между вызовами (найдено code review 01.08).
HERMES_LAST_ID=$(grep -h "^session_id: " "${SESSION_DIR}"/[0-9][0-9]-peer-hermes.md 2>/dev/null | tail -1 | sed 's/^session_id: //')
if [ -z "$HERMES_LAST_ID" ]; then
cat "$PROMPT_FILE" | bash "${ADAPTER_PATH[$vendor]}" > "$PEER_FILE" 2>/dev/null
STATUS=$?
else
cat "$PROMPT_FILE" | bash "${ADAPTER_PATH[$vendor]}" --session-id "$HERMES_LAST_ID" > "$PEER_FILE" 2>/dev/null
STATUS=$?
fi
else
if [ "$vendor" = "claude" ]; then
cat "$PROMPT_FILE" | bash "${ADAPTER_PATH[$vendor]}" > "$PEER_FILE" 2> "${PEER_FILE%.md}.err"
else
cat "$PROMPT_FILE" | bash "${ADAPTER_PATH[$vendor]}" --add-dir "$SESSION_DIR" > "$PEER_FILE" 2>/dev/null
fi
STATUS=$?
fi
rm -f "$PROMPT_FILE"
3р.1а Валидация реплики (WP-509)
Применить перед записью в round_skips или консенсус. Реплика считается valid, если все 4 проверки прошли (с исключением для discovery-раундов, см. ниже):
- По предмету: файл не пуст, frontmatter распарсен, тело содержит ответ на задачу раунда (не только формальные фразы / повтор условия).
- Роль исполнена:
- В discovery-раундах 0–2 (
PEER_COUNT >= 2):content_roleсовпадает с ролью, предложенной в00-writer.md.proposed_rolesдля этогоagent_id, либо peer явно предлагает другую ad-hoc роль с обоснованием. - В содержательных раундах (
ROUND >= 1после §2.5.3):content_roleсовпадает с ролью, согласованной вmeta.yaml.rolesдля этогоagent_id; если в реплике предлагается ad-hoc роль — см. §1.2 «In-session ad-hoc role signal».
- В discovery-раундах 0–2 (
- Нет побочных действий: в stdout-ответе нет маркеров вида
FILE_WRITTEN,MEMORY_UPDATED,TOOL_CALLEDи т.п.; кроме того, не проверять файловую систему напрямую — адаптер гарантирует изоляцию, но агент-участник может декларировать действия в тексте. - Непротиворечивость фактам: если реплика ссылается на конкретный факт (SHA, имя файла, статус РП, commit), проверить его по доступным источникам; при несовпадении —
failedс причинойfact_mismatch.
Если реплика invalid:
- Инкремент
meta.yaml.peer_attempts[vendor]. - Если
peer_attempts[vendor] < max_peer_attempts— повторить вызов этого же peer в том же раунде с уточняющим промптом (причина invalid + контекст раунда). - Если
peer_attempts[vendor] >= max_peer_attempts— зафиксироватьmeta.yaml.peer_failures += [{round: ROUND, vendor: vendor, reason: <проверка>}], установитьmeta.yaml.participant_status[vendor] = "failed", исключить vendor из требования консенсуса в §3р.3 (какround_skips, но с явным статусомfailed).
Пустой файл или STATUS != 0 → этот напарник пропущен в этом раунде (не блокирует раунд, DP.SC.154 «Раунды вместо бесконечного круга»): записать meta.yaml.round_skips.round_<NN>: [..., vendor], инкремент peer_attempts[vendor]; при исчерпании попыток — participant_status[vendor] = "failed" и peer_failures += [...].
3р.2 Показать пилоту
Резюме по КАЖДОМУ напарнику раунда (не только последнему) — кто что сказал, кратко.
3р.3 Проверить маркеры у всех реплик раунда
Применяется к репликам напарников этого раунда (3р.1) и, при возврате из 3р.4, к реплике писателя того же раунда — обработка маркеров одинаковая для обеих ролей, разница только в том, что писатель пишет последним в раунде.
ESCALATE_TO_USERу любого участника (включая писателя) → тот же процесс, что в §3.3 (записьescalation-NN.md, пауза на ответ пилота).OFFER_HANDOFF/ACCEPT_HANDOFF/REQUEST_HANDOFFу любого участника → при подтверждённомACCEPT_HANDOFF(реплика-адресат отвечает наOFFER_HANDOFFпредыдущей реплики) — append{round, from, to, reason}вmeta.yaml.handoff_historyИ обновитьmeta.yaml.write_token_holderна нового держателя.REQUEST_HANDOFFбез ответногоOFFER_HANDOFFдержателя — не меняет ничего, просьба зафиксирована в реплике и всё.CONSENSUS— «консенсус раунда»: учитываются только участники со статусомactive(неround_skipsэтого раунда и неparticipant_status == failed).DONE=true, если у каждого активного участника есть либоCONSENSUS, либоPASSбез встречных возражений в теле реплики. Хотя бы один содержательный контраргумент безCONSENSUSу активного участника → раунд не закрыт.- Если все активные участники поставили
CONSENSUS/PASS→result_classдля report.md =agreed. - Если консенсус достигнут среди активных, но один или несколько участников имеют
participant_status == failed(исчерпаны попытки в этом или предыдущих раундах) →DONE=true, ноresult_class=partial; отказавшиеся участники и причины фиксируются вreport.md §5. - Если ни один участник не имеет
CONSENSUS/PASS, но все технически ответили — раунд не закрыт, продолжаем (илиROUND >= rounds_limit→ §3р.5).
- Если все активные участники поставили
3р.4 Реплика писателя
Если 3р.3 уже установил DONE=true по репликам напарников этого раунда → писатель реплику в этом раунде не пишет, сразу к Шагу 3.5 (Decision Gate).
Иначе — писатель отвечает ($(printf '%02d' $ROUND)-writer.md, тот же формат frontmatter, что в Шаге 2, плюс те же маркеры CONSENSUS/ESCALATE_TO_USER/OFFER_HANDOFF/ACCEPT_HANDOFF/REQUEST_HANDOFF, что и у напарников — писатель тоже может держать или передавать write token). Применить к этой реплике проверку маркеров из 3р.3.
Если после этого DONE=true (писатель сам поставил CONSENSUS, приняв позицию раунда) → Шаг 3.5.
Иначе, если ROUND >= rounds_limit (default 6) → к 3р.5 (спор без консенсуса).
Иначе — ROUND += 1 → 3р.1.
3р.5 Спор между 3+ позициями (после исчерпания rounds_limit без консенсуса)
Учитывать
participant_status: участники со статусомfailedне голосуют и не блокируют принятие решения оставшимися.
Применить критерий обратимости (DP.SC.154 «Разрешение спора между 3+ позициями») до эскалации:
- Решение обратимо и в пределах полномочий активных участников → писатель фиксирует позицию большинства среди
active(2 из 3 или большинство при N>3), несогласие меньшинства — вreport.md§3 «Отвергнутые альтернативы,DONE=true,result_class=partialесли естьfailed-участники, иначеagreed` → Шаг 3.5. - Решение необратимо, вне полномочий, ИЛИ сам вопрос обратимости спорен →
ESCALATE_TO_USER: rounds_limit exhausted, no consensus, irreversible or scope-disputed→ пауза на пилота.
Шаг 3.5. Decision Gate (после консенсуса)
Когда срабатывает:
DONE=trueчерез CONSENSUS-маркер в Шаге 3.3 (турн-loop) или 3р.3 (round-loop) — не черезTURN >= 10/ROUND >= rounds_limitбез консенсуса, там сразу Шаг 3р.5 или Шаг 4. Зачем: консенсус ≠ реализация. Это легитимный choice-question для пилота (выбор объёма работы, не yes/no на готовое решение). Исключение из P5 — пилот сам подтвердил: «здесь от меня нужно согласование» (триггер 2026-05-30, WP-367 Ф5). Обязательно: перед запросом — краткое резюме консенсуса на пальцах, чтобы пилот мог осознанно выбрать. Запрос без резюме = механический «выберите А/Б» без понимания.
Извлечь резюме консенсуса: PEER_COUNT==1 → grep "^CONSENSUS:" "$PEER_FILE" | sed 's/^CONSENSUS: //'. PEER_COUNT>=2 → пройти все ${SESSION_DIR}/$(printf '%02d' $ROUND)-peer-*.md последнего раунда, взять резюме первого файла с маркером CONSENSUS: (если формулировки разошлись — писатель сводит их одним предложением сам, не берёт произвольно один голос).
Резюме на пальцах — обязательная часть. Формат (без технических терминов, кодов, путей):
Консенсус достигнут.
Что обсуждалось: <одной фразой>
К чему пришли: <2-3 строки человеческим языком — суть решения>
Что предлагается реализовать: <список изменений, по 1 строке каждое; без файлов, путей>
Сколько займёт реализация: ~<N>h (включая ревью + smoke + deploy)
Изменения у других пользователей: <если есть — что и как доставляется; если нет — «только локально»>
После резюме — choice question:
Что дальше?
А. Только зафиксировать → Шаг 4 (report.md + commit + close).
Реализация — отдельный РП/фаза при следующей сессии.
Б. Реализовать сейчас → ревью → проверить → задеплоить → Шаг 4.
Дождаться ответа пилота. Записать выбор в meta.yaml (Bash sed):
implementation_pipeline: false | true
- А →
IMPLEMENTATION=false, перейти к Шагу 4. - Б →
IMPLEMENTATION=true, перейти к Шагу 3.6.
Default при молчании пилота: Б (реализация сейчас), per правило 11 «финиш > отлог». Применять только если пилот явно не ответил в течение разумного времени (например, пропустил Decision Gate в скрипте).
Triggers automatic-defer (без запроса пилоту — сразу А):
- Реализация требует нового РП (новый scope, не покрытый текущим РП).
- Требуется ArchGate (новое архитектурное решение системного уровня).
- Контекст полностью переключился (другая часть системы; нужен новый framing).
При срабатывании trigger — анонс пилоту с резюме (НЕ запрос), потом Шаг 4.
Шаг 3.6. Implementation Pipeline (опциональный)
Активируется: только при
IMPLEMENTATION=trueв Шаге 3.5. Принцип: writer применяет решение → cold-context Agent делает code review → writer фиксит → built-in/verifyзапускает smoke → deploy → секция «Реализация и проверка» в report-draft.md. Завершение: в любой подшаг можно эскалировать к пилоту черезESCALATE_TO_USER:маркер (как в Шаге 3.3) — записатьescalation-NN.md, дождаться ответа.
3.6.1 Implementation
Анонс пилоту:
Реализация консенсуса <SESSION_ID>
Файлы: <list of files with absolute paths>
Репо: <repo names, separated by " · ">
Метод: Edit/Write tools напрямую
Writer применяет изменения через Edit/Write. Запрещено:
- Менять файлы вне анонсированного списка без нового анонса.
- Делать commit на этом этапе (commit — только Шаг 3.6.5).
Зафиксировать список изменённых файлов в переменной CHANGED_FILES (один путь на строку).
3.6.2 Code Review (cold-context)
Переменные итерации: REVIEW_ITER=1 при первом входе в 3.6.2, инкрементируется в 3.6.3 при Critical.
Сохранять отчёт в ${SESSION_DIR}/review-$(printf '%02d' $REVIEW_ITER).md (Write).
Вызвать Agent (subagent_type: general-purpose) с явным чек-листом:
Agent(
description: "Code review post-consensus",
subagent_type: "general-purpose",
prompt: """
Cold-context code review результатов peer-сессии <SESSION_ID>.
Контекст консенсуса: <резюме из 3.5>
Изменённые файлы:
<CHANGED_FILES>
Прочитай каждый файл (только указанные строки/функции, не весь файл если он большой).
Проверь по чек-листу:
1. asyncio runtime: ищи `wait_for(coro)` без `shield` → coroutine reuse. Ищи fire-and-forget tasks которые читают/пишут одну строку БД из разных мест.
2. Shell ordering: function call ДО function definition. `set -u` соблюдён?
3. SQL race: cross-file writers в одну строку без атомарности (UPDATE ... RETURNING или SELECT FOR UPDATE).
4. Lock enforcing: при collision — `exit N` или `log WARN && continue`? Если advisory — это intentional или баг?
5. Контекст-специфика консенсуса: <дополни из резюме, если есть инварианты>.
Верни отчёт в формате:
## Critical (must fix before deploy)
- <file:line-range>: <issue> | fix: <conkr>
## High
- ...
## Medium
- ...
## OK (что проверено и норм)
- ...
Не предлагай рефакторинг или стиль — только runtime баги и нарушения чек-листа.
"""
)
Сохранить отчёт ревьюера в ${SESSION_DIR}/review-NN.md где NN = printf '%02d' $REVIEW_ITER (Write).
3.6.3 Review Outcome
Показать пилоту краткое резюме отчёта (Critical + High count + один пример).
Если есть Critical:
- Применить фиксы (Edit) → инкремент
REVIEW_ITER += 1→ вернуться к 3.6.2 (новый review-NN.md). - Лимит итераций: 3. Если на 3-й итерации остался Critical → ESCALATE_TO_USER с приложением последнего review.
Если только High/Medium:
- Спросить пилота: «Есть N High и M Medium замечаний. Фиксить сейчас или после deploy?»
- Записать решение в meta.yaml (
review_iterations: <N>,unresolved: <count>).
Если только OK:
- Продолжить к 3.6.4.
3.6.4 Smoke Verification
Вызвать built-in /verify через Skill tool:
Skill(
skill: "verify",
args: "Smoke test changes from peer-session <SESSION_ID>. Files: <CHANGED_FILES>. Consensus invariants to check: <list из 3.5
Files (fmt-exocortex-template)
-
SKILL.md 83 KB
--- name: peer-conversation description: Многотуровый диалог писателя (Claude) с одним или несколькими напарниками (любой набор из kimi/codex/hermes/claude-headless) по задаче пилота (DP.SC.154). Ведёт turn-loop (2 участника) или round-loop (3+, WP-509), обнаруживает CONSENSUS/ESCALATE, после консенсуса — Decision Gate (зафиксировать vs реализовать → ревью → проверить → задеплоить), синтезирует report.md через Agent tool. argument-hint: "<описание задачи> [--peer kimi|codex|hermes|claude[,vendor2,...]] | --list | --interrupt <session_id> | --finalize <session_id>" version: 1.5.5 layer: L1 status: active browser_safe: false triggers: slash: [/peer-conversation] phrases: ["начни peer-сессию", "запусти диалог с Кими", "запусти диалог с Codex", "peer-сессия"] routing: executor: sonnet deterministic: false agents: single interaction: multi-step gates_required: [] gates_enforced: [] gates_rationale: "операционный скилл; WP Gate применим только при создании нового РП, не для операционных вызовов" --- # Peer Conversation (DP.SC.154) Задача: $ARGUMENTS > **Архитектура:** я (Claude) = писатель всегда (Skill tool доступен только мне в этой сессии). Напарник(и) — параметр `--peer` (default `kimi`), список из одного или нескольких зарегистрированных вендоров (§0в). При 2+ напарниках (N>2 участников целиком) — расширение WP-509, см. §0в и §3р. > Каждый напарник вызывается через свой `<vendor>-peer-adapter.sh` напрямую — Bash tool, stdin pipe. Контракт одинаковый у всех: stdin = промпт, stdout = реплика, exit 0-5 (см. §0в). **Напарнику запрещено писать файлы своими инструментами внутри `SESSION_DIR`** — только stdout (гонка file-write vs stdout-capture портит журнал, найдено WP-509 2026-07-30). > `list_peer_statuses` (Local Gateway) — координация файлов, **не** проверка доступности напарника CLI. > Gateway offline ≠ напарник недоступен. --- ## When to use Многотуровый диалог писателя (Claude) с одним или несколькими напарниками (любой набор зарегистрированных вендоров — Kimi, Codex, Hermes, второй Claude-инстанс headless) по задаче пилота (DP.SC.154). При одном напарнике — turn-loop (пара реплик). При двух и более напарниках — round-loop (WP-509): каждый раунд высказывается каждый участник, с явной role-discovery фазой (раунды 0–2), валидацией реплик по 4 критериям, лимитом повторных вызовов и классификацией результата `agreed|partial|escalated|not-agreed`. Обнаруживает CONSENSUS/ESCALATE, после консенсуса — Decision Gate (зафиксировать vs реализовать → ревью → проверить → задеплоить), синтезирует report.md через Agent tool. ## Scope boundary — не подменяет решения, зарезервированные за пилотом (найдено 2026-07-07) > Peer-сессия пригодна для: технических решений, дизайна, code review, поиска компромисса между подходами, подготовки кандидатов к решению. > **НЕ пригодна** для решений, которые процесс явно закрепляет за человеком (например, R15 Валидатор в `/apply-captures` — accept/reject/defer кандидатов знания; R1 Стратег — приоритеты месяца). Согласие двух агентов между собой — не решение пилота, даже единогласное и хорошо обоснованное. > > Если задача внутри пир-сессии требует такого решения — писатель обязан остановиться и спросить пилота напрямую в текущем чате (не через turn-файл), прежде чем фиксировать результат. См. `.claude/skills/apply-captures/SKILL.md` раздел «R15 = живой пилот, не агент» и `${IWE_GOVERNANCE_REPO:-DS-strategy}/inbox/bugs/bug-2026-07-07-r15-decisions-bypassed-pilot.md` — прецедент, из-за которого добавлено это ограничение. ## Шаг 0. Режим Определить режим из `$ARGUMENTS`: - `--list` → прочитать `${IWE_GOVERNANCE_REPO:-DS-strategy}/sessions/00-index.md`, вывести таблицу. Файла нет → явно сообщить «индекс сессий ещё не создан (его создаст первая пир-сессия, шаг 1.3); прошлые сессии смотри в дереве sessions/YYYY-MM/DD/» — не молчать (issue #568). Стоп. - `--interrupt <id>` → перейти к **Шагу 5 (interrupt-режим)**. Стоп после. - `--finalize <id>` → перейти к **Шагу 6 (finalize-режим)**. Стоп после. - Иначе → новая сессия, продолжать к Шагу 0в. --- ## Шаг 0в. Выбор напарника(ов) (`--peer`) > **Добавлено 2026-07-29** (АрхГейт: вариант «параметризовать» против «отдельный скилл на вендора» — эволюционируемость заблокировала копипаст, см. `sessions/2026-07/2026-07-29-*-wp401-peer-vendor.md`). **Расширено 2026-07-30 (WP-509):** список из N>1 напарников вместо одного. Извлечь `--peer <vendor1,vendor2,...>` из `$ARGUMENTS` (через запятую, без пробелов). Нет флага → `PEER_VENDORS=(kimi)` (обратная совместимость с версией до 1.4.0). Один vendor без запятой → `PEER_VENDORS=(<vendor>)`, поведение идентично версии 1.4.0 — отдельного code path для одного напарника не заводить, `PEER_COUNT=${#PEER_VENDORS[@]}` управляет веткой (turn loop §3 при `PEER_COUNT==1`, round loop §3р при `PEER_COUNT>=2`). **Реестр вендоров** (единственное место маппинга — OwnerIntegrity; новый вендор добавляется только здесь): | `PEER_VENDOR` | Адаптер | `peer_agent` (meta.yaml) | Поддерживает `--add-dir` | Поддерживает `--model` | Особый флаг | |---|---|---|---|---|---| | `kimi` | `scripts/kimi-peer-adapter.sh` | `kimi-headless` | да | да | — | | `codex` | `scripts/codex-peer-adapter.sh` | `codex` | да | да | — | | `hermes` | `scripts/hermes-peer-adapter.sh` | `hermes` | **нет** | **нет** | `--session-id <id>` вместо | | `claude` | `scripts/claude-peer-adapter.sh` | `claude-code-headless` | **нет** | да | text-only; контекст в stdin | Каждый элемент `PEER_VENDORS` валидировать отдельно. Неизвестный `PEER_VENDOR` в списке → СТОП **на этом элементе, не на всём списке**: сообщить пилоту, какой конкретно vendor не распознан («Напарник `<vendor>` не зарегистрирован. Известные: kimi, codex, hermes, claude.»), предложить продолжить с оставшимися распознанными или прервать целиком. Добавление нового вендора — правка таблицы выше + написание `<vendor>-peer-adapter.sh` по контракту §0в.1. **§0в.1 Общий контракт адаптера** (для добавления нового вендора): stdin = полный промпт хода (Bash pipe, не inline `echo` — inline попадает в командную строку и хук B7.7c может ложно заблокировать повторные вызовы); stdout = одна реплика с frontmatter; exit `0` = OK, `1` = general error, `2` = content-filter/PII violation, `3` = PII hard block, `5` = уже идёт сессия (pidfile lock). Для `claude` действует усиленный контракт WP-458: `--add-dir` запрещён, peer получает только минимальную текстовую проекцию в stdin и не имеет файловых или shell-инструментов. Коды 2-5 — не обязательны для нового адаптера, но `0`/`1` обязательны (Шаг 3.1/3р.1 проверяет `exit ≠ 0` как «напарник не ответил»). Адаптеры лежат в репозитории шаблона (`$IWE_TEMPLATE`), не в личном governance-репо пользователя — governance-репо хранит только стратегические данные пилота и своих скриптов-адаптеров не содержит. Построить для каждого `vendor` в `PEER_VENDORS`: `ADAPTER_PATH[$vendor]="${IWE_TEMPLATE:-$HOME/IWE/FMT-exocortex-template}/<адаптер из таблицы>"` и `PEER_AGENT_ID[$vendor]="<peer_agent из таблицы>"` (bash associative arrays; порядок вызова = порядок `PEER_VENDORS`). Используются везде ниже вместо хардкода `kimi-peer-adapter.sh`/`kimi-headless`. (issue #830) **§0в.2 Проверка доступности vendor'ов (capability-check, WP-509 Ф5).** После построения `ADAPTER_PATH`, до анонса напарников: 1. **Дубликаты** в списке (`--peer codex,codex`) → отклонить: N участников не подменяется повторными вызовами одного напарника; сообщить пилоту, стоп до исправления списка. 2. **Проверка каждого `vendor`** (накопить доступных в `remaining`): ```bash remaining=() for vendor in "${PEER_VENDORS[@]}"; do ok=true if [[ ! -x "${ADAPTER_PATH[$vendor]}" ]]; then ok=false; fi # адаптер отсутствует/не исполняем if [[ "$vendor" == hermes ]] && ! command -v hermes >/dev/null 2>&1; then ok=false; fi # CLI hermes недоступен $ok && remaining+=("$vendor") || : # недоступный — кандидат на исключение (п. 3) done ``` Живой probe-вызов CLI НЕ делать: ошибки всплывут при вызове по контракту адаптера (exit ≠ 0, Шаг 3.1/3р.1); vendor-specific знания сверх `hermes` в скилл не добавлять (иначе Шаг 0в расходится с адаптерами). 3. **Недоступный vendor** → сообщить пилоту какой именно и почему (нет адаптера / нет CLI), предложить продолжить с оставшимися или прервать — тот же UX, что для незарегистрированного vendor (стоп на элементе, не на списке). 4. **Нормализация после исключений (атомарно):** ```bash PEER_VENDORS=("${remaining[@]}") PEER_COUNT=${#PEER_VENDORS[@]} round_order=("${PEER_VENDORS[@]}") ``` Режим выбирается заново: `PEER_COUNT>=2` → round-loop (§3р), `==1` → turn-loop (§3 — сценарий «запрошены codex,hermes, остался codex» идёт классическим диалогом вдвоём, не усечённым кругом), `==0` → безусловный стоп с сообщением пилоту. Анонсировать пилоту: ``` Напарник: <PEER_VENDOR> (<peer_agent>) # PEER_COUNT == 1, как раньше ``` или при `PEER_COUNT >= 2`: ``` Напарники (<PEER_COUNT>): 1. kimi (kimi-headless) 2. codex (codex) ``` --- ## Шаг 0б. Открытие (WP Gate — только для новой сессии) Найти WP по задаче: прочитать `${IWE_GOVERNANCE_REPO:-DS-strategy}/WP-REGISTRY.md` (grep по ключевым словам) и `${IWE_GOVERNANCE_REPO:-DS-strategy}/current/WeekPlan W{N}.md`. Анонс пилоту: ``` Открываю peer-сессию (DP.SC.154) Роль: Писатель (Claude) | Напарник(и): <PEER_VENDOR (PEER_AGENT_ID)>[, ...] Задача: <задача> РП: WP-NNN «<название>» | или: не найден в плане Метод: turn-loop ≤10 ходов (PEER_COUNT==1) | round-loop ≤rounds_limit раундов (PEER_COUNT>=2, WP-509) ``` Если РП **не найден** в плане недели → полный WP Gate Ритуал (`memory/protocol-open.md §Сессия`): объявить артефакт + дождаться подтверждения пилота → **только после «да» переходить к Шагу 1**. Если РП найден → продолжать без ожидания. **Определение рекомендуемой модели писателя (WP-394 Ф4.6):** ``` # informational — pilot selects model at Claude Code startup, not auto-applied here verification_class = из WP контекста или из описания задачи WRITER_MODEL_RECOMMENDED = "sonnet" # default: закрытые задачи с тестами/чёткой проверкой if verification_class in ("open-loop", "problem-framing"): WRITER_MODEL_RECOMMENDED = "opus" ``` Анонсировать пилоту: ``` Модель писателя (рекомендуется): <WRITER_MODEL_RECOMMENDED> (Класс задачи: <verification_class>. Выбери модель при запуске Claude Code.) ``` **Подсказка маршрутизации агента (WP-383, информационная — не enforcement).** По классу работы есть рекомендуемый инициатор/агент. Это подсказка пилоту, не блокировка: | Класс работы | Рекомендуемый агент | |--------------|---------------------| | Уборка / форматирование / триаж | Kimi (дёшево, быстро) | | Верификация shallow (формат/чеклист/drift) | Kimi | | Верификация deep (cross-file invariant) | Claude (statefulness) | | Реализация multi-file / tight-loop | Claude (держит состояние сессии) | | Дизайн / scope / планирование | сильная модель (Claude/Opus или Kimi) | **Trigger эскалации (лог, не блок):** если пилот 2 раза подряд выбирает агента вопреки подсказке — записать сигнал «routing-таблица устарела или классификация неверна» в `inbox/WP-383/routing-drift.log` (создать при первом срабатывании). Не блокировать выбор пилота. > Источник таблицы: `${IWE_GOVERNANCE_REPO:-DS-strategy}/inbox/WP-383/routing-design-v1.md §3`. Statefulness-пробел Kimi закрыт автопередачей git-diff в `kimi-peer-adapter.sh` (§8). --- ## Шаг 1. Инициализация ```bash SESSIONS_DIR="$HOME/IWE/${IWE_GOVERNANCE_REPO:-DS-strategy}/sessions" TODAY=$(date +%Y-%m-%d) MONTH=$(date +%Y-%m) DAY=$(date +%d) DAY_DIR="$SESSIONS_DIR/$MONTH/$DAY" mkdir -p "$DAY_DIR" NUM=$(printf "%02d" $(( $(find "$DAY_DIR" -maxdepth 1 -type d -name "${TODAY}-[0-9][0-9]-*" 2>/dev/null | wc -l | tr -d ' ') + 1 ))) ``` Slug = первые 4 латинских слова из задачи строчными буквами через дефис (не-латиница и дата убираются). Никакой даты в slug — она уже в SESSION_ID. Если латиницы нет → `session`. `SESSION_ID="${TODAY}-${NUM}-${SLUG}"` `SESSION_DIR="${DAY_DIR}/${SESSION_ID}"` **1.0 Session-guard open (WP-398, обязательно, ДО любых Write/Edit в сессии).** Синхронизирует пир-сессию с `session-guard.sh` Scope gate — без этого коммит на Шаге 4.5 будет заблокирован pre-commit хуком (mtime файлов сессии старше семафора). WP берётся из Шага 0б (найденный или «day-close»/«unknown», если РП не назначен): ```bash IWE_AGENT=claude-code bash "${IWE_SCRIPTS:-$HOME/IWE/scripts}/session-guard.sh" open \ --wp "<WP-NNN из Шага 0б>" --agent claude-code --close-path peer-session \ --task "<задача одной строкой>" --slug "$SESSION_ID" ``` > **Не хардкодить `~/IWE/scripts/session-guard.sh`.** Пир-сессия 2026-07-31-16-wp484-new-order-cutover сначала внесла такой хардкод по образцу `day-close/SKILL.md`, но холодный ревью нашёл: у обычного пользователя шаблона `setup.sh` НЕ копирует корневую `scripts/` — существует только каталог скриптов внутри шаблона, и `${IWE_SCRIPTS:-...}` резолвится именно туда намеренно (issue #266, commit `835d5ea` — тот же хардкод уже один раз чинили этим фоллбэком). Хардкод в `day-close/SKILL.md` — недокументированный долг, ждущий той же поломки при промоции, не образец для копирования. Для author-mode расхождение реальное (FMT-копия `session-guard.sh` отстаёт от корневой на фиксы WP-484 Нить1) — но лечится синком `template-sync.sh` (с отдельного разрешения пилота, S-33) или личной правкой `~/.iwe-paths`, не хардкодом в файле, который промотируется всем пользователям шаблона. Если команда упала (exit ≠ 0) — не блокировать сессию: сообщить пилоту одной строкой «session-guard open не сработал (<причина>), продолжаю без семафора — на Шаге 4.5 возможна ручная разблокировка через touch/note-file» и идти дальше. Semaphore-файл session-guard создаёт СВОЙ ORZ-скаффолд-заготовку по пути `sessions/<MONTH>/<TODAY>-<CLEAN_SLUG>.md` — тот же путь, что закрывающий файл пир-сессии из Шага 4.4/4.5.0 (до 2026-08-03 эти два места ошибочно считали разные пути, см. пометку на Шаге 4.4); Шаг 4.5.0 дописывает в этот же файл финальное содержимое, а не создаёт новый. **1.1 Создать папку:** ```bash mkdir -p "$SESSION_DIR" ``` **1.2 Записать `meta.yaml`** (Write): ```yaml task_id: "" date: "<TODAY>" session_id: "<SESSION_ID>" start_time: "<ISO-8601 UTC>" end_time: "" writer_agent: "claude-code" personality: "<unassigned|UUID>" # WP-510 Патч 4, слой 3: writer_agent = конструктивная реализация; personality = какая ИИ-личность (если есть авторитетная запись в `current/AI Personalities Registry.md` для текущего хоста/раннера) вела сессию. Ищи по хосту/раннеру в реестре — не выдумывай; нет однозначного совпадения → "unassigned". Маршрутизирующая метка, не допуск к памяти. peer_agent: "<первый PEER_AGENT_ID из §0в>" # backward-compat (PEER_COUNT==1: единственный); полный список — peer_agents peer_cmd: "<первый PEER_VENDOR>-peer-adapter" # backward-compat; полный список — peer_cmds peer_agents: ["<PEER_AGENT_ID>", "..."] # WP-509: в порядке PEER_VENDORS, длина 1 при PEER_COUNT==1 (дублирует peer_agent); §4.2 читает ТОЛЬКО это поле для report.md peer: при PEER_COUNT>=2 peer_cmds: ["<vendor>-peer-adapter", "..."] # WP-509: та же длина/порядок, что peer_agents round_order: ["<vendor>", "..."] # WP-509: фиксированный порядок раунда (= PEER_VENDORS), только при PEER_COUNT>=2 write_token_holder: "writer_agent" # WP-509: держатель write token сейчас; default = writer_agent, меняется только через подтверждённый ACCEPT_HANDOFF (см. §3р.3) peer_model: "" status: "started" turns_count: 0 turns_limit: 10 # PEER_COUNT==1: лимит реплик, без изменений с v4 rounds_limit: 6 # WP-509: лимит раундов, применяется только при PEER_COUNT>=2, НЕ переопределяет turns_limit round_skips: {} # WP-509: {round_NN: [vendor, ...]} — кто не ответил/пропустил раунд; исключаются из требования "консенсус у каждого" в §3р.3 participant_status: {} # WP-509: {vendor: active|failed} — финальный статус участника после исчерпания попыток max_peer_attempts: 2 # WP-509: лимит повторных вызовов одного peer подряд peer_attempts: {} # WP-509: {vendor: N} — счётчик вызовов в текущем раунде peer_failures: [] # WP-509: [{round, vendor, reason}] — зафиксированные отказы участников handoff_history: [] # WP-509: [{round, from, to, reason}] — журнал OFFER_HANDOFF/ACCEPT_HANDOFF (write token, DP.SC.154 «Write token ≠ process_position») escalations_count: 0 extensions: [] result_path: "" task_description: "<задача>" implementation_pipeline: false review_iterations: 0 verify_status: "" deploy_shas: {} writer_model_recommended: "sonnet" # informational — pilot selects model at startup, not auto-applied; opus only for open-loop|problem-framing # Sequential role-discovery (WP-367) — заполняется во время Opening: # Если пилот задал явно — сразу заполни `roles`. # Если не задал — initiator в ход 0 заполняет `proposed_roles` в frontmatter 00-writer.md; # после согласования (ход 2) переноси в `roles`. roles: {} # финальное: {agent_id: [DP.ROLE.NNN, ...]} после consensus discovery_turns: 0 # сколько ходов ушло на role-discovery (не считается в turns_limit) # Двухосная модель (WP-367 Ф5, DP.SC.154 v4): ad_hoc_roles: {} # {role_name: {agent_id, rationale, first_used_turn}} — для каскада audit swap_history: [] # [{turn, from, to, reason}] — журнал SWAP_WRITER переходов ``` **Если пилот не назначил роли** при запуске сессии — initiator в ход/раунд 0 предлагает свою роль и роль **каждого** напарника (см. DP.SC.154 раздел «Opening сессии: Sequential role-discovery», при `PEER_COUNT>=2` — раздел «Role-discovery для N>2»). Discovery-ходы/раунды (0-2) **не входят** в `turns_limit`/`rounds_limit`. **In-session ad-hoc role signal** (DP.SC.154 v4, каскад Pack-расширения уровень 1). При использовании ad-hoc роли (нет в Pack `DP.ROLE.NNN`/`MIM.R.NNN`/`VR.R.NNN`) агент **обязан сразу** объявить пилоту — формат: ``` Беру ad-hoc роль «<имя>». В каталоге Pack такой нет. Обязанности: <одной строкой>. Метод: <одной строкой>. Предлагаю создать РП на формализацию (~30 мин: passport + scenarios + templates). Выбери: А. Создать сейчас → отдельный РП «pack-gap-<имя>» через create-wp.sh. Б. Отложить → продолжу как ad-hoc, сторож напомнит при Week Close. ``` Запись в `meta.yaml.ad_hoc_roles` идёт независимо от выбора (для back-up на уровне 2 — Week Close audit). Если пилот выбрал А — после сессии писатель открывает отдельный РП и делает формализацию. **1.3 Добавить строку в `sessions/00-index.md`** сверху таблицы (первая строка таблицы после `|---|`). Колонка «Агенты» — при `PEER_COUNT>=2` напарники соединяются через `+`: > **Файла нет — создать его сейчас** (issue #568: установка индекс не создаёт, и до этой правки шаг молча не исполнялся — восемь сессий подряд без единой записи). Временная мера до РП-526 (семейство MC переведёт индекс на snapshot-механику — при миграции этот блок удалить). Создаваемый файл обязан честно объявлять свою неполноту прямо в себе (не в stdout — вывод теряется, а файл читают через недели): > ```bash > IDX="${IWE_GOVERNANCE_REPO:-DS-strategy}/sessions/00-index.md" > if [ ! -f "$IDX" ]; then > mkdir -p "$(dirname "$IDX")" > { > echo "# Индекс пир-сессий" > echo "" > echo "<!-- index regenerated on $(date +%Y-%m-%d): created empty by peer-conversation step 1.3 (issue #568) — sessions before this date exist on disk but are NOT backfilled here; the folder tree sessions/YYYY-MM/DD/ is the authoritative record -->" > echo "" > echo "| Дата | Сессия | Задача | Агенты | Ходы | Эскалации | Статус | Отчёт |" > echo "|------|--------|--------|--------|------|-----------|--------|-------|" > } > "$IDX" > fi > ``` ``` | <TODAY> | <SESSION_ID> | <задача ≤50 симв> | claude-code / <PEER_VENDOR1>[+<PEER_VENDOR2>...] | 0 | 0 | started | — | ``` --- ## Шаг 2. Реплика писателя 00-writer.md Записать `${SESSION_DIR}/00-writer.md` (Write): ```markdown --- turn: 0 role: writer agent_id: claude-code timestamp: <ISO-8601 UTC> consensus: none --- <Моя начальная позиция — анализ задачи, тезисы, конкретные вопросы к напарнику(ам). При `PEER_COUNT>=2` — предложить содержательную роль каждому напарнику отдельно (не только «роль для группы»), с обоснованием на каждого (DP.SC.154 «Role-discovery для N>2»). НЕ пересказ задачи. Позиция с аргументами.> ``` Показать пилоту краткое резюме: что написал в 00-writer.md. --- ## Шаг 2.5. Role-discovery для N>2 (PEER_COUNT >= 2, WP-509) > **Пропустить, если PEER_COUNT == 1.** Для двух участников discovery сводится к предложению ролей в 00-writer.md и согласованию в turn-loop. После 00-writer.md запустить **раунды 0–2**, которые **не входят** в `rounds_limit`. ### 2.5.1 Раунд 0 — писатель предлагает роли В 00-writer.md (Шаг 2) писатель уже предлагает содержательную роль каждому напарнику. Дополнительно записать в frontmatter `00-writer.md`: ```yaml proposed_roles: { "<PEER_AGENT_ID>": "<role_name>", ... } suggested_initiator_role: "<role_name>" ``` ### 2.5.2 Раунд 1 — каждый peer подтверждает или спорит роль Вызвать каждого напарника по порядку `round_order` с промптом, аналогичным §3р.1, но с единственной задачей: - прочитать 00-writer.md; - согласиться с предложенной ролью, предложить правку или запросить уточнение у пилота; - явно подтвердить, что понимает ограничение «ответ только в stdout, никаких файловых операций в SESSION_DIR». Формат реплики: ```yaml --- turn: 0 role: peer agent_id: <PEER_AGENT_ID> content_role: <согласованная/предложенная роль> process_position: peer timestamp: <ISO-8601 UTC> consensus: none role_accepted: true | false | clarify --- <Обоснование принятия роли или запрос уточнения> ``` Если `role_accepted: clarify` — писатель уточняет у пилота и повторяет раунд 1 только для этого участника (не считается отдельным раундом discovery). ### 2.5.3 Раунд 2 — фиксация agreed_roles Писатель записывает `01-writer.md` (turn: 0, role: writer) с итоговой таблицей ролей: ```yaml --- turn: 0 role: writer agent_id: claude-code consensus: none agreed_roles: { "<PEER_AGENT_ID>": "<role_name>", ... } writer_role: "<role_name>" discovery_turns: 1 # сколько дополнительных проходов раунда 1 потребовалось --- ``` Обновить `meta.yaml`: ```yaml roles: { "claude-code": ["<writer_role>"], "<PEER_AGENT_ID>": ["<role_name>"], ... } discovery_turns: <N> ``` Только после этого переходить к **Шагу 3р (ROUND=1)** с содержательными раундами. --- ## Шаг 3. Turn loop (PEER_COUNT == 1) > **При `PEER_COUNT >= 2` — пропустить этот шаг целиком, перейти к Шагу 3р (round loop, WP-509).** Этот шаг не менялся с версии 1.4.0 — один напарник, поведение идентично. Переменные: `TURN=1`, `ESCALATIONS=0`, `DONE=false`. ### 3.1 Вызов напарника Прочитать все предыдущие реплики из `SESSION_DIR` в порядке нумерации. Составить промпт: ``` Ты — напарник (peer agent) в диалоговой сессии (DP.SC.154). Сессия: <SESSION_ID> Ход: <TURN> из 10 Задача: <задача> Контекстная проекция ниже — единственный источник о сессии. Не читай файлы и не используй инструменты: <минимальная текстовая проекция предыдущих реплик и проверяемых фактов> Напиши реплику в stdout с frontmatter: --- turn: <TURN> role: peer agent_id: <PEER_AGENT_ID> timestamp: <ISO-8601 UTC> consensus: none | proposed | reached | escalate --- <Твой ответ> Правило критика: найди ХОТЯ БЫ ОДИН тезис или допущение писателя, с которым не согласен. Не сдавайся после первого возражения — держись аргументированно. Если всё действительно ОК — объясни почему конкретно, не просто «согласен». Маркеры (строго в начале строки): CONSENSUS: <резюме> — если считаешь что договорились ESCALATE_TO_USER: <причина> — если писатель игнорирует существенное возражение ``` Вызов напарника через Bash — флаги зависят от вендора (таблица §0в): ```bash PEER_FILE="${SESSION_DIR}/$(printf '%02d' $TURN)-peer.md" if [ "$PEER_VENDOR" = "hermes" ]; then # hermes-peer-adapter.sh не принимает --add-dir/--model — свой --session-id. # НЕ путать с $SESSION_ID пир-сессии: адаптер всегда шлёт переданный --session-id # как `--resume <id>` в hermes CLI — на самом первом вызове этой пир-сессии # у Hermes ещё не существует диалога с ID пир-сессии (это два разных # пространства идентификаторов), `hermes chat --resume <несуществующий>` # падает молча (set -euo pipefail в адаптере гасит его же диагностику до того, # как она успевает напечататься) — найдено живьём 2026-07-31/08-01, WP-484/WP-509. # Родной session_id Hermes читаем С ДИСКА (последний уже записанный NN-peer.md), # не из shell-переменной — агент делает каждый вызов отдельным Bash-тулом, и # переменные не переживают границу между вызовами (найдено code review 01.08). HERMES_LAST_ID=$(grep -h "^session_id: " "${SESSION_DIR}"/[0-9][0-9]-peer.md 2>/dev/null | tail -1 | sed 's/^session_id: //') if [ -z "$HERMES_LAST_ID" ]; then echo "<промпт>" | bash "$ADAPTER_PATH" > "$PEER_FILE" 2>/dev/null else echo "<промпт>" | bash "$ADAPTER_PATH" --session-id "$HERMES_LAST_ID" > "$PEER_FILE" 2>/dev/null fi else printf '%s\n' "<промпт с минимальной текстовой проекцией>" | bash "$ADAPTER_PATH" \ > "$PEER_FILE" 2> "${PEER_FILE%.md}.err" fi ``` Если файл пустой или exit ≠ 0 → сообщить пилоту: «<PEER_VENDOR> не ответил. Повторить или прервать?» ### 3.2 Показать пилоту Прочитать `$PEER_FILE`. Вывести ключевые тезисы напарника (не всю реплику дословно — краткое резюме + цитаты ключевых позиций). ### 3.3 Проверить маркеры **ESCALATE_TO_USER:** Если `grep -q "^ESCALATE_TO_USER:" "$PEER_FILE"`: - Извлечь причину: `grep "^ESCALATE_TO_USER:" "$PEER_FILE" | sed 's/^ESCALATE_TO_USER: //'` - `ESCALATIONS += 1` - Записать `${SESSION_DIR}/escalation-$(printf '%02d' $((ESCALATIONS-1))).md`: ```markdown --- escalation_number: <N> turn: <TURN> timestamp: <now> reason: "<причина>" pilot_response: "" --- # Эскалация <N> (ход <TURN>) **Причина:** <причина> **Реплика напарника:** <PEER_FILE> **Ответ пилота:** (ввести ниже) ``` - Сообщить пилоту: «<PEER_VENDOR> эскалирует: <причина>. Нужно твоё решение.» - Дождаться ответа пилота, записать в `pilot_response` в escalation-файл. - Обновить `meta.yaml`: `escalations_count: <ESCALATIONS>` (Bash sed). **CONSENSUS:** Если `grep -q "^CONSENSUS:" "$PEER_FILE"`: - Извлечь резюме. - `DONE=true`, перейти к **Шагу 3.5 (Decision Gate)** — НЕ к Шагу 4 напрямую. ### 3.4 Реплика писателя Если `TURN >= 10` → `DONE=true`, перейти к Шагу 4. Написать `$(printf '%02d' $((TURN+1)))-writer.md` (Write): ```markdown --- turn: <TURN+1> role: writer agent_id: claude-code timestamp: <now> consensus: none --- <Моя реплика: ответ на аргументы напарника + учёт направления пилота> ``` `TURN += 1` → вернуться к 3.1. --- ## Шаг 3р. Round loop (PEER_COUNT >= 2, WP-509) > **Пропустить, если `PEER_COUNT == 1`** — там действует классический Шаг 3. Переменные: `ROUND=1`, `ESCALATIONS=0`, `DONE=false`. Порядок раунда = `PEER_VENDORS` в порядке `--peer` (записан в `meta.yaml.round_order` на Шаге 1.2). ### 3р.1 Вызов каждого напарника по порядку раунда `WRITE_TOKEN_HOLDER` — читать из `meta.yaml.write_token_holder` (default, записанный на Шаге 1.2, = `writer_agent`: писатель держит write token, пока не было ни одного `ACCEPT_HANDOFF`). Значение передаётся в промпт явно — напарники не обязаны сами читать `meta.yaml` в поисках держателя. Для `vendor` в `round_order` (по очереди, не параллельно — каждый следующий видит реплики предыдущих этого же раунда) составить промпт: ``` Ты — напарник (peer agent) в диалоговой сессии (DP.SC.154, N>2 участников). Сессия: <SESSION_ID> Раунд: <ROUND> из <rounds_limit> Задача: <задача> Текущий держатель write token: <WRITE_TOKEN_HOLDER> Для Claude: передай в его stdin минимальную текстовую проекцию всех нужных реплик и фактов этого раунда. Он не читает журнал сессии и не использует инструменты. Для остальных напарников применяй их собственный контракт. ВАЖНО: ответ только в stdout. Не используй свои файловые инструменты ни для одного файла в этой папке — это создаёт гонку с перехватом stdout и портит журнал сессии. Напиши реплику в stdout с frontmatter: --- turn: <ROUND> role: peer agent_id: <PEER_AGENT_ID> content_role: <согласованная роль или предложение, если ещё не согласована> process_position: peer timestamp: <ISO-8601 UTC> consensus: none | proposed | reached | escalate --- <Твой ответ> Правило критика: найди ХОТЯ БЫ ОДИН тезис, с которым не согласен — свой или другого напарника. Маркеры (строго в начале строки): PASS — если нечего добавить в этом раунде (не ошибка, явное право промолчать) CONSENSUS: <резюме> — если согласен с итогом ESCALATE_TO_USER: <причина> — если есть проигнорированное существенное возражение OFFER_HANDOFF: <agent_id> — <причина> — если хочешь предложить свой write token другому (только если ты сейчас держатель) ACCEPT_HANDOFF — если принимаешь предложенный тебе OFFER_HANDOFF REQUEST_HANDOFF: <причина> — необязывающая просьба к держателю ``` Вызов напарника через Bash (промпт — во временный файл, не inline `echo` — тот же B7.7c-риск, что в §0в.1): ```bash PEER_FILE="${SESSION_DIR}/$(printf '%02d' $ROUND)-peer-${vendor}.md" PROMPT_FILE=$(mktemp) # записать промпт (см. шаблон выше) в $PROMPT_FILE if [ "$vendor" = "hermes" ]; then # Тот же контракт и та же ловушка, что в Шаге 3.1 (turn-loop) — hermes не берёт # --add-dir, и переданный --session-id всегда трактуется как --resume. Родной # hermes session_id читаем С ДИСКА (последний уже записанный NN-peer-hermes.md), # не из shell-переменной — агент делает каждый вызов отдельным Bash-тулом, и # переменные не переживают границу между вызовами (найдено code review 01.08). HERMES_LAST_ID=$(grep -h "^session_id: " "${SESSION_DIR}"/[0-9][0-9]-peer-hermes.md 2>/dev/null | tail -1 | sed 's/^session_id: //') if [ -z "$HERMES_LAST_ID" ]; then cat "$PROMPT_FILE" | bash "${ADAPTER_PATH[$vendor]}" > "$PEER_FILE" 2>/dev/null STATUS=$? else cat "$PROMPT_FILE" | bash "${ADAPTER_PATH[$vendor]}" --session-id "$HERMES_LAST_ID" > "$PEER_FILE" 2>/dev/null STATUS=$? fi else if [ "$vendor" = "claude" ]; then cat "$PROMPT_FILE" | bash "${ADAPTER_PATH[$vendor]}" > "$PEER_FILE" 2> "${PEER_FILE%.md}.err" else cat "$PROMPT_FILE" | bash "${ADAPTER_PATH[$vendor]}" --add-dir "$SESSION_DIR" > "$PEER_FILE" 2>/dev/null fi STATUS=$? fi rm -f "$PROMPT_FILE" ``` **3р.1а Валидация реплики (WP-509)** Применить **перед** записью в `round_skips` или консенсус. Реплика считается `valid`, если все 4 проверки прошли (с исключением для discovery-раундов, см. ниже): 1. **По предмету:** файл не пуст, frontmatter распарсен, тело содержит ответ на задачу раунда (не только формальные фразы / повтор условия). 2. **Роль исполнена:** - В **discovery-раундах 0–2** (`PEER_COUNT >= 2`): `content_role` совпадает с ролью, предложенной в `00-writer.md.proposed_roles` для этого `agent_id`, либо peer явно предлагает другую ad-hoc роль с обоснованием. - В **содержательных раундах** (`ROUND >= 1` после §2.5.3): `content_role` совпадает с ролью, согласованной в `meta.yaml.roles` для этого `agent_id`; если в реплике предлагается ad-hoc роль — см. §1.2 «In-session ad-hoc role signal». 3. **Нет побочных действий:** в stdout-ответе нет маркеров вида `FILE_WRITTEN`, `MEMORY_UPDATED`, `TOOL_CALLED` и т.п.; кроме того, **не проверять файловую систему напрямую** — адаптер гарантирует изоляцию, но агент-участник может декларировать действия в тексте. 4. **Непротиворечивость фактам:** если реплика ссылается на конкретный факт (SHA, имя файла, статус РП, commit), проверить его по доступным источникам; при несовпадении — `failed` с причиной `fact_mismatch`. Если реплика `invalid`: - Инкремент `meta.yaml.peer_attempts[vendor]`. - Если `peer_attempts[vendor] < max_peer_attempts` — повторить вызов этого же peer в том же раунде с уточняющим промптом (причина invalid + контекст раунда). - Если `peer_attempts[vendor] >= max_peer_attempts` — зафиксировать `meta.yaml.peer_failures += [{round: ROUND, vendor: vendor, reason: <проверка>}]`, установить `meta.yaml.participant_status[vendor] = "failed"`, исключить vendor из требования консенсуса в §3р.3 (как `round_skips`, но с явным статусом `failed`). Пустой файл или `STATUS != 0` → этот напарник пропущен в этом раунде (не блокирует раунд, DP.SC.154 «Раунды вместо бесконечного круга»): записать `meta.yaml.round_skips.round_<NN>: [..., vendor]`, инкремент `peer_attempts[vendor]`; при исчерпании попыток — `participant_status[vendor] = "failed"` и `peer_failures += [...]`. ### 3р.2 Показать пилоту Резюме по КАЖДОМУ напарнику раунда (не только последнему) — кто что сказал, кратко. ### 3р.3 Проверить маркеры у всех реплик раунда Применяется к репликам напарников этого раунда (3р.1) **и**, при возврате из 3р.4, к реплике писателя того же раунда — обработка маркеров одинаковая для обеих ролей, разница только в том, что писатель пишет последним в раунде. - `ESCALATE_TO_USER` у любого участника (включая писателя) → тот же процесс, что в §3.3 (запись `escalation-NN.md`, пауза на ответ пилота). - `OFFER_HANDOFF`/`ACCEPT_HANDOFF`/`REQUEST_HANDOFF` у любого участника → при подтверждённом `ACCEPT_HANDOFF` (реплика-адресат отвечает на `OFFER_HANDOFF` предыдущей реплики) — append `{round, from, to, reason}` в `meta.yaml.handoff_history` И обновить `meta.yaml.write_token_holder` на нового держателя. `REQUEST_HANDOFF` без ответного `OFFER_HANDOFF` держателя — не меняет ничего, просьба зафиксирована в реплике и всё. - `CONSENSUS` — «консенсус раунда»: учитываются только участники со статусом `active` (не `round_skips` этого раунда и не `participant_status == failed`). `DONE=true`, если у каждого активного участника есть либо `CONSENSUS`, либо `PASS` без встречных возражений в теле реплики. Хотя бы один содержательный контраргумент без `CONSENSUS` у активного участника → раунд не закрыт. - Если все активные участники поставили `CONSENSUS`/`PASS` → `result_class` для report.md = `agreed`. - Если консенсус достигнут среди активных, но один или несколько участников имеют `participant_status == failed` (исчерпаны попытки в этом или предыдущих раундах) → `DONE=true`, но `result_class` = `partial`; отказавшиеся участники и причины фиксируются в `report.md §5`. - Если ни один участник не имеет `CONSENSUS`/`PASS`, но все технически ответили — раунд не закрыт, продолжаем (или `ROUND >= rounds_limit` → §3р.5). ### 3р.4 Реплика писателя Если 3р.3 уже установил `DONE=true` по репликам напарников этого раунда → писатель реплику в этом раунде не пишет, сразу к Шагу 3.5 (Decision Gate). Иначе — писатель отвечает (`$(printf '%02d' $ROUND)-writer.md`, тот же формат frontmatter, что в Шаге 2, плюс те же маркеры `CONSENSUS`/`ESCALATE_TO_USER`/`OFFER_HANDOFF`/`ACCEPT_HANDOFF`/`REQUEST_HANDOFF`, что и у напарников — писатель тоже может держать или передавать write token). Применить к этой реплике проверку маркеров из 3р.3. Если после этого `DONE=true` (писатель сам поставил `CONSENSUS`, приняв позицию раунда) → Шаг 3.5. Иначе, если `ROUND >= rounds_limit` (default 6) → к 3р.5 (спор без консенсуса). Иначе — `ROUND += 1` → 3р.1. ### 3р.5 Спор между 3+ позициями (после исчерпания rounds_limit без консенсуса) > Учитывать `participant_status`: участники со статусом `failed` не голосуют и не блокируют принятие решения оставшимися. Применить критерий обратимости (DP.SC.154 «Разрешение спора между 3+ позициями») **до** эскалации: - Решение обратимо и в пределах полномочий **активных** участников → писатель фиксирует позицию большинства среди `active` (2 из 3 или большинство при N>3), несогласие меньшинства — в `report.md` §3 «Отвергнутые альтернативы`, `DONE=true`, `result_class` = `partial` если есть `failed`-участники, иначе `agreed` → Шаг 3.5. - Решение необратимо, вне полномочий, ИЛИ сам вопрос обратимости спорен → `ESCALATE_TO_USER: rounds_limit exhausted, no consensus, irreversible or scope-disputed` → пауза на пилота. --- ## Шаг 3.5. Decision Gate (после консенсуса) > **Когда срабатывает:** `DONE=true` через CONSENSUS-маркер в Шаге 3.3 (турн-loop) или 3р.3 (round-loop) — не через `TURN >= 10`/`ROUND >= rounds_limit` без консенсуса, там сразу Шаг 3р.5 или Шаг 4. > **Зачем:** консенсус ≠ реализация. Это **легитимный choice-question** для пилота (выбор объёма работы, не yes/no на готовое решение). Исключение из P5 — пилот сам подтвердил: «здесь от меня нужно согласование» (триггер 2026-05-30, WP-367 Ф5). > **Обязательно:** перед запросом — **краткое резюме консенсуса на пальцах**, чтобы пилот мог осознанно выбрать. Запрос без резюме = механический «выберите А/Б» без понимания. Извлечь резюме консенсуса: `PEER_COUNT==1` → `grep "^CONSENSUS:" "$PEER_FILE" | sed 's/^CONSENSUS: //'`. `PEER_COUNT>=2` → пройти все `${SESSION_DIR}/$(printf '%02d' $ROUND)-peer-*.md` последнего раунда, взять резюме первого файла с маркером `CONSENSUS:` (если формулировки разошлись — писатель сводит их одним предложением сам, не берёт произвольно один голос). **Резюме на пальцах** — обязательная часть. Формат (без технических терминов, кодов, путей): ``` Консенсус достигнут. Что обсуждалось: <одной фразой> К чему пришли: <2-3 строки человеческим языком — суть решения> Что предлагается реализовать: <список изменений, по 1 строке каждое; без файлов, путей> Сколько займёт реализация: ~<N>h (включая ревью + smoke + deploy) Изменения у других пользователей: <если есть — что и как доставляется; если нет — «только локально»> ``` После резюме — choice question: ``` Что дальше? А. Только зафиксировать → Шаг 4 (report.md + commit + close). Реализация — отдельный РП/фаза при следующей сессии. Б. Реализовать сейчас → ревью → проверить → задеплоить → Шаг 4. ``` Дождаться ответа пилота. Записать выбор в `meta.yaml` (Bash sed): ```yaml implementation_pipeline: false | true ``` - **А** → `IMPLEMENTATION=false`, перейти к Шагу 4. - **Б** → `IMPLEMENTATION=true`, перейти к Шагу 3.6. **Default при молчании пилота:** Б (реализация сейчас), per правило 11 «финиш > отлог». Применять только если пилот явно не ответил в течение разумного времени (например, пропустил Decision Gate в скрипте). **Triggers automatic-defer** (без запроса пилоту — сразу А): 1. Реализация требует нового РП (новый scope, не покрытый текущим РП). 2. Требуется ArchGate (новое архитектурное решение системного уровня). 3. Контекст полностью переключился (другая часть системы; нужен новый framing). При срабатывании trigger — анонс пилоту с резюме (НЕ запрос), потом Шаг 4. --- ## Шаг 3.6. Implementation Pipeline (опциональный) > **Активируется:** только при `IMPLEMENTATION=true` в Шаге 3.5. > **Принцип:** writer применяет решение → cold-context Agent делает code review → writer фиксит → built-in `/verify` запускает smoke → deploy → секция «Реализация и проверка» в report-draft.md. > **Завершение:** в любой подшаг можно эскалировать к пилоту через `ESCALATE_TO_USER:` маркер (как в Шаге 3.3) — записать `escalation-NN.md`, дождаться ответа. ### 3.6.1 Implementation Анонс пилоту: ``` Реализация консенсуса <SESSION_ID> Файлы: <list of files with absolute paths> Репо: <repo names, separated by " · "> Метод: Edit/Write tools напрямую ``` Writer применяет изменения через Edit/Write. Запрещено: - Менять файлы вне анонсированного списка без нового анонса. - Делать commit на этом этапе (commit — только Шаг 3.6.5). Зафиксировать список изменённых файлов в переменной `CHANGED_FILES` (один путь на строку). ### 3.6.2 Code Review (cold-context) Переменные итерации: `REVIEW_ITER=1` при первом входе в 3.6.2, инкрементируется в 3.6.3 при Critical. Сохранять отчёт в `${SESSION_DIR}/review-$(printf '%02d' $REVIEW_ITER).md` (Write). Вызвать `Agent` (subagent_type: general-purpose) с явным чек-листом: ``` Agent( description: "Code review post-consensus", subagent_type: "general-purpose", prompt: """ Cold-context code review результатов peer-сессии <SESSION_ID>. Контекст консенсуса: <резюме из 3.5> Изменённые файлы: <CHANGED_FILES> Прочитай каждый файл (только указанные строки/функции, не весь файл если он большой). Проверь по чек-листу: 1. asyncio runtime: ищи `wait_for(coro)` без `shield` → coroutine reuse. Ищи fire-and-forget tasks которые читают/пишут одну строку БД из разных мест. 2. Shell ordering: function call ДО function definition. `set -u` соблюдён? 3. SQL race: cross-file writers в одну строку без атомарности (UPDATE ... RETURNING или SELECT FOR UPDATE). 4. Lock enforcing: при collision — `exit N` или `log WARN && continue`? Если advisory — это intentional или баг? 5. Контекст-специфика консенсуса: <дополни из резюме, если есть инварианты>. Верни отчёт в формате: ## Critical (must fix before deploy) - <file:line-range>: <issue> | fix: <conkr> ## High - ... ## Medium - ... ## OK (что проверено и норм) - ... Не предлагай рефакторинг или стиль — только runtime баги и нарушения чек-листа. """ ) ``` Сохранить отчёт ревьюера в `${SESSION_DIR}/review-NN.md` где `NN = printf '%02d' $REVIEW_ITER` (Write). ### 3.6.3 Review Outcome Показать пилоту краткое резюме отчёта (Critical + High count + один пример). **Если есть Critical:** - Применить фиксы (Edit) → инкремент `REVIEW_ITER += 1` → вернуться к 3.6.2 (новый review-NN.md). - Лимит итераций: 3. Если на 3-й итерации остался Critical → ESCALATE_TO_USER с приложением последнего review. **Если только High/Medium:** - Спросить пилота: «Есть N High и M Medium замечаний. Фиксить сейчас или после deploy?» - Записать решение в meta.yaml (`review_iterations: <N>`, `unresolved: <count>`). **Если только OK:** - Продолжить к 3.6.4. ### 3.6.4 Smoke Verification Вызвать built-in `/verify` через Skill tool: ``` Skill( skill: "verify", args: "Smoke test changes from peer-session <SESSION_ID>. Files: <CHANGED_FILES>. Consensus invariants to check: <list из 3.5
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.