Скачать спецификацию OpenAPI:Скачать
API Brabo. Каждый endpoint возвращает JSON, кроме четырёх явно
задокументированных с другим content-type (два SSE, метрики
и OAuth-callback, который перенаправляет).
Access-токен — это EdDSA JWT на 15 минут, предъявляемый в
Authorization: Bearer <token>. Публичный ключ находится по адресу
GET /.well-known/jwks.json.
Refresh не является JWT: это непрозрачный токен, который живёт в
cookie httpOnly (brabo_refresh, Path=/auth, SameSite=Strict) и
никогда не появляется ни в одном теле ответа. POST /auth/refresh и
POST /auth/logout читают cookie и требуют, чтобы заголовок X-CSRF-Token
совпадал с cookie brabo_csrf.
Ротация обязательна. Каждый refresh расходует предъявленный токен и выдаёт другой. Повторное предъявление уже израсходованного — признак кражи, и оно отзывает ВСЁ семейство, включая законную сессию. Клиент, который отправляет два одновременных refresh, выводит из строя сам себя; используйте один вызов в полёте.
Маршруты /internal/* — это поверхность api ↔ engine, и они находятся вне
JWT: они требуют заголовок X-Brabo-Service-Token с общим
секретом. Токен пользователя не открывает ни один из них.
Формат NestJS по умолчанию: { "statusCode": number, "message": string | string[], "error": string }.
| код | когда |
|---|---|
400 |
некорректное тело — ValidationPipe работает с whitelist и forbidNonWhitelisted, поэтому неизвестное поле тоже приводит к ошибке |
401 |
нет токена, токен истёк или недействительные учётные данные |
403 |
аутентифицирован, но без роли, которую требует RBAC, или неверный сервисный токен |
404 |
ресурс не существует или невидим для вызывающего |
409 |
конфликт состояния — недопустимый переход сессии, ресурс уже существует |
429 |
лимит частоты или прогрессивная блокировка |
Маршруты аутентификации отвечают намеренно неразличимо для несуществующего email, неверного пароля и заблокированной учётной записи: то же тело, тот же статус, то же время. Именно это закрывает перебор учётных записей, и это не дефект диагностики.
Скользящее окно в Postgres: 300 запросов на пользователя и 600 на IP каждые 60 с,
настраивается. Маршрут @Public() и сервисный маршрут освобождены — /auth/*
защищает прогрессивная блокировка, а не лимит частоты.
Несуществующий email, неверный пароль и заблокированная учётная запись возвращают ровно один и тот же ответ 401 — то же тело, тот же статус.
| email required | string |
| senha required | string |
{- "email": "fulano@brabo.dev",
- "senha": "correct horse battery staple"
}{- "accessToken": "string",
- "expiresIn": 900,
- "locale": "pt-BR"
}Успех ведёт на WEB_ORIGIN/ с уже установленными cookie сессии; неудача — на WEB_ORIGIN/login?oauth_error=1, без указания причины.
| provider required | string Перечисление: "github" "gitlab" |
| code required | string |
| state required | string |
Предъявленный refresh расходуется. Повторное предъявление уже ротированного токена отзывает ВСЁ семейство и записывает событие безопасности — даже если причиной была двойная отправка клиентом.
{- "accessToken": "string",
- "expiresIn": 900,
- "locale": "pt-BR"
}Отвечает 202 и для нового адреса, и для уже зарегистрированного — ответ не раскрывает, существует ли учётная запись. Во втором случае ничего не создаётся, а владелец адреса получает уведомление.
| email required | string Email учётной записи. |
| senha required | string >= 12 characters Пароль. Минимум 12 символов; требований к заглавным буквам, цифрам или символам нет. |
| nome | string |
{- "email": "fulano@brabo.dev",
- "senha": "correct horse battery staple",
- "nome": "Fulano de Tal"
}{- "message": "If the address is available, we've sent an email."
}Отвечает 202 и для известного, и для неизвестного адреса. Это также путь для тех, кто был импортирован из Keycloak и ещё не задал пароль.
| email required | string |
{- "email": "fulano@brabo.dev"
}{- "message": "If the address is available, we've sent an email."
}Отзывает ВСЕ сессии пользователя. Не выдаёт токены: тот, кто сбрасывает пароль, отправляется на вход, чтобы компрометация email не означала захват учётной записи за один шаг.
| token required | string Одноразовый токен, полученный по email. |
| novaSenha required | string >= 12 characters |
{- "token": "string",
- "novaSenha": "another pretty long passphrase"
}Декларативный индекс gate потока (ADR 0054). Экран берёт отсюда, какие шаги существуют и кто оценивает каждый, вместо того чтобы повторять список в коде — именно так он раньше незаметно устаревал.
{- "version": 1,
- "gates": [
- {
- "id": "qa-verificada",
- "fluxo": "pr",
- "dono": "area-qa",
- "entrada": [
- "pr-aberta"
], - "entregavel": "veredito-qa",
- "aprovacaoHumana": true,
- "severidade": "block"
}
]
}Цикл gate, застрявший более чем на 2 ч, паркуется, а не перезапускается сам (ADR 0207). Это жест человека, который его возобновляет.
| projectId required | string |
| taskId required | string |
| gate required | string |
{- "ok": true
}Публичный, потому что приходит БРАУЗЕР пользователя от провайдера, без сессии api. Это не значит без ограничений: state проверяется HMAC, и без валидного вызов отклоняется. Никогда не отвечает JSON — всегда перенаправляет, потому что сырое тело ошибки при навигации в браузере было бы ужасным опытом.
| provider required | string Перечисление: "github" "gitlab" |
| code required | string |
| state required | string |
Основа для заявленного незакрытого вопроса blame (ФАЗА 26b) — интерфейса, аннотирующего редактор построчно, ещё нет. truncated предупреждает, когда файл превысил лимит аннотируемых строк.
| projectId required | string |
| path required | string Путь к файлу для аннотирования. |
| ref | string Ветка, тег или sha. Если отсутствует, используется ветка репозитория по умолчанию. |
{- "ref": "dev",
- "path": "apps/api/src/main.ts",
- "lines": [
- {
- "line": 12,
- "commitSha": "0f3e8181fbd010b10c78db17b90ecb35fb8cc89c",
- "author": "Daniel Souza",
- "authorDate": "2026-08-04T12:00:00.000Z",
- "summary": "fix(api): fix the credential resolver"
}
], - "truncated": false
}Основа для расширенного выпадающего списка (ФАЗА 26b) — сегодня селектор ref является простым текстовым полем. ahead/behind отсчитываются от ветки репозитория ПО УМОЛЧАНИЮ; null, когда провайдер не может это вычислить (честная деградация, а не ошибка). Это НЕ listBranches (который использует bootstrap Gitflow) — это отдельная операция, потому что обогащение стоит лишнего вызова провайдера НА КАЖДУЮ ВЕТКУ.
| projectId required | string |
{- "items": [
- {
- "name": "feature/x",
- "commitSha": "0f3e8181fbd010b10c78db17b90ecb35fb8cc89c",
- "protected": false,
- "ahead": 3,
- "behind": 0,
- "pullRequest": {
- "number": 42,
- "state": "open"
}, - "producedBy": {
- "agentId": "dev-pieces",
- "moduleId": "pieces"
}
}
], - "truncated": false
}UTF-8, обрезается на лимите байтов с truncated: true, если он превышен. Путь, выходящий за папку проекта, отклоняется с 400 центральной проверкой из RN-095 — никогда с 404, что приглашало бы к зондированию.
| projectId required | string |
| path required | string Путь к файлу. |
| ref | string Ветка, тег или sha. |
{- "ref": "dev",
- "path": "apps/api/src/main.ts",
- "content": "import { NestFactory } from '@nestjs/core';\n",
- "truncated": false,
- "bytes": 1284
}Основа для навигируемого списка (ФАЗА 26b) — сегодня diff доступен только по известному id (например, пришедшему из Одобрений); этот маршрут решает, как дойти до id, не зная его наизусть. truncated предупреждает, когда список превысил лимит на вызов.
| projectId required | string |
| state | string Перечисление: "open" "merged" "closed" Если отсутствует, перечисляются все состояния. |
{- "items": [
- {
- "id": "2401938475",
- "number": 42,
- "title": "fix(api): the agents area is born with the project",
- "author": "daneiel",
- "state": "open",
- "sourceBranch": "feature/x",
- "targetBranch": "dev",
- "updatedAt": "2026-08-04T12:00:00.000Z"
}
], - "truncated": false
}Нулевой patch отличает случай, когда провайдер не передал текст (бинарный файл или слишком большой патч), от случая, когда текст вернулся пустым. Слияние двух заставило бы экран говорить, что изменённый бинарный файл не изменился. Поле truncated предупреждает, когда список файлов превысил лимит.
| projectId required | string |
| pullRequestId required | string |
{- "pullRequestId": "pr-1",
- "files": [
- {
- "path": "apps/api/src/main.ts",
- "previousPath": null,
- "status": "modified",
- "additions": 12,
- "deletions": 3,
- "patch": "@@ -1,3 +1,4 @@\n+import x\n"
}
], - "truncated": false
}Поиск НЕ является операцией контракта git: он составлен из дерева и содержимого, и поэтому это единственное чтение, стоимость которого растёт с размером репозитория. Его ограничивают три бюджета — пройденные каталоги, открытые файлы и возвращённые совпадения, — а truncated говорит, что он остановился. filesScanned показывает понесённые затраты.
| projectId required | string |
| q required | string Запрос, от 2 до 200 символов. |
| ref | string Ветка, тег или sha. |
| path | string Поддерево для поиска. Сужение именно позволяет избежать обрезки. |
{- "ref": "dev",
- "path": "",
- "query": "NestFactory",
- "matches": [
- {
- "path": "apps/api/src/main.ts",
- "line": 12,
- "text": " const app = await NestFactory.create(AppModule);"
}
], - "filesScanned": 87,
- "truncated": true
}Не рекурсивно, по замыслу контракта: вкладка навигирует по требованию, а запрос всего дерева большого репозитория — именно тот усилитель трафика, который эта фаза запрещает. truncated предупреждает, когда уровень превысил лимит записей.
| projectId required | string |
| ref | string Ветка, тег или sha. Если отсутствует, используется ветка репозитория по умолчанию. |
| path | string Каталог для перечисления. Отсутствующий или пустой — корень. |
{- "ref": "dev",
- "path": "apps/api",
- "entries": [
- {
- "path": "apps/api/src/main.ts",
- "name": "main.ts",
- "type": "file",
- "size": 1284
}
], - "truncated": false
}Не перенаправляет: возвращает URL, чтобы клиент решил, когда отправить браузер. state подписан HMAC, и именно это не даёт подделать callback.
| projectId required | string |
| provider required | string Перечисление: "github" "gitlab" |
{
}СИНХРОННО: ответ приходит только после выполнения всего bootstrap (постоянные ветки, защиты, шаблоны) — за этим нет ни worker, ни очереди. Это ИДЕМПОТЕНТНО И ВОЗОБНОВЛЯЕМО, поэтому повторный вызов после сбоя продолжает с шага, на котором произошёл сбой. Следите за прогрессом и читайте причину сбоя через GET /projects/:id/git/bootstrap.
| projectId required | string |
| provider required | string Перечисление: "local" "github" "gitlab" |
| name required | string Имя репозитория у провайдера. |
| visibility required | string Перечисление: "public" "private" |
| namespace | string Организация или группа. Если опущено, репозиторий создаётся в личной учётной записи того, кто зарегистрировал учётные данные. |
{- "name": "checkout",
- "visibility": "private",
- "namespace": "acme"
}{- "repository": {
- "id": "01JC4Z0000REPOSITORIO000001",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "provider": "github",
- "externalId": "904837261",
- "defaultBranch": "main",
- "visibility": "private",
- "origin": "created",
- "provisionedBy": "01JC4Z0000USUARIO0000000001",
- "createdAt": "2026-07-23T10:00:00.000Z",
- "updatedAt": "2026-07-23T10:00:00.000Z"
}, - "bootstrap": {
- "step": "create_dev_branch",
- "status": "running"
}
}Проверяет доступ через getRepo и составляет ПЛАН (dry-run) того, что сделал бы bootstrap, — недостающие ветки, недостающие защиты, файлы. В репозитории НИЧЕГО не выполняется: решите потом через plan/approve или plan/skip. Повторное подключение того же репозитория сходится (план генерируется заново без дублирования строки).
| projectId required | string |
| provider required | string Перечисление: "local" "github" "gitlab" |
| externalId required | string Идентификатор репозитория У ПРОВАЙДЕРА: |
{- "externalId": "acme/checkout"
}{- "repository": {
- "id": "01JC4Z0000REPOSITORIO000001",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "provider": "github",
- "externalId": "904837261",
- "defaultBranch": "main",
- "visibility": "private",
- "origin": "created",
- "provisionedBy": "01JC4Z0000USUARIO0000000001",
- "createdAt": "2026-07-23T10:00:00.000Z",
- "updatedAt": "2026-07-23T10:00:00.000Z"
}, - "plan": {
- "generatedAt": "2026-08-01T23:45:00.000Z",
- "steps": [
- {
- "step": "create_qa_branch",
- "actionType": "git_branch_create",
- "payload": {
- "branchName": "qa",
- "fromRef": "dev"
}
}
], - "diagnostics": [
- {
- "kind": "extra_branch",
- "detail": {
- "branchName": "develop",
- "protected": false
}
}
]
}, - "alreadyAdopted": false
}На каком шаге он находится, был ли сбой и где, и сколько было попыток. Пошаговые детали берутся из событий bootstrap.step_* выделенной сессии.
| projectId required | string |
{- "status": "provisioning",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "failedStep": null,
- "lastError": null,
- "attempts": 1
}protect_branches завершается сбоем в приватном репозитории на бесплатном тарифе, и мастер предупреждает об этом ЗАРАНЕЕ. Без этого выхода единственной кнопкой было «Попробовать снова», что всегда проваливается по той же причине, — а provision_failed заставляет панель перенаправлять проект обратно на страницу создания, делая его недоступным навсегда. Подтвердить можно только сбой при ЗАЩИТЕ: это последний шаг и единственный, сбой которого оставляет пригодный репозиторий. Блокировка merge продукта (RN-006) не зависит от защиты провайдера и продолжает действовать.
| projectId required | string |
{- "status": "provisioned"
}decision: null при наличии плана — это состояние, в котором НИЧЕГО не выполняется: ожидание одобрения или подключения как есть (RN-045).
| projectId required | string |
{- "plan": {
- "generatedAt": "2026-08-01T23:45:00.000Z",
- "steps": [
- {
- "step": "create_qa_branch",
- "actionType": "git_branch_create",
- "payload": {
- "branchName": "qa",
- "fromRef": "dev"
}
}
], - "diagnostics": [
- {
- "kind": "extra_branch",
- "detail": {
- "branchName": "develop",
- "protected": false
}
}
]
}, - "decision": null,
- "decidedAt": null,
- "decidedBy": null
}Одобрение — всё или ничего: одобрение отдельных шагов сломало бы каскад dev←main, qa←dev, rc←qa. Выполняется план, ЗАНОВО ВЫВЕДЕННЫЙ в момент выполнения, — равный показанному или меньше, но никогда не больше.
| projectId required | string |
| planGeneratedAt required | string <date-time>
|
{- "planGeneratedAt": "2026-08-01T23:45:00.000Z"
}{- "repository": {
- "id": "01JC4Z0000REPOSITORIO000001",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "provider": "github",
- "externalId": "904837261",
- "defaultBranch": "main",
- "visibility": "private",
- "origin": "created",
- "provisionedBy": "01JC4Z0000USUARIO0000000001",
- "createdAt": "2026-07-23T10:00:00.000Z",
- "updatedAt": "2026-07-23T10:00:00.000Z"
}, - "bootstrap": {
- "step": "create_dev_branch",
- "status": "running"
}
}Записывает решение и не трогает репозиторий. План остаётся сохранённым как доказательство того, что сознательно не было применено, а курсор bootstrap НЕ изменяется — ни один шаг не выполнялся.
| projectId required | string |
| planGeneratedAt required | string <date-time>
|
{- "planGeneratedAt": "2026-08-01T23:45:00.000Z"
}{- "repository": {
- "id": "01JC4Z0000REPOSITORIO000001",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "provider": "github",
- "externalId": "904837261",
- "defaultBranch": "main",
- "visibility": "private",
- "origin": "created",
- "provisionedBy": "01JC4Z0000USUARIO0000000001",
- "createdAt": "2026-07-23T10:00:00.000Z",
- "updatedAt": "2026-07-23T10:00:00.000Z"
}, - "bootstrap": {
- "step": "create_dev_branch",
- "status": "running"
}
}| projectId required | string |
{- "id": "01JC4Z0000REPOSITORIO000001",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "provider": "github",
- "externalId": "904837261",
- "defaultBranch": "main",
- "visibility": "private",
- "origin": "created",
- "provisionedBy": "01JC4Z0000USUARIO0000000001",
- "createdAt": "2026-07-23T10:00:00.000Z",
- "updatedAt": "2026-07-23T10:00:00.000Z"
}Выполняет select 1. Сообщает только, отвечает ли база данных; ни топологии, ни версии. Публичный, потому что вызывающий — kubelet, у которого нет токена.
{- "service": "api",
- "status": "ok",
- "timestamp": "2026-07-27T15:40:00.000Z",
- "details": { }
}НАМЕРЕННО НЕ касается базы данных. Если бы касался, медленный Postgres перезапустил бы ВСЕ реплики сразу и превратил деградацию в полный отказ. Pod убирается из балансировщика нагрузки через /health.
{- "service": "api",
- "status": "ok",
- "timestamp": "2026-07-27T15:40:00.000Z",
- "details": { }
}Не JSON: это text/plain в формате экспозиции. Публичный по необходимости (сборщик не несёт токена) и ограничен СЕТЬЮ — Ingress продакшена блокирует этот путь на границе, а NetworkPolicy разрешает только пространство имён мониторинга.
# HELP oban_queue_depth Oban queue depth # TYPE oban_queue_depth gauge oban_queue_depth{queue="default",state="available"} 0
Прокси GitHub Releases — platform принимает только linux-x64, linux-arm64, darwin-arm64. Без аутентификации: бинарный файл не секрет. Байты сверяются с checksums.txt той же Release до того, как что-либо будет отдано (ADR 0149, RN-525); маршрут НЕ проверяет подпись манифеста — это целостность, а не происхождение, и подпись проверяет install.sh.
Для установщика в одну строку, который работает там, где почтовый транспорт не настроен: сгенерированный .env не содержит почтовых переменных, MAIL_TRANSPORT падает в log, и обычная регистрация ждёт письма, которое никогда не придёт. Учётная запись рождается подтверждённой, потому что тот, кто запускает установщик, уже доказал нечто БОЛЕЕ СИЛЬНОЕ, чем контроль над почтовым ящиком, — он контролирует машину, .env и демон Docker. Обычная регистрация не затрагивается. Отклоняет с 409, если в установке есть ЛЮБОЙ пользователь: условие касается установки, а не этого email, поэтому восстановление при миграции глушит шаг той же проверкой. Пароль здесь никогда не генерируется и нигде не хранится, кроме как в виде хеша argon2id; личное рабочее пространство (RN-410) рождается в той же транзакции.
| email required | string Email владельца, введённый в TTY. Нормализуется до любой записи; именно его установщик записывает в свой маркер, поскольку email идентифицирует и не является секретом. |
| senha required | string Читается без эха и подтверждается установщиком. Используется и отбрасывается — никогда не записывается в |
| nome | string Отображаемое имя. Если опущено: имя личного рабочего пространства берётся из локальной части email (RN-410) — ничего не выдумывается. |
{- "email": "voce@exemplo.dev",
- "senha": "uma frase longa e minha",
- "nome": "Fulana de Tal"
}{- "userId": "01JC4Z0000USUARIO0000000001",
- "email": "voce@exemplo.dev",
- "workspaceId": "01JC4Z0000WORKSPACE00000001"
}Реестр ОПИСЫВАЕТ, а не исполняет: ни один gate не становится обязательным из-за него, и изменение поля здесь не меняет никакого поведения. Каждый gate говорит, где находится доказательство его прохождения (evidencia), потому что не каждое доказательство есть в журнале событий.
{- "version": 1,
- "gates": [
- {
- "id": "qa-verificada",
- "fluxo": "pr",
- "dono": "area-qa",
- "entrada": [
- "pr-aberta"
], - "entregavel": "veredito-qa",
- "verificacao": "script",
- "severidade": "block",
- "aprovacao_humana": true,
- "status": "active",
- "evidencia": { },
- "backlog": "ADR 0053"
}
]
}Если для того же name уже существует версия с ТЕМ ЖЕ хешем, новая версия не создаётся — возвращается существующая (попадание идемпотентности не меняет HTTP-статус: всегда 201, для простоты — тело идентично тому, что было бы создано).
| name required | string |
| version required | string |
| body required | string |
| hash required | string |
{- "name": "dev-agent-kickoff",
- "version": "3",
- "body": "You are the dev agent for the {{modulo}} module...",
- "hash": "sha256:9f2c..."
}{- "name": "dev-agent-kickoff",
- "version": "3",
- "body": "You are the dev agent for the {{modulo}} module...",
- "hash": "sha256:9f2c...",
- "active": true
}Конкретная version ищется по равенству; если опущена, ищется самая свежая версия active. 404, если шаблон (или запрошенная версия) не существует.
| name required | string |
| version | string Пример: version=3 |
{- "name": "dev-agent-kickoff",
- "version": "3",
- "body": "You are the dev agent for the {{modulo}} module...",
- "hash": "sha256:9f2c..."
}Для установщика в одну строку, сразу после создания первой учётной записи: локальному агенту нужны учётные данные, а единственные существовавшие были привязаны к ПРОЕКТУ — в установке, где проектов ещё нет. Пара генерируется НА МАШИНЕ, и сюда приходит только публичная половина; запишите возвращённый id в приватный JWK как kid (RN-475) — это единственная связь между файлом на диске и публичной половиной на сервере. userId в теле НЕТ НАМЕРЕННО: владелец — ЕДИНСТВЕННЫЙ пользователь установки, определяемый api, поэтому владение сервисным токеном никогда не означает выбор, чьи учётные данные выпустить. Регистрация ЗАМЕНЯЕТ: активные машинные ключи владельца отзываются в той же транзакции, так что переустановленная машина работает, а тысяча машинных ключей существовать не может. Ключи проекта (ADR 0118) никогда не затрагиваются.
| name required | string Имя, чтобы человек потом узнал эту МАШИНУ в списке ключей устройств каждого проекта, который она обслуживает. Не уникально. Установщик предлагает hostname; api ничего из него здесь не выводит. |
| publicKeyJwk required | string ПУБЛИЧНЫЙ JWK Ed25519 (RFC 8037), сериализованный как JSON. Пара генерируется на машине, и приватная половина никогда не передаётся — JWK с |
{- "name": "servidor-de-casa",
- "publicKeyJwk": "{\"kty\":\"OKP\",\"crv\":\"Ed25519\",\"x\":\"…\"}"
}{- "id": "01JC4Z0000CHAVE000000000001",
- "userId": "01JC4Z0000USUARIO0000000001",
- "name": "servidor-de-casa",
- "createdAt": "2026-09-12T12:00:00.000Z",
- "replacedKeyIds": [ ]
}Никогда не падает из-за одного провайдера: каждый отвечает за себя в отчёте, с причиной пропуска и источником сбоя, если он был. Провайдер, который не ответил, ПРОПУСКАЕТСЯ, а не помечается недоступным.
{- "porProvider": [
- {
- "provider": "openai",
- "descobertos": 3,
- "reencontrados": 1,
- "indisponibilizados": 0,
- "pulado": "sem_capability",
- "origemDaFalha": "infra",
- "detalhe": "openai responded with status 401"
}
]
}ТО ЖЕ дерево эпик → история → задача, что и во вкладке Backlog, через тот же сценарий использования (три чтения на проект, никогда N+1). Именно с ним PO видит эпик без историй и историю без задач, вместо того чтобы заново создавать уже существующее (RN-164).
| projectId required | string |
[- {
- "id": "01JC4Z0000EPICO000000000001",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "title": "Shopping cart",
- "description": "Everything the buyer does before paying.",
- "createdAt": "2026-07-25T08:30:00.000Z",
- "updatedAt": "2026-07-25T08:30:00.000Z",
- "stories": [
- {
- "id": "01JC4Z0000HISTORIA000000001",
- "epicId": "01JC4Z0000EPICO000000000001",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "title": "Add item to cart",
- "description": "As a buyer, I want to gather items before paying.",
- "rf": [
- "The cart accepts up to 50 items"
], - "rnf": [
- "The response stays under 200 ms at p95"
], - "businessRuleIds": [
- "RN-014"
], - "dod": [
- "Unit tests green"
], - "dor": [
- "Module defined"
], - "moduleIds": [
- "api"
], - "status": "ready",
- "proposedReady": true,
- "returnedReason": "DoD too generic — spell out the acceptance criteria.",
- "returnedAt": "2026-08-02T14:00:00.000Z",
- "archivedAt": null,
- "archivedReason": null,
- "createdAt": "2026-07-25T09:00:00.000Z",
- "updatedAt": "2026-07-25T09:40:00.000Z",
- "tasks": [
- {
- "id": "01JC4Z0000TAREFA00000000001",
- "storyId": "01JC4Z0000HISTORIA000000001",
- "title": "POST /cart/items endpoint",
- "description": "Accepts SKU and quantity; validates stock.",
- "status": "in_progress",
- "assignedTo": "dev-api",
- "blocked": false,
- "blockedReason": null,
- "blockedOrigin": null,
- "gateStatus": null,
- "gateCorrectionCount": 0,
- "module": "api",
- "createdAt": "2026-07-25T10:00:00.000Z",
- "updatedAt": "2026-07-25T12:30:00.000Z"
}
]
}
]
}
]Все artifact.business_rule из сессий проекта — не только текущей сессии, что было потолком контекста kickoff у PO, — с полным description и тем, какие истории уже ссылаются на каждое правило. uncoveredCount — это незакрытое: правило, которое не покрывает ни одна история (RN-164). Проект совсем без правил отвечает 200 с пустым списком: «ни одного правила ещё не собрано» — не ошибка.
| projectId required | string |
{- "rules": [
- {
- "id": "evt_01jc4z0000regra0000000001",
- "title": "Cart accepts at most 50 items",
- "description": "Above that, the addition is refused with 409.",
- "coveredByStoryIds": [
- "01JC4Z0000HISTORIA000000001"
], - "covered": true
}
], - "uncoveredCount": 2
}Вызывается только engine, когда Engine.Actions.TerminalExecutor решил, что команда должна выполняться внутри настоящего контейнера (execution_mode: container с ЗАРЕГИСТРИРОВАННОЙ строкой running). Проксирует в ContainerBrokerPort.exec (ADR 0130/0134) — последняя из пяти операций broker, получившая реального вызывающего. sucesso: false в ответе — никогда не выброшенная ошибка — это НОРМАЛЬНАЯ форма для отказавшего или недоступного broker: контейнер мог умереть или быть удалён извне между зарегистрированной строкой running и этим вызовом (RN-486, зарегистрированное и наблюдаемое никогда не сливаются), и это сбой команды, а не ошибка транспорта.
| projectId required | string |
| comando required | string Команда оболочки, интерпретируемая |
| cwd | string Рабочий каталог ВНУТРИ контейнера. Уже переведён из пути хоста engine (RN-492) — этот DTO не повторяет этот перевод. Если опущен, выполняется в |
| timeoutMs | number Таймаут в миллисекундах. Если опущен, используется значение broker по умолчанию. |
object AT-612: run in the task QA instance instead of the work container. Set by the engine from the gate context, never by the model. |
{- "comando": "npm test",
- "cwd": "/work/.worktrees/dev-api",
- "timeoutMs": 300000,
- "alvo": {
- "qa": true,
- "taskId": "e6e9d88a-9b63-468a-aec3-b7a11de27af8"
}
}{- "sucesso": true,
- "exitCode": 0,
- "output": "ok\n",
- "timedOut": false,
- "motivo": "o broker de container não respondeu em http://broker:8090: timeout"
}Идентичность проекта, режим выполнения и текущее решение Архитектора об образе. Broker перепроверяет всё это, прежде чем что-либо передать демону, — читать отсюда не то же самое, что доверять, и отказ называет поле. АБСОЛЮТНЫЙ путь не возвращается: источник bind разрешается демоном относительно файловой системы ХОСТА, и путь изнутри контейнера api молча смонтировал бы пустую папку. Передаётся localizacao (RN-503) — какой из двух корней broker использовать плюс относительный сегмент, который этот корень не покрывает.
| projectId required | string |
| papel | string Значение: "qa" |
| task | string |
{- "projectId": "f52be111-0000-4000-8000-000000000000",
- "projectSlug": "exp002",
- "workspaceId": "aaaaaaaa-0000-4000-8000-000000000000",
- "workspaceDirName": "exp002-f52be111",
- "executionMode": "container",
- "localizacao": {
- "tipo": "gerenciada",
- "segmento": "loja",
- "motivo": "o projeto está no modo \"runner\"…"
}, - "usuarioDaPasta": {
- "uid": 1000,
- "gid": 1000
}, - "imagem": {
- "image": "node:22-bookworm-slim",
- "network": "none",
- "resources": {
- "cpus": 2,
- "memoryMb": 4096,
- "pidsLimit": 512
}
}, - "imagemVersao": 3,
- "comandoDoApp": "string",
- "ports": [
- {
- "host": 8080,
- "container": 3000,
- "protocol": "tcp"
}
], - "dns": [
- "string"
], - "extraHosts": [
- {
- "name": "api.local",
- "ip": "10.0.0.5"
}
], - "qa": {
- "chave": "a1b2c3d4",
- "services": [
- { }
]
}
}Возвращает чистый origin (без встроенных учётных данных) и, для удалённого провайдера, токен ВЛАДЕЛЬЦА рабочего пространства (RN-058), расшифрованный на лету. Потребитель внедряет токен при каждом вызове и НИКОГДА не записывает его в файл — .git/config находится внутри папки, где RN-075 даёт dev agent автоматически одобренный доступ на чтение.
| projectId required | string |
{- "kind": "remote",
- "defaultBranch": "main",
- "token": "string",
- "username": "x-access-token"
}Вызывается только engine, после того как runner отправляет mirror_sync_result по каналу, — ТОТ ЖЕ путь, что и у workspace_confirm (runner → канал → engine → api), никогда не второй механизм: engine сообщает, а не пишет в таблицу. Это ТЕЛЕМЕТРИЯ, а не доменное событие: никогда не session_events (у раунда зеркала нет сессии, а session_id — NOT NULL, то же рассуждение, что сделало rag_searches таблицей) и никогда не proposed_action (запись зеркала — это конфигурация, объявленная пользователем, а не агент, просящий действовать). Исход определяет ok — никогда не наличие счётчика или сообщения: раунд, скопировавший 0 файлов, нормален и не должен быть неотличим от раунда, который не запускался. Запись никогда не ломает то, что измеряет: копирование уже завершено, когда это вызывается, и engine только логирует отказ здесь. Проект, у которого тем временем очистили назначение, ВСЁ РАВНО записывает — раунд состоялся, и его ошибка обычно и объясняет, что пошло не так.
| projectId required | string |
| ok required | boolean РЕАЛЬНЫЙ исход копирования, сообщённый локальным агентом после завершения, — никогда не оптимистичное «ok» до окончания раунда. |
| destination | string Назначение, которое раунд фактически разрешил (успех) или пытался использовать (сбой), на машине ПОЛЬЗОВАТЕЛЯ. ЗАМОРОЖЕНО в строке: показ текущего назначения проекта рядом со вчерашней копией был бы утверждением экрана о папке, которой тот раунд не касался. Опущено, когда сбой касается самого назначения. |
| filesCopied | number Обычные файлы, скопированные в этом раунде. |
| filesSkipped | number Записи, перечисленные git, которые не являются обычными файлами, — вложенный репозиторий (worktree dev agent — один из них), символическая ссылка или файл, исчезнувший между перечислением и копированием. |
| filesRefused | number Цели, отклонённые пофайловой защитой (символическая ссылка, выходящая за пределы назначения). Подсчитываются, никогда не замалчиваются. |
| error | string ИМЕНОВАННОЕ сообщение о сбое. При большой длине хранится обрезанным, с указанием на обрезку. Оно никогда не стирает последнюю успешную синхронизацию — какая строка ТЕКУЩАЯ, решается сравнением двух меток времени. |
{- "ok": true,
- "destination": "/home/you/mirrors/store",
- "filesCopied": 412,
- "filesSkipped": 3,
- "filesRefused": 0,
- "error": "o espelho não conseguiu listar o trabalho com o git: not a git repository"
}{- "recorded": true,
- "status": "synced"
}Одна запись на модуль ТЕКУЩЕГО module_map: что он потребляет (dependeDe, dependsOn карты) и что предоставляет (expoe, из текущего artifact.module_contracts Архитектора, или null, если ничего не объявлено). Это то, что читает dev agent вместо того, чтобы открывать worktree другого модуля (ADR 0200). Проект без контракта отвечает 200 со status: sem_contratos.
| projectId required | string |
{- "status": "sem_contratos",
- "version": 2,
- "modulos": [
- {
- "modulo": "game-session",
- "dependeDe": [
- "board-engine",
- "scoring"
], - "expoe": [
- {
- "tipo": "funcao",
- "assinatura": "placePiece(board: Board, piece: Piece, pos: Pos): Board",
- "descricao": "Returns a new board; never mutates the one passed in."
}
]
}
], - "contratosForaDoMapa": [ ]
}ТОТ ЖЕ отчёт, что и скрипт analise:funil (ADR 0089), — воронка сессия → коммит → PR → merge, реальное время выполнения и реальная частота развёртываний — через те же чистые функции и тот же запрос (apps/api/src/application/services/funil-metrics.ts), так что они никогда не расходятся для одного и того же факта. Закрывает docs/fluxo.yml (роль po, вход metricas-de-produto, ранее status: lacuna) (RN-407).
| projectId required | string |
{- "project": {
- "id": "01JC4Z0000PROJETO0000000001",
- "name": "exp003"
}, - "totalActionsConsidered": 18,
- "funnel": {
- "etapas": [
- {
- "etapa": "sessão produziu commit",
- "sessoes": 3,
- "taxaDaEtapaAnterior": 0.67
}
], - "sessoesComCommit": [
- "string"
], - "sessoesComPr": [
- "string"
], - "sessoesComMerge": [
- "string"
]
}, - "leadTimes": {
- "perSession": [
- {
- "sessionId": "01JC4Z0000SESSAO000000000001",
- "primeiroCommitEm": "2026-08-01T10:00:00.000Z",
- "primeiroMergeEm": "2026-08-01T12:00:00.000Z",
- "leadTimeMs": 7200000
}
], - "averageMs": 7200000
}, - "deploymentFrequency": [
- {
- "dia": "2026-08-01",
- "merges": 2
}
]
}Automatic, no proposal: it only reaches what carries the QA label (the broker guarantees it). Idempotent.
| projectId required | string |
| taskId required | string |
{- "sucesso": true,
- "status": "provisioning",
- "chave": "a1b2c3d4",
- "motivo": "string"
}Records the row, then asks the broker. sucesso: false is the NORMAL shape of a refused broker. At most ONE active instance per project: another task active -> 409 instancia_de_qa_ocupada. runner projects -> 409 qa_nao_suporta_runner.
| projectId required | string |
| taskId required | string |
{- "sucesso": true,
- "status": "provisioning",
- "chave": "a1b2c3d4",
- "motivo": "string"
}Вызывается только engine, после того как runner подключается и отправляет workspace_confirm по каналу. Runner — ИСТОЧНИК ИСТИНЫ для пути: api перезаписывает workspacePath тем, что он сообщил, после повторной лексической проверки (системный корень/пересечение с Brabo запрещены, даже если приходят от runner). Идемпотентно: повторное подключение с ТЕМ ЖЕ путём ничего заново не записывает.
| projectId required | string |
| path required | string Абсолютный путь, подтверждённый runner НА ХОСТЕ, — источник истины (RN-423). Здесь перед записью ЛЕКСИЧЕСКИ перепроверяется; недопустимый — 400, никогда не записывается. |
| sessionId | string <uuid> Самая свежая сессия проекта ( |
| actorId | string <uuid> Кто запросил тикет runner (владелец сокета, который подтвердил), — становится актором события |
{- "path": "/home/you/projects/store",
- "sessionId": "01JC4Z0000SESSAO00000000001",
- "actorId": "01JC4Z0000USUARIO0000000001"
}{- "verified": true,
- "workspacePath": "/home/you/projects/store",
- "changed": true
}The engine sends the task ids it still has in flight; every other active or failed instance is removed. Shared services and the QA network have no broker operation yet (declared gap).
| taskIdsEmVoo required | Массив из strings Task ids that still have a gate in flight; every other active instance is removed. |
| projectId | string <uuid> Limit the sweep to one project. |
{- "taskIdsEmVoo": [
- "string"
], - "projectId": "5a8591dd-4039-49df-9202-96385ba3eff8"
}{- "removidas": [
- "string"
], - "falhas": [
- {
- "taskId": "string",
- "motivo": "string"
}
]
}Тот же сценарий использования, что и у маршрута для человека, — никакого второго пути оценки. Неизвестный searchId/chunkId — это 400, который engine превращает в tool-result с ошибкой, чтобы модель исправилась (RN-061), но никогда не в падение.
| projectId required | string <uuid> |
| searchId required | string <uuid> |
| chunkId required | string <uuid> |
| verdict required | string Перечисление: "util" "irrelevante" |
| agent required | string Slug агента — голосующий. |
{- "projectId": "5a8591dd-4039-49df-9202-96385ba3eff8",
- "searchId": "9b1a67f9-9477-48e5-8a6c-11b70245e1d9",
- "chunkId": "90e1f3df-a8a1-438e-8dd5-10e45398d051",
- "verdict": "util",
- "agent": "qa"
}{- "searchId": "9b1a67f9-9477-48e5-8a6c-11b70245e1d9",
- "chunkId": "90e1f3df-a8a1-438e-8dd5-10e45398d051",
- "verdict": "util",
- "rank": 0
}Тот же механизм, что и POST /projects/:projectId/rag/search (маршрут для человека), в формате, который ожидает инструмент rag_search engine.
| projectId required | string <uuid> |
| query required | string |
| topK | number |
| sessionId | string <uuid> Сессия, в которой работает агент. Отсутствие означает, что у вызывающего не было сессии, — строка телеметрии всё равно записывается (RN-479), пропускается только повествование |
| agent | string Slug агента, записываемый как актор телеметрии ( |
{- "projectId": "5a8591dd-4039-49df-9202-96385ba3eff8",
- "query": "how the parallelism cap is decided",
- "topK": 5,
- "sessionId": "f6567dd8-e069-418e-8893-7d22fcf12459",
- "agent": "dev-lead"
}{- "searchId": "9b1a67f9-9477-48e5-8a6c-11b70245e1d9",
- "hits": [
- {
- "chunkId": "90e1f3df-a8a1-438e-8dd5-10e45398d051",
- "path": "docs/adr/0080-chat-rag-pipeline-indexacao.md",
- "chunk": "string",
- "score": 0,
- "excerpt": "string"
}
], - "degraded": true
}ЕДИНСТВЕННАЯ дверь, через которую агент касается git, терминала или расходов. permissions.json решает при создании, и deny побеждает любую автономию, предоставленную агенту.
| sessionId required | string |
| projectId required | string <uuid> |
| actionType required | string Перечисление: "terminal" "git_commit" "git_push" "pr_open" "spend" "git_repo_create" "git_branch_create" "git_branch_protect" "write_file" "open_adr_pr" "git_merge" "open_infra_pr" "instruction_patch" "parallelize" "raise_max_parallel" "propose_execution_plan" "assess_implementability" "container_start" "container_stop" "container_remove" "container_recreate" "container_start_via_runner" "qa_environment_start" |
required | object На этом маршруте всегда агент. |
required | object |
{- "projectId": "01JC4Z0000PROJETO0000000001",
- "actionType": "terminal",
- "actor": {
- "kind": "agent",
- "id": "dev-api"
}, - "payload": {
- "command": "pnpm test"
}
}{- "id": "01JC4Z8QK3M7YV2N5T9B0PXHRC",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "seq": 7,
- "actionType": "terminal",
- "payload": {
- "command": "pnpm test"
}, - "status": "pending",
- "resolvedPolicy": "require_approval",
- "actor": {
- "kind": "agent",
- "id": "dev-api"
}, - "decidedBy": null,
- "decidedAt": null,
- "rejectionReason": null,
- "executionResult": null,
- "createdAt": "2026-07-27T14:33:10.900Z",
- "updatedAt": "2026-07-27T14:33:10.900Z"
}Всё за один вызов, по образцу контекста Психолога. Участники приходят уже ИСКЛЮЧАЯ тех, кто отказался от профилирования, а решения пользователя в окне приходят отсюда, потому что их нет в журнале событий. Сами события engine читает прямо из Postgres — передавать их по HTTP было бы дороже, но не правильнее.
| projectId required | string |
{- "competencyCatalog": [
- "TypeScript",
- "Elixir",
- "Kubernetes"
], - "members": [
- {
- "userId": "01JC4Z0000USUARIO0000000002",
- "name": "Senior Dev",
- "email": "dev@brabo.dev",
- "role": "developer"
}
], - "queuedHypotheses": [
- {
- "queueId": "01JC4Z0000FILA00000000000001",
- "hypothesisId": "01JC4Z0000HIPOTESE000000001",
- "agenteAlvo": "dev-api",
- "hipotese": "The instructions don't say when the task is done.",
- "sugestao": "Add an explicit definition-of-done criterion.",
- "confiancaPercent": 72
}
], - "currentProfiles": [
- {
- "userId": "01JC4Z0000USUARIO0000000002",
- "competency": "TypeScript",
- "level": "advanced",
- "rationale": "Fixed three generic typing errors without help."
}
], - "instructions": [
- {
- "agent": "dev-api",
- "version": 4,
- "content": "# dev-api\n\n…"
}
], - "decisions": [
- {
- "actionType": "terminal",
- "status": "denied",
- "rejectionReason": "Command would delete the directory.",
- "decidedBy": "01JC4Z0000USUARIO0000000001",
- "decidedAt": "2026-07-27T13:00:00.000Z"
}
], - "windowFrom": "2026-07-27T12:00:00.000Z"
}Записывает запуск анамнеза с нулём профилей, чтобы следующий раунд начинался с windowTo, — без этого окно никогда не сдвигалось, и каждый тик платил за новый раунд по тому же материалу.
| sessionId required | string |
| projectId required | string <uuid> |
| windowFrom required | string <date-time> |
| windowTo required | string <date-time> |
| eventCount required | number |
{- "projectId": "01JC4Z0000PROJETO0000000001",
- "windowFrom": "2026-07-27T12:00:00.000Z",
- "windowTo": "2026-07-27T12:15:00.000Z",
- "eventCount": 12
}{- "runId": "01JC4Z0000RODADA00000000001",
- "profiles": [
- { }
]
}Артефакт — ЭТО событие artifact.c4_diagram: неизменяемое, версионированное и с автором, рядом с artifact.module_map. Требует текущего module_map — без него нечего рисовать на уровне Container (400).
| sessionId required | string |
| projectId required | string <uuid> |
| systemName required | string Имя системы/проекта для подписи диаграммы. |
| systemDescription | string |
Массив из objects (C4AtorInternalDto) Внешние акторы уровня Context (Simon Brown). Контейнеры уровня Container сюда НЕ входят: они берутся из текущего module_map проекта. |
{- "projectId": "5a8591dd-4039-49df-9202-96385ba3eff8",
- "systemName": "Brabo",
- "systemDescription": "Agent-orchestrated engineering platform.",
- "actors": [
- {
- "name": "User",
- "type": "person",
- "description": "Whoever operates the product via the web."
}
]
}{- "diagrama": {
- "systemName": "Brabo",
- "systemDescription": "Agent-orchestrated engineering platform.",
- "actors": [
- {
- "name": "User",
- "type": "person",
- "description": "Whoever operates the product through the web."
}
], - "contextDiagram": "C4Context\n title Context Diagram -- Brabo\n ...",
- "containerDiagram": "C4Container\n title Container Diagram -- Brabo\n ..."
}, - "version": 1
}completed (с вердиктом), failed (с источником) или dispensed (с обоснованием) — lead никогда не вызывает этот маршрут с промежуточным исходом: каждое делегирование рождается здесь уже разрешённым.
| sessionId required | string |
| projectId required | string <uuid> |
| area required | string Область, которая делегировала, — "qa" (Фаза 8b) или "infra" (Фаза 8c). Текст, а не перечисление. |
| taskId | string <uuid> Задача backlog за делегированием, если она есть. QA (Фаза 8b) всегда её отправляет; Infra (Фаза 8c) никогда — делегирование касается СЕССИИ, а не задачи. |
| leadAgent required | string |
| subagent required | string |
| status required | string Перечисление: "completed" "failed" "dispensed" |
| parecerArtifactId | string Id session_event |
| failureOrigin | string Перечисление: "infra" "modelo" "codigo" "politica" ИСТОЧНИК сбоя (ADR 0020/0038), никогда методом исключения, — обязателен, когда |
| failureReason | string |
| justification | string Почему lead решил НЕ делегировать — обязательно, когда |
{- "projectId": "01JC4Z0000PROJETO0000000001",
- "area": "qa",
- "taskId": "01JC4Z0000TASK00000000001",
- "leadAgent": "qa-lead",
- "subagent": "qa-automacao",
- "status": "completed",
- "parecerArtifactId": "evt_01jc4z0000parecer000000001",
- "failureOrigin": "infra",
- "failureReason": "iteration limit reached without emit_perf_seguranca_verdict",
- "justification": "story with no relevant performance NFR"
}{- "id": "01JC4Z0000DELEGACAO00000001",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": "01JC4Z0000SESSAO000000000001",
- "taskId": "01JC4Z0000TAREFA00000000001",
- "area": "qa",
- "leadAgent": "qa-lead",
- "subagent": "qa-automacao",
- "status": "completed",
- "parecerArtifactId": "evt_01jc4z0000parecer000000001",
- "failureOrigin": null,
- "failureReason": null,
- "justification": null,
- "createdAt": "2026-07-30T12:00:00.000Z"
}Один вызов со всем, что нужно промпту: вся история, разрешённые бизнес-правила и применимые ADR. module ограничивает ADR модулем этого dev; ADR без объявленного модуля СКВОЗНОЙ и приходит всегда. Если module опустить, приходит вся коллекция, что и нужно gate QA и SecOps при повторном использовании этого контекста.
| projectId required | string |
| taskId required | string |
| module | string Пример: module=api |
{- "task": {
- "id": "01JC4Z0000TAREFA00000000001",
- "storyId": "01JC4Z0000HISTORIA000000001",
- "title": "POST /cart/items endpoint",
- "description": "Accepts SKU and quantity; validates stock.",
- "status": "in_progress",
- "assignedTo": "dev-api",
- "blocked": false,
- "blockedReason": null,
- "blockedOrigin": null,
- "gateStatus": null,
- "gateCorrectionCount": 0,
- "module": "api",
- "createdAt": "2026-07-25T10:00:00.000Z",
- "updatedAt": "2026-07-25T12:30:00.000Z"
}, - "story": {
- "id": "01JC4Z0000HISTORIA000000001",
- "epicId": "01JC4Z0000EPICO000000000001",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "title": "Add item to cart",
- "description": "As a buyer, I want to gather items before paying.",
- "rf": [
- "The cart accepts up to 50 items"
], - "rnf": [
- "The response stays under 200 ms at p95"
], - "businessRuleIds": [
- "RN-014"
], - "dod": [
- "Unit tests green"
], - "dor": [
- "Module defined"
], - "moduleIds": [
- "api"
], - "status": "ready",
- "proposedReady": true,
- "returnedReason": "DoD too generic — spell out the acceptance criteria.",
- "returnedAt": "2026-08-02T14:00:00.000Z",
- "archivedAt": null,
- "archivedReason": null,
- "createdAt": "2026-07-25T09:00:00.000Z",
- "updatedAt": "2026-07-25T09:40:00.000Z"
}, - "businessRules": [
- {
- "title": "Cart accepts at most 50 items",
- "description": "Above that, the addition is refused with 409."
}
], - "adrs": [
- {
- "title": "0007 — Postgres queue instead of Redis",
- "content": "## Context\n\n…",
- "securityRelevant": false
}
], - "siblingTasks": [
- {
- "id": "01J0000000000000000000000",
- "title": "Expose POST /login returning a JWT",
- "status": "todo"
}
], - "moduleOpenTasks": [
- {
- "id": "01J0000000000000000000000",
- "title": "Expose POST /links",
- "status": "todo",
- "storyTitle": "Members create short links"
}
], - "moduleOpenTasksTotal": 0,
- "moduleContract": {
- "modulo": "board-engine",
- "expoe": [
- {
- "tipo": "funcao",
- "assinatura": "placePiece(board: Board, piece: Piece, pos: Pos): Board",
- "descricao": "Returns a new board; never mutates the one passed in."
}
]
}, - "primeiraDoModulo": true,
- "decisoes": [
- {
- "id": "string",
- "questionId": "string",
- "pergunta": "string",
- "decisao": "string",
- "decididaEm": "2019-08-24T14:15:22Z"
}
], - "decisoesDoProjeto": [
- {
- "id": "string",
- "questionId": "string",
- "pergunta": "string",
- "decisao": "string",
- "decididaEm": "2019-08-24T14:15:22Z"
}
]
}| sessionId required | string |
| projectId required | string <uuid> |
| title required | string |
| description | string |
{- "projectId": "01JC4Z0000PROJETO0000000001",
- "title": "Shopping cart",
- "description": "Everything the buyer does before paying."
}{- "id": "01JC4Z0000EPICO000000000001",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "title": "Shopping cart",
- "description": "Everything the buyer does before paying.",
- "createdAt": "2026-07-25T08:30:00.000Z",
- "updatedAt": "2026-07-25T08:30:00.000Z"
}Используется, чтобы ВОССТАНОВИТЬ историю разговорного агента и прочитать артефакты, нужные его kickoff. Эквивалентный маршрут для человека защищён RBAC; этот — сервисным токеном. latest=true возвращает ХВОСТ (всё ещё по возрастанию seq) и игнорирует afterSeq; types ограничивает страницу этими типами событий. Страница в любом случае ограничена 200 (ADR 0060).
| sessionId required | string |
| projectId required | string |
| afterSeq | string Пример: afterSeq=40 |
| limit | string Пример: limit=200 |
| latest | string Пример: latest=true Получает хвост журнала; игнорирует |
| types | string Пример: types=artifact.product_brief,artifact.business_rule Типы событий через запятую (не более 20). Только события этих типов учитываются в |
{- "items": [
- {
- "id": "01JC4Z8QK3M7YV2N5T9B0PXHRB",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "seq": 41,
- "type": "chat.message",
- "actor": {
- "kind": "agent",
- "id": "dev-api"
}, - "payload": {
- "role": "assistant",
- "content": "I'll open the epic first."
}, - "createdAt": "2026-07-27T14:30:58.512Z"
}
], - "nextCursor": 41
}Тот же сценарий использования и то же атомарное присвоение seq, что и у маршрута для человека. Гипотезы Психолога сюда НЕ проходят: у них свой маршрут с проверкой доказательств.
| sessionId required | string |
| projectId required | string <uuid> |
| type required | string См. |
| actorKind required | string Перечисление: "user" "agent" "system" |
| actorId required | string Slug агента, который его выпустил. |
required | object |
{- "projectId": "01JC4Z0000PROJETO0000000001",
- "type": "agent.tool_call",
- "actorKind": "agent",
- "actorId": "dev-api",
- "payload": {
- "tool": "ler_arquivo"
}
}{- "id": "01JC4Z8QK3M7YV2N5T9B0PXHRB",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "seq": 41,
- "type": "chat.message",
- "actor": {
- "kind": "agent",
- "id": "dev-api"
}, - "payload": {
- "role": "assistant",
- "content": "I'll open the epic first."
}, - "createdAt": "2026-07-27T14:30:58.512Z"
}nextAction в ответе — то, чему подчиняется engine: correct возвращает к dev, run_secops продвигает gate, done передаёт пользователю, await_user ставит цикл на паузу до ответа на открытый вопрос (RN-860, без учёта исправления), а blocked значит, что лимит исправлений исчерпан. MERGE никогда не автоматический — done лишь означает, что очередь человека.
| sessionId required | string |
| projectId required | string <uuid> |
| taskId required | string <uuid> |
| gate required | string Перечисление: "qa" "secops" |
| veredito required | string Перечисление: "approved" "changes_requested" "needs_user_decision"
|
| resumo required | string |
| itens required | Массив из strings Что нужно изменить, пункт за пунктом. |
| pergunta | string При |
| sugestao | string При |
| openQuestionId | string <uuid> При |
| maxCorrections | number Лимит циклов до того, как задача станет |
{- "projectId": "01JC4Z0000PROJETO0000000001",
- "taskId": "01JC4Z0000TAREFA00000000001",
- "gate": "qa",
- "veredito": "changes_requested",
- "resumo": "Missing tests for the insufficient-stock path.",
- "itens": [
- "No test for a nonexistent SKU"
], - "pergunta": "Should a nonexistent code answer 404?",
- "sugestao": "404 with a plain message.",
- "openQuestionId": "4c8aa973-c398-4859-8f2e-3fe00dc96a12",
- "maxCorrections": 3
}{- "nextAction": "run_secops",
- "task": {
- "id": "01JC4Z0000TAREFA00000000001",
- "storyId": "01JC4Z0000HISTORIA000000001",
- "title": "POST /cart/items endpoint",
- "description": "Accepts SKU and quantity; validates stock.",
- "status": "in_progress",
- "assignedTo": "dev-api",
- "blocked": false,
- "blockedReason": null,
- "blockedOrigin": null,
- "gateStatus": null,
- "gateCorrectionCount": 0,
- "module": "api",
- "createdAt": "2026-07-25T10:00:00.000Z",
- "updatedAt": "2026-07-25T12:30:00.000Z"
}, - "openQuestionId": "4c8aa973-c398-4859-8f2e-3fe00dc96a12"
}Рождается как offered. Принимает ЧЕЛОВЕК, через маршрут для человека, — агент не активирует агента. Два исключения. Предложение Креатив→PO, несущее product_brief, которого попросил клик «Я готов — потребность подтверждена», принимается от имени кликнувшего, с этим человеком как актором и implicito в payload (RN-658, ADR 0185). Handoff от PO к Архитектору принимается СИСТЕМОЙ, когда backlog покрыт (хотя бы одно бизнес-правило, ни одного без истории), а репозиторий local и в проекте нет учётных данных git (RN-660, ADR 0186); aceiteAutomatico говорит, произошло ли это, и если нет — почему. В любом случае ответ затем содержит status: accepted.
| sessionId required | string |
| projectId required | string <uuid> |
| fromAgent required | string |
| toAgent required | string |
| artifactId | string ULID session_event артефакта, который стал причиной handoff. |
| seAusente | boolean Только если его ещё никто не получил (ADR 0182, RN-636): при ожидающем предложении цели в ЛЮБОЙ сессии проекта вернуть его ( |
{- "projectId": "01JC4Z0000PROJETO0000000001",
- "fromAgent": "criativo",
- "toAgent": "po",
- "artifactId": "01JC4Z8QK3M7YV2N5T9B0PXHRB",
- "seAusente": true
}{- "id": "01JC4Z0000HANDOFF00000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "fromAgent": "criativo",
- "toAgent": "po",
- "artifactId": "01JC4Z0000ARTEFATO000000001",
- "status": "offered",
- "createdAt": "2026-07-24T10:00:00.000Z",
- "updatedAt": "2026-07-24T10:05:00.000Z",
- "desfecho": "criado",
- "aceiteAutomatico": {
- "aceito": true,
- "criterio": {
- "regras": 4,
- "cobertas": 4,
- "repositorio": "a_provisionar_local"
}, - "motivo": "regras_sem_historia"
}
}Каждая гипотеза должна ссылаться на события, которые СУЩЕСТВУЮТ в этой сессии, — выдуманные доказательства отклоняются с 400, и именно это отличает гипотезу от мнения. Предыдущий раунд сессии становится замещённым.
| sessionId required | string |
| projectId required | string <uuid> |
| tier required | string Перечисление: "leve" "pesada" Два уровня используют действительно разные модели; стоимость расходится. |
| triggeredBy required | string Перечисление: "auto" "manual" |
| eventCount required | number Размер проанализированного журнала. |
| cause | string Перечисление: "normal" "timeout" "kill" "crash" "node_shutdown" "unknown" Причина, КЛАССИФИЦИРОВАННАЯ engine. Именно она, а не терминальный статус, решает, обязателен ли |
required | Массив из objects (HypothesisDraftDto) |
{- "projectId": "01JC4Z0000PROJETO0000000001",
- "tier": "leve",
- "triggeredBy": "auto",
- "eventCount": 128,
- "cause": "timeout",
- "hypotheses": [
- {
- "agenteAlvo": "dev-api",
- "observacao": "The agent reopened the same task three times.",
- "hipotese": "The instructions don't say when the task is done.",
- "sugestao": "Add an explicit definition-of-done criterion.",
- "confiancaPercent": 72,
- "evidenceEventIds": [
- "01JC4Z8QK3M7YV2N5T9B0PXHRB"
], - "terminationAnalysis": {
- "causa": "heartbeat_timeout",
- "estadoDaSessao": "closed_abnormally",
- "analise": "The agent stopped responding after a 90s tool call."
}
}
]
}{- "analysisId": "01JC4Z0000ANALISE0000000001",
- "hypotheses": [
- {
- "id": "01JC4Z0000HIPOTESE000000001",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "analysisId": "01JC4Z0000ANALISE0000000001",
- "agenteAlvo": "dev-api",
- "observacao": "The agent reopened the same task three times.",
- "hipotese": "The instructions don't say when to consider the task done.",
- "sugestao": "Add an explicit definition-of-done criterion to the instruction.",
- "confiancaPercent": 72,
- "evidenceEventIds": [
- "01JC4Z8QK3M7YV2N5T9B0PXHRB"
], - "terminationAnalysis": {
- "causa": "heartbeat_timeout",
- "estadoDaSessao": "closed_abnormally",
- "analise": "The agent stopped responding after a 90s tool call."
}, - "status": "proposed",
- "decidedBy": null,
- "decidedAt": null,
- "createdAt": "2026-07-26T18:00:00.000Z",
- "updatedAt": "2026-07-26T18:00:00.000Z"
}
]
}Содержимое берётся из собственного payload proposed_action: инфраструктурный артефакт никогда не касается worktree, так же как ADR.
| prActionId required | string |
| projectId required | string |
{- "title": "api's production Dockerfile",
- "files": [
- {
- "path": "docker/api/Dockerfile.prod",
- "content": "FROM node:24.11.1-alpine3.21 AS build\n…"
}
]
}Текущий module_map плюс относящиеся к делу инфраструктурные ADR, плюс маршрутизация модулей Архитектора (ADR 0131) — кандидаты, среди которых Infra Lead выбирает при предложении container_start.
| projectId required | string |
{- "moduleMap": {
- "id": "01JC4Z0000MODULEMAP00000001",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "modules": [
- {
- "name": "api",
- "stack": "NestJS + Drizzle",
- "responsibility": "Business rules and HTTP surface.",
- "dependsOn": [
- "db"
], - "resources": {
- "cpus": 2,
- "memoryMb": 4096,
- "pidsLimit": 512
}
}
], - "version": 3,
- "createdAt": "2026-07-24T16:00:00.000Z"
}, - "adrs": [
- {
- "title": "0025 — Kubernetes deploy with Kustomize",
- "content": "## Context\n\n…"
}
], - "gitProvider": "github",
- "moduleRouting": {
- "status": "roteado",
- "roteamento": [
- {
- "modulo": "checkout-api",
- "imagemCandidata": "node:22-bookworm-slim",
- "porque": "This module is TypeScript on Node 22; the slim variant has the runtime and nothing else."
}
], - "version": 1,
- "eventId": "01JC4Z0000EVENTOROTEAMENTO01",
- "createdAt": "2026-08-30T12:00:00.000Z"
}, - "projectImage": {
- "image": "node:22-bookworm-slim",
- "rationale": "The module_map is all TypeScript over Node; the slim variant is enough and reduces surface.",
- "network": "none",
- "resources": {
- "cpus": 2,
- "memoryMb": 4096,
- "pidsLimit": 512
}
}
}Тот же конвейер и та же машина состояний, что и у PR dev.
| sessionId required | string |
| projectId required | string <uuid> |
| prActionId required | string <uuid> Действие |
| gate required | string Перечисление: "qa" "secops" |
| veredito required | string Перечисление: "approved" "changes_requested" |
| resumo required | string |
| itens required | Массив из strings |
| maxCorrections | number |
{- "projectId": "01JC4Z0000PROJETO0000000001",
- "prActionId": "01JC4Z8QK3M7YV2N5T9B0PXHRD",
- "gate": "secops",
- "veredito": "approved",
- "resumo": "Non-root image with no secret in ARG.",
- "itens": [ ],
- "maxCorrections": 3
}{- "nextAction": "done",
- "artifact": {
- "id": "01JC4Z0000INFRAARTEFATO00001",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "title": "api's production Dockerfile",
- "prActionId": "01JC4Z8QK3M7YV2N5T9B0PXHRD",
- "gateStatus": "awaiting_qa",
- "gateCorrectionCount": 0,
- "blocked": false,
- "blockedReason": null,
- "createdAt": "2026-07-26T09:00:00.000Z",
- "updatedAt": "2026-07-26T09:20:00.000Z"
}
}Становится proposed_action, а не прямой записью: изменение поведения агента — внешний эффект, и он проходит тот же конвейер одобрения, что и всё остальное. Это замыкает цикл гипотеза → патч.
| sessionId required | string |
| projectId required | string <uuid> |
| agent required | string |
| proposedContent required | string ВЕСЬ файл в итоговом виде, а не diff. |
| rationale required | string |
| hypothesisId | string <uuid> Гипотеза, ставшая причиной патча. Именно это замыкает цикл Анамнеза. |
{- "projectId": "01JC4Z0000PROJETO0000000001",
- "agent": "dev-api",
- "proposedContent": "# dev-api\n\nAlways run the suite before opening the PR.",
- "rationale": "The task was reopened three times for lack of a criterion.",
- "hypothesisId": "01JC4Z0000HIPOTESE000000001"
}{- "id": "01JC4Z8QK3M7YV2N5T9B0PXHRC",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "seq": 7,
- "actionType": "terminal",
- "payload": {
- "command": "pnpm test"
}, - "status": "pending",
- "resolvedPolicy": "require_approval",
- "actor": {
- "kind": "agent",
- "id": "dev-api"
}, - "decidedBy": null,
- "decidedAt": null,
- "rejectionReason": null,
- "executionResult": null,
- "createdAt": "2026-07-27T14:33:10.900Z",
- "updatedAt": "2026-07-27T14:33:10.900Z"
}Учёт ОБЯЗАТЕЛЕН: каждый ход записывает token_usage, и именно это придаёт бюджету смысл. Не записывает никакого события — журнал событий ведёт engine. Сбой провайдера возвращается в error с 200, потому что учёт в usage остаётся действительным: ход потратил, даже завершаясь сбоем.
| sessionId required | string |
| projectId required | string <uuid> |
| agentId | string Относит стоимость на этого агента в панели команды. |
required | Массив из objects История, уже собранная ContextManager engine. |
Массив из objects Инструменты, предлагаемые модели; | |
| catalogoCompleto | boolean Запрашивает ВЕСЬ каталог инструментов: маршрутизатор инструментов Jev (ADR 0179) для этого вызова пропускается. Engine устанавливает это, когда повторяет шаг, на котором ограниченное меню заставило модель ответить без вызова инструмента. |
{- "projectId": "01JC4Z0000PROJETO0000000001",
- "agentId": "dev-api",
- "messages": [
- {
- "role": "user",
- "content": "Read file X."
}
], - "tools": [
- { }
], - "catalogoCompleto": true
}{- "message": {
- "role": "assistant",
- "content": "I'll read the file before answering.",
- "toolCalls": [
- {
- "id": "call_01",
- "name": "read_file",
- "arguments": {
- "path": "src/index.ts"
}
}
]
}, - "usage": {
- "inputTokens": 1820,
- "outputTokens": 340,
- "costMicros": 52700,
- "estimated": false
}, - "error": null,
- "errorCode": null,
- "modelName": "llama3.2:3b",
- "truncated": true,
- "toolRouting": {
- "modelo": "typesafe/jev-1.13",
- "ofertadas": 9,
- "menuAntes": [
- "string"
], - "menuDepois": [
- "string"
], - "escolha": "create_story",
- "confianca": 0.91,
- "segunda": {
- "opcao": "create_task",
- "probabilidade": 0.06
}, - "anterior": "read_file",
- "aplicado": true,
- "motivoDaQueda": "timeout",
- "origemDaQueda": "infra",
- "detalheDaQueda": { },
- "latenciaMs": 212,
- "custoMicros": 52,
- "gastoNaoRegistrado": true
}
}Та же семантика, что и у llm-turn, доставляемая кадр за кадром. Кадр done несёт usage — без него ход вышел бы без учёта.
| sessionId required | string |
| projectId required | string <uuid> |
| agentId | string |
required | Массив из objects |
Массив из objects | |
| catalogoCompleto | boolean Запрашивает ВЕСЬ каталог инструментов: маршрутизатор инструментов Jev (ADR 0179) для этого вызова пропускается. Engine устанавливает это, когда повторяет шаг, на котором ограниченное меню заставило модель ответить без вызова инструмента. |
{- "projectId": "01JC4Z0000PROJETO0000000001",
- "agentId": "criativo",
- "messages": [
- {
- "role": "user",
- "content": "Summarize what we decided."
}
], - "tools": [
- { }
], - "catalogoCompleto": true
}Отказывается предлагать лимит, равный текущему или ниже его: Анамнез запускается периодически и предлагал бы одно и то же каждый раунд, заполняя шумом очередь, которую пользователю нужно читать.
| sessionId required | string |
| projectId required | string <uuid> |
| area required | string Область, чей лимит повысился бы. |
| proposto required | number >= 1 Предлагаемый лимит. Должен быть ВЫШЕ текущего — предложение того же или меньшего превращается в шум в очереди, которую пользователю нужно читать, а Анамнез запускается периодически. |
| rationale required | string Основано на НАБЛЮДАЕМЫХ РЕШЕНИЯХ, а не на впечатлении. |
{- "projectId": "01JC4Z0000PROJETO0000000001",
- "area": "dev",
- "proposto": 4,
- "rationale": "You authorized the same request four times in this window, and none was denied."
}{- "id": "01JC4Z8QK3M7YV2N5T9B0PXHRC",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "seq": 7,
- "actionType": "terminal",
- "payload": {
- "command": "pnpm test"
}, - "status": "pending",
- "resolvedPolicy": "require_approval",
- "actor": {
- "kind": "agent",
- "id": "dev-api"
}, - "decidedBy": null,
- "decidedAt": null,
- "rejectionReason": null,
- "executionResult": null,
- "createdAt": "2026-07-27T14:33:10.900Z",
- "updatedAt": "2026-07-27T14:33:10.900Z"
}Артефакт — ЭТО событие artifact.module_contracts: неизменяемое, версионированное и с автором, рядом с artifact.module_map. Каждый вызов несёт ВЕСЬ список и заменяет предыдущую версию. То, что модуль потребляет, здесь не пишется: это dependsOn текущего module_map.
| sessionId required | string |
| projectId required | string <uuid> |
required | Массив из objects (ContratoDeModuloInternalDto) ВЕСЬ список контрактов: каждый вызов — новая версия, заменяющая предыдущую. То, что модуль ПОТРЕБЛЯЕТ, здесь не указано — оно выводится из |
{- "projectId": "5a8591dd-4039-49df-9202-96385ba3eff8",
- "contratos": [
- {
- "modulo": "board-engine",
- "expoe": [
- {
- "tipo": "funcao",
- "assinatura": "placePiece(board: Board, piece: Piece, pos: Pos): Board",
- "descricao": "Returns a new board; never mutates the one passed in."
}
]
}
]
}{- "contratos": [
- {
- "modulo": "board-engine",
- "expoe": [
- {
- "tipo": "funcao",
- "assinatura": "placePiece(board: Board, piece: Piece, pos: Pos): Board",
- "descricao": "Returns a new board; never mutates the one passed in."
}
]
}
], - "version": 1
}История неизменяема: каждая публикация — ещё одна версия, а текущая — та, у которой наибольший version. ЦИКЛ зависимостей между модулями приводит к ОТКЛОНЕНИЮ карты с 400 — граф должен быть ацикличным.
| sessionId required | string |
| projectId required | string <uuid> |
required | Массив из objects Граф модулей. ЦИКЛ приводит к отклонению карты с 400 — проверка на уровне домена. Каждый модуль может объявить |
{- "projectId": "01JC4Z0000PROJETO0000000001",
- "modules": [
- {
- "name": "api",
- "stack": "NestJS",
- "responsibility": "Rules and HTTP",
- "dependsOn": [
- "db"
], - "resources": {
- "cpus": 1,
- "memoryMb": 1024,
- "pidsLimit": 256
}
}
]
}{- "id": "01JC4Z0000MODULEMAP00000001",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "modules": [
- {
- "name": "api",
- "stack": "NestJS + Drizzle",
- "responsibility": "Business rules and HTTP surface.",
- "dependsOn": [
- "db"
], - "resources": {
- "cpus": 2,
- "memoryMb": 4096,
- "pidsLimit": 512
}
}
], - "version": 3,
- "createdAt": "2026-07-24T16:00:00.000Z"
}Артефакт — ЭТО событие artifact.module_routing: неизменяемое, версионированное и с автором, рядом с artifact.module_map/artifact.project_image/artifact.c4_diagram. Каждый элемент проверяется тем же правилом, что и choose_project_image (явный тег/digest, latest отклоняется, нетривиальный rationale); имя модуля вне текущего module_map, пустой список или повторяющийся модуль отклоняются с 400.
| sessionId required | string |
| projectId required | string <uuid> |
required | Массив из objects (RoteamentoDeModuloInternalDto) Один элемент на модуль текущего module_map. Образ-кандидат — это предложение Архитектора, а не решение: Infra выбирает среди кандидатов на более позднем шаге. |
{- "projectId": "5a8591dd-4039-49df-9202-96385ba3eff8",
- "roteamento": [
- {
- "modulo": "checkout-api",
- "imagemCandidata": "node:22-bookworm-slim",
- "porque": "This module is TypeScript on Node 22; the slim variant has the runtime and nothing else."
}
]
}{- "roteamento": [
- {
- "modulo": "checkout-api",
- "imagemCandidata": "node:22-bookworm-slim",
- "porque": "This module is TypeScript on Node 22; the slim variant has the runtime and nothing else."
}
], - "version": 1
}Запрашивается SessionServer engine по таймауту heartbeat. pending: true заставляет перенести таймаут вместо закрытия, а motivo попадает в лог engine — сессию, которая не закрывается, не говоря почему, невозможно диагностировать.
| sessionId required | string |
{- "pending": true,
- "motivo": "handoff po → arquiteto aguardando aceite",
- "aguardandoUsuarioDesde": "2026-09-18T12:00:00.000Z"
}Компетенция вне каталога ОТКЛОНЯЕТСЯ, как и доказательство, ссылающееся на событие из другого проекта, — обе проверки существуют, потому что модель ошибается в обоих.
| sessionId required | string |
| projectId required | string <uuid> |
| windowFrom required | string <date-time> |
| windowTo required | string <date-time> |
| eventCount required | number |
required | Массив из objects (ProficiencyDraftDto) |
{- "projectId": "01JC4Z0000PROJETO0000000001",
- "windowFrom": "2026-07-27T12:00:00.000Z",
- "windowTo": "2026-07-27T12:15:00.000Z",
- "eventCount": 240,
- "profiles": [
- {
- "userId": "01JC4Z0000USUARIO0000000002",
- "competency": "TypeScript",
- "level": "avancado",
- "rationale": "Fixed three generic typing errors without help.",
- "evidenceEventIds": [
- "01JC4Z8QK3M7YV2N5T9B0PXHRB"
]
}
]
}{- "runId": "01JC4Z0000RODADA00000000001",
- "profiles": [
- { }
]
}Артефакт — ЭТО событие artifact.project_image: неизменяемое, версионированное и с автором. Пересмотр означает выпуск новой версии, никогда не перезапись. Пока его нет, контейнер проекта не поднимается, а вкладка Code отвечает 409.
| sessionId required | string |
| projectId required | string <uuid> |
| image required | string Ссылка OCI с явным ТЕГОМ или digest. |
| rationale required | string Почему этот образ. Минимум 10 символов. |
| network | string По умолчанию: "none" Перечисление: "none" "egress" Сетевая позиция контейнера. |
object Лимит ресурсов. Выше максимума решение ОТКЛОНЯЕТСЯ, а не молча понижается. |
{- "projectId": "5a8591dd-4039-49df-9202-96385ba3eff8",
- "image": "node:22-bookworm-slim",
- "rationale": "The module_map is TypeScript on Node 22; the slim variant has the runtime and nothing else.",
- "network": "none",
- "resources": {
- "cpus": 2,
- "memoryMb": 4096,
- "pidsLimit": 512
}
}{- "decisao": {
- "image": "node:22-bookworm-slim",
- "rationale": "The module_map is all TypeScript over Node; the slim variant is enough and reduces surface.",
- "network": "none",
- "resources": {
- "cpus": 2,
- "memoryMb": 4096,
- "pidsLimit": 512
}
}, - "version": 1,
- "containerDePe": {
- "status": "running",
- "imageVersion": 2
}
}alreadyAnalyzed даёт автоматическому пути ИДЕМПОТЕНТНОСТЬ: при true worker завершается сразу, не тратя ни одного токена. Предыдущие гипотезы приходят вместе, чтобы раунд не повторялся.
| sessionId required | string |
| projectId required | string |
{- "alreadyAnalyzed": false,
- "sessionStatus": "closed_abnormally",
- "terminationReason": "heartbeat_timeout",
- "businessRules": [
- {
- "id": "RN-014",
- "title": "Cart accepts at most 50 items"
}
], - "priorHypotheses": [
- {
- "agenteAlvo": "dev-api",
- "hipotese": "The instructions don't say when the task is done.",
- "sugestao": "Add an explicit definition-of-done criterion.",
- "confiancaPercent": 72
}
]
}Встраивает заголовок и заголовки правил проекта (100 самых свежих) моделью эмбеддингов RAG и предупреждает при косинусе ≥ порога. Никогда не отклоняет. Без провайдера эмбеддингов проверка ПРОПУСКАЕТСЯ с указанием причины, также описанной в журнале событий. Расход на эмбеддинги учитывается отдельной строкой token_usage (актор system/duplicata-semantica).
| sessionId required | string |
| projectId required | string <uuid> |
| kind required | string Значение: "business_rule" |
| title required | string Заголовок ТОЛЬКО ЧТО добавленного правила. Правила с тем же нормализованным заголовком (RN-080 делает его новым) исключаются из сравнения. |
{- "projectId": "01JC4Z0000PROJETO0000000001",
- "kind": "business_rule",
- "title": "Greeting with the caller name"
}{- "status": "warned",
- "similarTo": {
- "id": "01JC4Z0000HISTORIA000000001",
- "title": "Deterministic public greeting endpoint",
- "similarity": 0.83
}, - "closest": {
- "id": "01JC4Z0000HISTORIA000000001",
- "title": "Deterministic public greeting endpoint",
- "similarity": 0.83
}, - "similarity": 0.83,
- "threshold": 0.8,
- "compared": 12,
- "total": 12,
- "reason": "provider \"ollama\" did not answer",
- "message": "string"
}businessRuleIds питает покрытие правило→история. Каждый id должен ссылаться на СУЩЕСТВУЮЩЕЕ событие artifact.business_rule — проверка отклоняет выдуманный id. Ответ также содержит semanticDuplicate (RN-681): ПРЕДУПРЕЖДЕНИЕ на основе эмбеддингов по историям проекта, никогда не отказ.
| sessionId required | string |
| projectId required | string <uuid> |
| epicId required | string <uuid> |
| title required | string |
| description | string |
| rf | Массив из strings |
| rnf | Массив из strings |
| dod | Массив из strings |
| dor | Массив из strings |
| businessRuleIds | Массив из strings Каждый id должен ссылаться на СУЩЕСТВУЮЩЕЕ событие |
{- "projectId": "01JC4Z0000PROJETO0000000001",
- "epicId": "01JC4Z0000EPICO000000000001",
- "title": "Add item to cart",
- "description": "As a buyer, I want to gather items before paying.",
- "rf": [
- "The cart accepts up to 50 items"
], - "rnf": [
- "Response under 200ms at p95"
], - "dod": [
- "Unit tests green"
], - "dor": [
- "Module defined"
], - "businessRuleIds": [
- "RN-014"
]
}{- "id": "01JC4Z0000HISTORIA000000001",
- "epicId": "01JC4Z0000EPICO000000000001",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "title": "Add item to cart",
- "description": "As a buyer, I want to gather items before paying.",
- "rf": [
- "The cart accepts up to 50 items"
], - "rnf": [
- "The response stays under 200 ms at p95"
], - "businessRuleIds": [
- "RN-014"
], - "dod": [
- "Unit tests green"
], - "dor": [
- "Module defined"
], - "moduleIds": [
- "api"
], - "status": "ready",
- "proposedReady": true,
- "returnedReason": "DoD too generic — spell out the acceptance criteria.",
- "returnedAt": "2026-08-02T14:00:00.000Z",
- "archivedAt": null,
- "archivedReason": null,
- "createdAt": "2026-07-25T09:00:00.000Z",
- "updatedAt": "2026-07-25T09:40:00.000Z",
- "semanticDuplicate": {
- "status": "warned",
- "similarTo": {
- "id": "01JC4Z0000HISTORIA000000001",
- "title": "Deterministic public greeting endpoint",
- "similarity": 0.83
}, - "closest": {
- "id": "01JC4Z0000HISTORIA000000001",
- "title": "Deterministic public greeting endpoint",
- "similarity": 0.83
}, - "similarity": 0.83,
- "threshold": 0.8,
- "compared": 12,
- "total": 12,
- "reason": "provider \"ollama\" did not answer",
- "message": "string"
}
}История и её задачи уходят из backlog, покрытия, плана и claim; строка остаётся, и backlog.story_archived фиксирует это с PO как актором (RN-727).
| storyId required | string |
| projectId required | string <uuid> |
| reason | string |
{- "projectId": "01JC4Z0000PROJETO0000000001",
- "reason": "Duplicate of \"Register a user\"."
}{- "id": "01JC4Z0000HISTORIA000000001",
- "epicId": "01JC4Z0000EPICO000000000001",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "title": "Add item to cart",
- "description": "As a buyer, I want to gather items before paying.",
- "rf": [
- "The cart accepts up to 50 items"
], - "rnf": [
- "The response stays under 200 ms at p95"
], - "businessRuleIds": [
- "RN-014"
], - "dod": [
- "Unit tests green"
], - "dor": [
- "Module defined"
], - "moduleIds": [
- "api"
], - "status": "ready",
- "proposedReady": true,
- "returnedReason": "DoD too generic — spell out the acceptance criteria.",
- "returnedAt": "2026-08-02T14:00:00.000Z",
- "archivedAt": null,
- "archivedReason": null,
- "createdAt": "2026-07-25T09:00:00.000Z",
- "updatedAt": "2026-07-25T09:40:00.000Z"
}businessRuleIds ДОБАВЛЯЕТСЯ к уже связанным правилам; непустые списки заменяют текущие, а опущенные остаются. После записи история продвигается по ТОМУ ЖЕ критерию, что и при создании (ready в режиме auto, proposedReady в manual) (RN-720).
| sessionId required | string |
| storyId required | string |
| projectId required | string <uuid> |
| description | string |
| rf | Массив из strings |
| rnf | Массив из strings |
| dod | Массив из strings |
| dor | Массив из strings |
| businessRuleIds | Массив из strings ДОБАВЛЯЕТСЯ к правилам, на которые история уже ссылается (дополнение никогда не отвязывает правило); каждый новый id должен ссылаться на существующий |
| questionId | string <uuid> RN-859 (ADR 0214): открытый вопрос, на который пользователь УЖЕ ответил, — только тогда можно исправить историю |
{- "projectId": "01JC4Z0000PROJETO0000000001",
- "description": "As a buyer, I want to pay by card.",
- "rf": [
- "The cart accepts up to 50 items"
], - "rnf": [
- "Response under 200ms at p95"
], - "dod": [
- "Unit tests green"
], - "dor": [
- "Module defined"
], - "businessRuleIds": [
- "RN-014"
], - "questionId": "ffa0420e-f70b-4e5b-99a0-434bff860467"
}{- "id": "01JC4Z0000HISTORIA000000001",
- "epicId": "01JC4Z0000EPICO000000000001",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "title": "Add item to cart",
- "description": "As a buyer, I want to gather items before paying.",
- "rf": [
- "The cart accepts up to 50 items"
], - "rnf": [
- "The response stays under 200 ms at p95"
], - "businessRuleIds": [
- "RN-014"
], - "dod": [
- "Unit tests green"
], - "dor": [
- "Module defined"
], - "moduleIds": [
- "api"
], - "status": "ready",
- "proposedReady": true,
- "returnedReason": "DoD too generic — spell out the acceptance criteria.",
- "returnedAt": "2026-08-02T14:00:00.000Z",
- "archivedAt": null,
- "archivedReason": null,
- "createdAt": "2026-07-25T09:00:00.000Z",
- "updatedAt": "2026-07-25T09:40:00.000Z"
}Только история draft без задачи в работе; пишет backlog.story_updated с PO как актором (RN-727). Также ready, когда questionId ссылается на открытый вопрос, на который пользователь уже ответил (RN-859).
| storyId required | string |
| projectId required | string <uuid> |
| title | string |
| description | string |
| questionId | string <uuid> RN-859 (ADR 0214): открытый вопрос, на который пользователь УЖЕ ответил, — только тогда можно исправить историю |
{- "projectId": "01JC4Z0000PROJETO0000000001",
- "title": "Register a user",
- "description": "As a visitor, I want to sign up.",
- "questionId": "ffa0420e-f70b-4e5b-99a0-434bff860467"
}{- "id": "01JC4Z0000HISTORIA000000001",
- "epicId": "01JC4Z0000EPICO000000000001",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "title": "Add item to cart",
- "description": "As a buyer, I want to gather items before paying.",
- "rf": [
- "The cart accepts up to 50 items"
], - "rnf": [
- "The response stays under 200 ms at p95"
], - "businessRuleIds": [
- "RN-014"
], - "dod": [
- "Unit tests green"
], - "dor": [
- "Module defined"
], - "moduleIds": [
- "api"
], - "status": "ready",
- "proposedReady": true,
- "returnedReason": "DoD too generic — spell out the acceptance criteria.",
- "returnedAt": "2026-08-02T14:00:00.000Z",
- "archivedAt": null,
- "archivedReason": null,
- "createdAt": "2026-07-25T09:00:00.000Z",
- "updatedAt": "2026-07-25T09:40:00.000Z"
}Это перекрёстная проверка: модуль, которого нет в текущей карте, становится незакрытым вопросом архитектуры, а не проскальзывает незамеченным.
| sessionId required | string |
| projectId required | string <uuid> |
| storyId required | string <uuid> |
| moduleIds required | Массив из strings Имена из текущего |
{- "projectId": "01JC4Z0000PROJETO0000000001",
- "storyId": "01JC4Z0000HISTORIA000000001",
- "moduleIds": [
- "api",
- "web"
]
}{- "id": "01JC4Z0000HISTORIA000000001",
- "epicId": "01JC4Z0000EPICO000000000001",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "title": "Add item to cart",
- "description": "As a buyer, I want to gather items before paying.",
- "rf": [
- "The cart accepts up to 50 items"
], - "rnf": [
- "The response stays under 200 ms at p95"
], - "businessRuleIds": [
- "RN-014"
], - "dod": [
- "Unit tests green"
], - "dor": [
- "Module defined"
], - "moduleIds": [
- "api"
], - "status": "ready",
- "proposedReady": true,
- "returnedReason": "DoD too generic — spell out the acceptance criteria.",
- "returnedAt": "2026-08-02T14:00:00.000Z",
- "archivedAt": null,
- "archivedReason": null,
- "createdAt": "2026-07-25T09:00:00.000Z",
- "updatedAt": "2026-07-25T09:40:00.000Z"
}| sessionId required | string |
| projectId required | string <uuid> |
| storyId required | string <uuid> |
| title required | string |
| description | string |
{- "projectId": "01JC4Z0000PROJETO0000000001",
- "storyId": "01JC4Z0000HISTORIA000000001",
- "title": "Endpoint POST /cart/items",
- "description": "Accepts SKU and quantity; validates stock."
}{- "id": "01JC4Z0000TAREFA00000000001",
- "storyId": "01JC4Z0000HISTORIA000000001",
- "title": "POST /cart/items endpoint",
- "description": "Accepts SKU and quantity; validates stock.",
- "status": "in_progress",
- "assignedTo": "dev-api",
- "blocked": false,
- "blockedReason": null,
- "blockedOrigin": null,
- "gateStatus": null,
- "gateCorrectionCount": 0,
- "module": "api",
- "createdAt": "2026-07-25T10:00:00.000Z",
- "updatedAt": "2026-07-25T12:30:00.000Z"
}Автоматической разблокировки нет: разблокирует человек, через маршрут для человека. Именно это не даёт агенту бесконечно крутиться.
| sessionId required | string |
| taskId required | string |
| projectId required | string <uuid> |
| agentId required | string |
| reason required | string |
| diagnosis required | string Диагноз свободным текстом — человеческое описание произошедшего, а не структурированный источник сбоя. |
| origin | string Перечисление: "infra" "modelo" "codigo" "politica" ИСТОЧНИК сбоя: |
{- "projectId": "01JC4Z0000PROJETO0000000001",
- "agentId": "dev-api",
- "reason": "Gate correction cap reached.",
- "diagnosis": "Correction cycle exhausted without exit 0.",
- "origin": "infra"
}{- "id": "01JC4Z0000TAREFA00000000001",
- "storyId": "01JC4Z0000HISTORIA000000001",
- "title": "POST /cart/items endpoint",
- "description": "Accepts SKU and quantity; validates stock.",
- "status": "in_progress",
- "assignedTo": "dev-api",
- "blocked": false,
- "blockedReason": null,
- "blockedOrigin": null,
- "gateStatus": null,
- "gateCorrectionCount": 0,
- "module": "api",
- "createdAt": "2026-07-25T10:00:00.000Z",
- "updatedAt": "2026-07-25T12:30:00.000Z"
}Вызывается, когда PR dev agent готов к ревью.
| sessionId required | string |
| taskId required | string |
| projectId required | string <uuid> |
| agentId required | string |
{- "projectId": "01JC4Z0000PROJETO0000000001",
- "agentId": "dev-api"
}{- "gateStatus": "awaiting_qa"
}| sessionId required | string |
| taskId required | string |
| projectId required | string <uuid> |
| agentId required | string Двигать задачу может только агент, который её забрал. |
| status required | string Перечисление: "todo" "in_progress" "in_review" "done" |
{- "projectId": "01JC4Z0000PROJETO0000000001",
- "agentId": "dev-api",
- "status": "in_review"
}{- "id": "01JC4Z0000TAREFA00000000001",
- "storyId": "01JC4Z0000HISTORIA000000001",
- "title": "POST /cart/items endpoint",
- "description": "Accepts SKU and quantity; validates stock.",
- "status": "in_progress",
- "assignedTo": "dev-api",
- "blocked": false,
- "blockedReason": null,
- "blockedOrigin": null,
- "gateStatus": null,
- "gateCorrectionCount": 0,
- "module": "api",
- "createdAt": "2026-07-25T10:00:00.000Z",
- "updatedAt": "2026-07-25T12:30:00.000Z"
}Вызывается заданием повтора engine (до 3 раз, через 1, 3 и 10 мин). Тот же сценарий использования, что и разблокировка человеком, с актором system; задача, которая больше не заблокирована, остаётся нетронутой.
| sessionId required | string |
| taskId required | string |
| projectId required | string <uuid> |
| agentId required | string Dev agent, чей временный сбой запланировал повтор. |
{- "projectId": "01JC4Z0000PROJETO0000000001",
- "agentId": "dev-api"
}{- "id": "01JC4Z0000TAREFA00000000001",
- "storyId": "01JC4Z0000HISTORIA000000001",
- "title": "POST /cart/items endpoint",
- "description": "Accepts SKU and quantity; validates stock.",
- "status": "in_progress",
- "assignedTo": "dev-api",
- "blocked": false,
- "blockedReason": null,
- "blockedOrigin": null,
- "gateStatus": null,
- "gateCorrectionCount": 0,
- "module": "api",
- "createdAt": "2026-07-25T10:00:00.000Z",
- "updatedAt": "2026-07-25T12:30:00.000Z"
}АТОМАРНО по замыслу: при нескольких dev agent на одном модуле два одновременных claim не могут вернуть одну и ту же задачу. Если доступной задачи нет, возвращает пустой результат вместо ошибки.
| sessionId required | string |
| projectId required | string <uuid> |
| module required | string В выбор попадают только задачи этого модуля. |
| agentId required | string |
{- "projectId": "01JC4Z0000PROJETO0000000001",
- "module": "api",
- "agentId": "dev-api"
}{- "id": "01JC4Z0000TAREFA00000000001",
- "storyId": "01JC4Z0000HISTORIA000000001",
- "title": "POST /cart/items endpoint",
- "description": "Accepts SKU and quantity; validates stock.",
- "status": "in_progress",
- "assignedTo": "dev-api",
- "blocked": false,
- "blockedReason": null,
- "blockedOrigin": null,
- "gateStatus": null,
- "gateCorrectionCount": 0,
- "module": "api",
- "createdAt": "2026-07-25T10:00:00.000Z",
- "updatedAt": "2026-07-25T12:30:00.000Z"
}Не заблокированные и вне gate PR; пусто, если сессия не является сессией выполнения. Используется engine при остановке выполнения, чтобы заблокировать те, которые не удерживает ни один живой dev agent.
| sessionId required | string |
[- {
- "projectId": "5a8591dd-4039-49df-9202-96385ba3eff8",
- "taskId": "e6e9d88a-9b63-468a-aec3-b7a11de27af8",
- "assignedTo": "dev-api"
}
]Сюда доходят только завершения, которые НЕ вызвала api, — падение, kill, heartbeat_timeout, защитное выключение. Остановка, запланированная самой api, сюда не проходит: engine уже узнал о ней через outbox, и сообщать о ней обратно было бы эхом.
| sessionId required | string |
| projectId required | string <uuid> |
| to required | string Перечисление: "closing" "closed" "closed_abnormally"
|
| reason | string ИСТОЧНИК завершения, а не диагноз методом исключения. |
{- "projectId": "01JC4Z0000PROJETO0000000001",
- "to": "closed_abnormally",
- "reason": "heartbeat_timeout"
}{- "id": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "createdBy": "01JC4Z0000USUARIO0000000001",
- "status": "active",
- "kind": "criativa",
- "name": "Cart checkout",
- "nextSeq": 42,
- "createdAt": "2026-07-27T14:03:22.187Z",
- "updatedAt": "2026-07-27T14:31:09.004Z",
- "closedAt": null,
- "terminationReason": null,
- "traceParent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
}Слой ПРОВАЙДЕРА возможностей (ADR 0041) для девяти провайдеров, прочитанный из тех же экземпляров, что обслуживают вызовы. Возможность равна true, только если доказана против реального API. Экран читает отсюда routingPreference, прежде чем предложить предпочтение маршрутизации в binding (ADR 0166).
[- {
- "provider": "openrouter",
- "capabilities": {
- "streaming": true,
- "toolCalling": true,
- "listModels": true,
- "embeddings": false,
- "routingPreference": false,
- "stickySession": false,
- "priceCeiling": false,
- "promptCaching": false
}
}
]Каскад агент → область → проект → рабочее пространство, БЕЗ сессии: это конфигурация агента в проекте, а не конкретного разговора. origin: "agent" означает, что этот агент ОТКЛОНИЛСЯ от значения по умолчанию своей области; любой другой origin означает, что он наследует.
| projectId required | string |
| agentSlug required | string Пример: dev-api |
{- "modelId": "01JC4Z0000MODELO00000000001",
- "origin": "project",
- "routingPreference": null,
- "skipped": [
- {
- "scope": "agent",
- "modelId": "01JC4Z0000MODELO00000000001",
- "reason": "unavailable"
}
], - "herdadoDoLead": "secops"
}Применяется к каждой сессии этого проекта, у которой нет собственного binding, и только к ним — до ADR 0064 binding был глобальным и затрагивал и другие проекты. Так Психологу даётся дешёвая модель, а Архитектору дорогая в одном проекте, и так же агент ОТКЛОНЯЕТСЯ от значения по умолчанию своей области.
| projectId required | string |
| agentSlug required | string Пример: dev-api |
| modelId required | string <uuid> Модель для закрепления в этой области действия. Должна существовать в |
| routingPreference | string or null Перечисление: "price" "throughput" "latency" Как ХАБ выбирает upstream, обслуживающий модель (ADR 0166, RN-583). ОТСУТСТВИЕ сохраняет хранимое значение, если провайдер новой модели его принимает, и очищает его в противном случае; |
{- "modelId": "9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b",
- "routingPreference": "throughput"
}{- "id": "01JC4Z0000BINDING00000000001",
- "scope": "project",
- "scopeId": "01JC4Z0000PROJETO0000000001",
- "modelId": "01JC4Z0000MODELO00000000001",
- "routingPreference": null,
- "createdBy": "01JC4Z0000USUARIO0000000001",
- "createdAt": "2026-07-11T09:00:00.000Z",
- "updatedAt": "2026-07-11T09:00:00.000Z"
}УДАЛЯЕТ binding агента — не копирует в него модель области. Копирование выглядело бы на экране так же, но им не является: оно стало бы копией, и следующее изменение области молча оставило бы этого агента позади. 404, если он уже наследует.
| projectId required | string |
| agentSlug required | string Пример: dev-api |
СКОЛЬЗЯЩЕЕ окно в 30 дней, а не текущий календарный месяц: именно эту подпись показывает экран, а с календарным месяцем оценка проваливалась бы 1-го числа из-за смены страницы, а не из-за изменения использования. Учитывается только actor_kind = agent (RN-038). Агент, который никогда не запускался, не появляется в списке — экран показывает для него прочерк, что отличается от нуля.
| projectId required | string |
[- {
- "actorId": "dev-api",
- "costMicros": 3400000,
- "inputTokens": 128000,
- "outputTokens": 14200
}
]Значение по умолчанию, общее для lead и субагентов области. origin: "area" означает, что кто-то выбрал его для этой области; любой другой origin означает, что сама область наследует от проекта или рабочего пространства.
| projectId required | string |
| areaKey required | string Пример: qa |
{- "modelId": "01JC4Z0000MODELO00000000001",
- "origin": "project",
- "routingPreference": null,
- "skipped": [
- {
- "scope": "agent",
- "modelId": "01JC4Z0000MODELO00000000001",
- "reason": "unavailable"
}
], - "herdadoDoLead": "secops"
}Применяется к lead и к каждому субагенту области, у которого нет собственного binding (RN-102). Требует maintainer, потому что затрагивает всю область сразу, а выбор модели — это решение о расходах, та же причина, что и у лимита параллелизма.
| projectId required | string |
| areaKey required | string Пример: qa |
| modelId required | string <uuid> Модель для закрепления в этой области действия. Должна существовать в |
| routingPreference | string or null Перечисление: "price" "throughput" "latency" Как ХАБ выбирает upstream, обслуживающий модель (ADR 0166, RN-583). ОТСУТСТВИЕ сохраняет хранимое значение, если провайдер новой модели его принимает, и очищает его в противном случае; |
{- "modelId": "9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b",
- "routingPreference": "throughput"
}{- "id": "01JC4Z0000BINDING00000000001",
- "scope": "project",
- "scopeId": "01JC4Z0000PROJETO0000000001",
- "modelId": "01JC4Z0000MODELO00000000001",
- "routingPreference": null,
- "createdBy": "01JC4Z0000USUARIO0000000001",
- "createdAt": "2026-07-11T09:00:00.000Z",
- "updatedAt": "2026-07-11T09:00:00.000Z"
}Область перестаёт иметь собственное значение по умолчанию и начинает наследовать от проекта (или рабочего пространства). Отклонившиеся агенты продолжают отклоняться: их binding другой, и удаление этого не может решать за них.
| projectId required | string |
| areaKey required | string Пример: qa |
Значения в микро-USD. Требует maintainer — это информация о затратах.
| projectId required | string |
{- "id": "01JC4Z0000ORCAMENTO000000001",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": null,
- "limitMicros": 50000000,
- "spentMicros": 12400000,
- "policy": "block",
- "lastThresholdNotified": 70,
- "createdAt": "2026-07-12T08:00:00.000Z",
- "updatedAt": "2026-07-27T15:00:00.000Z"
}Лимит приходит в ДОЛЛАРАХ и переводится в микро-USD. При policy=block вызов, который превысил бы лимит, ОТКЛОНЯЕТСЯ, а не просто записывается.
| projectId required | string |
| limitUsd required | number >= 0 Лимит в ДОЛЛАРАХ — единственное место в контракте, где говорится в долларах, потому что именно их вводит человек. Api переводит его в микро-USD, и ответы приходят в микро-USD. |
| policy required | string Перечисление: "block" "allow"
|
{- "limitUsd": 50,
- "policy": "block"
}{- "id": "01JC4Z0000ORCAMENTO000000001",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": null,
- "limitMicros": 50000000,
- "spentMicros": 12400000,
- "policy": "block",
- "lastThresholdNotified": 70,
- "createdAt": "2026-07-12T08:00:00.000Z",
- "updatedAt": "2026-07-27T15:00:00.000Z"
}СЫРОЙ binding этой области действия — null, если проект только наследует от рабочего пространства.
| projectId required | string |
{- "id": "01JC4Z0000BINDING00000000001",
- "scope": "project",
- "scopeId": "01JC4Z0000PROJETO0000000001",
- "modelId": "01JC4Z0000MODELO00000000001",
- "routingPreference": null,
- "createdBy": "01JC4Z0000USUARIO0000000001",
- "createdAt": "2026-07-11T09:00:00.000Z",
- "updatedAt": "2026-07-11T09:00:00.000Z"
}| projectId required | string |
| modelId required | string <uuid> Модель для закрепления в этой области действия. Должна существовать в |
| routingPreference | string or null Перечисление: "price" "throughput" "latency" Как ХАБ выбирает upstream, обслуживающий модель (ADR 0166, RN-583). ОТСУТСТВИЕ сохраняет хранимое значение, если провайдер новой модели его принимает, и очищает его в противном случае; |
{- "modelId": "9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b",
- "routingPreference": "throughput"
}{- "id": "01JC4Z0000BINDING00000000001",
- "scope": "project",
- "scopeId": "01JC4Z0000PROJETO0000000001",
- "modelId": "01JC4Z0000MODELO00000000001",
- "routingPreference": null,
- "createdBy": "01JC4Z0000USUARIO0000000001",
- "createdAt": "2026-07-11T09:00:00.000Z",
- "updatedAt": "2026-07-11T09:00:00.000Z"
}Пакетная форма GET .../agent-bindings/:agentSlug и GET .../area-bindings/:areaKey: каждый запрошенный ключ получает РОВНО то значение, которое отвечает его маршрут для одного ключа (тот же каскад, те же origin, null, когда ни на одном уровне нет модели). Существует, чтобы вкладка Настройки читала 20 разрешённых binding одним запросом вместо 20 — лимит частоты действует на ПОЛЬЗОВАТЕЛЯ. Ключи через запятую, без дубликатов, а некорректный ключ или больше 64 ключей всего — это 400, никогда не молчаливое отбрасывание.
| projectId required | string |
| agents | string Пример: agents=criativo,po,dev-lead Slug агентов через запятую. |
| areas | string Пример: areas=dev,qa,infra Ключи областей через запятую. |
{- "agents": [
- {
- "key": "dev-lead",
- "binding": {
- "modelId": "01JC4Z0000MODELO00000000001",
- "origin": "project",
- "routingPreference": null,
- "skipped": [
- {
- "scope": "agent",
- "modelId": "01JC4Z0000MODELO00000000001",
- "reason": "unavailable"
}
], - "herdadoDoLead": "secops"
}
}
], - "areas": [
- {
- "key": "dev-lead",
- "binding": {
- "modelId": "01JC4Z0000MODELO00000000001",
- "origin": "project",
- "routingPreference": null,
- "skipped": [
- {
- "scope": "agent",
- "modelId": "01JC4Z0000MODELO00000000001",
- "reason": "unavailable"
}
], - "herdadoDoLead": "secops"
}
}
]
}Категория local — модели Ollama, которые не тратят деньги; cloud — платные модели API. Модель, которую рабочее пространство не активировало, здесь не появляется, но остаётся в исторических затратах — чтобы увидеть её, используйте маршрут каталога. Список относится к рабочему пространству-ВЛАДЕЛЬЦУ проекта: одна и та же модель может быть включена в одном рабочем пространстве и выключена в соседнем (ADR 0049).
| projectId required | string |
{- "local": {
- "ollama": [ ]
}, - "cloud": {
- "anthropic": [ ],
- "openai": [ ]
}
}Именно это питает живой счётчик токенов на экране сессии.
| sessionId required | string |
{- "id": "01JC4Z0000ORCAMENTO000000001",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": null,
- "limitMicros": 50000000,
- "spentMicros": 12400000,
- "policy": "block",
- "lastThresholdNotified": 70,
- "createdAt": "2026-07-12T08:00:00.000Z",
- "updatedAt": "2026-07-27T15:00:00.000Z"
}То же преобразование долларов в микро-USD, что и у бюджета проекта.
| sessionId required | string |
| limitUsd required | number >= 0 Лимит в ДОЛЛАРАХ — единственное место в контракте, где говорится в долларах, потому что именно их вводит человек. Api переводит его в микро-USD, и ответы приходят в микро-USD. |
| policy required | string Перечисление: "block" "allow"
|
{- "limitUsd": 50,
- "policy": "block"
}{- "id": "01JC4Z0000ORCAMENTO000000001",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": null,
- "limitMicros": 50000000,
- "spentMicros": 12400000,
- "policy": "block",
- "lastThresholdNotified": 70,
- "createdAt": "2026-07-12T08:00:00.000Z",
- "updatedAt": "2026-07-27T15:00:00.000Z"
}Server-Sent Events. Кадры delta несут инкрементальный текст, а done закрывает поток с учётом токенов и стоимости. Кадр metering_failed означает, что ОТВЕТ ушёл, но стоимость не была учтена, — сбой проявляется, а не исчезает. Если бюджет превышен при policy=block, поток несёт error и ни одного delta. Этот маршрут НЕ несёт агента: он отправляет только текст привязанной модели, без истории и без системного промпта. В сессии consultiva, где ни один агент не активировался, он отклоняется до каких-либо эффектов (RN-682) — вместо этого говорите с агентом через .../agents/:agent/message.
| projectId required | string |
| sessionId required | string |
| text required | string Сообщение пользователя. Ответ возвращается потоком SSE. |
{- "text": "Summarize what we have already decided about checkout."
}Возвращает binding, РАЗРЕШЁННЫЙ каскадом, а не сырой binding сессии: сессия без собственного binding использует binding проекта, и ответ null здесь был бы неверным ответом на верный вопрос. Поле origin говорит, из какой области действия пришло значение.
agentId необязателен и является агентом, ДЕЙСТВИТЕЛЬНО активным в сессии прямо сейчас (тем же, что использует RunLlmTurnUseCase для выполнения хода), — без него каскад видит только сессия→проект→рабочее пространство (плюс фиксированный откат к Креативу) и никогда не отражает PO/Архитектора/Dev Lead/область после handoff.
| projectId required | string |
| sessionId required | string |
| agentId | string Агент, активный в этой сессии в данный момент (например, "po", "arquiteto", "dev-lead"). |
{- "modelId": "01JC4Z0000MODELO00000000001",
- "origin": "project",
- "routingPreference": null,
- "skipped": [
- {
- "scope": "agent",
- "modelId": "01JC4Z0000MODELO00000000001",
- "reason": "unavailable"
}
], - "herdadoDoLead": "secops"
}Побеждает всё остальное в каскаде, пока живёт сессия.
| projectId required | string |
| sessionId required | string |
| modelId required | string <uuid> Модель для закрепления в этой области действия. Должна существовать в |
| routingPreference | string or null Перечисление: "price" "throughput" "latency" Как ХАБ выбирает upstream, обслуживающий модель (ADR 0166, RN-583). ОТСУТСТВИЕ сохраняет хранимое значение, если провайдер новой модели его принимает, и очищает его в противном случае; |
{- "modelId": "9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b",
- "routingPreference": "throughput"
}{- "id": "01JC4Z0000BINDING00000000001",
- "scope": "project",
- "scopeId": "01JC4Z0000PROJETO0000000001",
- "modelId": "01JC4Z0000MODELO00000000001",
- "routingPreference": null,
- "createdBy": "01JC4Z0000USUARIO0000000001",
- "createdAt": "2026-07-11T09:00:00.000Z",
- "updatedAt": "2026-07-11T09:00:00.000Z"
}Данные всегда были в token_usage, но без агрегации и маршрута панель команды не могла показать стоимость по карточке. Требуется та же роль, что и для бюджета сессии.
| sessionId required | string |
[- {
- "actorId": "dev-api",
- "costMicros": 3400000,
- "inputTokens": 128000,
- "outputTokens": 14200,
- "lastCall": {
- "inputTokens": 42000,
- "modelName": "qwen2.5-coder:7b",
- "contextWindow": 128000
}
}
]Аудитория УЧАСТНИКА. Только собственные строки вызывающего — актор берётся из аутентифицированного токена, и параметра для его подмены нет. В токенах и ОЦЕНОЧНОЙ стоимости, без разбивки по провайдеру или учётным данным: ключ, который работал, принадлежит владельцу (RN-058), и счёт по нему — его (RN-060). Ось провайдера появилась в отчёте владельца (ADR 0076) и остаётся недоступной отсюда — на этом маршруте нет параметра измерения, а тип репозитория отклоняет такую комбинацию (RN-187). Агент не учитывается: token_usage записывает, кто потратил, а не кто заказал расход.
| projectId required | string |
| dias | string Скользящее окно. По умолчанию 30, максимум 180. |
{- "projectId": "9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b",
- "dias": 30,
- "actorId": "9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b",
- "totalMicros": 1250000,
- "assinaturaMicros": 0,
- "inputTokens": 120000,
- "outputTokens": 35000,
- "chamadas": 42,
- "porSessao": [
- {
- "chave": "anthropic/claude-sonnet-4",
- "rotulo": "Loja",
- "actorKind": "agent",
- "costMicros": 1250000,
- "assinaturaMicros": 0,
- "inputTokens": 120000,
- "outputTokens": 35000,
- "chamadas": 42
}
], - "porDia": [
- {
- "dia": "2026-08-09",
- "costMicros": 1250000,
- "chamadas": 42
}
]
}The MEMBER's view of the PROJECT (RN-953, ADR 0233, revising 0063): total and cost per agent/area, NEVER provider or credential — the route has no dimension parameter and never asks for provider.
| projectId required | string |
| dias | string Скользящее окно. По умолчанию 30, максимум 180. |
{- "projectId": "5a8591dd-4039-49df-9202-96385ba3eff8",
- "dias": 30,
- "totalMicros": 1250000,
- "assinaturaMicros": 0,
- "inputTokens": 120000,
- "outputTokens": 35000,
- "chamadas": 42,
- "porAgente": [
- {
- "chave": "anthropic/claude-sonnet-4",
- "rotulo": "Loja",
- "actorKind": "agent",
- "costMicros": 1250000,
- "assinaturaMicros": 0,
- "inputTokens": 120000,
- "outputTokens": 35000,
- "chamadas": 42
}
], - "porArea": [
- {
- "chave": "anthropic/claude-sonnet-4",
- "rotulo": "Loja",
- "actorKind": "agent",
- "costMicros": 1250000,
- "assinaturaMicros": 0,
- "inputTokens": 120000,
- "outputTokens": 35000,
- "chamadas": 42
}
]
}Существует, потому что ключ, который тратят агенты, принадлежит ВЛАДЕЛЬЦУ рабочего пространства (RN-058), — тот, кто оплачивает счёт, должен видеть счёт. Группирует по провайдеру, потому что это единица учётных данных, и отделяет то, что ушло через АГЕНТА, от того, что ушло через человека в чате: оба выходят из одного ключа и отвечают на разные вопросы. Не возвращает никаких секретов.
| workspaceId required | string |
| meses required | string |
{- "workspaceId": "9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b",
- "ownerId": "9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b",
- "meses": 6,
- "totalMicros": 1250000,
- "porProvider": [
- {
- "provider": "openrouter",
- "temCredencial": true,
- "costMicros": 1250000,
- "inputTokens": 120000,
- "outputTokens": 35000,
- "chamadas": 42,
- "costMicrosAgentes": 900000,
- "costMicrosPessoas": 350000,
- "porMes": [
- {
- "mes": "2026-08-01T00:00:00.000Z",
- "costMicros": 1250000,
- "chamadas": 42
}
]
}
]
}По умолчанию фильтрует только ОФИЦИАЛЬНЫХ издателей (список разрешённых в domain/llm/huggingface-official-publishers.ts) — тот же дух «всегда ручная курация», что и у каталога LLM (ADR 0042), применённый к значку «официальный», а не к активации. includeCommunity=true включает всех издателей, каждый помечен official: false, чтобы экран показал предупреждение о безопасности, — никогда не скрывается.
| q required | string Текст поиска. |
| includeCommunity | string Передайте |
[- {
- "repoId": "meta-llama/Llama-3.1-8B-Instruct-GGUF",
- "publisher": "meta-llama",
- "downloads": 182034,
- "likes": 210,
- "official": true
}
]Первый из двух явных шагов: этот вызов лишь записывает намерение, в pending_confirmation. Ничего не скачивается до отдельного вызова POST .../pull-requests/:id/confirm — продукт никогда не выполняет автоматическое молчаливое скачивание.
| workspaceId required | string |
| repoId required | string
|
| estimatedSizeBytes | number Приходит из поиска, когда Hub опубликовал размер, — никогда не оценивается наугад на клиенте. |
{- "repoId": "meta-llama/Llama-3.1-8B-Instruct-GGUF",
- "estimatedSizeBytes": 4900000000
}{- "id": "9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b",
- "workspaceId": "9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b",
- "requestedBy": "9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b",
- "repoId": "meta-llama/Llama-3.1-8B-Instruct-GGUF",
- "estimatedSizeBytes": null,
- "status": "pending_confirmation",
- "confirmedAt": null,
- "failedReason": null,
- "createdAt": "2026-08-26T12:00:00.000Z",
- "updatedAt": "2026-08-26T12:00:00.000Z"
}| workspaceId required | string |
| id required | string |
{- "id": "9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b",
- "workspaceId": "9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b",
- "requestedBy": "9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b",
- "repoId": "meta-llama/Llama-3.1-8B-Instruct-GGUF",
- "estimatedSizeBytes": null,
- "status": "pending_confirmation",
- "confirmedAt": null,
- "failedReason": null,
- "createdAt": "2026-08-26T12:00:00.000Z",
- "updatedAt": "2026-08-26T12:00:00.000Z"
}Переводит pending_confirmation → confirmed → pulling, вызывает демон Ollama и при успехе делает upsert модели в каталог (models) и активирует её для ЭТОГО рабочего пространства. При сбое запрос заканчивается в failed с причиной, указывающей источник (infra | model | code | policy), — никогда не молчаливый сбой. Этот вызов выполняется СИНХРОННО в течение всего скачивания (почему — см. сценарий использования); используйте маршрут GET ниже для опроса, а не держите это соединение открытым.
| workspaceId required | string |
| id required | string |
{- "id": "9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b",
- "workspaceId": "9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b",
- "requestedBy": "9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b",
- "repoId": "meta-llama/Llama-3.1-8B-Instruct-GGUF",
- "estimatedSizeBytes": null,
- "status": "pending_confirmation",
- "confirmedAt": null,
- "failedReason": null,
- "createdAt": "2026-08-26T12:00:00.000Z",
- "updatedAt": "2026-08-26T12:00:00.000Z"
}The owner of record (workspaces.created_by) is whose key the agents spend (RN-058). Only provider NAMES — never a secret, id or date. ollama (local, no key) never appears.
| workspaceId required | string |
{- "providers": [
- "anthropic",
- "openrouter"
]
}СЫРОЙ binding этой области действия. Это корень каскада.
| workspaceId required | string |
{- "id": "01JC4Z0000BINDING00000000001",
- "scope": "project",
- "scopeId": "01JC4Z0000PROJETO0000000001",
- "modelId": "01JC4Z0000MODELO00000000001",
- "routingPreference": null,
- "createdBy": "01JC4Z0000USUARIO0000000001",
- "createdAt": "2026-07-11T09:00:00.000Z",
- "updatedAt": "2026-07-11T09:00:00.000Z"
}Применяется к каждому проекту, у которого нет собственного binding.
| workspaceId required | string |
| modelId required | string <uuid> Модель для закрепления в этой области действия. Должна существовать в |
| routingPreference | string or null Перечисление: "price" "throughput" "latency" Как ХАБ выбирает upstream, обслуживающий модель (ADR 0166, RN-583). ОТСУТСТВИЕ сохраняет хранимое значение, если провайдер новой модели его принимает, и очищает его в противном случае; |
{- "modelId": "9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b",
- "routingPreference": "throughput"
}{- "id": "01JC4Z0000BINDING00000000001",
- "scope": "project",
- "scopeId": "01JC4Z0000PROJETO0000000001",
- "modelId": "01JC4Z0000MODELO00000000001",
- "routingPreference": null,
- "createdBy": "01JC4Z0000USUARIO0000000001",
- "createdAt": "2026-07-11T09:00:00.000Z",
- "updatedAt": "2026-07-11T09:00:00.000Z"
}Same curation level as the pricing route. subscription makes the spend report show the cost apart from the US$ paid (RN-954). Never re-prices the past.
| modelId required | string |
| billing required | string Перечисление: "api" "subscription" "free" |
{- "billing": "subscription"
}{- "id": "01JC4Z0000MODELO00000000001",
- "provider": "anthropic",
- "name": "claude-opus-4-8",
- "displayName": "Claude Opus 4.8",
- "inputPricePerMillionMicros": 15000000,
- "outputPricePerMillionMicros": 75000000,
- "contextWindow": 200000,
- "supportsToolCalling": true,
- "supportsStreaming": true,
- "supportsVision": false,
- "supportsReasoning": true,
- "generatesImage": false,
- "reasoningBudget": 2048,
- "manualPricing": true,
- "availability": "available",
- "billing": "api",
- "lastSeenAt": "2026-07-30T03:00:00.000Z",
- "createdAt": "2026-07-01T00:00:00.000Z",
- "updatedAt": "2026-07-01T00:00:00.000Z"
}Сначала самые свежие. Это неизменяемый журнал: никогда не получает UPDATE, а строка несёт пару до/после, чтобы аудит не зависел от восстановления «до» по предыдущей строке.
| modelId required | string |
[- {
- "id": "01JC4Z0000PRECO000000000001",
- "modelId": "01JC4Z0000MODELO00000000001",
- "inputBeforeMicros": 2500000,
- "inputAfterMicros": 3000000,
- "outputBeforeMicros": 10000000,
- "outputAfterMicros": 12000000,
- "source": "manual",
- "changedBy": "01JC4Z0000USUARIO0000000001",
- "createdAt": "2026-08-01T12:00:00.000Z"
}
]Применяется с этого момента и НИКОГДА не переоценивает прошлое: каждая строка token_usage хранит цену, давшую её стоимость. Изменение остаётся записанным с парой до/после — его можно запросить на маршруте истории.
| modelId required | string |
| inputPricePerMillionMicros required | number Цена за миллион ВХОДНЫХ токенов, в микро-USD. |
| outputPricePerMillionMicros required | number Цена за миллион ВЫХОДНЫХ токенов, в микро-USD. |
{- "inputPricePerMillionMicros": 2500000,
- "outputPricePerMillionMicros": 10000000
}{- "id": "01JC4Z0000MODELO00000000001",
- "provider": "anthropic",
- "name": "claude-opus-4-8",
- "displayName": "Claude Opus 4.8",
- "inputPricePerMillionMicros": 15000000,
- "outputPricePerMillionMicros": 75000000,
- "contextWindow": 200000,
- "supportsToolCalling": true,
- "supportsStreaming": true,
- "supportsVision": false,
- "supportsReasoning": true,
- "generatesImage": false,
- "reasoningBudget": 2048,
- "manualPricing": true,
- "availability": "available",
- "billing": "api",
- "lastSeenAt": "2026-07-30T03:00:00.000Z",
- "createdAt": "2026-07-01T00:00:00.000Z",
- "updatedAt": "2026-07-01T00:00:00.000Z"
}Курация владельца, ПРИМЕНЯЕМАЯ ТОЛЬКО К ЭТОМУ РАБОЧЕМУ ПРОСТРАНСТВУ (ADR 0049). Затрагивает только isActive — availability — это то, что синхронизация наблюдала у провайдера, оно глобально и здесь не меняется. Весь пакет или ничего: несуществующий id приводит к сбою вызова без применения какой-либо строки.
| workspaceId required | string |
| modelIds required | Массив из strings <uuid> [ items <uuid > ] Модели для включения или выключения. Весь пакет или ничего. |
| isActive required | boolean
|
{- "modelIds": [
- "9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b"
], - "isActive": true
}[- {
- "id": "01JC4Z0000MODELO00000000001",
- "provider": "anthropic",
- "name": "claude-opus-4-8",
- "displayName": "Claude Opus 4.8",
- "inputPricePerMillionMicros": 15000000,
- "outputPricePerMillionMicros": 75000000,
- "contextWindow": 200000,
- "supportsToolCalling": true,
- "supportsStreaming": true,
- "supportsVision": false,
- "supportsReasoning": true,
- "generatesImage": false,
- "reasoningBudget": 2048,
- "manualPricing": true,
- "availability": "available",
- "billing": "api",
- "lastSeenAt": "2026-07-30T03:00:00.000Z",
- "createdAt": "2026-07-01T00:00:00.000Z",
- "updatedAt": "2026-07-01T00:00:00.000Z",
- "isActive": true,
- "uses": [
- "codigo"
], - "freeRoutingAlias": false
}
]Экран курации. Модель, обнаруженная синхронизацией, приходит как isActive: false и появляется только здесь, пока кто-нибудь её не активирует; модель, исчезнувшая у провайдера, приходит с availability: "unavailable" и никогда не удаляется, потому что на неё указывают binding и история затрат. isActive — ДЛЯ ЭТОГО рабочего пространства (ADR 0049); availability глобально, потому что это то, что синхронизация наблюдала у провайдера.
| workspaceId required | string |
{- "local": {
- "ollama": [ ]
}, - "cloud": {
- "anthropic": [ ],
- "openai": [ ]
}
}Тот же сценарий использования, что и у внутреннего маршрута, который планирует engine, — у кнопки обновления на экране курации нет собственного пути, поэтому никогда не бывает двух сверок, которые могли бы разойтись.
{- "porProvider": [
- {
- "provider": "openai",
- "descobertos": 3,
- "reencontrados": 1,
- "indisponibilizados": 0,
- "pulado": "sem_capability",
- "origemDaFalha": "infra",
- "detalhe": "openai responded with status 401"
}
]
}Ось, которую не публикует ни один каталог: «хорош для кода» — не возможность, объявленная каким-либо провайдером, это мнение оператора — и применяется ТОЛЬКО к этому рабочему пространству (ADR 0049). Ось независима от активации: пометка применения не включает модель в селекторе, а изменение применения не выключает то, что было включено. Список применений ЗАМЕНЯЕТ предыдущий.
| workspaceId required | string |
| modelIds required | Массив из strings <uuid> [ items <uuid > ] Модели для пометки. Весь пакет или ничего. |
| uses required | Массив из strings Элементы Перечисление: "codigo" "documentacao" "analise" "imagem" "conversa" ПОЛНЫЙ список применений — заменяет предыдущий, а не добавляет к нему. Пустой список снимает все пометки. Закрытый словарь: применение вне его приводит к сбою вызова, потому что свободный текст дал бы |
{- "modelIds": [
- "9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b"
], - "uses": [
- "codigo",
- "analise"
]
}[- {
- "id": "01JC4Z0000MODELO00000000001",
- "provider": "anthropic",
- "name": "claude-opus-4-8",
- "displayName": "Claude Opus 4.8",
- "inputPricePerMillionMicros": 15000000,
- "outputPricePerMillionMicros": 75000000,
- "contextWindow": 200000,
- "supportsToolCalling": true,
- "supportsStreaming": true,
- "supportsVision": false,
- "supportsReasoning": true,
- "generatesImage": false,
- "reasoningBudget": 2048,
- "manualPricing": true,
- "availability": "available",
- "billing": "api",
- "lastSeenAt": "2026-07-30T03:00:00.000Z",
- "createdAt": "2026-07-01T00:00:00.000Z",
- "updatedAt": "2026-07-01T00:00:00.000Z",
- "isActive": true,
- "uses": [
- "codigo"
], - "freeRoutingAlias": false
}
]Аудитория ВЛАДЕЛЬЦА. Ось ПРОВАЙДЕРА вернулась (ADR 0076, RN-186) и находится здесь, а не на маршруте участника, потому что разбивка по провайдеру — это разбивка по УЧЁТНЫМ ДАННЫМ, а учётные данные — дело того, кто платит (RN-060). Дополняет credential-spend, который продолжает отвечать на вопрос о СЧЁТЕ (по месяцам, с привязкой к существующему сегодня ключу). Человек и агент также идут отдельными блоками, по actor_kind. Скользящее окно в днях, а дневной ряд приходит ПЛОТНЫМ — день без расходов приходит как ноль, иначе спарклайн лжёт о темпе.
| workspaceId required | string |
| dias | string Скользящее окно. По умолчанию 30, максимум 180. |
| projectId | string Slice to ONE project of this workspace (RN-952). A project from another workspace answers 404. |
{- "workspaceId": "9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b",
- "ownerId": "9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b",
- "dias": 30,
- "projectId": { },
- "totalMicros": 1250000,
- "assinaturaMicros": 0,
- "inputTokens": 120000,
- "outputTokens": 35000,
- "chamadas": 42,
- "porModelo": [
- {
- "chave": "anthropic/claude-sonnet-4",
- "rotulo": "Loja",
- "actorKind": "agent",
- "costMicros": 1250000,
- "assinaturaMicros": 0,
- "inputTokens": 120000,
- "outputTokens": 35000,
- "chamadas": 42
}
], - "porProvider": [
- {
- "chave": "anthropic/claude-sonnet-4",
- "rotulo": "Loja",
- "actorKind": "agent",
- "costMicros": 1250000,
- "assinaturaMicros": 0,
- "inputTokens": 120000,
- "outputTokens": 35000,
- "chamadas": 42
}
], - "porProjeto": [
- {
- "chave": "anthropic/claude-sonnet-4",
- "rotulo": "Loja",
- "actorKind": "agent",
- "costMicros": 1250000,
- "assinaturaMicros": 0,
- "inputTokens": 120000,
- "outputTokens": 35000,
- "chamadas": 42
}
], - "porSessao": [
- {
- "chave": "anthropic/claude-sonnet-4",
- "rotulo": "Loja",
- "actorKind": "agent",
- "costMicros": 1250000,
- "assinaturaMicros": 0,
- "inputTokens": 120000,
- "outputTokens": 35000,
- "chamadas": 42
}
], - "porAtor": [
- {
- "chave": "anthropic/claude-sonnet-4",
- "rotulo": "Loja",
- "actorKind": "agent",
- "costMicros": 1250000,
- "assinaturaMicros": 0,
- "inputTokens": 120000,
- "outputTokens": 35000,
- "chamadas": 42
}
], - "porOwner": [
- {
- "chave": "anthropic/claude-sonnet-4",
- "rotulo": "Loja",
- "actorKind": "agent",
- "costMicros": 1250000,
- "assinaturaMicros": 0,
- "inputTokens": 120000,
- "outputTokens": 35000,
- "chamadas": 42
}
], - "porAgente": [
- {
- "chave": "anthropic/claude-sonnet-4",
- "rotulo": "Loja",
- "actorKind": "agent",
- "costMicros": 1250000,
- "assinaturaMicros": 0,
- "inputTokens": 120000,
- "outputTokens": 35000,
- "chamadas": 42
}
], - "porDia": [
- {
- "dia": "2026-08-09",
- "costMicros": 1250000,
- "chamadas": 42
}
]
}The non-owner's global page (RN-953). Only the caller's rows, by PROJECT and by day, without provider or credential.
| workspaceId required | string |
| dias | string Скользящее окно. По умолчанию 30, максимум 180. |
{- "workspaceId": "ef0efa32-d1c1-43d4-a5e2-fe7b4f00403c",
- "dias": 30,
- "actorId": "d2f1b55c-8cee-4a0d-af57-aebaeff77518",
- "totalMicros": 1250000,
- "assinaturaMicros": 0,
- "inputTokens": 120000,
- "outputTokens": 35000,
- "chamadas": 42,
- "porProjeto": [
- {
- "chave": "anthropic/claude-sonnet-4",
- "rotulo": "Loja",
- "actorKind": "agent",
- "costMicros": 1250000,
- "assinaturaMicros": 0,
- "inputTokens": 120000,
- "outputTokens": 35000,
- "chamadas": 42
}
], - "porDia": [
- {
- "dia": "2026-08-09",
- "costMicros": 1250000,
- "chamadas": 42
}
]
}{- "id": "01JC4Z0000PROJETO0000000001",
- "workspaceId": "01JC4Z0000WORKSPACE00000001",
- "name": "Checkout",
- "slug": "checkout",
- "workspaceDirName": "checkout-3f2b1c8e",
- "executionMode": "container",
- "workspacePath": null,
- "workspaceVerifiedAt": null,
- "mirrorPath": null,
- "language": "pt-BR",
- "createdBy": "01JC4Z0000USUARIO0000000001",
- "taskBudgetMicros": 500000,
- "maxConsecutiveBlocked": 3,
- "storyPromotion": "auto",
- "createdAt": "2026-07-21T11:00:00.000Z",
- "updatedAt": "2026-07-21T11:00:00.000Z"
}| projectId required | string |
| name | string >= 2 characters |
| slug | string^[a-z0-9]+(-[a-z0-9]+)*$ kebab-case. Уникален в пределах рабочего пространства. |
| maxConsecutiveBlocked | number Предохранитель (Фаза 12b — RN-047): сколько последовательных задач, закончившихся блокировкой, останавливают dev agent модуля в idle_tripped. Если опущено, используется значение домена по умолчанию (DEFAULT_MAX_CONSECUTIVE_BLOCKED). Применяется со СЛЕДУЮЩЕЙ активации выполнения — не затрагивает уже работающих агентов. |
| storyPromotion | string Перечисление: "manual" "auto" Кто продвигает историю из |
| language | string <= 35 characters Язык ПРОЕКТА (RN-619) — его используют общие артефакты и ходы без автора-человека. Любой код BCP-47, распознаваемый сервером, хранимый в каноническом виде; нераспознанный — это 400. Если опущен, проект начинает с эффективного языка ответов своего создателя (RN-618: выбор в учётной записи > подтверждённое обнаружение > язык интерфейса). |
{- "name": "Checkout",
- "slug": "checkout",
- "maxConsecutiveBlocked": 3,
- "storyPromotion": "auto",
- "language": "pt-BR"
}{- "id": "01JC4Z0000PROJETO0000000001",
- "workspaceId": "01JC4Z0000WORKSPACE00000001",
- "name": "Checkout",
- "slug": "checkout",
- "workspaceDirName": "checkout-3f2b1c8e",
- "executionMode": "container",
- "workspacePath": null,
- "workspaceVerifiedAt": null,
- "mirrorPath": null,
- "language": "pt-BR",
- "createdBy": "01JC4Z0000USUARIO0000000001",
- "taskBudgetMicros": 500000,
- "maxConsecutiveBlocked": 3,
- "storyPromotion": "auto",
- "createdAt": "2026-07-21T11:00:00.000Z",
- "updatedAt": "2026-07-21T11:00:00.000Z"
}Забирает с собой свои сессии, журнал событий и предложенные действия.
| projectId required | string |
{- "id": "01JC4Z0000PROJETO0000000001",
- "workspaceId": "01JC4Z0000WORKSPACE00000001",
- "name": "Checkout",
- "slug": "checkout",
- "workspaceDirName": "checkout-3f2b1c8e",
- "executionMode": "container",
- "workspacePath": null,
- "workspaceVerifiedAt": null,
- "mirrorPath": null,
- "language": "pt-BR",
- "createdBy": "01JC4Z0000USUARIO0000000001",
- "taskBudgetMicros": 500000,
- "maxConsecutiveBlocked": 3,
- "storyPromotion": "auto",
- "createdAt": "2026-07-21T11:00:00.000Z",
- "updatedAt": "2026-07-21T11:00:00.000Z"
}Пока статус sem_decisao, контейнер проекта не поднимается, а вкладка Code отвечает 409 — это gate RN-105. Чтобы видеть состояние, нужно то же разрешение, что и для просмотра проекта, поэтому viewer.
| projectId required | string |
{- "status": "sem_decisao",
- "decisao": {
- "image": "node:22-bookworm-slim",
- "rationale": "The module_map is all TypeScript over Node; the slim variant is enough and reduces surface.",
- "network": "none",
- "resources": {
- "cpus": 2,
- "memoryMb": 4096,
- "pidsLimit": 512
}
}, - "version": 0,
- "eventId": "01JC4Z0000EVENTO000000001",
- "decidedAt": { }
}null, когда проект никогда не создавался, — сегодня это обычный случай, потому что ни один реальный оркестратор ещё не переводит эту таблицу (RN-243/RN-267). status/imageVersion/resources — это то, что было ЗАПИСАНО; observado — то, что демон Docker сообщает прямо сейчас, по запросу через broker (ADR 0130). Они никогда не сливаются: контейнер, убитый извне, читается как записанный running и наблюдаемый exited. Когда посмотреть не удалось, observado равен null, а naoObservado говорит почему — он никогда не наследует записанное состояние (RN-468).
| projectId required | string |
{- "status": "provisioning",
- "imageVersion": 1,
- "resources": {
- "cpus": 2,
- "memoryMb": 4096,
- "pidsLimit": 512
}, - "failureReason": null,
- "createdAt": "2019-08-24T14:15:22Z",
- "statusChangedAt": "2019-08-24T14:15:22Z",
- "observado": {
- "containerId": "c0ffeebabe...",
- "nome": "brabo-exp002-f52be111",
- "estado": "running",
- "imagem": "node:22-bookworm-slim",
- "iniciadoEm": { }
}, - "naoObservado": "broker-nao-configurado",
- "detalheDaObservacao": null
}The current declaration (ports on 127.0.0.1, DNS, extra hosts), or null. It is what was DECLARED; it only takes effect when the container is recreated (container_recreate).
| projectId required | string |
{- "ports": [
- {
- "host": 8080,
- "container": 3000,
- "protocol": "tcp"
}
], - "dns": [
- "string"
], - "extraHosts": [
- {
- "name": "api.local",
- "ip": "10.0.0.5"
}
], - "version": 1
}Emits a new version of artifact.container_network (the current one replaces the previous). Not an external effect: nothing changes in Docker until a container_recreate is approved (AT-608, RN-932). 400 rede_invalida names the field.
| projectId required | string |
Массив из objects (PortaDto) Up to 10 TCP ports published on 127.0.0.1 only. | |
| dns | Массив из strings Up to 3 literal IPs. |
Массив из objects (HostExtraDto) Up to 10 |
{- "ports": [
- {
- "host": 8080,
- "container": 3000,
- "protocol": "tcp"
}
], - "dns": [
- "string"
], - "extraHosts": [
- {
- "name": "api.local",
- "ip": "10.0.0.5"
}
]
}{- "ports": [
- {
- "host": 8080,
- "container": 3000,
- "protocol": "tcp"
}
], - "dns": [
- "string"
], - "extraHosts": [
- {
- "name": "api.local",
- "ip": "10.0.0.5"
}
], - "version": 1
}Переносит execution_mode/workspacePath для УЖЕ существующего проекта (RN-447..450, ADR 0111) — отдельный маршрут, отделённый от PATCH, потому что он оркестрирует больше, чем запись столбца: переносит permissions.json в новый корень области, выводит из обращения строку жизненного цикла контейнера при выходе из режима container и отклоняет (409), пока любой dev agent проекта не простаивает, поскольку работающий агент сам не пересчитывает расположение своего worktree.
| projectId required | string |
| executionMode required | string Перечисление: "container" "mounted" "runner" Новый режим выполнения. Может повторять текущий режим проекта — в этом случае реально меняется только |
| workspacePath | string Абсолютный путь к папке пользователя — обязателен, когда |
{- "executionMode": "runner",
- "workspacePath": "/home/you/projects/store"
}{- "id": "01JC4Z0000PROJETO0000000001",
- "workspaceId": "01JC4Z0000WORKSPACE00000001",
- "name": "Checkout",
- "slug": "checkout",
- "workspaceDirName": "checkout-3f2b1c8e",
- "executionMode": "container",
- "workspacePath": null,
- "workspaceVerifiedAt": null,
- "mirrorPath": null,
- "language": "pt-BR",
- "createdBy": "01JC4Z0000USUARIO0000000001",
- "taskBudgetMicros": 500000,
- "maxConsecutiveBlocked": 3,
- "storyPromotion": "auto",
- "createdAt": "2026-07-21T11:00:00.000Z",
- "updatedAt": "2026-07-21T11:00:00.000Z"
}Только те, кто был связан ЗДЕСЬ, с ролью этой связи, — тот, кто попадает в проект только через рабочее пространство, не имеет строки и не появляется.
| projectId required | string |
[- {
- "userId": "01JC4Z0000USUARIO0000000002",
- "role": "maintainer",
- "name": "Senior Dev",
- "email": "dev@brabo.dev"
}
]Эта роль ПЕРЕОПРЕДЕЛЯЕТ то, что у человека есть в рабочем пространстве, в обе стороны: связывание кого-то как viewer здесь действительно ограничивает developer рабочего пространства в этом проекте. Два действия отклоняются с 403 и не могут быть нигде включены: понижение owner рабочего пространства и изменение СОБСТВЕННОЙ роли — вниз или вверх, поскольку самоповышение — та половина, которая повышает привилегии.
| projectId required | string |
| userId required | string <uuid> Id пользователя для связывания. |
| role required | string Перечисление: "viewer" "developer" "maintainer" "owner" Роль в этой связи. В ПРОЕКТЕ она ПЕРЕОПРЕДЕЛЯЕТ роль рабочего пространства в обе стороны — эффективной является эта роль, пока связь существует, выше ИЛИ ниже, — и два действия отклоняются с 403: понижение |
{- "userId": "3f1b2c8e-5a4d-4b7e-9c10-2d6f8a1b4c33",
- "role": "developer"
}{- "projectId": "01JC4Z0000PROJETO0000000001",
- "userId": "01JC4Z0000USUARIO0000000002",
- "role": "maintainer",
- "createdAt": "2026-07-22T08:05:00.000Z"
}Удаляет только связь с ПРОЕКТОМ. Тот, у кого есть роль в рабочем пространстве, продолжает видеть проект по наследованию, — и поскольку роль проекта ПЕРЕОПРЕДЕЛЯЕТ роль рабочего пространства в обе стороны, это наследование может быть НИЖЕ того, что давала строка. Поэтому удаление собственной строки отклоняется с 403 всякий раз, когда итоговый эффект — понижение (включая случай, когда роли в рабочем пространстве нет вовсе); удаление, когда рабочее пространство держит ту же роль, и удаление кого-либо другого по-прежнему работают.
| projectId required | string |
| userId required | string |
Абсолютный путь НА МАШИНЕ ПОЛЬЗОВАТЕЛЯ, куда локальный агент копирует работу (RN-515, ADR 0147), — папка ВНЕ смонтированной базы, в чём и состоит весь смысл существования зеркала. На проект и никогда не глобально: одно глобальное назначение помещало бы артефакты проекта B в папку проекта A, и пользователь узнал бы об этом по содержимому, а не из ошибки. mirrorPath: null ОЧИЩАЕТ его и выключает зеркало — ключ обязателен, его отсутствие даёт 400, потому что тело без поля было бы неотличимо от просьбы очистить. null — НОРМАЛЬНОЕ состояние проекта. maintainer, тот же минимум, что и у execution-mode и projects-base: этот маршрут говорит о пути в собственной файловой системе оператора. Он проверяет ТОЛЬКО ЛЕКСИЧЕСКУЮ форму и отклоняет с 400 назначение внутри workspacePath или содержащее его (оба направления одной петли), а также вообще любое назначение у проекта container. Он никогда не касается диска: API не видит машину, на которой будет находиться назначение. Запись зеркала НЕ является proposed_action — это конфигурация, объявленная пользователем, а не агент, просящий действовать.
| projectId required | string |
| mirrorPath required | string or null Абсолютный путь НА МАШИНЕ ПОЛЬЗОВАТЕЛЯ, куда локальный агент копирует работу проекта (RN-515, ADR 0147). Отправьте |
{- "mirrorPath": "/home/you/mirrors/store"
}{- "id": "01JC4Z0000PROJETO0000000001",
- "workspaceId": "01JC4Z0000WORKSPACE00000001",
- "name": "Checkout",
- "slug": "checkout",
- "workspaceDirName": "checkout-3f2b1c8e",
- "executionMode": "container",
- "workspacePath": null,
- "workspaceVerifiedAt": null,
- "mirrorPath": null,
- "language": "pt-BR",
- "createdBy": "01JC4Z0000USUARIO0000000001",
- "taskBudgetMicros": 500000,
- "maxConsecutiveBlocked": 3,
- "storyPromotion": "auto",
- "createdAt": "2026-07-21T11:00:00.000Z",
- "updatedAt": "2026-07-21T11:00:00.000Z"
}Телеметрия, которую локальный агент отправил по каналу после копирования работы в папку пользователя (ADR 0147, пункт 7), — никогда не журнал событий, потому что у раунда зеркала нет сессии, а session_events.session_id — NOT NULL (то же рассуждение, что сделало rag_searches таблицей). ТРИ ответа, которые никогда не сливаются в один (RN-088): never (ни один раунд не сообщался), synced (последний раунд скопировал — filesCopied может быть 0, что значит «посмотрел, и копировать было нечего») и failed. Сбой никогда не стирает последнюю успешную синхронизацию, а успех никогда не стирает последнюю ошибку: какой из них ТЕКУЩИЙ, выясняется сравнением двух меток времени, поэтому экран может сказать «сбой с сегодняшнего дня, последняя хорошая копия была вчера, 412 файлов». lastDestination ЗАМОРОЖЕН — он расходится с mirrorPath после того, как кто-то меняет назначение, потому что разрешение передаётся в join и меняется только при переподключении runner (RN-516). viewer, тот же минимум, что и для чтения проекта, который уже несёт mirrorPath.
| projectId required | string |
{- "mirrorPath": "/home/you/mirrors/store",
- "status": "synced",
- "lastSyncedAt": "2026-09-07T12:04:00.000Z",
- "filesCopied": 412,
- "filesSkipped": 3,
- "filesRefused": 0,
- "lastDestination": "/home/you/mirrors/store",
- "lastError": null,
- "lastErrorAt": null
}Это физический файл в корне рабочей области проекта, а не столбец базы данных.
| projectId required | string |
{- "allow": [
- "Terminal(pnpm test:*)",
- "Terminal(pnpm lint)"
], - "deny": [
- "Terminal(rm -rf *)",
- "Terminal(git push --force*)"
], - "ask": [
- "Terminal(pnpm add *)"
]
}Заменяет все три списка сразу. deny по-прежнему побеждает allow при оценке — разрешить через это что-то запрещённое невозможно.
| projectId required | string |
| allow required | Массив из strings Автоматически одобряемые шаблоны. |
| deny required | Массив из strings Запрещённые шаблоны. |
| ask required | Массив из strings Шаблоны, всегда требующие решения человека. |
{- "allow": [
- "Terminal(pnpm test:*)",
- "Terminal(pnpm lint)"
], - "deny": [
- "Terminal(rm -rf *)",
- "Terminal(git push --force*)"
], - "ask": [
- "Terminal(pnpm add *)"
]
}{- "allow": [
- "Terminal(pnpm test:*)",
- "Terminal(pnpm lint)"
], - "deny": [
- "Terminal(rm -rf *)",
- "Terminal(git push --force*)"
], - "ask": [
- "Terminal(pnpm add *)"
]
}Никогда не включает сырой токен — только то, что уже было выпущено.
| projectId required | string |
[- {
- "id": "01JC4Z0000PAT000000000001",
- "name": "laptop",
- "projectId": "01JC4Z0000PROJETO000000001",
- "createdAt": "2026-08-22T12:00:00.000Z",
- "expiresAt": null,
- "revokedAt": null,
- "lastUsedAt": null
}
]Сырой токен (brb_...) появляется только в ЭТОМ ответе — потом его нельзя восстановить, хранится только хеш. Используйте в --token/BRABO_ACCOUNT_TOKEN у brabo-runner.
| projectId required | string |
| name required | string Имя, чтобы вы потом узнали этот токен, — не уникально. |
| expiresInDays | number Истекает через N дней. Опущено = без срока действия (можно отозвать в любой момент, но сам он никогда не истекает). |
{- "name": "laptop",
- "expiresInDays": 90
}{- "id": "01JC4Z0000PAT000000000001",
- "name": "laptop",
- "projectId": "01JC4Z0000PROJETO000000001",
- "createdAt": "2026-08-22T12:00:00.000Z",
- "expiresAt": null,
- "revokedAt": null,
- "lastUsedAt": null,
- "token": "brb_9f8a..."
}Идемпотентно — повторный отзыв не является ошибкой. Начиная с RN-685 (ADR 0201) также РАЗРЫВАЕТ соединения runner, открытые с ЭТИМ токеном, и только их — ключ устройства того же пользователя остаётся, — и аннулирует его ещё не использованные тикеты. Недоступный engine НЕ приводит к сбою отзыва.
| projectId required | string |
| tokenId required | string |
Реагирование на инцидент — уволенный разработчик с утёкшим токеном (RN-427). Идемпотентно — повторный отзыв не является ошибкой. Начиная с RN-685 (ADR 0201) также РАЗРЫВАЕТ уже подключённый с этим токеном runner, и только его.
| projectId required | string |
| tokenId required | string |
Представление для maintainer для реагирования на инциденты (RN-427) — включает владельца каждого токена. Никогда не включает сырой токен.
| projectId required | string |
[- {
- "id": "01JC4Z0000PAT000000000001",
- "name": "laptop",
- "projectId": "01JC4Z0000PROJETO000000001",
- "createdAt": "2026-08-22T12:00:00.000Z",
- "expiresAt": null,
- "revokedAt": null,
- "lastUsedAt": null,
- "userId": "01JC4Z0000USUARIO000000001",
- "userEmail": "dev@exemplo.com"
}
]Никто не отзовёт то, чего не видит (RN-519). Включает и уже ОТОЗВАННЫЕ — исчезновение строки заставило бы экран утверждать, что ключа никогда не было. Никогда не возвращает публичный JWK, а приватный api никогда не видела. Нулевой lastUsedAt — признак ОСИРОТЕВШЕГО ключа: зарегистрированного и ни разу не использованного ни одним runner. Начиная с RN-543 также включает МАШИННЫЕ ключи вызывающего (especie: "maquina", projectId нулевой), которые обслуживают этот проект, не принадлежа ему, — без них в списке машинный ключ был бы невидим на любом экране.
| projectId required | string |
[- {
- "id": "01JC4Z0000CHAVE000000000001",
- "name": "laptop",
- "projectId": "01JC4Z0000PROJETO000000001",
- "especie": "projeto",
- "createdAt": "2026-08-27T12:00:00.000Z",
- "revokedAt": null,
- "lastUsedAt": null
}
]Идемпотентно — повторный отзыв не является ошибкой. Начиная с RN-520 также РАЗРЫВАЕТ живое соединение: раньше отзыв только не давал получить НОВЫЙ тикет, а уже подключённый runner продолжал выполнять одобренные команды. Начиная с RN-685 (ADR 0201) целью является КЛЮЧ: разрываются только соединения, открытые с ним, — во всех проектах, если он машинный, — а другой runner того же пользователя, подключённый с PAT или другим ключом, остаётся. Его ещё не использованные тикеты аннулируются. Недоступный engine или отсутствие подключённого runner НЕ приводят к сбою отзыва.
| projectId required | string |
| deviceKeyId required | string |
Аутентифицирует connect/3 сокета Phoenix /runner (топик terminal:<projectId>, роль "runner") — это НЕ переиспользованный JWT, и этот маршрут сам принимает только Personal Access Token вместо JWT сессии (ADR 0105). TTL 30 с и однократное использование. Отклоняет с 400, если проект не в режиме "runner" (RN-421): runner нечего обслуживать в проекте, код которого живёт в управляемом контейнере.
| projectId required | string |
{- "ticket": "string",
- "expiresAt": "2026-08-09T12:00:30.000Z",
- "engineWsUrl": "ws://localhost:4000/runner"
}Тот же сокет /runner, тот же топик, роль "terminal" — тот, кто ВИДИТ терминал (сама команда действительно выполняется, только если подключён runner). Действует для любого режима проекта.
| projectId required | string |
{- "ticket": "string",
- "expiresAt": "2026-08-09T12:00:30.000Z",
- "engineWsUrl": "ws://localhost:4000/runner"
}Аутентифицируется МАШИННЫМ ключом устройства (ADR 0154) — НИКОГДА JWT сессии и никогда учётными данными, привязанными к проекту, на которые отвечает 403. Возвращает проекты в execution_mode: "runner", в которых владелец ключа достигает как минимум developer (тот же минимум, что и у POST .../runner-ticket), с именем папки и состоянием проверки каждого. Агент спрашивает, а не сканирует диск: база машины принадлежит пользователю и может содержать папки, не являющиеся проектами.
[- {
- "projectId": "01JC4Z0000PROJETO000000001",
- "name": "Brabo",
- "workspaceDirName": "brabo-01jc4z",
- "workspaceVerifiedAt": null
}
]Одна строка на проект рабочего пространства — начиная с RN-521 проект, ни разу не создававший контейнер, ПРИСУТСТВУЕТ с registrado: null, а не отсутствует, потому что эта страница — путь человека для запуска первого. Наблюдаемое состояние запрашивается у broker только для строк provisioning/running и только в пределах бюджета на загрузку — см. naoVerificado у пропущенных строк и ADR 0136 с обоснованием.
| workspaceId required | string |
[- {
- "projectId": "01JC4Z0000PROJETO000001",
- "projectName": "exp002",
- "projectSlug": "exp002",
- "executionMode": "runner",
- "registrado": {
- "status": "running",
- "imageVersion": 1,
- "imagem": "node:22-bookworm-slim",
- "resources": {
- "cpus": 2,
- "memoryMb": 4096,
- "pidsLimit": 512
}, - "failureReason": null,
- "createdAt": "2019-08-24T14:15:22Z",
- "statusChangedAt": "2019-08-24T14:15:22Z"
}, - "temImagemDecidida": true,
- "workspaceVerifiedAt": { },
- "observado": {
- "containerId": "c0ffeebabe...",
- "nome": "brabo-exp002-f52be111",
- "estado": "running",
- "imagem": "node:22-bookworm-slim",
- "iniciadoEm": { }
}, - "naoObservado": "broker-nao-configurado",
- "detalheDaObservacao": null,
- "naoVerificado": null,
- "acaoPendente": {
- "id": "01JC4Z8QK3M7YV2N5T9B0PXHRC",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "seq": 7,
- "actionType": "terminal",
- "payload": {
- "command": "pnpm test"
}, - "status": "pending",
- "resolvedPolicy": "require_approval",
- "actor": {
- "kind": "agent",
- "id": "dev-api"
}, - "decidedBy": null,
- "decidedAt": null,
- "rejectionReason": null,
- "executionResult": null,
- "createdAt": "2026-07-27T14:33:10.900Z",
- "updatedAt": "2026-07-27T14:33:10.900Z"
}, - "decisaoVigente": {
- "version": 3,
- "image": "node:22",
- "network": "egress"
}, - "imagemDesatualizada": false,
- "brokerConfigurado": false
}
]status — это pending (по умолчанию) или failed. actionType фильтрует по типу — например, git_merge, чтобы вкладка PR нашла предложение merge для PR, не зная, какая сессия его создала. status=failed ТРЕБУЕТ actionType и перечисляет действия, выполнение которых завершилось сбоем, — вкладка PR читает отсюда последний отклонённый merge каждого PR (конфликт слияния несёт executionResult.conflictingFiles).
| projectId required | string |
| status | string Пример: status=pending |
| actionType | string Пример: actionType=git_merge |
[- {
- "id": "01JC4Z8QK3M7YV2N5T9B0PXHRC",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "seq": 7,
- "actionType": "terminal",
- "payload": {
- "command": "pnpm test"
}, - "status": "pending",
- "resolvedPolicy": "require_approval",
- "actor": {
- "kind": "agent",
- "id": "dev-api"
}, - "decidedBy": null,
- "decidedAt": null,
- "rejectionReason": null,
- "executionResult": null,
- "createdAt": "2026-07-27T14:33:10.900Z",
- "updatedAt": "2026-07-27T14:33:10.900Z"
}
]Только явно записанные правила. Агент без строки здесь откатывается к значению по умолчанию из permissions.json проекта.
| projectId required | string |
[- {
- "agentId": "dev-api",
- "actionType": "terminal",
- "mode": "auto_approve"
}
]Upsert по (агент, тип). НЕ переопределяет permissions.json: шаблон, уже находящийся в deny, остаётся заблокированным, сколько бы автономии здесь ни было предоставлено. actionType: "*" — это «автоматический режим» (RN-153), автономия для ЛЮБОГО типа действия этого агента; конкретное правило, записанное после, всё равно побеждает подстановочное для этого типа.
| projectId required | string |
| agentId required | string Slug агента. |
| actionType required | string Перечисление: "terminal" "git_commit" "git_push" "pr_open" "spend" "git_repo_create" "git_branch_create" "git_branch_protect" "write_file" "open_adr_pr" "git_merge" "open_infra_pr" "instruction_patch" "parallelize" "raise_max_parallel" "propose_execution_plan" "assess_implementability" "container_start" "container_stop" "container_remove" "container_recreate" "container_start_via_runner" "qa_environment_start" "*" Тип действия или |
| mode required | string Перечисление: "auto_approve" "require_approval" "deny" Автономия этого агента для этого типа. НЕ переопределяет |
{- "agentId": "dev-api",
- "actionType": "terminal",
- "mode": "auto_approve"
}Упорядочено по seq; используйте nextCursor как afterSeq. latest=true вместо этого получает ХВОСТ (самые новые limit действий, всё ещё по возрастанию) и игнорирует afterSeq. status=pending оставляет только действия, ожидающие решения, — с latest самые новые ожидающие, чтобы сессия с более чем 200 действиями не вытесняла новое ожидающее из окна (RN-637).
| projectId required | string |
| sessionId required | string |
| afterSeq | string Пример: afterSeq=6 |
| limit | string Пример: limit=50 |
| latest | string Пример: latest=true Получает хвост сессии; игнорирует |
| status | string Пример: status=pending Поддерживается только |
{- "items": [
- {
- "id": "01JC4Z8QK3M7YV2N5T9B0PXHRC",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "seq": 7,
- "actionType": "terminal",
- "payload": {
- "command": "pnpm test"
}, - "status": "pending",
- "resolvedPolicy": "require_approval",
- "actor": {
- "kind": "agent",
- "id": "dev-api"
}, - "decidedBy": null,
- "decidedAt": null,
- "rejectionReason": null,
- "executionResult": null,
- "createdAt": "2026-07-27T14:33:10.900Z",
- "updatedAt": "2026-07-27T14:33:10.900Z"
}
], - "nextCursor": 7
}Действие оценивается по permissions.json проекта при создании. resolvedPolicy в ответе говорит, что решила политика: auto_approve выходит уже готовым к выполнению, require_approval ждёт решения человека, а deny рождается отклонённым. Пути, выполняющего действие в обход этого, нет.
| projectId required | string |
| sessionId required | string |
| actionType required | string Перечисление: "terminal" "git_commit" "git_push" "pr_open" "spend" "git_repo_create" "git_branch_create" "git_branch_protect" "write_file" "open_adr_pr" "git_merge" "open_infra_pr" "instruction_patch" "parallelize" "raise_max_parallel" "propose_execution_plan" "assess_implementability" "container_start" "container_stop" "container_remove" "container_recreate" "container_start_via_runner" "qa_environment_start" Тип действия. У каждого типа своя минимальная роль: |
required | object Кто предлагает. |
required | object Параметры действия, с формой, своей для каждого |
{- "actionType": "terminal",
- "actor": {
- "kind": "agent",
- "id": "dev-api"
}, - "payload": {
- "command": "pnpm test"
}
}{- "id": "01JC4Z8QK3M7YV2N5T9B0PXHRC",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "seq": 7,
- "actionType": "terminal",
- "payload": {
- "command": "pnpm test"
}, - "status": "pending",
- "resolvedPolicy": "require_approval",
- "actor": {
- "kind": "agent",
- "id": "dev-api"
}, - "decidedBy": null,
- "decidedAt": null,
- "rejectionReason": null,
- "executionResult": null,
- "createdAt": "2026-07-27T14:33:10.900Z",
- "updatedAt": "2026-07-27T14:33:10.900Z"
}Применяется только к этому действию. Решение остаётся в журнале событий с id того, кто решил.
| projectId required | string |
| sessionId required | string |
| actionId required | string |
{- "id": "01JC4Z8QK3M7YV2N5T9B0PXHRC",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "seq": 7,
- "actionType": "terminal",
- "payload": {
- "command": "pnpm test"
}, - "status": "pending",
- "resolvedPolicy": "require_approval",
- "actor": {
- "kind": "agent",
- "id": "dev-api"
}, - "decidedBy": null,
- "decidedAt": null,
- "rejectionReason": null,
- "executionResult": null,
- "createdAt": "2026-07-27T14:33:10.900Z",
- "updatedAt": "2026-07-27T14:33:10.900Z"
}Помимо разрешения этого действия добавляет соответствующий шаблон в список allow проекта — будущие подходящие действия выходят auto_approved без вопросов. Шаблон, уже находящийся в deny, остаётся заблокированным. Одобрение и шаблон записываются в одной транзакции. Клик по действию, которое уже было ОДОБРЕНО, — идемпотентный успех (desfecho: ja_aprovada), шаблон записывается, только если его нет.
| projectId required | string |
| sessionId required | string |
| actionId required | string |
{- "id": "01JC4Z8QK3M7YV2N5T9B0PXHRC",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "seq": 7,
- "actionType": "terminal",
- "payload": {
- "command": "pnpm test"
}, - "status": "pending",
- "resolvedPolicy": "require_approval",
- "actor": {
- "kind": "agent",
- "id": "dev-api"
}, - "decidedBy": null,
- "decidedAt": null,
- "rejectionReason": null,
- "executionResult": null,
- "createdAt": "2026-07-27T14:33:10.900Z",
- "updatedAt": "2026-07-27T14:33:10.900Z",
- "desfecho": "aprovada",
- "padraoGravado": true
}Причина, если указана, остаётся в журнале событий вместе с решением.
| projectId required | string |
| sessionId required | string |
| actionId required | string |
| reason | string Остаётся в журнале событий вместе с отклонением. Необязательно, но рекомендуется. |
{- "reason": "The command would delete the working directory."
}{- "id": "01JC4Z8QK3M7YV2N5T9B0PXHRC",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "seq": 7,
- "actionType": "terminal",
- "payload": {
- "command": "pnpm test"
}, - "status": "pending",
- "resolvedPolicy": "require_approval",
- "actor": {
- "kind": "agent",
- "id": "dev-api"
}, - "decidedBy": null,
- "decidedAt": null,
- "rejectionReason": null,
- "executionResult": null,
- "createdAt": "2026-07-27T14:33:10.900Z",
- "updatedAt": "2026-07-27T14:33:10.900Z"
}Проект рождается с тремя областями (RN-094), поэтому список возвращается полным даже до появления module_map. Активация выполнения добавляет УЧАСТНИКОВ области dev, по одному на модуль.
| projectId required | string |
[- {
- "id": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "projectId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "key": "dev",
- "leadAgentId": "dev-lead",
- "maxParallel": 2,
- "budgetMicros": 20000000,
- "spentMicros": 4300000,
- "members": [
- "dev-api",
- "dev-web"
]
}
]Требует maintainer, по той же причине, что и лимит параллелизма: это НЕЗАВИСИМАЯ, дополнительная проверка рядом с бюджетами проекта и сессии (ADR 0110) — не каскад и не замена ни одному из них. Лимит приходит в ДОЛЛАРАХ и переводится в микро-USD на сервере, по тому же соглашению, что и BudgetsController. null снимает лимит.
| projectId required | string |
| key required | string |
| limitUsd required | object or null >= 0 Лимит в ДОЛЛАРАХ для области — то же соглашение «доллары на входе, микро-USD на выходе», что и у бюджетов проекта/сессии ( |
{- "limitUsd": 20
}{- "id": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "projectId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "key": "dev",
- "leadAgentId": "dev-lead",
- "maxParallel": 2,
- "budgetMicros": 20000000,
- "spentMicros": 4300000,
- "members": [
- "dev-api",
- "dev-web"
]
}Требует maintainer, а не developer, по той же причине, что и активация выполнения: повышать лимит — значит решать, сколько продукт может тратить, не спрашивая. Применяется к СЛЕДУЮЩИМ запросам — то, что уже ждёт вашего решения, продолжает ждать.
| projectId required | string |
| key required | string |
| maxParallel required | number >= 1 Сколько агентов lead области может иметь в сессии БЕЗ запроса вашего разрешения. Минимум 1 — ноль не означает «без ограничений», а означает недопустимую конфигурацию, и трактовка его как неограниченного превратила бы опечатку в неограниченные расходы. |
{- "maxParallel": 4
}{- "id": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "projectId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "key": "dev",
- "leadAgentId": "dev-lead",
- "maxParallel": 2,
- "budgetMicros": 20000000,
- "spentMicros": 4300000,
- "members": [
- "dev-api",
- "dev-web"
]
}Требует maintainer, а не developer, потому что git-действия dev agent наследуют роль ТОГО, КТО АКТИВИРОВАЛ, в оценке IAM — активация с ролью developer оставила бы агентов без возможности открыть PR. Каждый модуль в module_map получает агента в изолированном worktree.
| projectId required | string |
| taskBudgetMicros | number Бюджет на задачу в микро-USD. Если опущен, используется значение домена по умолчанию (DEFAULT_TASK_BUDGET_MICROS). |
| maxGateCorrections | number Сколько циклов dev↔gate, прежде чем задача станет |
| devAgentImpl | string Перечисление: "real" "noop"
|
| terminalAllowPatterns | Массив из strings Шаблоны, разрешённые в |
| originSessionId | string Id сессии ЧАТА, из которой пришёл клик «активировать выполнение», — творческой/консультационной сессии Dev Lead/PO, никогда не сессии выполнения. Если опущен (устаревший вызывающий, например активация из Обзора без контекста сессии), никакая сессия не закрывается, как и раньше. Если указан, она закрывается в конце — но только если у неё нет ожидающего handoff/действия/хода (RN-135), и никогда, если это сама сессия выполнения. |
{- "taskBudgetMicros": 500000,
- "maxGateCorrections": 3,
- "devAgentImpl": "real",
- "terminalAllowPatterns": [
- "Terminal(pnpm test:*)",
- "Terminal(pnpm lint)"
], - "originSessionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}{- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "modules": [
- "api",
- "web"
]
}Самая свежая сессия active, уже несущая execution.activated, — ТОТ ЖЕ критерий, который activate использует, чтобы решить, активировать повторно или создать (RN-139). null, когда выполнение не идёт. Существует, чтобы вкладка Исполнители перестала выводить эту сессию из самой свежей сессии проекта, которая меняется от сессии к сессии без всякой подсказки в интерфейсе.
| projectId required | string |
{- "id": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "createdBy": "01JC4Z0000USUARIO0000000001",
- "status": "active",
- "kind": "criativa",
- "name": "Cart checkout",
- "nextSeq": 42,
- "createdAt": "2026-07-27T14:03:22.187Z",
- "updatedAt": "2026-07-27T14:31:09.004Z",
- "closedAt": null,
- "terminationReason": null,
- "traceParent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
}Сбрасывает счётчик последовательных заблокированных задач (RN-047) и возвращает агента к попыткам забрать работу. Это единственный выход из idle_tripped — автоматической разблокировки нет.
| projectId required | string |
| sessionId required | string |
| agentId required | string |
{- "ok": true
}Проходит через ЛИМИТ области dev (RN-083): ниже него агент стартует сразу, в собственном worktree; выше — ответ является proposed_action типа parallelize, ожидающим вашего решения, и НИЧЕГО не стартует, пока вы не решите. Значение лимита настраивается для каждого lead в Настройках.
| projectId required | string |
| sessionId required | string |
| module required | string Имя модуля, как оно указано в |
{- "module": "api"
}{- "estado": "aguardando_autorizacao",
- "actionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "ativosNaSessao": 2,
- "maxParallel": 2,
- "motivo": "string"
}Сбрасывает счётчик исправлений gate и возвращает задачу агенту. Это ручной выход для лимита исправлений — автоматической разблокировки нет.
| projectId required | string |
| sessionId required | string |
| taskId required | string |
{- "ok": true
}Сначала самые свежие, каждая уже с diff относительно предыдущей, вычисленным на сервере.
| projectId required | string |
| agent required | string Пример: dev-api |
[- {
- "id": "01JC4Z0000VERSAO00000000001",
- "version": 4,
- "content": "# dev-api\n\nAlways run the suite before opening the PR.",
- "createdBy": null,
- "sourceActionId": null,
- "sourceHypothesisId": "01JC4Z0000HIPOTESE000000001",
- "note": "Explicit definition-of-done criterion",
- "createdAt": "2026-07-26T14:00:00.000Z",
- "isCurrent": true,
- "diff": {
- "lines": [
- {
- "kind": "add",
- "content": "Always run the suite before opening the PR.",
- "lineNo": 42
}
], - "additions": 3,
- "deletions": 1
}
}
]История неизменяема: восстановление ничего не удаляет, оно СОЗДАЁТ новую версию со старым содержимым. Требует maintainer, потому что меняет поведение агента с этого момента, — та же калибровка, что и у патча.
| projectId required | string |
| agent required | string Пример: dev-api |
| version required | string Пример: 2 Версия для восстановления. |
{- "agent": "dev-api",
- "restoredFrom": 2,
- "toVersion": 5,
- "cacheInvalidated": true
}В отличие от маршрута события по сессии: здесь сессия не известна. Именно это позволяет фишке доказательства в профиле попадать на нужное событие, поскольку окно Анамнеза охватывает несколько сессий.
| projectId required | string |
| eventId required | string |
{- "id": "01JC4Z8QK3M7YV2N5T9B0PXHRB",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "seq": 41,
- "type": "chat.message",
- "actor": {
- "kind": "agent",
- "id": "dev-api"
}, - "payload": {
- "role": "assistant",
- "content": "I'll open the epic first."
}, - "createdAt": "2026-07-27T14:30:58.512Z"
}Исходит из тех, у кого ЕСТЬ версия в проекте, а не из статического списка — именно поэтому появляются dev agent по модулям (dev-api).
| projectId required | string |
[- {
- "agent": "dev-api",
- "versions": [
- {
- "id": "01JC4Z0000VERSAO00000000001",
- "version": 4,
- "content": "# dev-api\n\nAlways run the suite before opening the PR.",
- "createdBy": null,
- "sourceActionId": null,
- "sourceHypothesisId": "01JC4Z0000HIPOTESE000000001",
- "note": "Explicit definition-of-done criterion",
- "createdAt": "2026-07-26T14:00:00.000Z",
- "isCurrent": true,
- "diff": {
- "lines": [
- {
- "kind": "add",
- "content": "Always run the suite before opening the PR.",
- "lineNo": 42
}
], - "additions": 3,
- "deletions": 1
}
}
]
}
]По умолчанию СОБСТВЕННЫЙ профиль пользователя; агрегированное представление команды — только для тех, кто администрирует проект. Профиль компетенций — это данные О человеке, и наименее неожиданное значение по умолчанию — видеть свой.
| projectId required | string |
[- {
- "id": "01JC4Z0000PERFIL00000000001",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "userId": "01JC4Z0000USUARIO0000000002",
- "competency": "TypeScript",
- "level": "avancado",
- "rationale": "Fixed three generic typing errors without help.",
- "evidenceEventIds": [
- "01JC4Z8QK3M7YV2N5T9B0PXHRB"
], - "createdAt": "2026-07-26T12:00:00.000Z",
- "updatedAt": "2026-07-26T12:00:00.000Z",
- "userName": "Senior Dev",
- "userEmail": "dev@brabo.dev"
}
]Только СОБСТВЕННЫЙ профиль вызывающего, никогда чужой. Отказ от профилирования идёт вместе, потому что без него следующий раунд вывел бы всё заново, и удаление было бы косметическим. Намеренно viewer: профилирование охватывает всех участников, поэтому требование developer оставило бы профилированного viewer без возможности удалить свой.
| projectId required | string |
{- "deleted": 2,
- "optedOut": true
}Текущая карта — та, у которой наибольший version (история неизменяема). Незакрытые вопросы — это перекрёстная проверка история↔карта: история без модуля или указывающая на несуществующий модуль.
| projectId required | string |
{- "moduleMap": {
- "id": "01JC4Z0000MODULEMAP00000001",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "modules": [
- {
- "name": "api",
- "stack": "NestJS + Drizzle",
- "responsibility": "Business rules and HTTP surface.",
- "dependsOn": [
- "db"
], - "resources": {
- "cpus": 2,
- "memoryMb": 4096,
- "pidsLimit": 512
}
}
], - "version": 3,
- "createdAt": "2026-07-24T16:00:00.000Z"
}, - "adrs": [
- {
- "actionId": "01JC4Z8QK3M7YV2N5T9B0PXHRC",
- "title": "0007 — Postgres queue instead of Redis",
- "status": "executed",
}
], - "pendencies": [
- {
- "storyId": "01JC4Z0000HISTORIA000000001",
- "title": "Add item to cart",
- "status": "ready",
- "reason": "missing_module",
- "missing": [
- "pagamentos"
]
}
], - "c4Diagram": {
- "status": "sem_diagrama",
- "diagrama": {
- "systemName": "Brabo",
- "systemDescription": "Agent-orchestrated engineering platform.",
- "actors": [
- {
- "name": "User",
- "type": "person",
- "description": "Whoever operates the product through the web."
}
], - "contextDiagram": "C4Context\n title Context Diagram -- Brabo\n ...",
- "containerDiagram": "C4Container\n title Container Diagram -- Brabo\n ..."
}, - "version": 0,
- "eventId": "01JC4Z0000EVENTO000000002",
- "createdAt": { }
}
}Вложенное и полное, за один вызов. АРХИВНЫЕ истории (RN-727) не перечисляются. Backlog пишут агенты, через маршруты /internal/*.
| projectId required | string |
[- {
- "id": "01JC4Z0000EPICO000000000001",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "title": "Shopping cart",
- "description": "Everything the buyer does before paying.",
- "createdAt": "2026-07-25T08:30:00.000Z",
- "updatedAt": "2026-07-25T08:30:00.000Z",
- "stories": [
- {
- "id": "01JC4Z0000HISTORIA000000001",
- "epicId": "01JC4Z0000EPICO000000000001",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "title": "Add item to cart",
- "description": "As a buyer, I want to gather items before paying.",
- "rf": [
- "The cart accepts up to 50 items"
], - "rnf": [
- "The response stays under 200 ms at p95"
], - "businessRuleIds": [
- "RN-014"
], - "dod": [
- "Unit tests green"
], - "dor": [
- "Module defined"
], - "moduleIds": [
- "api"
], - "status": "ready",
- "proposedReady": true,
- "returnedReason": "DoD too generic — spell out the acceptance criteria.",
- "returnedAt": "2026-08-02T14:00:00.000Z",
- "archivedAt": null,
- "archivedReason": null,
- "createdAt": "2026-07-25T09:00:00.000Z",
- "updatedAt": "2026-07-25T09:40:00.000Z",
- "tasks": [
- {
- "id": "01JC4Z0000TAREFA00000000001",
- "storyId": "01JC4Z0000HISTORIA000000001",
- "title": "POST /cart/items endpoint",
- "description": "Accepts SKU and quantity; validates stock.",
- "status": "in_progress",
- "assignedTo": "dev-api",
- "blocked": false,
- "blockedReason": null,
- "blockedOrigin": null,
- "gateStatus": null,
- "gateCorrectionCount": 0,
- "module": "api",
- "createdAt": "2026-07-25T10:00:00.000Z",
- "updatedAt": "2026-07-25T12:30:00.000Z"
}
]
}
]
}
]Сопоставляет правила проекта с businessRuleIds историй. Правило вообще без истории — это ОТКРЫТИЕ, незакрытый вопрос для PO, а не ошибка.
| projectId required | string |
{- "rules": [
- {
- "ruleId": "RN-014",
- "title": "Cart accepts at most 50 items",
- "coveredByStoryIds": [
- "01JC4Z0000HISTORIA000000001"
], - "covered": true
}
], - "uncoveredCount": 2
}Артефакты InfraAgent (Dockerfile, compose, CI). Они проходят ТЕ ЖЕ gate QA и SecOps, что и PR dev, без задачи или worktree за ними.
| projectId required | string |
[- {
- "id": "01JC4Z0000INFRAARTEFATO00001",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "title": "api's production Dockerfile",
- "prActionId": "01JC4Z8QK3M7YV2N5T9B0PXHRD",
- "gateStatus": "awaiting_qa",
- "gateCorrectionCount": 0,
- "blocked": false,
- "blockedReason": null,
- "createdAt": "2026-07-26T09:00:00.000Z",
- "updatedAt": "2026-07-26T09:20:00.000Z"
}
]Статус берётся из журнала событий: открытый — это artifact.open_question без open_question.answered. Сначала новые, не более 50, с total.
| projectId required | string |
| status | string По умолчанию: "open" Перечисление: "open" "answered" "all" |
{- "itens": [
- {
- "questionId": "ffa0420e-f70b-4e5b-99a0-434bff860467",
- "eventId": "string",
- "sessionId": "string",
- "pergunta": "string",
- "sugestao": "string",
- "afeta": {
- "regras": [
- "string"
], - "historias": [
- "string"
], - "tarefas": [
- "string"
]
}, - "autor": "criativo",
- "criadaEm": "2019-08-24T14:15:22Z",
- "status": "open",
- "autorDaSessao": "string",
- "resposta": {
- "texto": "string",
- "aceitouSugestao": true,
- "decisionEventId": "string",
- "respondidaPor": "string",
- "respondidaEm": "2019-08-24T14:15:22Z"
}
}
], - "total": 0
}Отвечает только автор сессии, в которой родился вопрос. Пишет decision_record (актор: пользователь, с questionId) и open_question.answered и просит engine возобновить поставленный на паузу gate QA каждой затронутой задачи (RN-860), по мере возможности.
| projectId required | string |
| questionId required | string |
| resposta | string Ответ свободным текстом. Отправьте это ИЛИ |
| aceitarSugestao | boolean Принять предложение агента как ответ. |
{- "resposta": "404 for a code that does not exist.",
- "aceitarSugestao": true
}{- "questionId": "ffa0420e-f70b-4e5b-99a0-434bff860467",
- "eventId": "string",
- "sessionId": "string",
- "pergunta": "string",
- "sugestao": "string",
- "afeta": {
- "regras": [
- "string"
], - "historias": [
- "string"
], - "tarefas": [
- "string"
]
}, - "autor": "criativo",
- "criadaEm": "2019-08-24T14:15:22Z",
- "status": "open",
- "autorDaSessao": "string",
- "resposta": {
- "texto": "string",
- "aceitouSugestao": true,
- "decisionEventId": "string",
- "respondidaPor": "string",
- "respondidaEm": "2019-08-24T14:15:22Z"
}
}RN-852 (ADR 0213): текущий artifact.run_instructions, выпускаемый, когда выполняется open_infra_pr с run. ОБЪЯВЛЕН агентом Infra и никогда не проверяется на машине читателя. null, когда его нет.
| projectId required | string |
{- "command": "cp .env.example .env && docker compose up --build",
- "envExample": true,
- "app": {
- "command": "npm start",
- "port": 3000
}, - "qa": { },
- "version": 1,
- "eventId": "01JC4Z0000EVENTORUNINSTRUC01",
- "declaredAt": "2026-10-11T12:00:00.000Z"
}История уходит из backlog, покрытия, очереди продвижения, плана Dev Lead и claim — а вместе с ней и её задачи. Ничего не удаляется: строка остаётся с archivedAt, а backlog.story_archived записывает, кто, почему и какие задачи. Только история draft без задачи в работе.
| projectId required | string |
| storyId required | string |
| reason | string Необязательно; записывается в |
{- "reason": "Duplicate of \"Register a user\"."
}{- "id": "01JC4Z0000HISTORIA000000001",
- "epicId": "01JC4Z0000EPICO000000000001",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "title": "Add item to cart",
- "description": "As a buyer, I want to gather items before paying.",
- "rf": [
- "The cart accepts up to 50 items"
], - "rnf": [
- "The response stays under 200 ms at p95"
], - "businessRuleIds": [
- "RN-014"
], - "dod": [
- "Unit tests green"
], - "dor": [
- "Module defined"
], - "moduleIds": [
- "api"
], - "status": "ready",
- "proposedReady": true,
- "returnedReason": "DoD too generic — spell out the acceptance criteria.",
- "returnedAt": "2026-08-02T14:00:00.000Z",
- "archivedAt": null,
- "archivedReason": null,
- "createdAt": "2026-07-25T09:00:00.000Z",
- "updatedAt": "2026-07-25T09:40:00.000Z"
}История уходит из очереди предложений, а причина становится сообщением, закреплённым в сессии PO, — тот же шаблон, что и при возврате gate к dev. Если PO этой сессии больше не запущен, возврат всё равно записывается: это решение пользователя, и оно не зависит от того, слушает ли агент.
| projectId required | string |
| storyId required | string |
| reason required | string Причина ОБЯЗАТЕЛЬНА: она становится сообщением, закреплённым в сессии PO, а возврат без причины возвращает работу без информации. |
{- "reason": "The acceptance criteria don't cover the payment refusal case."
}{- "ok": true
}Только история draft без задачи в работе (in_progress/in_review). Пишет backlog.story_updated с прежним и новым заголовком. Та же роль, что и у других записей backlog (продвижение/возврат).
| projectId required | string |
| storyId required | string |
| title required | string Обрезанный по краям; от 1 до 200 символов. |
{- "title": "Register a user"
}{- "id": "01JC4Z0000HISTORIA000000001",
- "epicId": "01JC4Z0000EPICO000000000001",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "title": "Add item to cart",
- "description": "As a buyer, I want to gather items before paying.",
- "rf": [
- "The cart accepts up to 50 items"
], - "rnf": [
- "The response stays under 200 ms at p95"
], - "businessRuleIds": [
- "RN-014"
], - "dod": [
- "Unit tests green"
], - "dor": [
- "Module defined"
], - "moduleIds": [
- "api"
], - "status": "ready",
- "proposedReady": true,
- "returnedReason": "DoD too generic — spell out the acceptance criteria.",
- "returnedAt": "2026-08-02T14:00:00.000Z",
- "archivedAt": null,
- "archivedReason": null,
- "createdAt": "2026-07-25T09:00:00.000Z",
- "updatedAt": "2026-07-25T09:40:00.000Z"
}Шаг человека, который режим manual (по умолчанию начиная с Фазы 12c) возвращает пользователю: до продвижения НИ ОДНА задача истории не может быть взята ни одним dev agent. Продвижение высвобождает пакет задач сразу и будит простаивающих агентов модуля (Фаза 12b). Пакет НЕ является «всё или ничего» — то, что не прошло, возвращается как failed с причиной, а то, что прошло, продвигается.
| projectId required | string |
| storyIds required | Массив из strings Id историй для продвижения в |
{- "storyIds": [
- "01JC2XK9Q7",
- "01JC2XKB4M"
]
}{- "promoted": [
- "string"
], - "failed": [
- {
- "storyId": "01JC2XK9Q7",
- "reason": "Story is not ready: missing dod, dor"
}
]
}Каждая гипотеза ссылается на события, которые её подтверждают (evidenceEventIds), — именно это делает её проверяемой, а не принимаемой на веру.
| projectId required | string |
[- {
- "id": "01JC4Z0000HIPOTESE000000001",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "analysisId": "01JC4Z0000ANALISE0000000001",
- "agenteAlvo": "dev-api",
- "observacao": "The agent reopened the same task three times.",
- "hipotese": "The instructions don't say when to consider the task done.",
- "sugestao": "Add an explicit definition-of-done criterion to the instruction.",
- "confiancaPercent": 72,
- "evidenceEventIds": [
- "01JC4Z8QK3M7YV2N5T9B0PXHRB"
], - "terminationAnalysis": {
- "causa": "heartbeat_timeout",
- "estadoDaSessao": "closed_abnormally",
- "analise": "The agent stopped responding after a 90s tool call."
}, - "status": "proposed",
- "decidedBy": null,
- "decidedAt": null,
- "createdAt": "2026-07-26T18:00:00.000Z",
- "updatedAt": "2026-07-26T18:00:00.000Z"
}
]Переводит proposed → accepted. НЕ proposed_action: Психолог только читает, и здесь нет ничего с внешним эффектом.
| projectId required | string |
| hypothesisId required | string |
{- "id": "01JC4Z0000HIPOTESE000000001",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "analysisId": "01JC4Z0000ANALISE0000000001",
- "agenteAlvo": "dev-api",
- "observacao": "The agent reopened the same task three times.",
- "hipotese": "The instructions don't say when to consider the task done.",
- "sugestao": "Add an explicit definition-of-done criterion to the instruction.",
- "confiancaPercent": 72,
- "evidenceEventIds": [
- "01JC4Z8QK3M7YV2N5T9B0PXHRB"
], - "terminationAnalysis": {
- "causa": "heartbeat_timeout",
- "estadoDaSessao": "closed_abnormally",
- "analise": "The agent stopped responding after a 90s tool call."
}, - "status": "proposed",
- "decidedBy": null,
- "decidedAt": null,
- "createdAt": "2026-07-26T18:00:00.000Z",
- "updatedAt": "2026-07-26T18:00:00.000Z"
}Переводит proposed → dismissed. Та же логика, что и при принятии.
| projectId required | string |
| hypothesisId required | string |
{- "id": "01JC4Z0000HIPOTESE000000001",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "analysisId": "01JC4Z0000ANALISE0000000001",
- "agenteAlvo": "dev-api",
- "observacao": "The agent reopened the same task three times.",
- "hipotese": "The instructions don't say when to consider the task done.",
- "sugestao": "Add an explicit definition-of-done criterion to the instruction.",
- "confiancaPercent": 72,
- "evidenceEventIds": [
- "01JC4Z8QK3M7YV2N5T9B0PXHRB"
], - "terminationAnalysis": {
- "causa": "heartbeat_timeout",
- "estadoDaSessao": "closed_abnormally",
- "analise": "The agent stopped responding after a 90s tool call."
}, - "status": "proposed",
- "decidedBy": null,
- "decidedAt": null,
- "createdAt": "2026-07-26T18:00:00.000Z",
- "updatedAt": "2026-07-26T18:00:00.000Z"
}costMicros суммируется из token_usage раунда. Именно это делает разницу в стоимости между лёгкой и тяжёлой сортировкой ВИДИМОЙ, а не просто заявленной, — два уровня используют действительно разные модели.
| projectId required | string |
[- {
- "id": "01JC4Z0000ANALISE0000000001",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "tier": "leve",
- "triggeredBy": "auto",
- "supersedes": null,
- "superseded": false,
- "supersededAt": null,
- "eventCountAtAnalysis": 128,
- "createdAt": "2026-07-26T18:00:00.000Z",
- "costMicros": 4200,
- "hypothesisCount": 3
}
]Только чтение — без побочных эффектов. enabled: false означает, что пауза — решение ПРОДУКТА (не ошибка): существующие анализы и гипотезы ею не затрагиваются.
{- "enabled": false
}Требует maintainer, а не developer, потому что снова запускает ToolLoop и ТРАТИТ реальный бюджет — в отличие от принятия или отклонения, которые лишь меняют локальное состояние. Предыдущий анализ помечается как замещённый.
| projectId required | string |
| sessionId required | string |
{- "ok": true
}Реальный подсчёт (никогда не оценка) файлов .md в docs/docs/adr репозитория проекта против того, сколько из них имеют фрагмент, и сессий проекта против того, сколько имеют фрагмент. Не включает «N минут назад» для этих трёх областей — нет столбца метки времени индексации по области, и угаданное число лгало бы (то же правило, что ADR 0042 для оценки моделей). local (RN-455) — единственное исключение: lastAttachedAt — это реальный MAX(created_at) по этой области, потому что у загрузки из браузера нет состояния репозитория для сравнения.
| projectId required | string |
{- "docs": {
- "filesInRepo": 0,
- "filesIndexed": 0,
- "truncated": true
}, - "adr": {
- "filesInRepo": 0,
- "filesIndexed": 0,
- "truncated": true
}, - "session": {
- "sessionsInProject": 0,
- "sessionsIndexed": 0
}, - "local": {
- "filesIndexed": 0,
- "folderName": "string",
- "lastAttachedAt": "string"
}, - "chunksTotal": 0,
- "chunksWithoutVector": 0
}Единственный сигнал ИСТИНЫ в измерении RAG: задержка и частота деградации говорят, ВЫПОЛНИЛСЯ ли поиск, но никогда — был ли он ПРАВ. Та же роль, что и у search: кто может читать результат, тот может его оценивать. Повторный голос за тот же фрагмент того же поиска перезаписывает ваш собственный голос, а не добавляет второй (уникален на актора), поэтому метрика измеряет точность, а не энтузиазм.
| projectId required | string |
| searchId required | string <uuid>
|
| chunkId required | string <uuid> Оцениваемый фрагмент. Должен быть одним из результатов, которые вернул ТОТ поиск, иначе 400. |
| verdict required | string Перечисление: "util" "irrelevante" Два значения, а не шкала 1–5: более тонкая шкала порождает различия в мерке у разных голосующих, которые никакая агрегация потом не восстановит. |
{- "searchId": "9b1a67f9-9477-48e5-8a6c-11b70245e1d9",
- "chunkId": "90e1f3df-a8a1-438e-8dd5-10e45398d051",
- "verdict": "util"
}{- "searchId": "9b1a67f9-9477-48e5-8a6c-11b70245e1d9",
- "chunkId": "90e1f3df-a8a1-438e-8dd5-10e45398d051",
- "verdict": "util",
- "rank": 0
}Текстовое содержимое, уже прочитанное браузером (<input webkitdirectory>), никогда не путь на хосте — в этом различии весь смысл ADR 0113. Идемпотентная полная перестройка области local: каждый вызов удаляет и пересоздаёт её из файлов, отправленных В ЭТОТ раз, поэтому повторная загрузка и есть механизм пересинхронизации. Отклоняет (400) при превышении лимитов числа файлов/общего объёма в байтах вместо молчаливой обрезки — это однократный жест пользователя с выбором папки перед ним, а не фоновое сканирование. Отдельный слишком большой файл или файл с нераспознанным расширением только пропускается и никогда не приводит к сбою пакета.
| projectId required | string |
| folderName required | string Имя папки, выбранное в браузере, — показывается в панели покрытия. |
required | Массив из objects (LocalFolderFileDto) До 500 файлов. Точный лимит суммы байтов (RAG_LOCAL_TOTAL_BYTES_LIMIT) и список разрешённых расширений на файл применяются сценарием использования, а не этим DTO, — массив по отдельности допустимых строк всё равно может в сумме дать слишком большой payload. |
{- "folderName": "brabo",
- "files": [
- {
- "path": "src/domain/rag/chunking.ts",
- "content": "string"
}
]
}{- "folderName": "string",
- "filesIndexed": 0,
- "filesSkipped": 0,
- "chunksCreated": 0,
- "embedding": {
- "available": true,
- "embedded": 0,
- "skipped": 0,
- "reason": "string"
}
}«Переиндексировать сейчас» из панели RAG в Чате. Удаляет и пересоздаёт фрагменты трёх честных областей (RN-219) из ТЕКУЩЕГО состояния — автоматического наблюдателя на push/закрытие сессии нет (решение записано в ADR 0079/0080). Может занять время в проектах с большим числом сессий: выполняется один проход индексации на сессию, каждый из которых может вызывать провайдера эмбеддингов пакетно.
| projectId required | string |
{- "docs": {
- "filesScanned": 0,
- "docsChunks": 0,
- "adrChunks": 0,
- "truncated": true,
- "embedding": {
- "available": true,
- "embedded": 0,
- "skipped": 0,
- "reason": "string"
}
}, - "sessions": {
- "total": 0,
- "indexed": 0,
- "chunksCreated": 0
}, - "embeddingAvailable": true,
- "embeddingReason": "string"
}Сочетает косинусное сходство (pgvector) с лексическим ts_rank (tsvector), каждое из независимого запроса, объединённые взвешенной суммой и отсечённые порогом (ADR 0080). vectorAvailable: false предупреждает, когда провайдер эмбеддингов не ответил и поиск выполнился только с лексическим сигналом, — он никогда не притворяется, что выполнил полный гибридный поиск.
| projectId required | string |
| query required | string От 2 до 500 символов — то же правило, что и у поиска во вкладке Code. |
| scopes | Массив из strings Элементы Перечисление: "docs" "adr" "session" "local" Если отсутствует, поиск идёт по всем четырём областям (RN-219/454). |
| limit | number От 1 до 10 — если отсутствует, используется лимит. |
{- "query": "how does the PR gate work",
- "scopes": [
- "docs"
], - "limit": 10
}{- "query": "string",
- "searchId": "9b1a67f9-9477-48e5-8a6c-11b70245e1d9",
- "hits": [
- {
- "chunkId": "string",
- "scope": "docs",
- "content": "string",
- "score": 0,
- "vectorScore": 0,
- "lexicalScore": 0,
- "origin": { }
}
], - "vectorAvailable": true,
- "vectorUnavailableReason": "string"
}Каждый элемент несёт technical: true только для сессии, открытой при создании репозитория (связанной из repo_bootstraps).
| projectId required | string |
[- {
- "id": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "createdBy": "01JC4Z0000USUARIO0000000001",
- "status": "active",
- "kind": "criativa",
- "name": "Cart checkout",
- "nextSeq": 42,
- "createdAt": "2026-07-27T14:03:22.187Z",
- "updatedAt": "2026-07-27T14:31:09.004Z",
- "closedAt": null,
- "terminationReason": null,
- "traceParent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
- "technical": false
}
]Сессия рождается в created и является контейнером для всего, что делают агенты: журнал событий, предложенные действия и бюджет токенов привязаны к ней. kind ОБЯЗАТЕЛЕН и записывается: consultiva (консультационная) только беседует, criativa (творческая) производит и является единственной, которая принимает execution.activated.
| projectId required | string |
| kind required | string Перечисление: "consultiva" "criativa" Намерение сессии. |
| name | string <= 80 characters Понятное имя, необязательно. Не заменяет хештег id — экраны составляют их вместе. Пустое считается отсутствующим. |
{- "kind": "criativa",
- "name": "Cart checkout"
}{- "id": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "createdBy": "01JC4Z0000USUARIO0000000001",
- "status": "active",
- "kind": "criativa",
- "name": "Cart checkout",
- "nextSeq": 42,
- "createdAt": "2026-07-27T14:03:22.187Z",
- "updatedAt": "2026-07-27T14:31:09.004Z",
- "closedAt": null,
- "terminationReason": null,
- "traceParent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
}| projectId required | string |
| sessionId required | string |
{- "id": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "createdBy": "01JC4Z0000USUARIO0000000001",
- "status": "active",
- "kind": "criativa",
- "name": "Cart checkout",
- "nextSeq": 42,
- "createdAt": "2026-07-27T14:03:22.187Z",
- "updatedAt": "2026-07-27T14:31:09.004Z",
- "closedAt": null,
- "terminationReason": null,
- "traceParent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
}Имя — это навигационная подпись, и оно НЕ заменяет хештег id: экраны составляют их вместе, а без имени деградируют до одного хештега. null или пустая строка очищают имя. Изменить kind нельзя: это намерение при создании, и изменение позже превратило бы его в состояние.
| projectId required | string |
| sessionId required | string |
| name required | object or null <= 80 characters Новое имя. |
{- "name": "Cart checkout"
}{- "id": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "createdBy": "01JC4Z0000USUARIO0000000001",
- "status": "active",
- "kind": "criativa",
- "name": "Cart checkout",
- "nextSeq": 42,
- "createdAt": "2026-07-27T14:03:22.187Z",
- "updatedAt": "2026-07-27T14:31:09.004Z",
- "closedAt": null,
- "terminationReason": null,
- "traceParent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
}Human gesture, with no cap: the automatic resume happens once at boot (ADR 0226) and this is the button for the rest. The reply is the ACCEPT (the turn goes up in the engine); progress arrives on the session channel. Requires developer.
| projectId required | string |
| sessionId required | string |
| agent required | string |
{- "ok": true
}Бизнес-правила, записи решений и артефакты по агентам, подсчитанные по ВСЕМУ журналу сессии (экран читает хвост из 200 событий), плюс файлы, затронутые git_commit/git_push в этой сессии и в сессии выполнения, которую активировал её одобренный план Dev Lead (RN-828). Начиная с RN-844 также возвращает ЭЛЕМЕНТЫ правил и решений (последние 50 каждого; итоги остаются в regras/decisoes) и файлы PR, открытых в этих сессиях, прочитанные из того же diff, что читает вкладка PR, не более чем для 20 PR за вызов; prsNaoLidas говорит, сколько было пропущено или завершилось сбоем. Только чтение; новой таблицы нет.
| projectId required | string |
| sessionId required | string |
{- "sessoesLidas": [
- "string"
], - "regras": 11,
- "decisoes": 6,
- "itensDeRegras": [
- {
- "eventId": "evt_01JC4Z",
- "seq": 42,
- "actorId": "po",
- "titulo": "Login exige e-mail",
- "descricao": "string",
- "origens": 2
}
], - "itensDeDecisoes": [
- {
- "eventId": "evt_01JC4Z",
- "seq": 43,
- "actorId": "criativo",
- "escolha": "SQLite em arquivo",
- "contexto": "string"
}
], - "artefatosPorAgente": [
- {
- "actorId": "po",
- "quantos": 7
}
], - "arquivosTocados": {
- "itens": [
- {
- "actionId": "01JC4Z0000ACAO00000000000001",
- "path": "src/app.ts",
- "additions": 12,
- "deletions": 3
}
], - "total": 140,
- "prsNaoLidas": 0
}
}Журнал НЕИЗМЕНЯЕМ и упорядочен по seq. Обычная пагинация инкрементальна: передавайте nextCursor предыдущего ответа как afterSeq. latest=true делает обратное — получает ХВОСТ и игнорирует afterSeq, для экрана, который открывается с конца, а не восстанавливает всю сессию с начала (ADR 0021).
| projectId required | string |
| sessionId required | string |
| afterSeq | string Пример: afterSeq=40 Возвращает события с |
| limit | string Пример: limit=50 |
| latest | string Пример: latest=true Получает хвост журнала; игнорирует |
| actionId | string Пример: actionId=01JC4Z0000ACAO00000000000001 Только события, у которых |
{- "items": [
- {
- "id": "01JC4Z8QK3M7YV2N5T9B0PXHRB",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "seq": 41,
- "type": "chat.message",
- "actor": {
- "kind": "agent",
- "id": "dev-api"
}, - "payload": {
- "role": "assistant",
- "content": "I'll open the epic first."
}, - "createdAt": "2026-07-27T14:30:58.512Z"
}
], - "nextCursor": 41
}Только добавление: seq присваивается сервером, и ничто в журнале потом не обновляется и не удаляется. Endpoint для редактирования нет, по замыслу.
| projectId required | string |
| sessionId required | string |
| type required | string Тип события. Полный перечень — в |
required | object (ActorDto) |
required | object Свободная форма, своя для каждого |
{- "type": "chat.message",
- "actor": {
- "kind": "agent",
- "id": "dev-api"
}, - "payload": {
- "role": "user",
- "content": "I want a new checkout."
}
}{- "id": "01JC4Z8QK3M7YV2N5T9B0PXHRB",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "seq": 41,
- "type": "chat.message",
- "actor": {
- "kind": "agent",
- "id": "dev-api"
}, - "payload": {
- "role": "assistant",
- "content": "I'll open the epic first."
}, - "createdAt": "2026-07-27T14:30:58.512Z"
}Существует, чтобы доказательства гипотез Психолога могли дойти до процитированного события независимо от того, на какую страницу оно попало, и от фильтров ленты.
| projectId required | string |
| sessionId required | string |
| eventId required | string |
{- "id": "01JC4Z8QK3M7YV2N5T9B0PXHRB",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "seq": 41,
- "type": "chat.message",
- "actor": {
- "kind": "agent",
- "id": "dev-api"
}, - "payload": {
- "role": "assistant",
- "content": "I'll open the epic first."
}, - "createdAt": "2026-07-27T14:30:58.512Z"
}Повторно открываются только closed/closed_abnormally, и только в active — closing никогда не возвращается (ADR 0183). Сессия сохраняет журнал событий, артефакты, отвеченные вопросы и handoff; kind не затрагивается. НОВОЕ событие session.reopened записывает прежние closedAt и terminationReason, которые строка очищает. Сессия, несущая execution.activated, отклоняется (sessao_com_execucao): откройте новую сессию и активируйте выполнение там. Без ограничения по времени. Требует developer (ADR 0184).
| projectId required | string |
| sessionId required | string |
{- "id": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "createdBy": "01JC4Z0000USUARIO0000000001",
- "status": "active",
- "kind": "criativa",
- "name": "Cart checkout",
- "nextSeq": 42,
- "createdAt": "2026-07-27T14:03:22.187Z",
- "updatedAt": "2026-07-27T14:31:09.004Z",
- "closedAt": null,
- "terminationReason": null,
- "traceParent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
}Определяется для ВЫЗЫВАЮЩЕГО, никогда для сессии: переопределение сессии > выбор в Учётной записи > обнаруженный и подтверждённый > язык интерфейса (RN-618). Возвращает победителя, его происхождение и каждое звено цепочки — и, когда ваши недавние сообщения указывают на другой язык и экран должен о нём спросить, detectionQuestion (RN-624).
| projectId required | string |
| sessionId required | string |
{- "language": "pt-BR",
- "origin": "interface",
- "sessionOverride": null,
- "account": "automatico",
- "detected": null,
- "interfaceLocale": "pt-BR",
- "detectionQuestion": "es"
}Только для вызывающего, только в этой сессии (RN-618) — другие участники и ваш выбор в Учётной записи не затрагиваются. null возвращает к наследованию из Учётной записи.
| projectId required | string |
| sessionId required | string |
| language required | string or null <= 35 characters Язык, на котором агенты отвечают ВАМ, только в ЭТОЙ сессии (RN-618) — другие участники никогда не затрагиваются. Любой код BCP-47, распознаваемый сервером, хранимый в каноническом виде. Отправьте |
{- "language": "en"
}{- "language": "pt-BR",
- "origin": "interface",
- "sessionOverride": null,
- "account": "automatico",
- "detected": null,
- "interfaceLocale": "pt-BR",
- "detectionQuestion": "es"
}Тикет аутентифицирует сокет Phoenix сессии session:<id> при connect/3 в engine — это НЕ переиспользованный JWT. TTL 30 с и однократное использование: каждое переподключение (включая автоматические) запрашивает новый тикет. scope: "heartbeat" требует роли viewer; scope: "terminal" требует developer, ту же минимальную роль, что и у действий терминала.
| projectId required | string |
| sessionId required | string |
| scope required | string Перечисление: "heartbeat" "terminal"
|
{- "scope": "heartbeat"
}{- "ticket": "string",
- "expiresAt": "2026-08-09T12:00:30.000Z"
}Переход проверяется машиной состояний домена, а не клиентом: created → active → closing → closed | closed_abnormally. Недопустимый переход — 409.
| projectId required | string |
| sessionId required | string |
| status required | string Перечисление: "created" "active" "closing" "closed" "closed_abnormally" Целевое состояние. Машина состояний отклоняет недопустимый переход с 409 — из |
{- "status": "closing"
}{- "id": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "createdBy": "01JC4Z0000USUARIO0000000001",
- "status": "active",
- "kind": "criativa",
- "name": "Cart checkout",
- "nextSeq": 42,
- "createdAt": "2026-07-27T14:03:22.187Z",
- "updatedAt": "2026-07-27T14:31:09.004Z",
- "closedAt": null,
- "terminationReason": null,
- "traceParent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
}Кнопка «Стоп» в поле ввода (RN-122): прерывает текущий вызов LLM в engine, обрывая соединение посреди потока, — это реально экономит токены, а не просто останавливает отрисовку на клиенте. Идемпотентно: без хода в процессе принимается без эффекта.
| projectId required | string |
| sessionId required | string |
| agent required | string Пример: po Slug активного агента. |
{- "ok": true
}Ответ — это лишь подтверждение, и он возвращается при ПРИНЯТИИ — до окончания хода агента (ADR 0163). То, что отвечает агент, приходит через журнал событий и канал сессии, а не через этот вызов. Если агент посреди хода, сообщение НЕ отклоняется: оно попадает в очередь агента (entrega: "enfileirada") и читается вместе с другими поставленными в очередь за один ход, когда текущий закончится; пока оно ждёт, его можно отменить (RN-673, ADR 0191).
| projectId required | string |
| sessionId required | string |
| agent required | string Пример: po Slug разговорного агента, который читает сообщение: criativo, po, arquiteto, dev-lead, ux-designer или staff. Любой другой slug — включая infra — отклоняется с 422; оно никогда не доставляется агенту по умолчанию (RN-584). |
| text required | string [ 1 .. 10000 ] characters Сообщение пользователя активному агенту сессии. |
{- "text": "I want to allow discount coupons at checkout."
}{- "ok": true,
- "mensagemId": "01JC4Z0000EVENTO000000000001",
- "entrega": "enfileirada",
- "posicao": 2
}Сообщение, отправленное, пока агент посреди хода, ждёт в очереди и читается, когда ход закончится (RN-673). Пока оно ждёт, ОТПРАВИВШИЙ его может его отменить: engine удаляет его из очереди и записывает chat.message_cancelled; модели оно никогда не показывается. Чужое сообщение — 403.
| projectId required | string |
| sessionId required | string |
| agent required | string Пример: po Slug агента, в очереди которого находится сообщение. |
| messageId required | string Пример: 01JC4Z0000EVENTO000000000001 Id события |
{- "ok": true
}Прямой командой можно запустить только Креатив. Любой другой агент требует ПРИНЯТОГО handoff, указывающего на него, — правило живёт в домене, а не здесь, и попытка обойти его возвращает 409.
| projectId required | string |
| sessionId required | string |
| agent required | string Пример: criativo Slug агента. |
{- "agent": "po",
- "status": "active"
}Записывает chat.structured_question_answered и повторно отправляет ответы агенту как обычное сообщение. На набор вопросов можно ответить только один раз. Возвращается при ПРИНЯТИИ, до окончания хода агента (ADR 0163): ход продолжается в engine, а его ход, завершение и сбои приходят через канал сессии и журнал событий (agent.status, agent.response, agent.error) — никогда через этот ответ.
| projectId required | string |
| sessionId required | string |
| agent required | string Пример: criativo Slug агента, который задал вопросы (и прочитает ответ). |
| questionSetId required | string Пример: 01JC4Z0000EVENTO000000000001 Id отвеченного события |
required | object Ответ по | ||
| |||
{- "answers": {
- "name": "Easy Checkout",
- "users": "Small business owners",
- "platform": "Web"
}
}{- "ok": true
}Выделенный endpoint вместо повторного использования readiness, принадлежащего Креативу: это две разные вехи сессии, и их смешение сделало бы журнал событий неоднозначным. Возвращается при ПРИНЯТИИ, до окончания хода агента (ADR 0163): ход продолжается в engine, а его ход, завершение и сбои приходят через канал сессии и журнал событий (agent.status, agent.response, agent.error) — никогда через этот ответ. Handoff к Dev Lead по-прежнему идёт ПОСЛЕ handoff к Infra: engine удерживает его, пока не закончится завершающий ход. Идемпотентно (ADR 0182, RN-635): цель, у которой уже есть ожидающее предложение или которая активна в проекте, повторно не запускается, а если обе такие — ничего не записывается.
| projectId required | string |
| sessionId required | string |
{- "ok": true,
- "desfecho": "confirmado",
- "jaAtendidos": [
- {
- "toAgent": "infra",
- "motivo": "oferta_pendente"
}
]
}Записывает necessity.validated. Требует, чтобы Креатив уже консолидировал product_brief в этой сессии (RN-406), — без него отклоняется: подтверждать нечего. Начиная с ADR 0185 (RN-657) клик «Я готов — потребность подтверждена» (POST .../readiness) сам записывает это событие, и веб больше не вызывает этот маршрут; он остаётся для сессий, чей клик готовности появился раньше этого ADR.
| projectId required | string |
| sessionId required | string |
{- "ok": true
}| projectId required | string |
| sessionId required | string |
[- {
- "id": "01JC4Z0000HANDOFF00000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "fromAgent": "criativo",
- "toAgent": "po",
- "artifactId": "01JC4Z0000ARTEFATO000000001",
- "status": "offered",
- "createdAt": "2026-07-24T10:00:00.000Z",
- "updatedAt": "2026-07-24T10:05:00.000Z"
}
]Рождается как offered, в точности как собственный offer_handoff агента, — единственная разница в том, кто решил. toAgent должен быть в адресуемом каталоге (lead области или агент без области); субагент или неизвестный slug отклоняется с 400. Не более одного ожидающего предложения на (проект, цель): см. desfecho (ADR 0182, RN-635).
| projectId required | string |
| sessionId required | string |
| toAgent required | string Slug целевого агента. Должен быть lead или агентом без области — никогда субагентом. |
{- "toAgent": "staff"
}{- "id": "01JC4Z0000HANDOFF00000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "fromAgent": "criativo",
- "toAgent": "po",
- "artifactId": "01JC4Z0000ARTEFATO000000001",
- "status": "offered",
- "createdAt": "2026-07-24T10:00:00.000Z",
- "updatedAt": "2026-07-24T10:05:00.000Z",
- "desfecho": "criado"
}Одно-единственное: переход в accepted И запуск агента назначения. Это обычный путь активации для любого агента, кроме Креатива.
| projectId required | string |
| sessionId required | string |
| handoffId required | string |
{- "id": "01JC4Z0000HANDOFF00000000001",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "projectId": "01JC4Z0000PROJETO0000000001",
- "fromAgent": "criativo",
- "toAgent": "po",
- "artifactId": "01JC4Z0000ARTEFATO000000001",
- "status": "offered",
- "createdAt": "2026-07-24T10:00:00.000Z",
- "updatedAt": "2026-07-24T10:05:00.000Z"
}Это кнопка, запускающая product_brief и handoff к PO. Записывает readiness.confirmed в журнал событий. Возвращается при ПРИНЯТИИ, до окончания хода агента (ADR 0163): ход продолжается в engine, а его ход, завершение и сбои приходят через канал сессии и журнал событий (agent.status, agent.response, agent.error) — никогда через этот ответ.
| projectId required | string |
| sessionId required | string |
{- "ok": true
}Authenticated by a MACHINE device key (never a session JWT, never a project-bound credential). Same envelope encryption as POST /users/me/credentials. 409 credencial_existente when the user already has this provider and substituir is not true. The key is never logged nor returned.
| provider required | string Перечисление: "anthropic" "openai" "openrouter" "nvidia-nim" "together" "deepinfra" "bitdeer" "vultr" "9router" "opencode-zen" |
| apiKey required | string <= 512 characters The API key in plain text. Encrypted before touching the database; NEVER logged and NEVER returned. |
| origem required | string <= 200 characters Tool and path the key was read from. Audit log only; never a value. |
| substituir | boolean Must be |
{- "provider": "anthropic",
- "apiKey": "string",
- "origem": "codex: ~/.codex/auth.json",
- "substituir": true
}{- "provider": "anthropic",
- "substituida": true
}Роль не требуется: учётные данные относятся к тому, кто вызвал, а не к рабочему пространству или проекту. Охватывает ключи LLM и токены git в одном списке.
[- {
- "id": "01JC4Z0000CREDENCIAL00000001",
- "provider": "anthropic",
- "createdAt": "2026-07-10T10:00:00.000Z",
- "updatedAt": "2026-07-10T10:00:00.000Z"
}
]Upsert по (пользователь, провайдер). Ключ шифруется envelope-шифрованием до того, как коснётся базы данных, а ответ несёт только ПРОЕКЦИЮ — никогда сам секрет, даже зашифрованный.
| provider required | string Перечисление: "anthropic" "openai" "openrouter" "nvidia-nim" "together" "deepinfra" "bitdeer" "vultr" "9router" "opencode-zen" |
| apiKey required | string <= 512 characters Ключ открытым текстом. Он шифруется envelope-шифрованием до того, как коснётся базы данных, и НИКОГДА не возвращается ни в одном ответе — даже зашифрованным. Лимит в 512 символов — защита от абсурдного payload, а не проверка формата: годен ли ключ, отвечает провайдер на |
{- "provider": "anthropic",
- "apiKey": "sk-ant-api03-…"
}{- "id": "01JC4Z0000CREDENCIAL00000001",
- "provider": "anthropic",
- "createdAt": "2026-07-10T10:00:00.000Z",
- "updatedAt": "2026-07-10T10:00:00.000Z"
}Проверка — явное действие, отдельное от регистрации (ADR 0050): api расшифровывает секрет, вызывает провайдера и возвращает ТОЛЬКО вердикт — ключ никогда не возвращается, даже частично. Отвечает 200 для всех трёх исходов, потому что запрос был обработан; refused — это исход, а не ошибка протокола. 404, когда нет учётных данных для (пользователь, провайдер), — тогда и проверять нечего.
| provider required | string Пример: openrouter |
{- "resultado": "recusado",
- "motivo": "connection test failed for openrouter: openrouter responded 401"
}Собственный путь, а не users/me/credentials, потому что тело другое (token, а перечисление провайдеров — github/gitlab). Токен шифруется и сохраняется без предварительной проверки (ADR 0050); чтобы проверить его — POST /users/me/credentials/{provider}/test. Чтение, удаление и проверка переиспользуют маршруты users/me/credentials — это та же таблица.
| provider required | string Перечисление: "github" "gitlab" |
| token required | string <= 512 characters Personal access token. Шифруется envelope-шифрованием до того, как коснётся базы данных, и никогда не возвращается, даже зашифрованным. Регистрация НЕ проверяет токен (ADR 0050) — для проверки используйте |
{- "provider": "github",
- "token": "ghp_…"
}{- "id": "01JC4Z0000CREDENCIAL00000001",
- "provider": "anthropic",
- "createdAt": "2026-07-10T10:00:00.000Z",
- "updatedAt": "2026-07-10T10:00:00.000Z"
}На уровне учётной записи, без проекта в пути (RN-611): установка в одну строку создаёт машинный ключ до появления любого проекта, а все остальные списки — по проекту. Возвращает ТОЛЬКО машинные ключи (especie: "maquina", projectId: null) и ТОЛЬКО ключи вызывающего — ключи проекта остаются в списке своего проекта, и никто не видит ключи другого пользователя (RN-519). Отозванные ключи ВКЛЮЧЕНЫ: регистрация нового машинного ключа отзывает предыдущий (RN-552), и именно в этом списке это видно. lastUsedAt — записанное использование, никогда не живое соединение; null означает, что ключ ни разу не использовался.
[- {
- "id": "01JC4Z0000CHAVE000000000001",
- "name": "laptop",
- "projectId": "01JC4Z0000PROJETO000000001",
- "especie": "projeto",
- "createdAt": "2026-08-27T12:00:00.000Z",
- "revokedAt": null,
- "lastUsedAt": null
}
]Идемпотентно — повторный отзыв не является ошибкой. Тот же отзыв, что и DELETE /projects/{projectId}/runner-device-keys/{deviceKeyId}: он также разрывает ЖИВЫЕ соединения, открытые с этим ключом, во ВСЕХ проектах (RN-520/RN-543). Начиная с RN-685 (ADR 0201) целью является КЛЮЧ: другой runner того же пользователя, подключённый с PAT или другим ключом, остаётся. Ещё не использованные тикеты ключа аннулируются. Если проектов ещё нет, разрывать нечего. Ключ ПРОЕКТА, несуществующий ключ и ключ другого пользователя получают один и тот же 404.
| deviceKeyId required | string |
{- "locale": "pt-BR",
- "responseLanguage": "automatico",
- "detectedLanguage": null,
- "detectedLanguageConfirmedAt": "2019-08-24T14:15:22Z",
- "effectiveResponseLanguage": {
- "language": "pt-BR",
- "origin": "interface"
}
}Оба поля необязательны и независимы — отправка одного никогда не затрагивает другое, а тело без обоих — это 400. locale (интерфейс) остаётся ограниченным списком pt-BR/en. responseLanguage (RN-618) — это automatico или любой распознанный код BCP-47, хранимый в каноническом виде; нераспознанный код — это 400, и ничего из тела не записывается.
| locale | string Перечисление: "pt-BR" "en" "ru" "zh-Hans" Язык интерфейса. Ограничен списком — не свободный BCP-47. |
| responseLanguage | string <= 35 characters Язык, на котором отвечают агенты (RN-618): |
{- "locale": "en",
- "responseLanguage": "es"
}{- "locale": "pt-BR",
- "responseLanguage": "automatico",
- "detectedLanguage": null,
- "detectedLanguageConfirmedAt": "2019-08-24T14:15:22Z",
- "effectiveResponseLanguage": {
- "language": "pt-BR",
- "origin": "interface"
}
}Единственный способ, которым обнаруженный язык становится предпочтением (RN-624): одно лишь обнаружение никогда ничего не меняет. confirm записывает detectedLanguage + detectedLanguageConfirmedAt; decline записывает отказ, чтобы о том же языке больше не спрашивали. Возвращает предпочтения после ответа.
| language required | string <= 35 characters Язык, о котором был вопрос, — |
| answer required | string Перечисление: "confirm" "decline"
|
{- "language": "es",
- "answer": "confirm"
}{- "locale": "pt-BR",
- "responseLanguage": "automatico",
- "detectedLanguage": null,
- "detectedLanguageConfirmedAt": "2019-08-24T14:15:22Z",
- "effectiveResponseLanguage": {
- "language": "pt-BR",
- "origin": "interface"
}
}Уже отфильтровано по связи — глобального списка нет. Каждый элемент несёт собственную роль вызывающего.
[- {
- "workspace": {
- "id": "01JC4Z0000WORKSPACE00000001",
- "name": "Acme Corp",
- "slug": "acme-corp",
- "createdBy": "01JC4Z0000USUARIO0000000001",
- "toolRouterEnabled": true,
- "createdAt": "2026-07-20T09:12:00.000Z",
- "updatedAt": "2026-07-20T09:12:00.000Z"
}, - "role": "maintainer"
}
]Роль не требуется: создатель становится owner. Это единственная точка входа для того, кто ещё не принадлежит ни одному рабочему пространству.
| name required | string >= 2 characters |
| slug required | string^[a-z0-9]+(-[a-z0-9]+)*$ kebab-case. Уникален в системе и виден в URL. |
{- "name": "Acme Corp",
- "slug": "acme-corp"
}{- "id": "01JC4Z0000WORKSPACE00000001",
- "name": "Acme Corp",
- "slug": "acme-corp",
- "createdBy": "01JC4Z0000USUARIO0000000001",
- "toolRouterEnabled": true,
- "createdAt": "2026-07-20T09:12:00.000Z",
- "updatedAt": "2026-07-20T09:12:00.000Z"
}| workspaceId required | string |
{- "id": "01JC4Z0000WORKSPACE00000001",
- "name": "Acme Corp",
- "slug": "acme-corp",
- "createdBy": "01JC4Z0000USUARIO0000000001",
- "toolRouterEnabled": true,
- "createdAt": "2026-07-20T09:12:00.000Z",
- "updatedAt": "2026-07-20T09:12:00.000Z"
}| workspaceId required | string |
| name | string >= 2 characters |
| slug | string^[a-z0-9]+(-[a-z0-9]+)*$ kebab-case. Уникален в системе и виден в URL. |
{- "name": "Acme Corp",
- "slug": "acme-corp"
}{- "id": "01JC4Z0000WORKSPACE00000001",
- "name": "Acme Corp",
- "slug": "acme-corp",
- "createdBy": "01JC4Z0000USUARIO0000000001",
- "toolRouterEnabled": true,
- "createdAt": "2026-07-20T09:12:00.000Z",
- "updatedAt": "2026-07-20T09:12:00.000Z"
}Требует owner. Забирает с собой свои проекты, сессии и их историю.
| workspaceId required | string |
{- "id": "01JC4Z0000WORKSPACE00000001",
- "name": "Acme Corp",
- "slug": "acme-corp",
- "createdBy": "01JC4Z0000USUARIO0000000001",
- "toolRouterEnabled": true,
- "createdAt": "2026-07-20T09:12:00.000Z",
- "updatedAt": "2026-07-20T09:12:00.000Z"
}Кто связан с РАБОЧИМ ПРОСТРАНСТВОМ, с ролью рабочего пространства — ролью, которую наследует каждый проект, если строка проекта её не переопределяет. Дополняет GET projects/:projectId/members, который перечисляет только строки проекта: тот, кто попадает в проект только через рабочее пространство, появляется здесь, а не там. Только id, имя, email и роль.
| workspaceId required | string |
[- {
- "userId": "01JC4Z0000USUARIO0000000002",
- "role": "maintainer",
- "name": "Senior Dev",
- "email": "dev@brabo.dev"
}
]Только owner может менять состав участников. Роль здесь наследуют ВСЕ проекты рабочего пространства. Эта роль НЕОБХОДИМА, но не ДОСТАТОЧНА: изменение СВОЕЙ СОБСТВЕННОЙ роли здесь отклоняется с 403 в обе стороны и не может быть нигде включено. Уровня выше, который удержал бы падение, нет, поэтому самопонижение было бы невосстановимо через интерфейс. Понижение ДРУГОГО owner по-прежнему разрешено — это единственный способ отозвать владение.
| workspaceId required | string |
| userId required | string <uuid> Id пользователя для связывания. |
| role required | string Перечисление: "viewer" "developer" "maintainer" "owner" Роль в этой связи. В ПРОЕКТЕ она ПЕРЕОПРЕДЕЛЯЕТ роль рабочего пространства в обе стороны — эффективной является эта роль, пока связь существует, выше ИЛИ ниже, — и два действия отклоняются с 403: понижение |
{- "userId": "3f1b2c8e-5a4d-4b7e-9c10-2d6f8a1b4c33",
- "role": "developer"
}{- "workspaceId": "01JC4Z0000WORKSPACE00000001",
- "userId": "01JC4Z0000USUARIO0000000002",
- "role": "maintainer",
- "createdAt": "2026-07-22T08:00:00.000Z"
}Только owner, как и upsert. Удаляет строку рабочего пространства И, в той же транзакции, строки пользователя во всех проектах этого рабочего пространства (роль проекта переопределяет роль рабочего пространства, поэтому их сохранение оставило бы пользователя внутри этих проектов), а также ключи устройств ПРОЕКТА и personal access token пользователя в них. После этого живое соединение runner этого пользователя в каждом проекте разрывается, по мере возможности. Машинные ключи устройств и открытые сессии не затрагиваются — они перестают достигать этого рабочего пространства, потому что роль разрешается в отсутствие.
Удаление СЕБЯ отклоняется с 403 и не может быть нигде включено: уровня выше, который удержал бы падение, нет. Это же условие защищает последнего owner — только owner может вызвать этот маршрут, и никто не удаляет себя, поэтому каждый успешный вызов оставляет как минимум вызывающего в роли owner. Удаление ДРУГОГО owner разрешено — так отзывается владение, — кроме ТИТУЛЬНОГО ВЛАДЕЛЬЦА (createdBy), отклоняемого с 409 criador_do_workspace, пока PUT :workspaceId/owner-of-record не передаст титул другому владельцу: ключ LLM, который тратят агенты, и отчёт о расходах принадлежат ему. Идемпотентно: у цели без строки рабочего пространства всё равно очищаются строки проектов этого рабочего пространства, и она получает 204.
| workspaceId required | string |
| userId required | string |
Титульный владелец — это createdBy: чьи учётные данные LLM тратят агенты и чьи учётные данные git используют действия агентов (RN-058), и кому принадлежит отчёт о расходах (RN-060). Он не даёт авторизации — её дают роли. Цель уже должна быть owner рабочего пространства (иначе 409 titular_precisa_ser_owner). С момента коммита ходы агентов ищут учётные данные НОВОГО титульного владельца; если у него нет учётных данных для провайдера модели, ход заканчивается существующим исходом «учётные данные не зарегистрированы» для этого провайдера — этот маршрут не проверяет это заранее. Вызвать его может любой owner, в том числе для другого владельца. Передача текущему титульному владельцу ничего не делает.
| workspaceId required | string |
| userId required | string <uuid> Id пользователя, который становится титульным владельцем. Уже должен быть |
{- "userId": "3f1b2c8e-5a4d-4b7e-9c10-2d6f8a1b4c33"
}{- "id": "01JC4Z0000WORKSPACE00000001",
- "name": "Acme Corp",
- "slug": "acme-corp",
- "createdBy": "01JC4Z0000USUARIO0000000001",
- "toolRouterEnabled": true,
- "createdAt": "2026-07-20T09:12:00.000Z",
- "updatedAt": "2026-07-20T09:12:00.000Z"
}Выбор папки для режима Mounted, обслуживаемый api, а не runner (RN-504). ЖЁСТКО ограничен BRABO_PROJECTS_BASE (ADR 0141): path необязателен и по умолчанию равен самой базе, а любой path вне её — это 400, некорректный запрос, а не проблема прав, поскольку ни одна роль не просматривает что-либо вне базы.
Ограничен намеренно: только каталоги, не более 500 (отсортированных ДО обрезки, о чём говорит truncado), никогда рекурсивно, записи с точкой в начале исключены, а символические ссылки сообщаются, но никогда не разыменовываются. То, что осталось за бортом, ПОДСЧИТЫВАЕТСЯ (arquivos, simbolicos), чтобы папка, полная кода, никогда не выглядела пустой.
Парного POST нет: создание папки относится к материализации смонтированной рабочей области, а не к выбору.
| path | string Абсолютный путь для перечисления. Если опущен — сама база. Должен быть внутри |
{- "base": "/home/voce/brabo",
- "path": "/home/voce/brabo/clientes",
- "entries": [
- "api",
- "loja",
- "website"
], - "truncado": false,
- "arquivos": 12,
- "simbolicos": 1
}| workspaceId required | string |
[- {
- "id": "01JC4Z0000PROJETO0000000001",
- "workspaceId": "01JC4Z0000WORKSPACE00000001",
- "name": "Checkout",
- "slug": "checkout",
- "workspaceDirName": "checkout-3f2b1c8e",
- "executionMode": "container",
- "workspacePath": null,
- "workspaceVerifiedAt": null,
- "mirrorPath": null,
- "language": "pt-BR",
- "createdBy": "01JC4Z0000USUARIO0000000001",
- "taskBudgetMicros": 500000,
- "maxConsecutiveBlocked": 3,
- "storyPromotion": "auto",
- "createdAt": "2026-07-21T11:00:00.000Z",
- "updatedAt": "2026-07-21T11:00:00.000Z"
}
]| workspaceId required | string |
| name required | string >= 2 characters |
| slug required | string^[a-z0-9]+(-[a-z0-9]+)*$ kebab-case. Уникален в пределах рабочего пространства. |
| maxConsecutiveBlocked | number Предохранитель (Фаза 12b — RN-047): сколько последовательных задач, закончившихся блокировкой, останавливают dev agent модуля в idle_tripped. Если опущено, используется значение домена по умолчанию (DEFAULT_MAX_CONSECUTIVE_BLOCKED). Применяется со СЛЕДУЮЩЕЙ активации выполнения — не затрагивает уже работающих агентов. |
| storyPromotion | string Перечисление: "manual" "auto" Кто продвигает историю из |
| executionMode | string Перечисление: "container" "mounted" "runner" ГДЕ ВЫПОЛНЯЕТСЯ команда этого проекта (RN-169/RN-421 — ADR 0072/0104). |
| workspacePath | string АБСОЛЮТНЫЙ путь к папке, обязательный, когда |
| language | string <= 35 characters Язык ПРОЕКТА (RN-619) — его используют общие артефакты и ходы без автора-человека. Любой код BCP-47, распознаваемый сервером, хранимый в каноническом виде; нераспознанный — это 400. Если опущен, проект начинает с эффективного языка ответов своего создателя (RN-618: выбор в учётной записи > подтверждённое обнаружение > язык интерфейса). |
{- "name": "Checkout",
- "slug": "checkout",
- "maxConsecutiveBlocked": 3,
- "storyPromotion": "auto",
- "executionMode": "container",
- "workspacePath": "/home/you/projects/store",
- "language": "pt-BR"
}{- "id": "01JC4Z0000PROJETO0000000001",
- "workspaceId": "01JC4Z0000WORKSPACE00000001",
- "name": "Checkout",
- "slug": "checkout",
- "workspaceDirName": "checkout-3f2b1c8e",
- "executionMode": "container",
- "workspacePath": null,
- "workspaceVerifiedAt": null,
- "mirrorPath": null,
- "language": "pt-BR",
- "createdBy": "01JC4Z0000USUARIO0000000001",
- "taskBudgetMicros": 500000,
- "maxConsecutiveBlocked": 3,
- "storyPromotion": "auto",
- "createdAt": "2026-07-21T11:00:00.000Z",
- "updatedAt": "2026-07-21T11:00:00.000Z"
}Единственная папка на машине оператора, которую видят контейнеры api и engine, смонтированная по идентичности (ADR 0141). null — нормальное состояние, никогда не ошибка, — означает, что у этой установки нет BRABO_PROJECTS_BASE, поэтому мастер проекта вообще не должен предлагать режим Mounted. Одно и то же значение для каждого рабочего пространства: это конфигурация установки, а workspaceId лишь ограничивает авторизацию.
brokerConfigurado отвечает на вторую половину того же вопроса: container и mounted поднимают контейнер только через broker (ADR 0144), поэтому без него мастер не выбирает заранее Mounted и предлагает вместо этого Runner (ADR 0161, RN-573).
{- "projectsBase": "/home/voce/brabo",
- "brokerConfigurado": false
}Одна строка на проект рабочего пространства с заблокированной задачей — питает точку статуса в боковой панели дашборда одним запросом вместо N вызовов к backlog.
| workspaceId required | string |
[- {
- "projectId": "01JC4Z0000PROJETO0000000001",
- "blockedTaskCount": 2
}
]Одна строка на проект рабочего пространства с git-провайдером, статусом создания, бюджетом, последней активностью, историями, ожидающими продвижения, и фактами, определяющими состав агентов (RN-090).
Та же идея, что и projects-status, этажом выше: сетка карточек — это чтение РАБОЧЕГО ПРОСТРАНСТВА, а не N чтений проектов. Прежний путь делал семь опрашиваемых запросов на карточку, и при 23 проектах один только дашборд пробивал лимит в 300 запросов/мин.
| workspaceId required | string |
[- {
- "projectId": "01JC4Z0000PROJETO0000000001",
- "provider": "github",
- "provisioningStatus": "provisioned",
- "budget": {
- "limitMicros": 50000000,
- "spentMicros": 12500000
}, - "latestSessionId": "01JC4Z0000SESSAO00000000001",
- "latestSeq": 41,
- "lastEvent": {
- "id": "01JC4Z8QK3M7YV2N5T9B0PXHRB",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "seq": 41,
- "type": "chat.message",
- "actor": {
- "kind": "agent",
- "id": "dev-api"
}, - "payload": {
- "role": "assistant",
- "content": "I'll open the epic first."
}, - "createdAt": "2026-07-27T14:30:58.512Z"
}, - "storiesAwaitingPromotion": 2,
- "pendingApprovalsCount": 8,
- "onlineAgentCount": 2,
- "roster": {
- "executionActivated": true,
- "moduleNames": [
- "api",
- "web"
], - "gatesEverOpened": true,
- "delegatedSubagents": [
- "qa-automacao"
], - "activatedAgents": [
- "arquiteto",
- "infra"
], - "infraActive": false,
- "uxDesignerActive": false,
- "staffActive": false
}
}
]Число проектов, агенты, потратившие токены в этом месяце, и расходы этого месяца — питает строку сводки дашборда проектов.
| workspaceId required | string |
{- "activeProjects": 4,
- "agentCount": 6,
- "spentMicros": 12500000
}Включено по умолчанию. Когда включено И модель хода от OpenRouter, шаг агента с двумя или более инструментами сначала спрашивает Jev, какой инструмент подходит, и модели чата предлагается только этот инструмент и предыдущий. Jev никогда ничего не одобряет и не отклоняет: действие, требующее одобрения, по-прежнему его требует. Любой сбой Jev откатывается ко всему каталогу. Выключение прекращает все вызовы к Jev, а вместе с ними и его затраты.
| workspaceId required | string |
| enabled required | boolean Выбирает ли Jev инструмент для каждого шага агента. Включено по умолчанию; действует, только когда модель хода от OpenRouter. |
{- "enabled": false
}{- "id": "01JC4Z0000WORKSPACE00000001",
- "name": "Acme Corp",
- "slug": "acme-corp",
- "createdBy": "01JC4Z0000USUARIO0000000001",
- "toolRouterEnabled": true,
- "createdAt": "2026-07-20T09:12:00.000Z",
- "updatedAt": "2026-07-20T09:12:00.000Z"
}Выдвижная панель колокольчика для всего рабочего пространства (RN-091). Каждый курсор говорит, до какого seq этот проект уже прочитан, а ответ несёт то, что пришло после этого в самой свежей сессии каждого.
Это POST, потому что это единственный глагол с ТЕЛОМ, а не потому, что он меняет состояние. Сервер не знает, где остановился каждый читатель: «прочитано до сюда» — это seq на проект, хранимый в браузере, и endpoint «отметить как прочитанное» (намеренно) нет. Поэтому граница должна ПЕРЕДАВАТЬСЯ в запросе, а пар десятки — в query string это превращается в длинный URL, который прокси обрезает, и к тому же поместило бы id проектов пользователя в журналы доступа. Маршрут идемпотентен и без побочных эффектов: отвечает 200, никогда 201.
Заменяет по одному запросу НА ПРОЕКТ при открытой панели — 23 проекта стоили 286 запросов/мин при лимите 300.
| workspaceId required | string |
required | Массив из objects (UnreadCursorDto) <= 200 items Один курсор на запрашиваемый проект. ПУСТОЙ список возвращает пустой список — «я ничего не просил» не значит «дай мне всё». Проект из другого рабочего пространства молча игнорируется, потому что курсор приходит из локального хранилища вызывающего и может нести остатки старого рабочего пространства. | ||||
Array (<= 200 items)
| |||||
{- "cursors": [
- {
- "projectId": "01JC4Z0000PROJETO0000000001",
- "afterSeq": 41
}
]
}[- {
- "projectId": "01JC4Z0000PROJETO0000000001",
- "sessionId": "01JC4Z0000SESSAO00000000001",
- "events": [
- {
- "id": "01JC4Z8QK3M7YV2N5T9B0PXHRB",
- "sessionId": "01JC4Z8QK3M7YV2N5T9B0PXHRA",
- "seq": 41,
- "type": "chat.message",
- "actor": {
- "kind": "agent",
- "id": "dev-api"
}, - "payload": {
- "role": "assistant",
- "content": "I'll open the epic first."
}, - "createdAt": "2026-07-27T14:30:58.512Z"
}
]
}
]