Перейти к содержанию

Рабочее место человека

Рабочее место — персональный ассистент сотрудника поверх Control Plane: одно окно с непрерывной беседой и экраном «Важное». Страница для тех, кто им пользуется, и для администратора, который его разворачивает. Обоснование решений — TAI-ADR-0051.

Что видит человек

Окно одно, других бесед нет:

  • Беседа с ассистентом — по центру. Ассистент читает задачи, согласования, события и память компании инструментами Control Plane и отвечает по ним, а не по памяти диалога.
  • «Важное» — справа (на узком экране — вкладка). Список считает ядро (GET /api/v1/me/attention, CP-ADR-0071): решения, назначенные человеку или его роли, результаты на проверке, сроки, заблокированная работа, проваленные поручения. У каждого элемента причина и правило. Кнопки на месте: согласовать или отклонить, принять или вернуть, «Подробнее», «Обсудить» (элемент становится репликой в беседе), «Не нужно было показывать» (обратная связь в ядро).

Список «Важного» обновляется сам, как только событие ядра касается человека.

Ассистент приходит сам

Когда происходит то, что касается человека, ассистент пишет в беседу без вопроса: «результат по поручению готов — принять?». Что считать значимым, задают правила (attention-rules.json, см. ниже). События за 20 секунд сводятся в одно сообщение. Второстепенное и всё, что случилось, пока окно было закрыто, ассистент собирает в одну сводку «пока вас не было», которую пишет при следующем открытии окна.

Напоминания («напомни в пятницу проверить отчёт») ассистент ставит в той же беседе и в назначенное время приходит с ними сам.

Что ассистент делает

Действие Инструменты Как
Узнать, кто человек и что ждёт cp_whoami, cp_attention, cp_find_tasks, cp_get_task Чтение
Поручить работу агенту или человеку cp_agents, cp_delegate Черновик: что, кому, критерии готовности, срок — затем подтверждение
Следить за ходом и принять результат cp_run_progress, cp_review Перед приёмкой показывает evidence; возврат — с конкретным замечанием, оно станет заданием следующей итерации
Сделать свою работу cp_take_task, cp_checkpoint, cp_register_artifact, cp_complete_task, cp_release_task, cp_active_work Claim и run от имени человека; результат проверяет ядро, как у агента
Вызвать скилл реестра cp_invoke_skill Показывает, что изменится снаружи
Задачи, комментарии, решения cp_create_task, cp_update_task, cp_comment_task, cp_decide_approval Мутация
Память компании cp_recall, cp_remember Через Control Plane
Выбрать workspace cp_workspaces, cp_select_workspace Сужает «Важное» и поиск

Каждая мутация — с подтверждением человека

Инструмент, который меняет состояние в ядре, не выполняется без явного подтверждения в окне. Права — права самого человека в Control Plane: ассистент не может больше, чем человек.

Приватность

Беседу видит только её владелец. Организации видны объекты ядра и журнал действий (задачи, согласования, артефакты, события), но не переписка. Когда principal отключают в IAM, его беседа и рабочие копии удаляются не позже чем через минуту.

Как это устроено

flowchart LR
    browser[Браузер] --> edge[Caddy /harness/*]
    edge --> launcher[harness-launcher]
    launcher -->|OIDC PKCE| kc[Keycloak]
    launcher -->|federation:exchange| iam[IAM]
    launcher -->|docker-socket-proxy| docker[(Docker)]
    launcher -->|HTTP/WS + HMAC| h1[контейнер человека A]
    launcher -->|HTTP/WS + HMAC| h2[контейнер человека B]
    h1 -->|PAT → access token| cp[Control Plane]
  • Контейнер на человека. Launcher создаёт его из образа human-harness при первом входе: volume harness-data-<principal> в /data (беседа, рабочие копии), каталог секретов человека — только на чтение. Портов наружу у контейнера нет.
  • Вход. Keycloak (Authorization Code + PKCE, клиент human-harness) → IAM federation:exchange → IAM principal. Контейнер выбирается только по подписанной cookie launcher'а; в контейнер запрос уходит с заголовком X-Harness-Launcher (HMAC секретом контейнера), без него контейнер отвечает 401.
  • Identity в ядре. Контейнер действует Platform Access Token человека (read+write, без admin), который выпускает bootstrap.
  • Модель. Ход ассистента исполняет Claude Code по подписке человека (claude-oauth-token). Токен читается на каждый ход и передаётся только процессу Claude Code.
  • Сон. После 30 минут без запросов и без идущего хода контейнер останавливается и просыпается к ближайшему напоминанию или при следующем входе. Холодный старт — секунды.

Развёртывание

Нужны профили core, platform (Keycloak) и edge; рабочие места — профиль harness корневого compose.yml:

Сервис Что делает
harness-image Только сборка образа рабочего места (up сразу завершает сервис)
harness-docker-proxy Docker API для launcher: только контейнеры и volume; exec, images, networks, build закрыты
harness-launcher Вход, прокси HTTP/WebSocket, жизненный цикл контейнеров
make up PROFILES="core platform edge harness"
make bootstrap ARGS="--harness-people deploy/harness-people.json"

Реестр людей

deploy/harness-people.json — кому выдано рабочее место:

{
  "people": [
    {"iamPrincipalId": "operator"},
    {"iamPrincipalId": "<principal-id>", "name": "Alice Example", "email": "alice@example.com"}
  ]
}

"operator" — оператор bootstrap. name и email становятся автором коммитов человека. Шаг 8 bootstrap для каждого человека:

  1. выпускает PAT (control-plane:read, control-plane:write) и пишет secrets/harness/<principal-id>/credentials.json;
  2. пишет ключ cookie launcher'а secrets/harness/cookie-secret и реестр secrets/harness/people.json;
  3. регистрирует audience human-harness (harness:use, harness:inbound).

Файлы, которые человек кладёт в свой каталог secrets/harness/<principal-id>/ сам (права 0600):

Файл Что Без него
claude-oauth-token Токен подписки Claude (claude setup-token) Ассистент отвечает сообщением об авторизации; «Важное» работает
forge-token Личный токен forge (github.com) Нет доступа к приватным репозиториям

Права на Linux

Launcher и контейнеры работают под uid 10001. Каталог secrets/harness должен принадлежать этому uid: chown -R 10001:10001 secrets/harness.

Keycloak и IAM

  • Клиент human-harness (public, PKCE S256, redirect https://platform.example.com/harness/*, audience iam-service) есть в шаблоне realm. На уже импортированном realm его заводит deploy/staging/platform/keycloak-harness-client.py (Admin API).
  • В IAM нужен identity provider keycloak с audience iam-service и связь внешней identity человека с его principal — см. Федерация identity.
  • Права binding человека в Control Plane: для своей работы — tasks.claim, для скиллов — skills.invoke.

Переменные launcher

Переменная Значение в compose
LAUNCHER_PUBLIC_URL ${TAIMEN_PUBLIC_URL}/harness
LAUNCHER_OIDC_ISSUER, LAUNCHER_OIDC_CLIENT_ID realm platform, клиент human-harness
LAUNCHER_IAM_URL, LAUNCHER_IAM_ISSUER, LAUNCHER_IAM_TENANT IAM внутри сети, публичный issuer, IAM_TENANT_ID
LAUNCHER_IAM_BOOTSTRAP_TOKEN_FILE Bootstrap-токен IAM — единственный путь к статусу principal для удаления бесед отключённых
LAUNCHER_IMAGE, LAUNCHER_NETWORK Образ рабочего места и сеть compose
LAUNCHER_HARNESS_MEMORY_MB, LAUNCHER_IDLE_MINUTES HARNESS_MEM_LIMIT_MB (1536), HARNESS_IDLE_MINUTES (30)
LAUNCHER_HARNESS_ENV Окружение контейнера: адрес ядра, IAM, tenant, HARNESS_APP_NAME, HARNESS_TRUSTED_HOSTS

Имя приложения в окне задаёт HARNESS_APP_NAME, название компании берётся из tenant.

Журнал launcher

JSON-строки в stdout (docker compose logs harness-launcher):

Событие Когда
login, login.refused Вход человека; отказ IAM
container.created, container.started, container.ready Старт; ms — время до готовности
container.slept, container.woken Сон и пробуждение по напоминанию
conversation.deleted Principal отключён или удалён — контейнер и volume удалены
sweep.disabled Нет bootstrap-токена IAM — удаление бесед отключено

Правила значимости

Правила по умолчанию поставляются с рабочим местом; установка может заменить их своим файлом (HARNESS_ATTENTION_RULES в окружении контейнера). Правила проверяются по порядку, срабатывает первое подходящее.

Поле Смысл
key, version Идентичность key@version, видна у каждого пункта
on Точные типы событий ядра
concerns approval-mine, task-assignee, task-creator, task-involves, any
when Условия по полям события через точку; "$me" — principal человека
skipOwnActions Пропускать собственные действия человека (по умолчанию true)
significance now — ассистент приходит сам; digest — только в сводку
message, action Шаблоны {{путь}}: что произошло и какое действие предложить
{
  "key": "delegation.run-succeeded",
  "version": 1,
  "on": ["run.succeeded"],
  "concerns": "task-creator",
  "significance": "now",
  "message": "Исполнитель закончил работу по {{task.publicId}} «{{task.title}}».",
  "action": "Посмотри результат и предложи приёмку или следующий шаг."
}

Типичные проблемы

Симптом Причина и что делать
«Рабочее место не выдано» Principal нет в secrets/harness/people.json: добавить в реестр, повторить bootstrap, перезапустить launcher
«Вход запрещён» IAM отказал в federation: principal отключён или внешняя identity не связана
«Рабочее место не успело запуститься» Контейнер не ответил за 30 с: docker logs harness-<principal-id>
Ассистент отвечает «Claude Code не авторизован» Нет или истёк claude-oauth-token; файл перечитывается на каждый ход, перезапуск не нужен
«Важное» пустое с пометкой endpoint_missing Control Plane без GET /me/attention — обновить ядро

См. также