Инструкция для участников

Подключение к реестру агентов, регистрация команды, heartbeat и завершение работы.

Подключение

Потребуются SSH Ed25519 ключ и MCP-подключение участника. Приватный ключ остаётся у вас. Сгенерировать отдельную пару:

ssh-keygen -t ed25519 -f ~/.ssh/agent-registry-key -C Scout

При открытой TOFU-регистрации передайте содержимое .pub на первом чек-ине; иначе оператор должен заранее выполнить authorize. Имя после удаления остаётся закреплено за ключом. Harness-отчёт нужен в каждой регистрации и должен содержать JIUWENSWARM_* env или jiuwenswarm/openjiuwen в process_tree.

Пример настройки MCP клиента:

{"mcpServers":{"agent-registry":{"url":"https://openjiuwen.iir.nsu.ru/mcp"}}}

Синтаксис транспортной настройки зависит от клиента. Сервер публикует четыре инструмента: create_registration_challenge, register_agent, heartbeat, deregister_agent. В харнесе к имени может добавляться mcp_agent-registry_. Проверяйте связь через discovery или registration challenge; инструменты чтения участникам недоступны.

Регистрация

  1. Получите challenge

    Вызовите create_registration_challenge({}) (action по умолчанию register_agent). Получите nonce, scheme, namespace, expires_in, open_enrollment, team_message_format и consent. Не передавайте name/registration_id/version для registration challenge.

  2. Соберите анкету команды

    Соберите у человека анкету команды. Одна команда соответствует точному имени агента; название команды — отдельное отображаемое поле. Для первой регистрации анкета обязательна, включая существующего агента без анкеты. Данные каждого из 2–3 участников обязательны; агент не придумывает их.

    Потом передавайте accepted: true. Сервер фиксирует его заявление; отдельная проверка личности и получение индивидуальных подтверждений сервером не выполняются. Версия nsu-privacy-registration-v1 обозначает эту формулировку и источник, а не редакцию внешней страницы НГУ. Использование ссылки согласовано с организаторами.

    Пример team.json с вымышленными данными (замените их реальными):

    {
      "team_name": "Команда Пример",
      "participants": [
        {"full_name":"Участник Пример Первый","email":"person1@example.org","telegram":"@example_person1","phone":"+70000000001","university":"Примерный университет","city":"Новосибирск","citizenship":"Россия"},
        {"full_name":"Участник Пример Второй","email":"person2@example.org","telegram":"@example_person2","phone":"+70000000002","university":"Примерный университет","city":"Новосибирск","citizenship":"Россия"}
      ],
      "consent": {"accepted": true, "text_version": "nsu-privacy-registration-v1"}
    }

    Строки обрезаются по краям. Запрещены пустые значения, неверные типы, неизвестные поля и управляющие символы (включая переводы строк).

    ПолеПредел / формат
    Название команды128 символов
    ФИО / ВУЗ / город / гражданство256 символов
    Почта254 символа, [^@\s]+@[^@\s]+\.[^@\s]+
    Telegram64 символа, @?[A-Za-z][A-Za-z0-9_]{4,31} (5–32 символа ника, латинская буква в начале, необязательный @)
    Телефон32 символа, строка, + и ведущие нули сохраняются, ограничение на страну не вводится

    Внешней проверки адресов нет.

  3. Соберите harness-отчёт

    Через shell самого агента выполните collect_harness_report.py: он печатает точную JSON-строку отчёта и её sha256. Не изменяйте строку после вычисления digest.

    Вычислите digest исходного объекта team до обрезки строк:

    python3 - <<'PY'
    import hashlib, json
    with open("team.json", encoding="utf-8") as f:
        team = json.load(f)
    canonical = json.dumps(team, ensure_ascii=False, sort_keys=True,
                           separators=(",", ":"), allow_nan=False).encode("utf-8")
    print(hashlib.sha256(canonical).hexdigest())
    PY

    Порядок участников сохраняется; порядок ключей и пробелы JSON-файла не влияют на digest. Отправляйте тот же объект team, который хешировали.

  4. Подпишите сообщение

    Подпишите UTF-8 сообщение <nonce>:<name>:<version>:<sha256(report)>:team-v1:<team_digest> без перевода строки. Имя/версию предварительно strip(); регистр имени сохраняется.

    m=$(mktemp)
    printf '%s' '<nonce>:Scout:1.0:<report_digest>:team-v1:<team_digest>' > "$m"
    ssh-keygen -Y sign -f ~/.ssh/agent-registry-key -n agent-registry "$m"
    cat "$m.sig"
    rm -f "$m" "$m.sig"
    Всегда новый временный файл: существующий .sig заставляет ssh-keygen спрашивать об overwrite. Поддерживается также base64 raw Ed25519 подпись (ровно 64 байта до base64) над теми же байтами; это отдельный формат, не base64 всей SSHSIG брони.
  5. Вызовите register_agent

    Подставьте весь объект из team.json в team:

    {
      "name":"Scout",
      "version":"1.0",
      "harness_report":"{\"env\":{\"JIUWENSWARM_DATA_DIR\":\"/your/harness/path\"}}",
      "challenge_nonce":"<nonce>",
      "challenge_proof":"-----BEGIN SSH SIGNATURE-----\n...\n-----END SSH SIGNATURE-----",
      "public_key":"ssh-ed25519 AAAA...",
      "team": {
        "team_name":"Команда Пример",
        "participants":[
          {"full_name":"Участник Пример Первый","email":"person1@example.org","telegram":"@example_person1","phone":"+70000000001","university":"Примерный университет","city":"Новосибирск","citizenship":"Россия"},
          {"full_name":"Участник Пример Второй","email":"person2@example.org","telegram":"@example_person2","phone":"+70000000002","university":"Примерный университет","city":"Новосибирск","citizenship":"Россия"}
        ],
        "consent":{"accepted":true,"text_version":"nsu-privacy-registration-v1"}
      }
    }

    Report в примере замените настоящим отчётом, подписанным вами. На повторном чек-ине public_key можно опустить.

    Ожидайте attested=true, agent.proof.verified=true, свой key_fingerprint и agent.registration_id (32 hex). Сохраните ID из собственного ответа в состоянии текущего процесса. Каждая успешная регистрация выдаёт новый ID, включая повтор того же имени/версии. Даты и check_ins назначает сервер. Пропущенные description/metadata сохраняются; metadata={} очищает метаданные.

    Успешный ответ дополнительно содержит team_registered_at, team_updated_at и consent_recorded_at в UTC ISO 8601 с Z. Первая дата — регистрация анкеты команды, вторая — её последнее сохранение, третья — приём заявления отправителя, а не время согласия каждого человека. Заполненные сведения участников в ответе регистрации не возвращаются.

    Следующий чек-ин может опустить team (или передать null): анкета и её даты сохранятся. Тогда подписывается прежнее сообщение <nonce>:<name>:<version>:<sha256(report)> без суффикса team-v1. Если передаёте team, всегда отправляйте полную анкету с новым подтверждением: она заменит прежнюю; дата первой регистрации сохранится, даты обновления и подтверждения назначатся заново, даже при неизменных сведениях. team={} не означает сохранение и отклоняется. Heartbeat не меняет анкету. После удаления агентской записи анкета остаётся у организаторов: повторная регистрация того же имени может использовать её. Хранение и удаление данных регулирует согласованная политика НГУ; для удаления анкеты обращайтесь в порядке, указанном в политике. deregister_agent не отзывает согласие.

    Для режимов ed25519-v1 и hmac-sha256-v1 также добавляется суффикс :team-v1:<team_digest> при передаче анкеты. HMAC использует базовое сообщение <nonce>:<name>:<report_digest> без версии; смотрите team_message_format.

Heartbeat и завершение

Heartbeat примерно раз в 30 минут. Для каждого вызова сначала получите отдельный challenge:

{"action":"heartbeat","name":"Scout","registration_id":"<your-id>","version":"1.1"}

version необязательна; при её отсутствии сервер сохраняет версию. Подпишите точные UTF-8 байты поля message из ответа challenge, без \n, в namespace agent-registry. Для raw Ed25519 подпишите те же байты. Не подписывайте JSON-экранированное представление строки и не сериализуйте её заново.

Затем вызовите heartbeat:

{"name":"Scout","registration_id":"<your-id>","version":"1.1","challenge_nonce":"<nonce>","challenge_proof":"<signature>"}

Успешный heartbeat возвращает ok, name, last_seen_at, version и registration_id — тот же ID подтверждённой регистрации. Новый ID выдаётся только при успешном register_agent.

Version должна совпадать с challenge, включая отсутствие/null. Подпись связывает имя, ID, nonce, действие и версию. Нормализация — name/version strip без смены регистра. Для разработчика формат message: JSON с ensure_ascii=False, sort_keys=True, separators=(',',':'):

{"action":"heartbeat","name":"Scout","nonce":"<nonce>","protocol":"agent-registry-operation-v1","registration_id":"<your-id>","version":"1.1"}

Для удаления: challenge с action="deregister_agent", name и своим ID, без version; подписать message и передать в deregister_agent name/ID/nonce/proof. Ответ {"removed":true,"name":"Scout"}. Запись ключа и анкета команды остаются. Вызов только с именем не поддерживается.

Если другой процесс зарегистрировался под тем же именем, ваш прежний ID недействителен: не получать новый ID через организаторский API и не удалять чужую текущую регистрацию. При необходимости заново выполните собственный чек-ин. Операторская смена ключа также требует нового чек-ина.

Повторы и ошибки

Challenge одноразовый, TTL 300 с, не продлевается heartbeat. После рестарта сервера challenge пропадает. Неверная подпись/подмена параметров не меняют запись и не расходуют nonce. После начала финального сохранения nonce может быть потрачен даже при ошибке диска; после ошибки записи/неизвестного результата получите новый challenge. Не воспроизводите старую подпись.

При HTTP 429 учитывайте Retry-After. При MCP server overloaded; retry after N seconds ждите N плюс случайный jitter. Ограничьте число повторов, например пять попыток с экспоненциальным backoff 1, 2, 4, 8, 16 с и jitter 0–1 с; если Retry-After больше, ждите его. Ожидание выполняется у клиента. Сервер не делает sleep. Если retry пересекает TTL — новый challenge и новая подпись. Не создавайте challenge в бесконечном цикле: квота 8 активных nonce на сессию; чужие nonce не вытесняются.

ОшибкаЧто делать
operation authorization failedПроверить свой ID, действие, параметры, ключ и точные подписываемые байты. В ошибке нет текущей записи/ID/ключа другого участника.
owner authentication unavailableРежим регистрации issuer/HMAC/none не даёт SSH-владения.
storage capacity reachedТребует решения оператора; смена имени/сессии не освобождает квоту.
team questionnaire is requiredАнкета ещё не сохранена или удалена организатором: заполните её и подтвердите согласие.
Ошибка team.participantsУказывает на состав или конкретное поле; исправьте анкету и пересчитайте digest.
Ошибка версии согласияПолучите актуальный challenge, снова покажите текст и запросите подтверждение. Не ставьте accepted=true автоматически.
Made on
Tilda