Files
obsidian-vault/family/how-to/hermes-eagle-mac.md
T

407 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: Hermes на Eagle (Mac M4 Max) — Настройка и подводные камни
type: reference
namespace: work
tags:
- hermes
- mac
- eagle
- claude-proxy
- zulip
- pitfalls
created: '2026-05-21'
updated: '2026-05-22'
last_synced: '2026-05-22'
confidence: 0.9
---
# Hermes на Eagle (Mac M4 Max) — Настройка и подводные камни
Hermes работает нативно (не в Docker) на Mac через `hermes gateway`.
Транспорт — Zulip (запущен в Docker). Провайдер модели — openclaw-claude-proxy
(см. ниже).
## Компоненты
| Компонент | Расположение | Запуск |
|-----------|-------------|--------|
| Hermes config | `~/.hermes/config.yaml` | — |
| claude-proxy (Claude proxy) | `/opt/homebrew/bin/claude-proxy` | launchd `ai.claude-proxy` |
| Zulip stack | `~/Developer/zulip/docker-compose.yml` | `docker compose up -d` |
| Obsidian MCP | mcpvault | встроен в Hermes toolset |
---
## Claude CLI прокси — обход rate limit Claude API
> **Актуальный прокси (июнь 2026):** Python `claude-code-openai-wrapper` на
> порту 8090. Детальная документация: [[claude-python-cli-proxy]].
### Проблема
`provider: claude-code` в Hermes использует OAuth-токен напрямую через
API Anthropic — и упирается в rate limit подписки. Лимиты сбрасываются
раз в час. API-ключа нет (политика организации).
### Почему не cmappy
cmappy (`claude-max-proxy-py`) молча выбрасывает поле `tools` из запроса —
передаёт только текст в `claude --print`. Результат: Hermes не может
использовать **ни один инструмент** (скиллы, MCP, терминал). Только голый
чат.
### Решение: openclaw-claude-proxy
[mehdic/claude-proxy](https://github.com/mehdic/claude-proxy) (npm:
`openclaw-claude-proxy`) — Node.js сервер, запускает `claude --print` как
subprocess и предоставляет OpenAI-совместимый `/v1/chat/completions` на
порту 3456. **Поддерживает tool_use** — инжектирует схемы инструментов в
системный промпт, парсит JSON tool_call из ответа, возвращает стандартный
OpenAI `tool_calls`. Caller (Hermes) сам выполняет инструменты.
**Важно:** `CLAUDE_PROXY_TOOLS_TRANSLATION=1` НЕ включать — этот режим
выполняет MCP инструменты внутри CLI и Hermes ничего не получает.
### Установка
```bash
npm install -g openclaw-claude-proxy
```
### Wrapper-скрипт (обязателен для launchd)
`~/.local/bin/claude-proxy-start.sh`:
```bash
#!/bin/zsh
# launchd не наследует среду login-сессии — токен нужно загружать явно
set -a
source /Users/admin/.hermes/.env 2>/dev/null
set +a
export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:$PATH"
exec /opt/homebrew/bin/claude-proxy 3456
```
**Pitfall:** без явного `source ~/.hermes/.env` claude-proxy не видит
`CLAUDE_CODE_OAUTH_TOKEN` и прогревочные процессы падают с "Not logged in".
**Pitfall:** без явного PATH Claude CLI не найден (`/opt/homebrew/bin/claude`
не в launchd PATH).
### launchd сервис
`~/Library/LaunchAgents/ai.claude-proxy.plist`:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>ai.claude-proxy</string>
<key>ProgramArguments</key>
<array>
<string>/Users/admin/.local/bin/claude-proxy-start.sh</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>HOME</key><string>/Users/admin</string>
</dict>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key><true/>
<key>StandardOutPath</key>
<string>/Users/admin/.hermes/logs/claude-proxy.log</string>
<key>StandardErrorPath</key>
<string>/Users/admin/.hermes/logs/claude-proxy.log</string>
</dict>
</plist>
```
```bash
launchctl load ~/Library/LaunchAgents/ai.claude-proxy.plist
```
**Pitfall при перезагрузке:** если старый процесс ещё держит порт 3456:
```bash
lsof -ti :3456 | xargs kill -9
launchctl unload ~/Library/LaunchAgents/ai.claude-proxy.plist
launchctl load ~/Library/LaunchAgents/ai.claude-proxy.plist
```
### Конфигурация Hermes
`~/.hermes/config.yaml` (секция model):
```yaml
model:
default: claude-sonnet-4-6
# provider: claude-code # отключён — упирается в rate limit OAuth API
provider: custom
base_url: 'http://localhost:3456/v1'
```
**Pitfall:** `provider: openai` не существует в Hermes — нужно `custom`.
**Pitfall:** `base_url` должен включать `/v1` (Hermes дописывает
`/chat/completions`). Без `/v1` → 404.
---
## Zulip Docker — подводные камни
### RabbitMQ: пользователи сбрасываются после перезапуска
**Симптом:** Zulip отдаёт 500 на `/api/v1/register`. В логах RabbitMQ —
паника Khepri (Raft WAL). Пользователи в RabbitMQ исчезают.
**Причина:** RabbitMQ 4.x использует Khepri вместо Mnesia. При переполнении
диска WAL не может записаться → Khepri сбрасывает состояние → пользователи
исчезают. `RABBITMQ_DEFAULT_USER/PASS` применяются только при **первом
старте** с пустым volume — повторный запуск их не восстанавливает.
**Решение:**
1. `docker system prune` — освободить место на диске (Docker VM sparse disk
не освобождает место автоматически).
2. Добавить `RABBITMQ_ERLANG_COOKIE` в env rabbitmq (стабилизирует cookie
через перезапуски).
3. При повреждённом volume — стереть и пересоздать:
```bash
docker compose down
docker volume rm zulip_zulip-rabbitmq
docker compose up -d
```
### Log rotation (обязательно!)
Без ротации логи заполняют Docker VM (~6 ГБ за несколько месяцев).
`docker-compose.yml` — добавить к каждому сервису:
```yaml
# zulip:
logging:
driver: json-file
options:
max-size: "50m"
max-file: "5"
# rabbitmq, memcached, redis:
logging:
driver: json-file
options:
max-size: "20m"
max-file: "3"
```
### Docker VM sparse disk
Mac Docker Desktop использует sparse virtual disk. Место, освобождённое
внутри VM, не возвращается хосту автоматически. `docker system prune`
запускает compaction.
---
## Obsidian MCP
Везде используется `mcpvault` (не `obsidian-mcp`).
| Конфиг | Путь |
|--------|------|
| Claude Code CLI | `~/.claude/.mcp.json` |
| Claude Desktop App | `~/Library/Application Support/Claude/claude_desktop_config.json` |
| Hermes | встроен через toolset |
Пример конфига (одинаковый для обоих):
```json
{
"mcpServers": {
"obsidian": {
"command": "mcpvault",
"args": ["/Users/admin/obsidian"]
}
}
}
```
**Pitfall:** `obsidian-mcp` (npm) был удалён — если остался в конфиге,
Claude падает с "Failed to spawn process". Проверить логи:
`~/Library/Logs/Claude/mcp-server-obsidian.log`.
---
---
## План работ: миграция портов Eagle и Whale (2026-06-17)
### Текущее состояние
Eagle и Кит (Whale) — два отдельных gateway на одном Mac. Оба читают
один `config.yaml` (`~/.hermes/`). Кит сидит на порту 8645, Eagle — на 8644
(занял свободный, так как 8645 был занят).
Нужно: Eagle → 8645, Кит → другой порт.
### Шаг 1. Сменить порт Кита
Файл: `~/.hermes/hermes-whale/config.yaml`
Поменять порт webhook с 8645 на 8646:
```yaml
platforms:
webhook:
extra:
port: 8646 # было 8645
```
### Шаг 2. Убить второй gateway (PID 96351)
Это дубль — запущен руками из того же конфига, сидит на 8644:
```bash
kill 96351
```
PID актуальный — проверить перед kill.
### Шаг 3. Перезапустить Eagle на 8645
Через Eagle Dashboard (или `launchctl kickstart ai.hermes.gateway` — только
по явной команде).
### Шаг 4. Проверить
1. `curl -s http://127.0.0.1:8645` → 200 (Eagle жив)
2. `curl -s -X POST http://127.0.0.1:8645/webhooks/eagle -d '{"message":"test"}'`
→ 202 Accepted
3. `@**Орёл**` в Zulip → Eagle отвечает
4. Роутер шлёт на `host.docker.internal:8645/webhooks/eagle` — проверить логи
---
---
### Шаг 5. Поправить роутер — route `/webhooks/whale`
В конфиге роутера (`~/Docker/zulip-router/config.yaml`) добавить бота `Кит`
с webhook на новый порт (8646):
```yaml
bots:
- name: "Кит"
aliases: ["Whale", "whale", "кит"]
webhook: "http://host.docker.internal:8646/webhooks/whale"
```
### Шаг 6. Проверить @mention всех ботов
- `@**Орёл**` → Eagle отвечает
- `@**Кит**` → Кит отвечает
- `@**Валера**` → Валера отвечает (через роутер)
- `@**Клавдий**` → Клавдий отвечает (через роутер)
### Шаг 7. Обновить доку zulip-router.md
Записать:
- Актуальную схему роутинга с портами
- Статус TODO (проверено/не проверено)
- Проблему с trigger=owner
---
## Что сделано по zulip-router (история)
### 2026-06-16 — деплой zulip-router
- Написан код роутера (`~/Developer/zulip-router/`, Go, ~260 LOC)
- Создан compose (`~/Docker/zulip-router/docker-compose.yaml`)
- Сеть `balda_default` (external) — чтобы видел balda-контейнеры
- `.env` с API key Eagle (`eagle-bot@zulip.qentra.top`)
- `docker-compose up -d` — запущен
- Events API polling отключён у обоих balda-ботов (`events_polling.enabled: false`)
### 2026-06-17 — отладка
- Eagle добавлен в конфиг роутера как бот, webhook на `:8644`
- Кит добавлен в конфиг роутера как бот, webhook на `:8644`
- Выяснено: роутер форвардит все сообщения из треда с ownership,
даже если @mention адресован другому боту (проблема trigger=owner)
- Актуальный код роутера в контейнере не совпадает с кодом в репе
### Статус проблем
- [ ] **trigger=owner** — роутер не фильтрует @mention другого бота.
Должен: если @mention другого бота — ownership ставится, но НЕ форвардить.
- [ ] **Код в репе vs контейнере** — рассинхронизация.
Надо синхронизировать: `~/Developer/zulip-router/` → контейнер.
---
## Связанные страницы
Eagle использует webhook platform для приёма сообщений от zulip-router.
Напрямую в Zulip через Events API Eagle **не подписан** — все сообщения
приходят через роутер.
### Конфигурация
`~/.hermes/config.yaml` (секция `platforms`):
```yaml
platforms:
webhook:
enabled: true
extra:
host: 127.0.0.1
port: 8645
routes:
eagle:
secret: INSECURE_NO_AUTH
prompt: '{{message.content}}'
deliver: zulip
```
### Порт 8645
Eagle слушает webhook на **127.0.0.1:8645**. Это **единственный** порт Eagle.
### Схема работы
```
Zulip
└── Events API → [zulip-router] (Go, Docker)
├── @Валера → POST balda-agent:8091
├── @Клавдий → POST claudio-agent:8092
└── @Орёл / @Кит → POST host.docker.internal:8645/webhooks/{eagle,whale}
```
- Роутер использует credentials Eagle (`eagle-bot@zulip.qentra.top`)
- Eagle принимает сообщения **только через webhook** — не через Zulip поллинг
- `@**Орёл**` в Zulip → роутер подхватывает → POST `/webhooks/eagle` → Eagle отвечает
### Проблема (2026-06-17)
Eagle не отвечал на `@**Орёл**`. Причины:
1. **Второй gateway (PID 96351)** был запущен вручную из того же `~/.hermes/` и
занял порт 8644, так как 8645 был уже занят Китом (PID 90402).
2. Кит (Whale) по ошибке сидел на порту 8645 — порту Eagle.
3. Роутер слал на `:8644`, а Eagle ждал на `:8645`.
**Что сделано:**
- Конфиг Eagle приведён к состоянию: порт 8645, webhook с route `eagle`
- Второй gateway (PID 96351) — удалить (дубликат)
- Кита пересадить на другой порт (не 8645)
### TODO
- [ ] Пересадить Кита с 8645 на другой порт (8646?)
- [ ] Убить PID 96351 (дубль)
- [ ] Перезапустить Eagle на 8645
- [ ] Проверить `@**Орёл**` через роутер
---
## Связанные страницы
- [[claude-python-cli-proxy]] — Python Claude CLI proxy (claude-code-openai-wrapper, порт 8090)
- [[tech/hermes-docker-kraken]] — Hermes на Кракене (Docker)
- [[tech/kraken-network]] — сетевая топология
- [[concepts/hermes-deployment-patterns]] — сравнение трёх моделей деплоя Hermes