Рабочее место человека¶
Рабочее место — персональный ассистент сотрудника поверх 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при первом входе: volumeharness-data-<principal>в/data(беседа, рабочие копии), каталог секретов человека — только на чтение. Портов наружу у контейнера нет. - Вход. Keycloak (Authorization Code + PKCE, клиент
human-harness) → IAMfederation: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 для каждого человека:
- выпускает PAT (
control-plane:read,control-plane:write) и пишетsecrets/harness/<principal-id>/credentials.json; - пишет ключ cookie launcher'а
secrets/harness/cookie-secretи реестрsecrets/harness/people.json; - регистрирует 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, redirecthttps://platform.example.com/harness/*, audienceiam-service) есть в шаблоне realm. На уже импортированном realm его заводитdeploy/staging/platform/keycloak-harness-client.py(Admin API). - В IAM нужен identity provider
keycloakс audienceiam-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 — обновить ядро |