Files
invest-harness/designDocument/api/executor-api.md
T

8.6 KiB
Raw Blame History

Runner Protocol v1

Новый контракт Invest Harness для заменяемых исполнителей. Стандартный движок подключается адаптером; он не обязан понимать этот API напрямую. ExecutionSpec одинаков для удалённой и локальной реализации

Регистрация

Admin создаёт POST /v1/runner-pools/{poolId}/registration-grants с {expiresInSeconds:600}; ответ 201 {grant, expiresAt}. Grant одноразовый, не журналируется. Runner вызывает POST /v1/runners/register с {grant, name, protocolVersion:"1", capabilities}; ответ 201 {runnerId, poolId, credential, expiresAt}. Долгоживущая идентичность хранится в защищённом хранилище исполнителя; credential обновляется через POST /v1/runners/{id}/credentials/rotate с текущей идентичностью и отзывается через DELETE /v1/runners/{id} администратором

Capabilities: os, architecture, executionKinds:["oci"], features:["cancel-v1"], maxConcurrency. checkpoint-v1 и input-v1 опциональны. Профиль пула задаёт доверенную изоляцию и квоты отдельно от заявления capabilities

Выполнение

Метод и путь Запрос Ответ
POST /v1/runner-jobs/claim runnerId, claimRequestId, availableSlots, capabilitiesRevision 200 Assignment или 204
POST /v1/attempts/{id}/heartbeat leaseEpoch, sequence, progress? 200 {leaseUntil, executionToken, tokenExpiresAt, cancelRequested}
POST /v1/attempts/{id}/started leaseEpoch, sequence 200 Attempt
POST /v1/attempts/{id}/events leaseEpoch, events:[RunEvent] 200 {acceptedThrough}
POST /v1/attempts/{id}/uploads leaseEpoch, purpose, mediaType, sizeBytes, digest 201 UploadGrant
POST /v1/attempts/{id}/complete leaseEpoch, completionId, outcome, resultManifest?, error? 200 Attempt
POST /v1/attempts/{id}/suspend leaseEpoch, checkpointArtifactId, inputRequest 200 Run

Assignment: attemptId, runId, leaseEpoch, leaseUntil, executionSpec, attemptContext, executionToken, tokenExpiresAt. См. схему. Execution token scoped к Attempt. Запрос claim authenticated идентичностью Runner; последующие операции — execution token. Назначение создаётся сервером атомарно; один claim выдаёт максимум один Attempt, availableSlots не разрешает превысить maxConcurrency пула

Outcome: succeeded/failed/cancelled. Success требует ResultManifest и сохранённых объектов, даже если artifacts пуст. Error: code, message, retryableHint; решение о retry принимает платформа. Complete повторяется с тем же completionId и телом; другой payload после terminal state → 409. Поздние heartbeat и события с прежним epoch → 409 LEASE_EXPIRED. Повтор уже принятого complete с тем же completionId и телом возвращает сохранённый ответ даже после истечения lease, но никогда не меняет состояние. Для этого replay допускается с действующей идентичностью назначенного Runner; новый terminal report с устаревшим epoch запрещён. События могут доставляться повторно; уникальны по attemptId + eventId

Suspend допустим только для checkpoint-v1 + input-v1. InputRequest: id, schema, prompt, expiresAt. Платформа принимает checkpoint, переводит Run в waiting_input, отзывает lease; новый Attempt получает checkpointRef и проверенный ответ в AttemptContext, отдельно от ExecutionSpec. Без checkpoint протокол не обещает сохранение памяти процесса

Локальный процесс

Адаптер materialize-ит immutable ExecutionSpec в /input/execution.json, входные файлы — read-only /input/files; вывод — /output. Разрешённые secrets передаются отдельным каналом и не сериализуются в spec. Entrypoint — массив аргументов, не shell-строка. События JSONL поступают через отдельный pipe или файл адаптера, stdout/stderr считаются логами. Адаптер читает /output/result.json и загружает разрешённые артефакты; ссылки вне /output и symlink traversal запрещены

Соглашение путей относится к стандартному OCI-адаптеру. Другой Runner может реализовать иной транспорт при сохранении семантики ExecutionSpec и результатов. Локальный offline Runner не вызывает облачные claim/heartbeat, но ведёт эквивалентное локальное состояние

Минимальная проверка совместимости

Runner обязан показать: корректный success; failure; отказ при несовместимом spec; отмену; тайм-аут; повтор complete без дублей; отказ с просроченным lease; изоляцию файлов между задачами; корректный upload digest; отсутствие secrets в событиях. Resume проверяется только при соответствующей capability. Managed и customer-managed пулы отображаются раздельно

Восстановление потерянного ответа

claimRequestId уникален в пределах runnerId. Повтор claim с тем же ID и телом возвращает то же назначение (или исходный 204), не занимает новый слот. Дедупликация хранится 24 часа; перед повторным получением работы после reconnect Runner вызывает GET /v1/runners/{id}/assignments (200 {items:[Assignment]}) со своей идентичностью. Endpoint возвращает только активные назначения этого Runner и перевыпускает их короткие токены. Если lease уже утрачен, старую работу прекращают; восстановление не продлевает истёкший lease

Heartbeat продлевает lease и executionToken не дальше deadlineAt. Complete ID и хеш тела сохраняются до удаления Run. Sequence монотонен внутри потока событий; heartbeat/started имеют отдельный счётчик controlSequence (поле sequence в этих запросах), чтобы доставка логов не блокировала lease. Повтор с тем же номером и другим телом — 409. Конфликт события по eventId либо sequence также даёт 409. Клиент сериализует control-запросы

ResultManifest валидируется по схеме, а output — дополнительно по outputSchemaRef шаблона. InputRequest schema использует тот же ограниченный профиль, что формы настроек. Событие input_requested информационное; только успешный suspend меняет состояние

Повтор claim не возобновляет утраченную аренду: если сохранённое назначение уже неактивно, сервер возвращает 409 ASSIGNMENT_EXPIRED. Перед запуском из повторного ответа Runner проверяет leaseUntil по серверному времени. ClaimRequestId с иным телом возвращает 409. Поля runId в Assignment и вложенном ExecutionSpec должны совпадать; server validator проверяет это отдельно от JSON Schema