docs: Domain Model, State Machines, Connector API, Handoff, Operations, Security, ADR, Roadmap (+15 улучшений ТЗ)
This commit is contained in:
parent
37f058938c
commit
3f9a925f24
15 changed files with 3125 additions and 2 deletions
26
README.md
26
README.md
|
|
@ -15,11 +15,35 @@
|
||||||
|
|
||||||
## Документы
|
## Документы
|
||||||
|
|
||||||
|
### Нормативные
|
||||||
- [Каноническое ТЗ](docs/SPECIFICATION.md)
|
- [Каноническое ТЗ](docs/SPECIFICATION.md)
|
||||||
- [Архитектура](docs/ARCHITECTURE.md)
|
- [Архитектура](docs/ARCHITECTURE.md)
|
||||||
- [Исследование интеграций](docs/RESEARCH.md)
|
- [Domain Model](docs/DOMAIN_MODEL.md)
|
||||||
|
- [State Machines](docs/STATE_MACHINES.md)
|
||||||
|
- [Connector API](docs/CONNECTOR_API.md)
|
||||||
|
- [Handoff Contract](docs/HANDOFF_CONTRACT.md)
|
||||||
- [Матрица трассируемости](docs/TRACEABILITY.md)
|
- [Матрица трассируемости](docs/TRACEABILITY.md)
|
||||||
- [Открытые решения](docs/OPEN-QUESTIONS.md)
|
- [Открытые решения](docs/OPEN-QUESTIONS.md)
|
||||||
|
|
||||||
|
### Исследования и адаптеры
|
||||||
|
- [Исследование интеграций](docs/RESEARCH.md)
|
||||||
|
- [Hermes Agent Adapter](docs/adapters/HERMES.md)
|
||||||
|
|
||||||
|
### Паттерны
|
||||||
|
- [Worker Orchestration](docs/patterns/WORKER_ORCHESTRATION.md)
|
||||||
|
- [Credential Pools](docs/patterns/CREDENTIAL_POOLS.md)
|
||||||
|
|
||||||
|
### Операции
|
||||||
|
- [Scheduler, Events, Errors, SLO](docs/OPERATIONS.md)
|
||||||
|
- [Security & Performance](docs/SECURITY_AND_PERFORMANCE.md)
|
||||||
|
- [Monitoring & Health-check](docs/operations/MONITORING.md)
|
||||||
|
- [Backup & Recovery](docs/operations/BACKUP.md)
|
||||||
|
|
||||||
|
### Проектирование
|
||||||
|
- [ADR и Sequence Diagrams](docs/ADR_AND_SEQUENCES.md)
|
||||||
|
- [Roadmap](docs/ROADMAP.md)
|
||||||
|
|
||||||
|
### Участие
|
||||||
- [Правила участия](CONTRIBUTING.md)
|
- [Правила участия](CONTRIBUTING.md)
|
||||||
|
|
||||||
## Ключевой принцип
|
## Ключевой принцип
|
||||||
|
|
|
||||||
128
docs/ADR_AND_SEQUENCES.md
Normal file
128
docs/ADR_AND_SEQUENCES.md
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
# Architecture Decision Records
|
||||||
|
|
||||||
|
**Версия:** 0.1.0-draft | **Статус:** Draft
|
||||||
|
|
||||||
|
## Шаблон ADR
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# ADR-NNN: Краткое название
|
||||||
|
|
||||||
|
**Статус:** proposed | accepted | deprecated | superseded
|
||||||
|
**Дата:** YYYY-MM-DD
|
||||||
|
**Владельцы:** @username
|
||||||
|
**Связано:** OQ-XXX, FR-XXX, NFR-XXX
|
||||||
|
|
||||||
|
## Контекст
|
||||||
|
|
||||||
|
Описание проблемы, требующей архитектурного решения. Какие силы действуют?
|
||||||
|
Какие ограничения?
|
||||||
|
|
||||||
|
## Варианты и анализ
|
||||||
|
|
||||||
|
### Вариант A: Название
|
||||||
|
- **Плюсы:** ...
|
||||||
|
- **Минусы:** ...
|
||||||
|
- **Риски:** ...
|
||||||
|
|
||||||
|
### Вариант B: Название
|
||||||
|
- **Плюсы:** ...
|
||||||
|
- **Минусы:** ...
|
||||||
|
|
||||||
|
## Решение
|
||||||
|
|
||||||
|
Выбран **Вариант X** потому что ...
|
||||||
|
|
||||||
|
## Последствия
|
||||||
|
|
||||||
|
### Положительные
|
||||||
|
- ...
|
||||||
|
|
||||||
|
### Отрицательные
|
||||||
|
- ...
|
||||||
|
|
||||||
|
### Безопасность и приватность
|
||||||
|
- ...
|
||||||
|
|
||||||
|
### Операционные
|
||||||
|
- ...
|
||||||
|
|
||||||
|
## Миграция и откат
|
||||||
|
|
||||||
|
Как перейти от текущего состояния к новому? Как откатиться?
|
||||||
|
|
||||||
|
## Валидация
|
||||||
|
|
||||||
|
Как проверить, что решение работает?
|
||||||
|
```
|
||||||
|
|
||||||
|
## Каталог ADR
|
||||||
|
|
||||||
|
| Номер | Название | Статус | Дата |
|
||||||
|
|---|---|---|---|
|
||||||
|
| ADR-001 | Client stack: React + Tauri + Capacitor | proposed | — |
|
||||||
|
| ADR-002 | Auth: OIDC + local bootstrap | proposed | — |
|
||||||
|
| ADR-003 | Persistence: PostgreSQL + Object Storage | proposed | — |
|
||||||
|
| ADR-004 | WSS transport for Connector | proposed | — |
|
||||||
|
| ADR-005 | Adapter pattern: versioned, capability-based | proposed | — |
|
||||||
|
| ADR-006 | Handoff format: JSON Schema v1 | proposed | — |
|
||||||
|
| ADR-007 | Event Bus: PostgreSQL NOTIFY/LISTEN vs Redis | proposed | — |
|
||||||
|
| ADR-008 | Memory embeddings: pgvector vs external | proposed | — |
|
||||||
|
| ADR-009 | Connector sandbox: container vs systemd | proposed | — |
|
||||||
|
| ADR-010 | API versioning: URL-based (/v1, /v2) | proposed | — |
|
||||||
|
|
||||||
|
## Sequence Diagrams (текстовые)
|
||||||
|
|
||||||
|
### Запуск проекта (happy path)
|
||||||
|
|
||||||
|
```
|
||||||
|
User → API: POST /projects (name, workspace)
|
||||||
|
API → DB: INSERT project
|
||||||
|
API → User: 201 { project_id }
|
||||||
|
|
||||||
|
User → API: POST /tasks (project_id, title)
|
||||||
|
API → Scheduler: enqueue(task)
|
||||||
|
Scheduler → Connector: create_run(agent, goal)
|
||||||
|
Connector → Agent: run(goal)
|
||||||
|
Agent → Connector: stream_events(...)
|
||||||
|
Connector → API: stream_events(...)
|
||||||
|
API → User: WSS push events
|
||||||
|
Agent → Connector: run_completed
|
||||||
|
Connector → API: run_completed
|
||||||
|
API → User: notification
|
||||||
|
```
|
||||||
|
|
||||||
|
### Handoff
|
||||||
|
|
||||||
|
```
|
||||||
|
Agent_A → Connector_A: handoff(agent_B, bundle)
|
||||||
|
Connector_A → API: handoff_request
|
||||||
|
API → Scheduler: enqueue(agent_B, bundle)
|
||||||
|
Scheduler → Connector_B: create_run(agent_B, bundle)
|
||||||
|
Agent_B → Connector_B: run_accepted
|
||||||
|
Connector_B → API: handoff_ack
|
||||||
|
API → Connector_A: handoff_ack
|
||||||
|
```
|
||||||
|
|
||||||
|
### Approval
|
||||||
|
|
||||||
|
```
|
||||||
|
Agent → Connector: approval_request(action="rm -rf /build", risk=high)
|
||||||
|
Connector → API: approval_request
|
||||||
|
API → EventBus: approval.requested
|
||||||
|
EventBus → User: push notification
|
||||||
|
User → API: POST /approvals/{id}/approve
|
||||||
|
API → Connector: approval_granted
|
||||||
|
Connector → Agent: continue
|
||||||
|
Agent → Connector: action_executed
|
||||||
|
```
|
||||||
|
|
||||||
|
### Восстановление после reconnect
|
||||||
|
|
||||||
|
```
|
||||||
|
Connector → API: register (reconnect)
|
||||||
|
API → Connector: register_ack + pending_commands[]
|
||||||
|
Connector → Agent: resume(run_id)
|
||||||
|
Agent → Connector: stream_events (с последнего checkpoint)
|
||||||
|
Connector → API: stream_events
|
||||||
|
API → User: events replayed
|
||||||
|
```
|
||||||
228
docs/CONNECTOR_API.md
Normal file
228
docs/CONNECTOR_API.md
Normal file
|
|
@ -0,0 +1,228 @@
|
||||||
|
# Connector API v1
|
||||||
|
|
||||||
|
**Версия:** 0.1.0-draft | **Статус:** Draft
|
||||||
|
|
||||||
|
## Transport
|
||||||
|
|
||||||
|
- **Протокол:** WSS (WebSocket Secure) + HTTPS fallback
|
||||||
|
- **Аутентификация:** mTLS (клиентский сертификат) + JWT
|
||||||
|
- **Формат:** JSON (все payloads)
|
||||||
|
- **Сжатие:** per-message deflate (опционально)
|
||||||
|
- **Keepalive:** ping/pong каждые 30 секунд
|
||||||
|
|
||||||
|
## Endpoints
|
||||||
|
|
||||||
|
### 1. register
|
||||||
|
Регистрация коннектора при первом подключении.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// → Request
|
||||||
|
{
|
||||||
|
"type": "register",
|
||||||
|
"connector_id": "uuid",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"host": "server01.example.com",
|
||||||
|
"adapters": ["hermes", "openclaw"]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ← Response
|
||||||
|
{
|
||||||
|
"type": "register_ack",
|
||||||
|
"connector_id": "uuid",
|
||||||
|
"server_time": "2026-07-20T12:00:00Z",
|
||||||
|
"config": { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. heartbeat
|
||||||
|
Периодический health-check.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// → Request (каждые 30s)
|
||||||
|
{
|
||||||
|
"type": "heartbeat",
|
||||||
|
"connector_id": "uuid",
|
||||||
|
"timestamp": "2026-07-20T12:00:30Z",
|
||||||
|
"metrics": {
|
||||||
|
"cpu_pct": 45.2,
|
||||||
|
"memory_mb": 512,
|
||||||
|
"active_runs": 2,
|
||||||
|
"queued_runs": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ← Response
|
||||||
|
{ "type": "heartbeat_ack" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. capabilities
|
||||||
|
Объявление возможностей коннектора и его агентов.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// → Request
|
||||||
|
{
|
||||||
|
"type": "capabilities",
|
||||||
|
"connector_id": "uuid",
|
||||||
|
"agents": [{
|
||||||
|
"agent_id": "uuid",
|
||||||
|
"runtime": "hermes",
|
||||||
|
"model": "opencode-go/kimi-k2.7-code",
|
||||||
|
"tools": ["terminal", "browser", "file", "delegation"],
|
||||||
|
"max_turns": 60,
|
||||||
|
"supports": ["streaming", "cancellation", "handoff"]
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. create_run
|
||||||
|
Запуск задачи на агенте.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// → Request
|
||||||
|
{
|
||||||
|
"type": "create_run",
|
||||||
|
"run_id": "uuid",
|
||||||
|
"agent_id": "uuid",
|
||||||
|
"goal": "Research GRPO papers",
|
||||||
|
"context_bundle": {
|
||||||
|
"objective": "...",
|
||||||
|
"acceptance_criteria": ["..."],
|
||||||
|
"memory_keys": ["key1", "key2"],
|
||||||
|
"artifacts": ["artifact-id-1"]
|
||||||
|
},
|
||||||
|
"constraints": {
|
||||||
|
"max_tokens": 100000,
|
||||||
|
"timeout_seconds": 600,
|
||||||
|
"tools": ["terminal", "browser"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ← Response
|
||||||
|
{
|
||||||
|
"type": "run_accepted",
|
||||||
|
"run_id": "uuid",
|
||||||
|
"status": "queued"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. stream_events
|
||||||
|
Поток событий от агента (server → client push).
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "run_event",
|
||||||
|
"run_id": "uuid",
|
||||||
|
"event": "tool_call",
|
||||||
|
"timestamp": "2026-07-20T12:01:00Z",
|
||||||
|
"payload": {
|
||||||
|
"tool": "terminal",
|
||||||
|
"command": "ls -la",
|
||||||
|
"output": "total 48\n..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Типы событий: `thinking`, `tool_call`, `tool_result`, `progress`, `warning`, `error`, `completion`.
|
||||||
|
|
||||||
|
### 6. cancel_run
|
||||||
|
Отмена запущенной задачи.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// → Request
|
||||||
|
{ "type": "cancel_run", "run_id": "uuid" }
|
||||||
|
// ← Response
|
||||||
|
{ "type": "run_cancelled", "run_id": "uuid" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. handoff
|
||||||
|
Передача контекста между агентами.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// → Request
|
||||||
|
{
|
||||||
|
"type": "handoff",
|
||||||
|
"from_run_id": "uuid",
|
||||||
|
"to_agent_id": "uuid",
|
||||||
|
"bundle": {
|
||||||
|
"objective": "...",
|
||||||
|
"progress": "...",
|
||||||
|
"decisions": ["..."],
|
||||||
|
"artifacts": ["id1"],
|
||||||
|
"open_questions": ["..."]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8. upload_artifact / download_artifact
|
||||||
|
Загрузка/выгрузка артефактов через Object Storage.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// → upload_artifact
|
||||||
|
{
|
||||||
|
"type": "upload_artifact",
|
||||||
|
"run_id": "uuid",
|
||||||
|
"name": "results.csv",
|
||||||
|
"content_type": "text/csv",
|
||||||
|
"size_bytes": 1024
|
||||||
|
}
|
||||||
|
// ← Response: { "upload_url": "https://...", "artifact_id": "uuid" }
|
||||||
|
|
||||||
|
// → download_artifact
|
||||||
|
{ "type": "download_artifact", "artifact_id": "uuid" }
|
||||||
|
// ← Response: { "download_url": "https://..." }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9. approve
|
||||||
|
Запрос подтверждения опасного действия.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// → Request (connector → server)
|
||||||
|
{
|
||||||
|
"type": "approval_request",
|
||||||
|
"run_id": "uuid",
|
||||||
|
"action": "rm -rf /tmp/build",
|
||||||
|
"risk_level": "high"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ← Response (server → connector, after human approval)
|
||||||
|
{
|
||||||
|
"type": "approval_granted",
|
||||||
|
"approval_id": "uuid",
|
||||||
|
"approved_by": "user@example.com"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Коды ошибок
|
||||||
|
|
||||||
|
| Код | Описание |
|
||||||
|
|---|---|
|
||||||
|
| 4001 | Invalid request format |
|
||||||
|
| 4002 | Unknown message type |
|
||||||
|
| 4003 | Agent not found |
|
||||||
|
| 4004 | Run not found |
|
||||||
|
| 4005 | Agent busy (max concurrent runs) |
|
||||||
|
| 4006 | Quota exceeded |
|
||||||
|
| 4007 | Unauthorized action |
|
||||||
|
| 4008 | Approval denied |
|
||||||
|
| 5001 | Connector internal error |
|
||||||
|
| 5002 | Adapter error |
|
||||||
|
|
||||||
|
## Retry Policy
|
||||||
|
|
||||||
|
| Ошибка | Стратегия |
|
||||||
|
|---|---|
|
||||||
|
| Network timeout | Exponential backoff: 1s, 2s, 4s, 8s, 16s, затем каждые 30s |
|
||||||
|
| 5001/5002 | Мгновенный retry × 3, затем fail |
|
||||||
|
| 4005 | Отложить run в очередь, retry через 60s |
|
||||||
|
| 4006 | Остановить run, уведомить operator |
|
||||||
|
|
||||||
|
## Таймауты
|
||||||
|
|
||||||
|
| Операция | Таймаут |
|
||||||
|
|---|---|
|
||||||
|
| register | 10s |
|
||||||
|
| heartbeat response | 5s |
|
||||||
|
| create_run accept | 30s |
|
||||||
|
| stream_event доставка | 60s (затем reconnect) |
|
||||||
|
| cancel_run подтверждение | 15s |
|
||||||
|
| upload_artifact URL | 300s |
|
||||||
203
docs/DOMAIN_MODEL.md
Normal file
203
docs/DOMAIN_MODEL.md
Normal file
|
|
@ -0,0 +1,203 @@
|
||||||
|
# Domain Model — Agent Control Center
|
||||||
|
|
||||||
|
**Версия:** 0.1.0-draft
|
||||||
|
**Статус:** Draft
|
||||||
|
**Нормативность:** supporting; при конфликте приоритет у SPECIFICATION.md
|
||||||
|
|
||||||
|
## Сущности
|
||||||
|
|
||||||
|
### Organization
|
||||||
|
| Поле | Тип | Описание |
|
||||||
|
|---|---|---|
|
||||||
|
| id | UUID | Первичный ключ |
|
||||||
|
| name | string | Название организации |
|
||||||
|
| slug | string | Уникальный идентификатор для URL |
|
||||||
|
| billing_email | string | Email для биллинга |
|
||||||
|
| created_at | timestamptz | Дата создания |
|
||||||
|
|
||||||
|
**Связи:** 1 → N Workspace
|
||||||
|
|
||||||
|
### Workspace
|
||||||
|
| Поле | Тип | Описание |
|
||||||
|
|---|---|---|
|
||||||
|
| id | UUID | PK |
|
||||||
|
| organization_id | UUID | FK → Organization |
|
||||||
|
| name | string | Название рабочего пространства |
|
||||||
|
| owner_account_id | UUID | FK → Account (владелец) |
|
||||||
|
| ai_lockout | boolean | Блокировка AI-действий |
|
||||||
|
| created_at | timestamptz | |
|
||||||
|
|
||||||
|
**Связи:** 1 → N Project, 1 → N Member
|
||||||
|
|
||||||
|
### Project
|
||||||
|
| Поле | Тип | Описание |
|
||||||
|
|---|---|---|
|
||||||
|
| id | UUID | PK |
|
||||||
|
| workspace_id | UUID | FK → Workspace |
|
||||||
|
| name | string | |
|
||||||
|
| description | text | |
|
||||||
|
| status | enum | active, archived, deleted |
|
||||||
|
| created_at | timestamptz | |
|
||||||
|
|
||||||
|
**Связи:** 1 → N Task, 1 → N Artifact
|
||||||
|
|
||||||
|
### Task
|
||||||
|
| Поле | Тип | Описание |
|
||||||
|
|---|---|---|
|
||||||
|
| id | UUID | PK |
|
||||||
|
| project_id | UUID | FK → Project |
|
||||||
|
| title | string | |
|
||||||
|
| description | text | |
|
||||||
|
| priority | enum | low, medium, high, critical |
|
||||||
|
| status | enum | backlog, todo, in_progress, review, done |
|
||||||
|
| assignee_id | UUID | FK → Account (опционально) |
|
||||||
|
| created_at | timestamptz | |
|
||||||
|
|
||||||
|
**Связи:** 1 → N Run
|
||||||
|
|
||||||
|
### Run
|
||||||
|
| Поле | Тип | Описание |
|
||||||
|
|---|---|---|
|
||||||
|
| id | UUID | PK |
|
||||||
|
| task_id | UUID | FK → Task |
|
||||||
|
| agent_id | UUID | FK → Agent |
|
||||||
|
| connector_id | UUID | FK → Connector |
|
||||||
|
| status | enum | см. State Machine |
|
||||||
|
| goal | text | Цель запуска |
|
||||||
|
| context_bundle | jsonb | Bounded context |
|
||||||
|
| started_at | timestamptz | |
|
||||||
|
| completed_at | timestamptz | |
|
||||||
|
| error_message | text | |
|
||||||
|
|
||||||
|
### Agent
|
||||||
|
| Поле | Тип | Описание |
|
||||||
|
|---|---|---|
|
||||||
|
| id | UUID | PK |
|
||||||
|
| connector_id | UUID | FK → Connector |
|
||||||
|
| name | string | |
|
||||||
|
| runtime | enum | hermes, openclaw, claude, codex, gemini, generic |
|
||||||
|
| model | string | Идентификатор модели |
|
||||||
|
| tools | jsonb | Доступные инструменты |
|
||||||
|
| capabilities | jsonb | FK → Capability Registry |
|
||||||
|
| config_ref | string | Ссылка на конфигурацию |
|
||||||
|
|
||||||
|
### Connector
|
||||||
|
| Поле | Тип | Описание |
|
||||||
|
|---|---|---|
|
||||||
|
| id | UUID | PK |
|
||||||
|
| name | string | |
|
||||||
|
| host | string | Адрес сервера |
|
||||||
|
| status | enum | online, offline, degraded |
|
||||||
|
| version | string | Версия коннектора |
|
||||||
|
| last_heartbeat | timestamptz | |
|
||||||
|
| capabilities | jsonb | |
|
||||||
|
|
||||||
|
### Artifact
|
||||||
|
| Поле | Тип | Описание |
|
||||||
|
|---|---|---|
|
||||||
|
| id | UUID | PK |
|
||||||
|
| project_id | UUID | FK → Project |
|
||||||
|
| run_id | UUID | FK → Run (опционально) |
|
||||||
|
| name | string | |
|
||||||
|
| type | string | MIME-тип |
|
||||||
|
| size_bytes | bigint | |
|
||||||
|
| storage_key | string | Ключ в Object Storage |
|
||||||
|
| checksum | string | SHA-256 |
|
||||||
|
| created_at | timestamptz | |
|
||||||
|
|
||||||
|
### Memory
|
||||||
|
| Поле | Тип | Описание |
|
||||||
|
|---|---|---|
|
||||||
|
| id | UUID | PK |
|
||||||
|
| scope | string | user, project, workspace |
|
||||||
|
| scope_id | UUID | ID области видимости |
|
||||||
|
| key | string | Ключ записи |
|
||||||
|
| value | text | Содержимое |
|
||||||
|
| provenance | string | Источник (agent_id, user_id) |
|
||||||
|
| version | int | Версия записи |
|
||||||
|
| created_at | timestamptz | |
|
||||||
|
|
||||||
|
### Skill
|
||||||
|
| Поле | Тип | Описание |
|
||||||
|
|---|---|---|
|
||||||
|
| id | UUID | PK |
|
||||||
|
| name | string | Уникальное имя |
|
||||||
|
| version | string | Семантическая версия |
|
||||||
|
| content | text | SKILL.md |
|
||||||
|
| signature | text | Подпись (опционально) |
|
||||||
|
| review_status | enum | draft, reviewed, approved, deprecated |
|
||||||
|
| created_by | string | agent_id или user_id |
|
||||||
|
| created_at | timestamptz | |
|
||||||
|
|
||||||
|
### WikiPage
|
||||||
|
| Поле | Тип | Описание |
|
||||||
|
|---|---|---|
|
||||||
|
| id | UUID | PK |
|
||||||
|
| workspace_id | UUID | FK → Workspace |
|
||||||
|
| path | string | Путь страницы |
|
||||||
|
| title | string | |
|
||||||
|
| content | text | Markdown |
|
||||||
|
| version | int | |
|
||||||
|
| updated_at | timestamptz | |
|
||||||
|
|
||||||
|
### Approval
|
||||||
|
| Поле | Тип | Описание |
|
||||||
|
|---|---|---|
|
||||||
|
| id | UUID | PK |
|
||||||
|
| run_id | UUID | FK → Run |
|
||||||
|
| action | string | Описание действия |
|
||||||
|
| risk_level | enum | low, medium, high, critical |
|
||||||
|
| requested_by | UUID | Кто запросил |
|
||||||
|
| status | enum | pending, approved, denied, expired |
|
||||||
|
| decided_by | UUID | Кто принял решение |
|
||||||
|
| created_at | timestamptz | |
|
||||||
|
| decided_at | timestamptz | |
|
||||||
|
|
||||||
|
### AuditEvent
|
||||||
|
| Поле | Тип | Описание |
|
||||||
|
|---|---|---|
|
||||||
|
| id | UUID | PK |
|
||||||
|
| event_type | string | Тип события |
|
||||||
|
| actor_id | UUID | Кто совершил |
|
||||||
|
| target_type | string | Тип цели |
|
||||||
|
| target_id | UUID | ID цели |
|
||||||
|
| payload | jsonb | Детали события |
|
||||||
|
| created_at | timestamptz | |
|
||||||
|
|
||||||
|
### User / Account
|
||||||
|
| Поле | Тип | Описание |
|
||||||
|
|---|---|---|
|
||||||
|
| id | UUID | PK |
|
||||||
|
| email | string | Уникальный |
|
||||||
|
| display_name | string | |
|
||||||
|
| password_hash | string | |
|
||||||
|
| mfa_enabled | boolean | |
|
||||||
|
| created_at | timestamptz | |
|
||||||
|
|
||||||
|
### Role
|
||||||
|
| Поле | Тип | Описание |
|
||||||
|
|---|---|---|
|
||||||
|
| id | UUID | PK |
|
||||||
|
| name | string | owner, admin, lead, operator, contributor, viewer |
|
||||||
|
| permissions | jsonb | Список разрешений |
|
||||||
|
|
||||||
|
**Связи:** User N ↔ M Role (через user_roles)
|
||||||
|
|
||||||
|
## Индексы
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Поиск задач по проекту и статусу
|
||||||
|
CREATE INDEX idx_task_project_status ON task(project_id, status);
|
||||||
|
|
||||||
|
-- Поиск запусков по агенту и статусу
|
||||||
|
CREATE INDEX idx_run_agent_status ON run(agent_id, status);
|
||||||
|
|
||||||
|
-- Поиск артефактов по проекту
|
||||||
|
CREATE INDEX idx_artifact_project ON artifact(project_id);
|
||||||
|
|
||||||
|
-- Аудит по времени
|
||||||
|
CREATE INDEX idx_audit_created ON audit_event(created_at DESC);
|
||||||
|
|
||||||
|
-- Поиск memory по scope
|
||||||
|
CREATE INDEX idx_memory_scope ON memory(scope, scope_id, key);
|
||||||
|
```
|
||||||
211
docs/HANDOFF_CONTRACT.md
Normal file
211
docs/HANDOFF_CONTRACT.md
Normal file
|
|
@ -0,0 +1,211 @@
|
||||||
|
# Handoff Contract v1
|
||||||
|
|
||||||
|
**Версия:** 0.1.0-draft | **Статус:** Draft
|
||||||
|
|
||||||
|
## Назначение
|
||||||
|
|
||||||
|
Контракт определяет формат передачи контекста между агентами при handoff.
|
||||||
|
Гарантирует, что принимающий агент получает минимально достаточный bounded context
|
||||||
|
для продолжения работы без повторного исследования.
|
||||||
|
|
||||||
|
## JSON Schema
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://agent-control-center/schemas/handoff-v1.json",
|
||||||
|
"type": "object",
|
||||||
|
"required": ["handoff_id", "from_agent", "to_agent", "objective", "bundle"],
|
||||||
|
"properties": {
|
||||||
|
"handoff_id": { "type": "string", "format": "uuid" },
|
||||||
|
"version": { "const": "1.0" },
|
||||||
|
"timestamp": { "type": "string", "format": "date-time" },
|
||||||
|
|
||||||
|
"from_agent": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["agent_id", "runtime"],
|
||||||
|
"properties": {
|
||||||
|
"agent_id": { "type": "string" },
|
||||||
|
"runtime": { "enum": ["hermes", "openclaw", "claude", "codex", "gemini"] },
|
||||||
|
"model": { "type": "string" },
|
||||||
|
"run_id": { "type": "string" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"to_agent": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["agent_id"],
|
||||||
|
"properties": {
|
||||||
|
"agent_id": { "type": "string" },
|
||||||
|
"expected_capabilities": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "type": "string" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"objective": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["goal"],
|
||||||
|
"properties": {
|
||||||
|
"goal": { "type": "string", "maxLength": 500 },
|
||||||
|
"acceptance_criteria": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "type": "string" },
|
||||||
|
"maxItems": 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"bundle": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"progress": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"summary": { "type": "string", "maxLength": 1000 },
|
||||||
|
"completed_steps": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "type": "string" },
|
||||||
|
"maxItems": 20
|
||||||
|
},
|
||||||
|
"current_step": { "type": "string" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"decisions": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"decision": { "type": "string" },
|
||||||
|
"rationale": { "type": "string" },
|
||||||
|
"alternatives_considered": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "type": "string" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"maxItems": 15
|
||||||
|
},
|
||||||
|
"artifacts": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"artifact_id": { "type": "string" },
|
||||||
|
"name": { "type": "string" },
|
||||||
|
"type": { "type": "string" },
|
||||||
|
"relevance": { "type": "string", "maxLength": 200 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"maxItems": 20
|
||||||
|
},
|
||||||
|
"memory": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"key": { "type": "string" },
|
||||||
|
"summary": { "type": "string", "maxLength": 300 },
|
||||||
|
"provenance": { "type": "string" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"maxItems": 10
|
||||||
|
},
|
||||||
|
"open_questions": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "type": "string" },
|
||||||
|
"maxItems": 10
|
||||||
|
},
|
||||||
|
"constraints": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"must_not_change": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "type": "string" }
|
||||||
|
},
|
||||||
|
"budget_remaining": { "type": "number" },
|
||||||
|
"deadline": { "type": "string", "format": "date-time" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ограничения размера
|
||||||
|
|
||||||
|
| Поле | Макс. размер |
|
||||||
|
|---|---|
|
||||||
|
| handoff целиком | 64 KB |
|
||||||
|
| objective.goal | 500 символов |
|
||||||
|
| progress.summary | 1000 символов |
|
||||||
|
| decisions | 15 записей |
|
||||||
|
| artifacts | 20 ссылок |
|
||||||
|
| memory keys | 10 ключей |
|
||||||
|
|
||||||
|
## Пример
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"handoff_id": "a1b2c3d4-...",
|
||||||
|
"version": "1.0",
|
||||||
|
"timestamp": "2026-07-20T12:00:00Z",
|
||||||
|
"from_agent": {
|
||||||
|
"agent_id": "worker-research",
|
||||||
|
"runtime": "hermes",
|
||||||
|
"model": "opencode-go/kimi-k2.7-code",
|
||||||
|
"run_id": "run-123"
|
||||||
|
},
|
||||||
|
"to_agent": {
|
||||||
|
"agent_id": "worker-code",
|
||||||
|
"expected_capabilities": ["terminal", "file", "python"]
|
||||||
|
},
|
||||||
|
"objective": {
|
||||||
|
"goal": "Реализовать EMA-кроссовер стратегию",
|
||||||
|
"acceptance_criteria": [
|
||||||
|
"Код проходит тесты",
|
||||||
|
"Бэктест на Brent > 0% return",
|
||||||
|
"Документация в docstring"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"bundle": {
|
||||||
|
"progress": {
|
||||||
|
"summary": "Проанализированы 3 стратегии EMA-кроссовера. Выбран вариант с period=(3,10).",
|
||||||
|
"completed_steps": ["Собраны данные Brent", "Протестированы параметры"],
|
||||||
|
"current_step": "Реализация на Python"
|
||||||
|
},
|
||||||
|
"decisions": [{
|
||||||
|
"decision": "EMA(3,10) вместо EMA(5,20)",
|
||||||
|
"rationale": "Лучше Sharpe на исторических данных",
|
||||||
|
"alternatives_considered": ["EMA(5,20)", "SMA(10,30)"]
|
||||||
|
}],
|
||||||
|
"artifacts": [{
|
||||||
|
"artifact_id": "art-001",
|
||||||
|
"name": "brent_2024.csv",
|
||||||
|
"type": "csv",
|
||||||
|
"relevance": "Рыночные данные для бэктеста"
|
||||||
|
}],
|
||||||
|
"memory": [{
|
||||||
|
"key": "brent_seasonality",
|
||||||
|
"summary": "Brent показывает сезонный рост Q2-Q3",
|
||||||
|
"provenance": "worker-research, run-123"
|
||||||
|
}],
|
||||||
|
"open_questions": [
|
||||||
|
"Учитывать ли спред при расчёте комиссии?"
|
||||||
|
],
|
||||||
|
"constraints": {
|
||||||
|
"must_not_change": ["сигнатура run_single_pass()"],
|
||||||
|
"budget_remaining": 50000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Валидация
|
||||||
|
|
||||||
|
- schema_version MUST быть "1.0"
|
||||||
|
- handoff_id MUST быть уникальным
|
||||||
|
- bundle НЕ должен содержать provider secrets или raw chain-of-thought
|
||||||
|
- Принимающий агент SHOULD подтвердить получение в течение 30s
|
||||||
124
docs/OPERATIONS.md
Normal file
124
docs/OPERATIONS.md
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
# Operations: Scheduler, Events, Errors, SLO
|
||||||
|
|
||||||
|
**Версия:** 0.1.0-draft | **Статус:** Draft
|
||||||
|
|
||||||
|
## 1. Scheduler
|
||||||
|
|
||||||
|
Приоритетная очередь запусков с поддержкой fair scheduling.
|
||||||
|
|
||||||
|
### Очереди
|
||||||
|
|
||||||
|
| Очередь | Приоритет | Описание |
|
||||||
|
|---|---|---|
|
||||||
|
| Priority Queue | 0 (высший) | Критические задачи, ручной запуск |
|
||||||
|
| Fair Queue | 1 | Обычные задачи, round-robin по workspace |
|
||||||
|
| Retry Queue | 2 | Задачи после transient failure |
|
||||||
|
| Delayed Queue | 3 | Отложенные задачи (cron, schedule) |
|
||||||
|
| Approval Queue | — | Задачи, ожидающие approval (блокируют слот агента) |
|
||||||
|
|
||||||
|
### Параметры планировщика
|
||||||
|
|
||||||
|
| Параметр | Значение |
|
||||||
|
|---|---|
|
||||||
|
| Макс. concurrent runs на connector | 5 |
|
||||||
|
| Макс. concurrent runs на agent | 1 |
|
||||||
|
| Preemption | Выключена в MVP |
|
||||||
|
| Fair share | Минимум 1 слот на workspace |
|
||||||
|
| Retry backoff | 1min, 5min, 15min, 1h, затем fail |
|
||||||
|
| Max queued per workspace | 100 |
|
||||||
|
|
||||||
|
## 2. Event Bus
|
||||||
|
|
||||||
|
Durable event bus с гарантией at-least-once доставки.
|
||||||
|
|
||||||
|
### События
|
||||||
|
|
||||||
|
| Событие | Payload | Частота |
|
||||||
|
|---|---|---|
|
||||||
|
| `run.created` | run_id, task_id, agent_id, goal | ~1/min |
|
||||||
|
| `run.started` | run_id, connector_id, timestamp | ~1/min |
|
||||||
|
| `run.completed` | run_id, duration, tokens, cost | ~1/min |
|
||||||
|
| `run.failed` | run_id, error_code, error_message | ~0.1/min |
|
||||||
|
| `run.cancelled` | run_id, cancelled_by | ~0.05/min |
|
||||||
|
| `approval.requested` | approval_id, run_id, action, risk | ~2/day |
|
||||||
|
| `approval.granted` | approval_id, approved_by | ~2/day |
|
||||||
|
| `approval.denied` | approval_id, denied_by, reason | ~0.5/day |
|
||||||
|
| `connector.online` | connector_id, version, agents | ~0.1/day |
|
||||||
|
| `connector.offline` | connector_id, last_heartbeat | ~0.1/day |
|
||||||
|
| `connector.degraded` | connector_id, missed_heartbeats | ~0.2/day |
|
||||||
|
| `artifact.created` | artifact_id, run_id, name, size | ~5/min |
|
||||||
|
| `memory.updated` | memory_id, key, scope, version | ~1/min |
|
||||||
|
| `skill.registered` | skill_id, name, version | ~0.1/day |
|
||||||
|
|
||||||
|
### Гарантии доставки
|
||||||
|
|
||||||
|
| Свойство | Значение |
|
||||||
|
|---|---|
|
||||||
|
| Доставка | At-least-once |
|
||||||
|
| Порядок | Per-run (внутри одного run — строгий порядок) |
|
||||||
|
| Дедупликация | По event_id (idempotency key) |
|
||||||
|
| Хранение | 30 дней |
|
||||||
|
| Версионирование | Все схемы эволюционируют additive-only |
|
||||||
|
|
||||||
|
## 3. Error Catalog
|
||||||
|
|
||||||
|
Единый каталог ошибок с кодами по подсистемам.
|
||||||
|
|
||||||
|
| Код | Подсистема | Описание | HTTP |
|
||||||
|
|---|---|---|---|
|
||||||
|
| AUTH-001 | Auth | Invalid credentials | 401 |
|
||||||
|
| AUTH-002 | Auth | Token expired | 401 |
|
||||||
|
| AUTH-003 | Auth | Insufficient permissions | 403 |
|
||||||
|
| CONN-001 | Connector | Registration failed | 400 |
|
||||||
|
| CONN-002 | Connector | Heartbeat timeout | 408 |
|
||||||
|
| CONN-003 | Connector | Version incompatible | 409 |
|
||||||
|
| RUN-001 | Run | Agent busy | 409 |
|
||||||
|
| RUN-002 | Run | Quota exceeded | 429 |
|
||||||
|
| RUN-003 | Run | Invalid goal format | 400 |
|
||||||
|
| RUN-004 | Run | Run not found | 404 |
|
||||||
|
| APPROVAL-001 | Approval | Self-approval denied | 403 |
|
||||||
|
| APPROVAL-002 | Approval | Approval expired | 410 |
|
||||||
|
| ARTIFACT-001 | Artifact | Upload failed | 500 |
|
||||||
|
| ARTIFACT-002 | Artifact | Size limit exceeded | 413 |
|
||||||
|
| MEM-001 | Memory | Key conflict | 409 |
|
||||||
|
| SKILL-001 | Skill | Signature invalid | 403 |
|
||||||
|
| WIKI-001 | Wiki | Page locked | 423 |
|
||||||
|
|
||||||
|
## 4. SLO / SLA
|
||||||
|
|
||||||
|
Целевые показатели качества обслуживания.
|
||||||
|
|
||||||
|
### SLO (Service Level Objectives)
|
||||||
|
|
||||||
|
| Метрика | Цель | Окно |
|
||||||
|
|---|---|---|
|
||||||
|
| API availability | 99.5% | monthly |
|
||||||
|
| p95 latency (REST read) | < 200ms | rolling 1h |
|
||||||
|
| p95 latency (REST write) | < 500ms | rolling 1h |
|
||||||
|
| Event delivery (p95) | < 2s | rolling 1h |
|
||||||
|
| Run dispatch (p99) | < 10s | rolling 1h |
|
||||||
|
| Search latency (p95) | < 500ms | rolling 1h |
|
||||||
|
| Artifact upload (p95) | < 30s per MB | rolling 1h |
|
||||||
|
|
||||||
|
### SLA (Service Level Agreements) — целевые, не контрактные в MVP
|
||||||
|
|
||||||
|
| Метрика | Цель |
|
||||||
|
|---|---|
|
||||||
|
| Time to detect connector offline | < 90s |
|
||||||
|
| Time to recover connector | < 5min (ручной) |
|
||||||
|
| Max data loss (events) | < 5min окно |
|
||||||
|
| RPO (артефакты) | < 1h |
|
||||||
|
| RTO (полное восстановление) | < 4h |
|
||||||
|
|
||||||
|
## 5. API Versioning
|
||||||
|
|
||||||
|
| Версия | Статус | Дата |
|
||||||
|
|---|---|---|
|
||||||
|
| /api/v1 | Draft | Q3 2026 |
|
||||||
|
| /api/v2 | Planned | Q1 2027 (breaking changes) |
|
||||||
|
|
||||||
|
**Правила:**
|
||||||
|
- MAJOR version в URL (/api/v1, /api/v2)
|
||||||
|
- MINOR changes — additive (новые поля, эндпоинты)
|
||||||
|
- Deprecation: минимум 3 месяца уведомления через Sunset header
|
||||||
|
- Старые версии: 6 месяцев поддержки после выхода новой MAJOR
|
||||||
110
docs/ROADMAP.md
Normal file
110
docs/ROADMAP.md
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
# Roadmap — Agent Control Center
|
||||||
|
|
||||||
|
**Версия:** 0.1.0-draft | **Статус:** Draft
|
||||||
|
|
||||||
|
## Фазы
|
||||||
|
|
||||||
|
### M0 — Foundations (4 недели)
|
||||||
|
**Цель:** спецификация утверждена, репозиторий готов к реализации.
|
||||||
|
|
||||||
|
| Задача | Статус |
|
||||||
|
|---|---|
|
||||||
|
| Закрыть блокирующие OQ-001..OQ-005 | ⏳ |
|
||||||
|
| Утвердить SPECIFICATION.md (G1 PASS) | ⏳ |
|
||||||
|
| Утвердить Domain Model | ⏳ |
|
||||||
|
| Утвердить State Machines | ⏳ |
|
||||||
|
| Настроить CI + линтеры | ⏳ |
|
||||||
|
| Развернуть dev-окружение | ⏳ |
|
||||||
|
|
||||||
|
### M1 — Core (8 недель)
|
||||||
|
**Цель:** минимальный Control Plane: workspace, project, task, agent registry.
|
||||||
|
|
||||||
|
| Компонент | Что входит |
|
||||||
|
|---|---|
|
||||||
|
| Identity & Policy | OIDC, local bootstrap admin |
|
||||||
|
| Project Service | CRUD projects, tasks |
|
||||||
|
| Connector Gateway | WSS, register, heartbeat |
|
||||||
|
| Agent Registry | Adapters: Hermes, OpenClaw |
|
||||||
|
| Run Orchestrator | create_run, stream_events, cancel |
|
||||||
|
| Approval Service | Basic approve/deny для destructive actions |
|
||||||
|
| Audit Service | Immutable audit log |
|
||||||
|
|
||||||
|
### M2 — Context (6 недель)
|
||||||
|
**Цель:** общий контекст между агентами.
|
||||||
|
|
||||||
|
| Компонент | Что входит |
|
||||||
|
|---|---|
|
||||||
|
| Context Broker | Bounded context bundle, redaction |
|
||||||
|
| Memory Service | Scoped memory, versions, search |
|
||||||
|
| Skill Registry | Skill upload, versioning, signing |
|
||||||
|
| Wiki Service | Markdown pages, links, search |
|
||||||
|
| Handoff | Полный handoff contract |
|
||||||
|
| Artifact Storage | Upload/download, checksums |
|
||||||
|
|
||||||
|
### M3 — Operations (6 недель)
|
||||||
|
**Цель:** observability, quotas, мониторинг.
|
||||||
|
|
||||||
|
| Компонент | Что входит |
|
||||||
|
|---|---|
|
||||||
|
| Usage Service | Quota tracking, budget alerts |
|
||||||
|
| Scheduler | Priority + Fair Queue |
|
||||||
|
| Event Bus | Durable delivery, replay |
|
||||||
|
| Monitoring | Health dashboard, SLO метрики |
|
||||||
|
| Backup/Restore | Git-based config backup |
|
||||||
|
| Error Catalog | Единые коды ошибок |
|
||||||
|
|
||||||
|
### M4 — Clients (8 недель)
|
||||||
|
**Цель:** три клиента с общим UI-ядром.
|
||||||
|
|
||||||
|
| Клиент | Платформа |
|
||||||
|
|---|---|
|
||||||
|
| Web / PWA | React + TypeScript |
|
||||||
|
| Desktop | Tauri (Windows, macOS, Linux) |
|
||||||
|
| Android | Capacitor wrapper |
|
||||||
|
|
||||||
|
**Функциональность клиентов:**
|
||||||
|
- Workspace/project/task management
|
||||||
|
- Agent dashboard + live run monitor
|
||||||
|
- Approval UI (push notifications)
|
||||||
|
- Memory/Wiki browser
|
||||||
|
- Skill browser + install
|
||||||
|
- Kanban board
|
||||||
|
|
||||||
|
### M5 — Enterprise (8 недель)
|
||||||
|
**Цель:** production-ready для нескольких tenants.
|
||||||
|
|
||||||
|
| Компонент | Что входит |
|
||||||
|
|---|---|
|
||||||
|
| Multi-tenancy | Организации, SSO/SAML |
|
||||||
|
| Connector sandbox | Container-based (Docker/podman) |
|
||||||
|
| Advanced approval | Dual-control, custom policies |
|
||||||
|
| Notifications | Email, FCM, webhook |
|
||||||
|
| SLA monitoring | SLO панель, инциденты |
|
||||||
|
| Capacity | 50 users, 20 connectors, 100 concurrent runs |
|
||||||
|
|
||||||
|
## Временная шкала
|
||||||
|
|
||||||
|
```
|
||||||
|
M0 ──── M1 ──────── M2 ────── M3 ────── M4 ──────── M5 ──────
|
||||||
|
│ │ │ │ │ │ │
|
||||||
|
0 4 12 18 24 32 40 недель
|
||||||
|
```
|
||||||
|
|
||||||
|
## Зависимости между фазами
|
||||||
|
|
||||||
|
```
|
||||||
|
M0 (Spec) ──► M1 (Core) ──► M2 (Context) ──► M3 (Ops)
|
||||||
|
│ │
|
||||||
|
└──────► M4 (Clients) ◄┘
|
||||||
|
│
|
||||||
|
└──► M5 (Enterprise)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Критерии готовности MVP (M1+M2)
|
||||||
|
|
||||||
|
- [ ] 1 workspace, 1 project, 3 агента (Hermes) — create/run/monitor
|
||||||
|
- [ ] Handoff между 2 агентами — контекст не потерян
|
||||||
|
- [ ] Approval для destructive action — работает через UI
|
||||||
|
- [ ] Memory/Wiki — запись и поиск
|
||||||
|
- [ ] Web-клиент — полный цикл управления
|
||||||
|
- [ ] 5 последовательных ручных прогонов без ошибок
|
||||||
95
docs/SECURITY_AND_PERFORMANCE.md
Normal file
95
docs/SECURITY_AND_PERFORMANCE.md
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
# Security & Performance
|
||||||
|
|
||||||
|
**Версия:** 0.1.0-draft | **Статус:** Draft
|
||||||
|
|
||||||
|
## 1. Security
|
||||||
|
|
||||||
|
### 1.1 Ротация секретов
|
||||||
|
|
||||||
|
| Секрет | Период ротации | Автоматически |
|
||||||
|
|---|---|---|
|
||||||
|
| Connector mTLS cert | 90 дней | ✅ (ACME/внутренний CA) |
|
||||||
|
| API signing keys | 30 дней | ✅ |
|
||||||
|
| OAuth refresh tokens | По истечению | ✅ (провайдер) |
|
||||||
|
| User sessions | 24 часа | ✅ (JWT expiry) |
|
||||||
|
| API keys (provider) | Вручную | ❌ |
|
||||||
|
|
||||||
|
### 1.2 Управление ключами
|
||||||
|
|
||||||
|
- **Иерархия:** Root CA → Intermediate CA → Connector cert
|
||||||
|
- **Хранение:** Vault/HSM для production; зашифрованный файл для dev
|
||||||
|
- **Доступ:** Только Connector Gateway (machine identity)
|
||||||
|
- **Отзыв:** CRL + OCSP, задержка распространения ≤ 5 минут
|
||||||
|
|
||||||
|
### 1.3 Шифрование
|
||||||
|
|
||||||
|
| Уровень | Метод |
|
||||||
|
|---|---|
|
||||||
|
| Transport | TLS 1.3 (min) |
|
||||||
|
| Data at rest | AES-256-GCM (Object Storage, DB) |
|
||||||
|
| Secrets | Vault transit engine |
|
||||||
|
| Backups | GPG (асимметричное, offline-ключ) |
|
||||||
|
|
||||||
|
### 1.4 Аудит
|
||||||
|
|
||||||
|
- Все события → append-only audit log
|
||||||
|
- Защита от удаления/модификации: WORM storage (30 дней min)
|
||||||
|
- Экспорт: CSV/JSON, фильтрация по actor, target, time range
|
||||||
|
- Критические события: real-time alert (connector offline, mass approval deny)
|
||||||
|
|
||||||
|
### 1.5 Отзыв коннекторов
|
||||||
|
|
||||||
|
- **Немедленный:** Revoke cert → disconnect active WSS → block re-registration
|
||||||
|
- **Плановый:** Deprecation notice (7 дней) → revoke → cleanup
|
||||||
|
- **Аварийный:** One-click kill switch (Operator/Admin role)
|
||||||
|
|
||||||
|
### 1.6 Резервное копирование
|
||||||
|
|
||||||
|
- Конфигурация: Git-репо (hermes-config-backup паттерн)
|
||||||
|
- Базы данных: ежедневно, retention 30 дней
|
||||||
|
- Проверка восстановления: ежемесячно (automated restore test)
|
||||||
|
|
||||||
|
## 2. Performance Targets
|
||||||
|
|
||||||
|
### 2.1 Нагрузка MVP
|
||||||
|
|
||||||
|
| Параметр | Значение |
|
||||||
|
|---|---|
|
||||||
|
| Simultaneous users | 5 (MVP), 50 (M5) |
|
||||||
|
| Active connectors | 3 (MVP), 20 (M5) |
|
||||||
|
| Concurrent runs | 10 (MVP), 100 (M5) |
|
||||||
|
| Events/sec | 5 (MVP), 50 (M5) |
|
||||||
|
| Total artifacts | 1 000 (MVP), 100 000 (M5) |
|
||||||
|
| Wiki pages | 100 (MVP), 10 000 (M5) |
|
||||||
|
| Memory entries | 1 000 (MVP), 100 000 (M5) |
|
||||||
|
|
||||||
|
### 2.2 Latency Budget
|
||||||
|
|
||||||
|
| Компонент | Бюджет (p95) |
|
||||||
|
|---|---|
|
||||||
|
| API Gateway | 50ms |
|
||||||
|
| Auth (JWT verify) | 10ms |
|
||||||
|
| DB query (read) | 20ms |
|
||||||
|
| DB query (write) | 50ms |
|
||||||
|
| Object Storage | 100ms |
|
||||||
|
| Event Bus publish | 30ms |
|
||||||
|
| **Total REST read** | **≤ 200ms** |
|
||||||
|
| **Total REST write** | **≤ 500ms** |
|
||||||
|
|
||||||
|
### 2.3 Throughput
|
||||||
|
|
||||||
|
| Операция | Цель |
|
||||||
|
|---|---|
|
||||||
|
| REST API req/sec | 100 (однопоточный) |
|
||||||
|
| WSS connections | 50 одновременных |
|
||||||
|
| Event delivery | 100 events/sec |
|
||||||
|
| Search queries | 20 QPS |
|
||||||
|
|
||||||
|
### 2.4 Capacity Planning
|
||||||
|
|
||||||
|
| Ресурс | Min (MVP) | Recommended |
|
||||||
|
|---|---|---|
|
||||||
|
| CPU | 2 cores | 4 cores |
|
||||||
|
| RAM | 4 GB | 8 GB |
|
||||||
|
| Disk | 20 GB SSD | 100 GB SSD |
|
||||||
|
| Network | 100 Mbps | 1 Gbps |
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
| Статус | **Draft — implementation blocked** |
|
| Статус | **Draft — implementation blocked** |
|
||||||
| Владелец решения | Product owner / operator |
|
| Владелец решения | Product owner / operator |
|
||||||
| Нормативный документ | Этот файл |
|
| Нормативный документ | Этот файл |
|
||||||
| Поддерживающие документы | `ARCHITECTURE.md`, `RESEARCH.md`, `OPEN-QUESTIONS.md` |
|
| Поддерживающие документы | `ARCHITECTURE.md`, `RESEARCH.md`, `OPEN-QUESTIONS.md`, `adapters/HERMES.md`, `patterns/WORKER_ORCHESTRATION.md`, `patterns/CREDENTIAL_POOLS.md`, `operations/MONITORING.md`, `operations/BACKUP.md` |
|
||||||
|
|
||||||
> Разработка продукта не начинается до закрытия блокирующих решений, G1 PASS и явного утверждения оператором. Формулировки MUST/SHALL обязательны; SHOULD требуют обоснования отклонения.
|
> Разработка продукта не начинается до закрытия блокирующих решений, G1 PASS и явного утверждения оператором. Формулировки MUST/SHALL обязательны; SHOULD требуют обоснования отклонения.
|
||||||
|
|
||||||
|
|
|
||||||
118
docs/STATE_MACHINES.md
Normal file
118
docs/STATE_MACHINES.md
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
# State Machines — Agent Control Center
|
||||||
|
|
||||||
|
**Версия:** 0.1.0-draft | **Статус:** Draft
|
||||||
|
|
||||||
|
## Run State Machine
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────┐
|
||||||
|
│ QUEUED │
|
||||||
|
└────┬────┘
|
||||||
|
│ dispatch
|
||||||
|
┌────▼────┐
|
||||||
|
│ PENDING │
|
||||||
|
└────┬────┘
|
||||||
|
│ connector accepts
|
||||||
|
┌────▼────┐
|
||||||
|
│ RUNNING │◄──────────────┐
|
||||||
|
└────┬────┘ │
|
||||||
|
┌──────────────┼──────────────┐ │
|
||||||
|
▼ ▼ ▼ │
|
||||||
|
┌──────────┐ ┌───────────┐ ┌──────────┐ │
|
||||||
|
│COMPLETED │ │ FAILED │ │CANCELLED │ │
|
||||||
|
└──────────┘ └───────────┘ └──────────┘ │
|
||||||
|
│
|
||||||
|
┌──────────┐ │
|
||||||
|
│ PAUSED │───────────────────────────────┘
|
||||||
|
└──────────┘ resume
|
||||||
|
```
|
||||||
|
|
||||||
|
**Переходы:**
|
||||||
|
| Из | В | Условие |
|
||||||
|
|---|---|---|
|
||||||
|
| QUEUED | PENDING | Scheduler dispatch |
|
||||||
|
| PENDING | RUNNING | Connector accept |
|
||||||
|
| PENDING | FAILED | Timeout / connector offline |
|
||||||
|
| RUNNING | COMPLETED | Agent finished successfully |
|
||||||
|
| RUNNING | FAILED | Error / timeout |
|
||||||
|
| RUNNING | CANCELLED | User cancel / policy |
|
||||||
|
| RUNNING | PAUSED | User pause / preemption |
|
||||||
|
| PAUSED | RUNNING | User resume |
|
||||||
|
| PAUSED | CANCELLED | User cancel while paused |
|
||||||
|
|
||||||
|
**Инварианты:**
|
||||||
|
- COMPLETED/FAILED/CANCELLED — терминальные, не могут переходить
|
||||||
|
- Только один Run активен на Agent одновременно (MUST)
|
||||||
|
- CANCELLED → terminal; cleanup resources within 30s (SHOULD)
|
||||||
|
|
||||||
|
## Task State Machine
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────┐ ┌──────────┐ ┌───────────┐
|
||||||
|
│ BACKLOG │────►│ TODO │────►│ IN_PROGRESS│
|
||||||
|
└─────────┘ └──────────┘ └─────┬─────┘
|
||||||
|
▲ │
|
||||||
|
│ ┌────────────┼────────────┐
|
||||||
|
│ ▼ ▼ ▼
|
||||||
|
│ ┌─────────┐ ┌─────────┐ ┌──────┐
|
||||||
|
└──────────────│ DONE │ │ BLOCKED │ │ REVIEW│
|
||||||
|
└─────────┘ └─────────┘ └──┬───┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────┐
|
||||||
|
│ DONE │
|
||||||
|
└─────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Project State Machine
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────┐ ┌────────┐ ┌──────────┐
|
||||||
|
│ DRAFT │────►│ ACTIVE │────►│ ARCHIVED │
|
||||||
|
└─────────┘ └────────┘ └──────────┘
|
||||||
|
│ ▲
|
||||||
|
└───────────────┘
|
||||||
|
reactivate
|
||||||
|
```
|
||||||
|
|
||||||
|
## Connector State Machine
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────┐ ┌────────┐ ┌───────────┐
|
||||||
|
│REGISTERING│───►│ ONLINE │────►│ DEGRADED │
|
||||||
|
└──────────┘ └───┬────┘ └─────┬─────┘
|
||||||
|
│ │
|
||||||
|
▼ ▼
|
||||||
|
┌─────────┐ ┌──────────┐
|
||||||
|
│ OFFLINE │◄───│ OFFLINE │
|
||||||
|
└─────────┘ └──────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**Переходы:**
|
||||||
|
| Событие | Переход |
|
||||||
|
|---|---|
|
||||||
|
| Heartbeat OK (каждые 30s) | → ONLINE |
|
||||||
|
| Heartbeat missed × 3 | ONLINE → DEGRADED |
|
||||||
|
| Heartbeat missed × 10 | DEGRADED → OFFLINE |
|
||||||
|
| Reconnect | OFFLINE → ONLINE |
|
||||||
|
|
||||||
|
## Approval State Machine
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────┐ ┌───────────┐
|
||||||
|
│ PENDING │────►│ APPROVED │
|
||||||
|
└────┬────┘ └───────────┘
|
||||||
|
│
|
||||||
|
├──────────►┌──────────┐
|
||||||
|
│ │ DENIED │
|
||||||
|
│ └──────────┘
|
||||||
|
│
|
||||||
|
└──────────►┌──────────┐
|
||||||
|
│ EXPIRED │ (timeout 5 min)
|
||||||
|
└──────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**Правила:**
|
||||||
|
- PENDING → APPROVED/DENIED: только authorized approver (не self-approval для high/critical)
|
||||||
|
- PENDING → EXPIRED: автоматически через 5 минут
|
||||||
|
- EXPIRE → новый Approval (MUST)
|
||||||
349
docs/adapters/HERMES.md
Normal file
349
docs/adapters/HERMES.md
Normal file
|
|
@ -0,0 +1,349 @@
|
||||||
|
# Hermes Agent Adapter Specification
|
||||||
|
|
||||||
|
**Версия:** 0.1.0-draft
|
||||||
|
**Дата:** 2026-07-20
|
||||||
|
**Статус:** Draft — implementation blocked
|
||||||
|
**Зависит от:** `SPECIFICATION.md` (§6.2 Connectors and agents), `ARCHITECTURE.md` (§4 Connector), `RESEARCH.md`
|
||||||
|
|
||||||
|
> Этот документ детализирует адаптер Hermes Agent в рамках Agent Adapter Protocol (AAP) согласно `SPECIFICATION.md`. Реализация запрещена до закрытия G0/G1 и явного утверждения оператором. Формулировки MUST/SHALL обязательны; SHOULD требуют обоснования отклонения.
|
||||||
|
|
||||||
|
## 1. Обзор
|
||||||
|
|
||||||
|
Hermes Agent — open-source фреймворк ИИ-агентов от Nous Research, работающий в терминале, desktop-приложении, мессенджерах и IDE. Адаптер ACC подключается через HTTP API Hermes (`/v1/chat/completions`, `/v1/responses`, `/v1/runs`) и использует систему профилей для изоляции рабочих нагрузок.
|
||||||
|
|
||||||
|
Hermes обеспечивает: provider-agnostic model routing, credential pools, профили, навыки (skills), память, cron, gateway (Telegram/20+ платформ), делегирование, сессионный поиск, tools (terminal, browser, file, code execution), плагины, MCP-серверы и webhook'и.
|
||||||
|
|
||||||
|
## 2. Профили
|
||||||
|
|
||||||
|
Hermes использует профили для изоляции независимых экземпляров агента. Каждый профиль имеет собственный `config.yaml`, `.env`, `skills/`, `plugins/`, `cron/` и `memories/`.
|
||||||
|
|
||||||
|
### 2.1 Базовый профиль
|
||||||
|
|
||||||
|
| Профиль | Назначение | Модель по умолчанию | Провайдер |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `default` | Управляющий оркестратор | `deepseek-v4-pro` | `nvidia` |
|
||||||
|
|
||||||
|
Профиль `default` является точкой входа: принимает пользовательские запросы, маршрутизирует подзадачи worker-профилям через `delegate_task`, агрегирует результаты.
|
||||||
|
|
||||||
|
### 2.2 Worker-профили
|
||||||
|
|
||||||
|
| Профиль | Назначение | Модель | Провайдер | Типовые задачи |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `worker-code` | Разработка и генерация кода | `kimi-k2.7-code` | `opencode-go` | Написание, рефакторинг, отладка кода; PR; code review |
|
||||||
|
| `worker-fast` | Быстрые задачи, не требующие глубокого анализа | `grok-4.5` | `fireworks` | Простые скрипты, форматирование, быстрый поиск |
|
||||||
|
| `worker-research` | Исследования и анализ | `gemini-3-flash-preview` | `gemini` | Анализ документации, исследование API, literature review |
|
||||||
|
| `worker-review` | Рецензирование и проверка качества | `deepseek-v4-pro` | `nvidia` | Code review, security audit, проверка спецификаций |
|
||||||
|
|
||||||
|
Каждый worker-профиль SHOULD иметь ограниченный набор инструментов, соответствующий его роли.
|
||||||
|
|
||||||
|
## 3. Модели и провайдеры
|
||||||
|
|
||||||
|
### 3.1 Модели
|
||||||
|
|
||||||
|
| Модель | Провайдер | Контекст (прибл.) | Назначение | Статус |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `kimi-k2.7-code` | `opencode-go` | 128K | Генерация кода, сложный рефакторинг | Активен |
|
||||||
|
| `grok-4.5` | `fireworks` | 128K | Быстрые ответы, простые задачи | Активен |
|
||||||
|
| `gemini-3-flash-preview` | `gemini` | 1M | Исследования, анализ больших документов | Активен |
|
||||||
|
| `deepseek-v4-pro` | `nvidia` | 128K | Оркестрация, review, сложное рассуждение | Активен |
|
||||||
|
|
||||||
|
Модели резервируются: при недоступности одной моделью SHOULD срабатывать fallback-цепочка (см. `WORKER_ORCHESTRATION.md`).
|
||||||
|
|
||||||
|
### 3.2 Провайдеры
|
||||||
|
|
||||||
|
| Провайдер | Тип | Аутентификация | Env-переменная |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `opencode-go` | OpenCode Go API | API key | `OPENCODE_GO_API_KEY` |
|
||||||
|
| `fireworks` | OpenAI-совместимый кастомный | API key | `FIREWORKS_API_KEY` |
|
||||||
|
| `gemini` | Google Gemini API | API key | `GOOGLE_API_KEY` или `GEMINI_API_KEY` |
|
||||||
|
| `nvidia` | NVIDIA AI API | API key | `NVIDIA_API_KEY` |
|
||||||
|
| `ollama` | Локальный | Без аутентификации | — |
|
||||||
|
|
||||||
|
**Custom OpenAI-совместимый провайдер (`fireworks`):** настраивается через `model.base_url` и `model.api_key` в `config.yaml`. Должен реализовывать `/v1/chat/completions` с поддержкой tool calling. Конфигурация:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
model:
|
||||||
|
provider: fireworks
|
||||||
|
base_url: "https://api.fireworks.ai/inference/v1"
|
||||||
|
api_key: "${FIREWORKS_API_KEY}"
|
||||||
|
model: grok-4.5
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Инструменты
|
||||||
|
|
||||||
|
Hermes предоставляет следующие инструментальные наборы (toolsets), релевантные для адаптера:
|
||||||
|
|
||||||
|
| Инструмент | Назначение | Доступность в профилях |
|
||||||
|
|---|---|---|
|
||||||
|
| `terminal` | Выполнение shell-команд и управление процессами | Все профили |
|
||||||
|
| `browser` | Браузерная автоматизация (Chromium) | `default`, `worker-research` |
|
||||||
|
| `file` | Чтение/запись/поиск/редактирование файлов | Все профили |
|
||||||
|
| `delegation` | Делегирование подзадач sub-agent'ам | `default` (оркестратор) |
|
||||||
|
| `cronjob` | Управление запланированными задачами | `default` |
|
||||||
|
| `memory` | Персистентная память между сессиями | `default` |
|
||||||
|
| `session_search` | Поиск по истории сессий | `default` |
|
||||||
|
| `skills` | Просмотр и установка навыков | `default` |
|
||||||
|
| `gateway` | Управление gateway-сообщениями | `default` |
|
||||||
|
| `web` | Веб-поиск и извлечение контента | `default`, `worker-research` |
|
||||||
|
| `code_execution` | Песочница для выполнения Python | `worker-code` |
|
||||||
|
|
||||||
|
Worker-профили SHOULD иметь минимально необходимый набор инструментов для выполнения своей роли.
|
||||||
|
|
||||||
|
### 4.1 Делегирование (`delegate_task`)
|
||||||
|
|
||||||
|
`delegate_task` порождает sub-agent с изолированным контекстом и терминальной сессией.
|
||||||
|
|
||||||
|
**Параметры:**
|
||||||
|
- `goal` (обязательный) — цель подзадачи
|
||||||
|
- `context` — контекстная информация
|
||||||
|
- `role` — `leaf` (по умолчанию; не может ре-делегировать) или `orchestrator` (может порождать своих worker'ов)
|
||||||
|
- `background` — если `true`, возвращает handle немедленно
|
||||||
|
- `tasks` — массив задач для параллельного выполнения (fan-out)
|
||||||
|
|
||||||
|
**Ограничения:**
|
||||||
|
- `max_spawn_depth` (в `delegation` секции `config.yaml`) ограничивает глубину вложенности оркестраторов
|
||||||
|
- `max_concurrent_children` (по умолчанию 3) — максимум параллельных детей
|
||||||
|
- Максимальное количество итераций: `delegation.max_iterations` (по умолчанию 50)
|
||||||
|
|
||||||
|
**Важно:** делегирование не durable — дочерний процесс живёт в рамках родительского процесса. Для задач, которые должны пережить перезапуск, использовать `cronjob`.
|
||||||
|
|
||||||
|
## 5. Gateway
|
||||||
|
|
||||||
|
Hermes Gateway обеспечивает подключение к 20+ мессенджинг-платформам.
|
||||||
|
|
||||||
|
### 5.1 Telegram
|
||||||
|
|
||||||
|
| Параметр | Значение |
|
||||||
|
|---|---|
|
||||||
|
| Режим | Polling (long polling) |
|
||||||
|
| Fallback IPs | Поддерживаются альтернативные IP-адреса для обхода блокировок |
|
||||||
|
| Формат сообщений | MarkdownV2 / HTML |
|
||||||
|
| Доставка уведомлений | Через gateway adapter |
|
||||||
|
| Команды | `/approve`, `/deny`, `/restart`, `/sethome`, `/status`, `/agents` |
|
||||||
|
|
||||||
|
Настройка Telegram polling:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
gateway:
|
||||||
|
platforms:
|
||||||
|
telegram:
|
||||||
|
token: "${TELEGRAM_BOT_TOKEN}"
|
||||||
|
polling: true
|
||||||
|
fallback_ips:
|
||||||
|
- "149.154.167.220"
|
||||||
|
- "149.154.167.50"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 Поддерживаемые платформы
|
||||||
|
|
||||||
|
Discord, Slack, WhatsApp (Baileys + Business Cloud API), iMessage (Photon), Signal, Email, SMS, Matrix, Mattermost, Microsoft Teams, LINE, SimpleX, ntfy, Google Chat, Home Assistant, DingTalk, Feishu, WeCom, Weixin (WeChat), Raft, API Server, Webhooks.
|
||||||
|
|
||||||
|
Каждая платформа конфигурируется в секции `gateway.platforms.<name>`.
|
||||||
|
|
||||||
|
## 6. Cron
|
||||||
|
|
||||||
|
Планировщик Hermes поддерживает durable scheduled jobs.
|
||||||
|
|
||||||
|
### 6.1 Синтаксис расписаний
|
||||||
|
|
||||||
|
| Формат | Пример | Описание |
|
||||||
|
|---|---|---|
|
||||||
|
| Длительность | `"30m"`, `"2h"`, `"90s"` | Интервал между запусками |
|
||||||
|
| "Every"-фраза | `"every monday 9am"`, `"every 6 hours"` | Человеко-читаемое расписание |
|
||||||
|
| 5-полевой cron | `"0 9 * * *"` | Стандартный cron-синтаксис |
|
||||||
|
| ISO timestamp | `"2026-07-20T15:00:00Z"` | Однократный запуск |
|
||||||
|
|
||||||
|
### 6.2 Параметры задания
|
||||||
|
|
||||||
|
| Параметр | Описание |
|
||||||
|
|---|---|
|
||||||
|
| `skills` | Список навыков для загрузки |
|
||||||
|
| `model`/`provider` | Переопределение модели/провайдера |
|
||||||
|
| `script` | Pre-run скрипт сбора данных; `no_agent=True` делает скрипт единственным действием |
|
||||||
|
| `context_from` | Цепочка: вывод задания A → контекст задания B |
|
||||||
|
| `workdir` | Рабочая директория (загружает `AGENTS.md`/`CLAUDE.md` из неё при наличии) |
|
||||||
|
| Доставка | Multi-platform: Telegram, Email, Webhooks |
|
||||||
|
|
||||||
|
### 6.3 Инварианты
|
||||||
|
|
||||||
|
- 3-минутный hard interrupt на каждый run
|
||||||
|
- `.tick.lock` предотвращает дублирование тиков между процессами
|
||||||
|
- Cron-сессии передают `skip_memory=True` по умолчанию
|
||||||
|
- Доставка обрамляется header/footer, не зеркалируется в gateway-сессию
|
||||||
|
|
||||||
|
### 6.4 Watch patterns
|
||||||
|
|
||||||
|
При запуске через `terminal(background=True, watch_patterns=[...])` Hermes отслеживает вывод фонового процесса на совпадение с шаблонами. Rate limit: не более 1 уведомления в 15 секунд; после 3 последовательных окон с dropped matches отключается.
|
||||||
|
|
||||||
|
## 7. Skills (навыки)
|
||||||
|
|
||||||
|
Skills — переиспользуемые процедуры, сохраняемые агентом.
|
||||||
|
|
||||||
|
### 7.1 Жизненный цикл
|
||||||
|
|
||||||
|
```
|
||||||
|
create → use → idle → stale → archive
|
||||||
|
↑ ↓
|
||||||
|
└── restore ←──────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
- Только навыки с `created_by: "agent"` подлежат автоматическому curator'у
|
||||||
|
- Bundled + hub-установленные навыки неприкосновенны
|
||||||
|
- Максимальное деструктивное действие — archive (никогда не delete)
|
||||||
|
- Pinned навыки исключены из всех авто-переходов
|
||||||
|
|
||||||
|
### 7.2 Curator
|
||||||
|
|
||||||
|
Фоновый процесс обслуживания навыков:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
hermes curator status # статус
|
||||||
|
hermes curator run # ручной запуск
|
||||||
|
hermes curator pin NAME # защита от авто-архивации
|
||||||
|
hermes curator archive NAME # ручная архивация
|
||||||
|
hermes curator restore NAME # восстановление
|
||||||
|
hermes curator backup # создание снапшота
|
||||||
|
```
|
||||||
|
|
||||||
|
Consolidation (объединение пересекающихся навыков) **выключен по умолчанию** (`curator.consolidate: false`), требует aux-модель и явного включения.
|
||||||
|
|
||||||
|
### 7.3 Категории навыков
|
||||||
|
|
||||||
|
- **Встроенные** — поставляются с Hermes (code review, planning, TDD, debugging, и др.)
|
||||||
|
- **Пользовательские** — создаются агентом в процессе работы
|
||||||
|
- **Hub** — устанавливаются из реестра `hermes skills install`
|
||||||
|
|
||||||
|
## 8. Память
|
||||||
|
|
||||||
|
Hermes ведёт два файла памяти:
|
||||||
|
|
||||||
|
| Файл | Назначение |
|
||||||
|
|---|---|
|
||||||
|
| `MEMORY.md` | Факты, решения, уроки, выученные в процессе работы |
|
||||||
|
| `USER.md` | Предпочтения пользователя, окружение, постоянные параметры |
|
||||||
|
|
||||||
|
Память опционально может использовать внешние провайдеры (Honcho, Mem0) через `hermes memory setup`.
|
||||||
|
|
||||||
|
## 9. Конфигурация
|
||||||
|
|
||||||
|
### 9.1 config.yaml
|
||||||
|
|
||||||
|
Основной конфигурационный файл (`~/.hermes/config.yaml` или `$HERMES_HOME/config.yaml`). Ключевые секции для адаптера:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
model:
|
||||||
|
default: deepseek-v4-pro
|
||||||
|
provider: nvidia
|
||||||
|
base_url: "https://integrate.api.nvidia.com/v1"
|
||||||
|
|
||||||
|
agent:
|
||||||
|
max_turns: 90
|
||||||
|
tool_use_enforcement: true
|
||||||
|
|
||||||
|
terminal:
|
||||||
|
backend: local
|
||||||
|
timeout: 180
|
||||||
|
|
||||||
|
delegation:
|
||||||
|
model: kimi-k2.7-code
|
||||||
|
provider: opencode-go
|
||||||
|
max_iterations: 50
|
||||||
|
max_spawn_depth: 2
|
||||||
|
max_concurrent_children: 3
|
||||||
|
|
||||||
|
gateway:
|
||||||
|
platforms:
|
||||||
|
telegram:
|
||||||
|
token: "${TELEGRAM_BOT_TOKEN}"
|
||||||
|
polling: true
|
||||||
|
|
||||||
|
curator:
|
||||||
|
enabled: true
|
||||||
|
consolidate: false
|
||||||
|
interval_hours: 24
|
||||||
|
stale_after_days: 30
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.2 .env
|
||||||
|
|
||||||
|
Секреты и API-ключи:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
OPENCODE_GO_API_KEY=sk-...
|
||||||
|
FIREWORKS_API_KEY=fw-...
|
||||||
|
GOOGLE_API_KEY=AIza...
|
||||||
|
NVIDIA_API_KEY=nvapi-...
|
||||||
|
TELEGRAM_BOT_TOKEN=123456:ABC-DEF...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.3 Профили
|
||||||
|
|
||||||
|
Каждый профиль — изолированная директория `~/.hermes/profiles/<name>/` с собственными `config.yaml`, `.env`, `skills/`, `plugins/`, `cron/`, `memories/`.
|
||||||
|
|
||||||
|
Создание профиля:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
hermes profile create worker-code
|
||||||
|
hermes profile create worker-fast
|
||||||
|
hermes profile create worker-research
|
||||||
|
hermes profile create worker-review
|
||||||
|
```
|
||||||
|
|
||||||
|
## 10. Возможности адаптера (capabilities)
|
||||||
|
|
||||||
|
При handshake с ACC адаптер Hermes публикует следующий набор capabilities:
|
||||||
|
|
||||||
|
| Capability | Значение | Примечание |
|
||||||
|
|---|---|---|
|
||||||
|
| `stream` | `true` | HTTP SSE / JSON Lines |
|
||||||
|
| `cancel` | `true` | Через `cancel` в run API |
|
||||||
|
| `checkpoint` | `true` | Observable checkpoint schema |
|
||||||
|
| `approvals` | `true` | Built-in approval flow |
|
||||||
|
| `tools` | `["terminal", "browser", "file", "web", "code_execution", ...]` | Зависит от профиля |
|
||||||
|
| `mcp` | `true` | MCP client support |
|
||||||
|
| `artifacts` | `true` | Файловая система + object refs |
|
||||||
|
| `usage_exact` | `false` | Hermes не предоставляет authoritative usage API |
|
||||||
|
| `usage_estimated` | `true` | Token usage из LLM-ответов |
|
||||||
|
| `filesystem_scope` | `["cwd", "home"]` | Ограничивается рабочей директорией |
|
||||||
|
| `structured_output` | `true` | JSON mode в моделях |
|
||||||
|
| `profiles` | `["default", "worker-code", "worker-fast", "worker-research", "worker-review"]` | Доступные профили |
|
||||||
|
| `resume` | `true` | Session resume через session ID |
|
||||||
|
|
||||||
|
## 11. Соответствие AAP
|
||||||
|
|
||||||
|
Адаптер Hermes реализует Agent Adapter Protocol (`ARCHITECTURE.md` §4.2):
|
||||||
|
|
||||||
|
| AAP-метод | Реализация в Hermes |
|
||||||
|
|---|---|
|
||||||
|
| `handshake()` | `GET /v1/status` → identity, version, capabilities |
|
||||||
|
| `health()` | `GET /v1/health` → status, latency, active_runs |
|
||||||
|
| `start(run_spec, context)` | `POST /v1/runs` → native_run_ref |
|
||||||
|
| `stream(run_ref, cursor)` | `GET /v1/runs/{id}/events?cursor=` → SSE |
|
||||||
|
| `checkpoint(run_ref, reason)` | `POST /v1/runs/{id}/checkpoint` → checkpoint bundle |
|
||||||
|
| `cancel(run_ref, mode)` | `POST /v1/runs/{id}/cancel` → outcome |
|
||||||
|
| `resume(run_ref)` | `POST /v1/runs/{id}/resume` → outcome |
|
||||||
|
| `usage(scope)` | `GET /v1/usage` → measured/estimated/unknown signal |
|
||||||
|
| `artifacts(run_ref)` | `GET /v1/runs/{id}/artifacts` → metadata + refs |
|
||||||
|
|
||||||
|
## 12. Ограничения и риски
|
||||||
|
|
||||||
|
| Ограничение | Влияние | Мера |
|
||||||
|
|---|---|---|
|
||||||
|
| Hermes не предоставляет authoritative usage API | Бюджетирование только estimated | Явная маркировка confidence; ручной budget policy |
|
||||||
|
| Делегирование не durable | Потеря задачи при падении родителя | Критичные задачи → cronjob |
|
||||||
|
| Worker-профили требуют отдельных процессов | Накладные расходы на запуск | Keep-warm пул worker'ов в production |
|
||||||
|
| Adapter версионирование отстаёт от Hermes | Incompatible handshake | Pinned version + contract tests + N-1 policy |
|
||||||
|
| Gateway polling подвержен сетевым сбоям | Задержка доставки | Fallback IPs, метрики polling latency |
|
||||||
|
|
||||||
|
## 13. Ссылки
|
||||||
|
|
||||||
|
- [Hermes Agent Documentation](https://hermes-agent.nousresearch.com/docs/)
|
||||||
|
- [Hermes API Server](https://hermes-agent.nousresearch.com/docs/user-guide/features/api-server)
|
||||||
|
- [Hermes Profiles](https://hermes-agent.nousresearch.com/docs/user-guide/profiles)
|
||||||
|
- [SPECIFICATION.md](../SPECIFICATION.md) — каноническое ТЗ ACC
|
||||||
|
- [ARCHITECTURE.md](../ARCHITECTURE.md) — архитектура ACC и AAP
|
||||||
|
- [RESEARCH.md](../RESEARCH.md) — исследование интеграций
|
||||||
|
- [WORKER_ORCHESTRATION.md](../patterns/WORKER_ORCHESTRATION.md) — паттерны оркестрации worker-профилей
|
||||||
|
- [CREDENTIAL_POOLS.md](../patterns/CREDENTIAL_POOLS.md) — паттерны credential pools
|
||||||
|
- [MONITORING.md](../operations/MONITORING.md) — мониторинг
|
||||||
|
- [BACKUP.md](../operations/BACKUP.md) — бэкап конфигурации
|
||||||
704
docs/operations/BACKUP.md
Normal file
704
docs/operations/BACKUP.md
Normal file
|
|
@ -0,0 +1,704 @@
|
||||||
|
# Backup & Recovery
|
||||||
|
|
||||||
|
**Версия:** 0.1.0-draft
|
||||||
|
**Дата:** 2026-07-20
|
||||||
|
**Статус:** Draft
|
||||||
|
**Зависит от:** `SPECIFICATION.md` (§10 Data lifecycle, §7.2 Reliability), `docs/adapters/HERMES.md`
|
||||||
|
|
||||||
|
> Этот документ описывает процедуры бэкапа и восстановления конфигурации Hermes Agent в составе Agent Control Center. Реализация запрещена до G0/G1 и утверждения оператором.
|
||||||
|
|
||||||
|
## 1. Обзор
|
||||||
|
|
||||||
|
Система бэкапа ACC охватывает:
|
||||||
|
- Конфигурацию Hermes Agent (config.yaml, .env, профили)
|
||||||
|
- Пользовательские навыки (skills)
|
||||||
|
- Память (MEMORY.md, USER.md)
|
||||||
|
- Сессионные данные (опционально)
|
||||||
|
|
||||||
|
Бэкап НЕ включает:
|
||||||
|
- Секреты (API-ключи) — управляются отдельно через secret manager
|
||||||
|
- Бинарные артефакты и кэши (audio_cache, сессионные файлы)
|
||||||
|
- Логи (сохраняются согласно retention policy)
|
||||||
|
|
||||||
|
## 2. Структура конфигурации Hermes
|
||||||
|
|
||||||
|
### 2.1 Основные компоненты
|
||||||
|
|
||||||
|
```
|
||||||
|
~/.hermes/
|
||||||
|
├── config.yaml # Основная конфигурация
|
||||||
|
├── .env # Переменные окружения (API-ключи, секреты)
|
||||||
|
├── auth.json # OAuth-токены и credential pools
|
||||||
|
├── state.db # SQLite база сессий (state store)
|
||||||
|
├── profiles/ # Профили
|
||||||
|
│ ├── default/
|
||||||
|
│ │ ├── config.yaml
|
||||||
|
│ │ ├── .env
|
||||||
|
│ │ ├── skills/
|
||||||
|
│ │ └── memories/
|
||||||
|
│ ├── worker-code/
|
||||||
|
│ │ └── ...
|
||||||
|
│ ├── worker-fast/
|
||||||
|
│ │ └── ...
|
||||||
|
│ ├── worker-research/
|
||||||
|
│ │ └── ...
|
||||||
|
│ └── worker-review/
|
||||||
|
│ └── ...
|
||||||
|
├── skills/ # Пользовательские навыки
|
||||||
|
│ ├── *.md # SKILL.md файлы
|
||||||
|
│ └── .usage.json # Метаданные curator'а
|
||||||
|
├── sessions/ # Сессионные файлы (опционально)
|
||||||
|
├── logs/ # Логи (НЕ бэкапируются)
|
||||||
|
├── audio_cache/ # Аудиокэш (НЕ бэкапируется)
|
||||||
|
└── backups/ # Локальные снапшоты
|
||||||
|
├── latest/ # Последний снапшот
|
||||||
|
│ ├── config.yaml
|
||||||
|
│ ├── config.yaml.sig
|
||||||
|
│ └── manifest.json
|
||||||
|
└── archive/ # Архив снапшотов
|
||||||
|
└── 2026-07-20T14-00-00Z.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 Приоритеты бэкапа
|
||||||
|
|
||||||
|
| Компонент | Приоритет | RPO | Без секретов |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `config.yaml` (все профили) | CRITICAL | 1 час | Да |
|
||||||
|
| `skills/` | HIGH | 6 часов | Да |
|
||||||
|
| `memories/` (MEMORY.md, USER.md) | HIGH | 6 часов | Да |
|
||||||
|
| `profiles/*/config.yaml` | HIGH | 1 час | Да |
|
||||||
|
| `cron/` конфигурации | MEDIUM | 24 часа | Да |
|
||||||
|
| `sessions/` | LOW | 24 часа | Нет (секреты в сессиях) |
|
||||||
|
| `state.db` | LOW | 24 часа | Нет |
|
||||||
|
|
||||||
|
## 3. Процедура снапшота
|
||||||
|
|
||||||
|
### 3.1 Скрипт: `scripts/update_backup.py`
|
||||||
|
|
||||||
|
```python
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
update_backup.py — создание снапшота конфигурации Hermes Agent.
|
||||||
|
|
||||||
|
Использование:
|
||||||
|
python scripts/update_backup.py [--output DIR] [--exclude-secrets] [--dry-run]
|
||||||
|
|
||||||
|
Снапшот включает:
|
||||||
|
- config.yaml всех профилей
|
||||||
|
- .env (с redacted секретами при --exclude-secrets)
|
||||||
|
- skills/ (только SKILL.md файлы)
|
||||||
|
- memories/ (MEMORY.md, USER.md)
|
||||||
|
- cron/ конфигурации
|
||||||
|
|
||||||
|
Исключает:
|
||||||
|
- sessions/
|
||||||
|
- logs/
|
||||||
|
- audio_cache/
|
||||||
|
- state.db (опционально)
|
||||||
|
- auth.json (секреты)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import shutil
|
||||||
|
import tarfile
|
||||||
|
import hashlib
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
HERMES_HOME = Path(os.environ.get("HERMES_HOME", Path.home() / ".hermes"))
|
||||||
|
BACKUP_DIR = HERMES_HOME / "backups"
|
||||||
|
LATEST_DIR = BACKUP_DIR / "latest"
|
||||||
|
ARCHIVE_DIR = BACKUP_DIR / "archive"
|
||||||
|
|
||||||
|
# Директории для бэкапа
|
||||||
|
INCLUDE_DIRS = [
|
||||||
|
HERMES_HOME / "skills",
|
||||||
|
HERMES_HOME / "profiles",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Файлы для бэкапа
|
||||||
|
INCLUDE_FILES = [
|
||||||
|
HERMES_HOME / "config.yaml",
|
||||||
|
HERMES_HOME / ".env",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Директории для исключения
|
||||||
|
EXCLUDE_PATTERNS = [
|
||||||
|
"**/sessions/",
|
||||||
|
"**/logs/",
|
||||||
|
"**/audio_cache/",
|
||||||
|
"**/state.db",
|
||||||
|
"**/auth.json",
|
||||||
|
"**/__pycache__/",
|
||||||
|
"**/*.pyc",
|
||||||
|
]
|
||||||
|
|
||||||
|
def create_snapshot(output_dir: Path, exclude_secrets: bool = False):
|
||||||
|
"""Создать снапшот конфигурации."""
|
||||||
|
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H-%M-%SZ")
|
||||||
|
snapshot_dir = output_dir / timestamp
|
||||||
|
|
||||||
|
snapshot_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
manifest = {
|
||||||
|
"version": "0.1.0",
|
||||||
|
"created_at": timestamp,
|
||||||
|
"hermes_home": str(HERMES_HOME),
|
||||||
|
"files": [],
|
||||||
|
"exclude_secrets": exclude_secrets,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Копирование конфигурационных файлов
|
||||||
|
for src in INCLUDE_FILES:
|
||||||
|
if src.exists():
|
||||||
|
dst = snapshot_dir / src.relative_to(HERMES_HOME)
|
||||||
|
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
shutil.copy2(src, dst)
|
||||||
|
|
||||||
|
# Redact секреты в .env если нужно
|
||||||
|
if exclude_secrets and dst.name == ".env":
|
||||||
|
redact_env_secrets(dst)
|
||||||
|
|
||||||
|
file_hash = hash_file(dst)
|
||||||
|
manifest["files"].append({
|
||||||
|
"path": str(src.relative_to(HERMES_HOME)),
|
||||||
|
"hash": file_hash,
|
||||||
|
"size": dst.stat().st_size,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Копирование директорий с исключениями
|
||||||
|
for src_dir in INCLUDE_DIRS:
|
||||||
|
if src_dir.exists() and src_dir.is_dir():
|
||||||
|
_copy_with_exclusions(
|
||||||
|
src_dir,
|
||||||
|
snapshot_dir / src_dir.relative_to(HERMES_HOME),
|
||||||
|
manifest,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Сохранение манифеста
|
||||||
|
manifest_path = snapshot_dir / "manifest.json"
|
||||||
|
manifest_path.write_text(json.dumps(manifest, indent=2, ensure_ascii=False))
|
||||||
|
|
||||||
|
# Обновление симлинка latest
|
||||||
|
if LATEST_DIR.exists():
|
||||||
|
if LATEST_DIR.is_symlink():
|
||||||
|
LATEST_DIR.unlink()
|
||||||
|
else:
|
||||||
|
shutil.rmtree(LATEST_DIR)
|
||||||
|
os.symlink(snapshot_dir, LATEST_DIR, target_is_directory=True)
|
||||||
|
|
||||||
|
# Создание архива
|
||||||
|
archive_path = ARCHIVE_DIR / f"{timestamp}.tar.gz"
|
||||||
|
archive_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with tarfile.open(archive_path, "w:gz") as tar:
|
||||||
|
tar.add(snapshot_dir, arcname=timestamp)
|
||||||
|
|
||||||
|
print(f"Snapshot created: {snapshot_dir}")
|
||||||
|
print(f"Archive: {archive_path}")
|
||||||
|
print(f"Manifest: {len(manifest['files'])} files")
|
||||||
|
|
||||||
|
# Очистка старых архивов (> 30 дней)
|
||||||
|
cleanup_old_archives(days=30)
|
||||||
|
|
||||||
|
def redact_env_secrets(env_path: Path):
|
||||||
|
"""Заменить значения секретов на REDACTED."""
|
||||||
|
content = env_path.read_text()
|
||||||
|
redacted_lines = []
|
||||||
|
for line in content.split("\n"):
|
||||||
|
if "=" in line and not line.startswith("#"):
|
||||||
|
key, _ = line.split("=", 1)
|
||||||
|
if any(s in key.upper() for s in ["KEY", "TOKEN", "SECRET", "PASSWORD"]):
|
||||||
|
redacted_lines.append(f"{key}=REDACTED")
|
||||||
|
else:
|
||||||
|
redacted_lines.append(line)
|
||||||
|
else:
|
||||||
|
redacted_lines.append(line)
|
||||||
|
env_path.write_text("\n".join(redacted_lines))
|
||||||
|
|
||||||
|
def hash_file(path: Path) -> str:
|
||||||
|
"""SHA-256 хеш файла."""
|
||||||
|
sha = hashlib.sha256()
|
||||||
|
with open(path, "rb") as f:
|
||||||
|
for chunk in iter(lambda: f.read(8192), b""):
|
||||||
|
sha.update(chunk)
|
||||||
|
return sha.hexdigest()
|
||||||
|
|
||||||
|
def cleanup_old_archives(days: int = 30):
|
||||||
|
"""Удалить архивы старше N дней."""
|
||||||
|
cutoff = datetime.now(timezone.utc).timestamp() - days * 86400
|
||||||
|
for archive in ARCHIVE_DIR.glob("*.tar.gz"):
|
||||||
|
if archive.stat().st_mtime < cutoff:
|
||||||
|
archive.unlink()
|
||||||
|
print(f"Removed old archive: {archive.name}")
|
||||||
|
|
||||||
|
|
||||||
|
def _copy_with_exclusions(src: Path, dst: Path, manifest: dict):
|
||||||
|
"""Копировать директорию с исключениями."""
|
||||||
|
from fnmatch import fnmatch
|
||||||
|
|
||||||
|
for root, dirs, files in os.walk(src):
|
||||||
|
root_path = Path(root)
|
||||||
|
|
||||||
|
# Пропустить исключённые директории
|
||||||
|
dirs[:] = [
|
||||||
|
d for d in dirs
|
||||||
|
if not any(fnmatch(str(root_path / d), pat) for pat in EXCLUDE_PATTERNS)
|
||||||
|
]
|
||||||
|
|
||||||
|
for fname in files:
|
||||||
|
fpath = root_path / fname
|
||||||
|
if any(fnmatch(str(fpath), pat) for pat in EXCLUDE_PATTERNS):
|
||||||
|
continue
|
||||||
|
|
||||||
|
rel = fpath.relative_to(HERMES_HOME)
|
||||||
|
dest = dst / rel.relative_to(src)
|
||||||
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
shutil.copy2(fpath, dest)
|
||||||
|
|
||||||
|
file_hash = hash_file(dest)
|
||||||
|
manifest["files"].append({
|
||||||
|
"path": str(rel),
|
||||||
|
"hash": file_hash,
|
||||||
|
"size": dest.stat().st_size,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import argparse
|
||||||
|
parser = argparse.ArgumentParser(description="Hermes config snapshot")
|
||||||
|
parser.add_argument("--output", type=Path, default=BACKUP_DIR,
|
||||||
|
help="Output directory")
|
||||||
|
parser.add_argument("--exclude-secrets", action="store_true",
|
||||||
|
help="Redact secrets in .env files")
|
||||||
|
parser.add_argument("--dry-run", action="store_true",
|
||||||
|
help="Show what would be backed up, without copying")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.dry_run:
|
||||||
|
print("Files to backup:")
|
||||||
|
for f in INCLUDE_FILES:
|
||||||
|
if f.exists():
|
||||||
|
print(f" {f}")
|
||||||
|
for d in INCLUDE_DIRS:
|
||||||
|
if d.exists():
|
||||||
|
print(f" {d}/")
|
||||||
|
else:
|
||||||
|
create_snapshot(args.output, exclude_secrets=args.exclude_secrets)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Ручной снапшот
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Стандартный снапшот
|
||||||
|
python scripts/update_backup.py
|
||||||
|
|
||||||
|
# Снапшот с redacted секретами (для хранения в Git)
|
||||||
|
python scripts/update_backup.py --exclude-secrets
|
||||||
|
|
||||||
|
# Предварительный просмотр
|
||||||
|
python scripts/update_backup.py --dry-run
|
||||||
|
|
||||||
|
# В указанную директорию
|
||||||
|
python scripts/update_backup.py --output /mnt/backups/hermes/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 Автоматизация через cron
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Добавить задание в crontab
|
||||||
|
# Ежечасный снапшот с исключением секретов
|
||||||
|
0 * * * * cd ~/acc-local && python scripts/update_backup.py --exclude-secrets --output ~/.hermes/backups/
|
||||||
|
|
||||||
|
# Ежедневный полный снапшот
|
||||||
|
0 2 * * * cd ~/acc-local && python scripts/update_backup.py --output ~/.hermes/backups/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Процедура восстановления
|
||||||
|
|
||||||
|
### 4.1 Скрипт: `scripts/restore_backup.py`
|
||||||
|
|
||||||
|
```python
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
restore_backup.py — восстановление конфигурации Hermes Agent из снапшота.
|
||||||
|
|
||||||
|
Использование:
|
||||||
|
python scripts/restore_backup.py --snapshot DIR
|
||||||
|
python scripts/restore_backup.py --latest
|
||||||
|
python scripts/restore_backup.py --list
|
||||||
|
python scripts/restore_backup.py --verify DIR
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import shutil
|
||||||
|
import hashlib
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
HERMES_HOME = Path(os.environ.get("HERMES_HOME", Path.home() / ".hermes"))
|
||||||
|
BACKUP_DIR = HERMES_HOME / "backups"
|
||||||
|
LATEST_DIR = BACKUP_DIR / "latest"
|
||||||
|
ARCHIVE_DIR = BACKUP_DIR / "archive"
|
||||||
|
RESTORE_DIR = HERMES_HOME / "restore_tmp"
|
||||||
|
|
||||||
|
|
||||||
|
def list_snapshots():
|
||||||
|
"""Показать доступные снапшоты."""
|
||||||
|
if not ARCHIVE_DIR.exists():
|
||||||
|
print("No archives found.")
|
||||||
|
return
|
||||||
|
|
||||||
|
archives = sorted(ARCHIVE_DIR.glob("*.tar.gz"), reverse=True)
|
||||||
|
for i, arc in enumerate(archives):
|
||||||
|
size_mb = arc.stat().st_size / (1024 * 1024)
|
||||||
|
mtime = datetime.fromtimestamp(arc.stat().st_mtime)
|
||||||
|
print(f" [{i}] {arc.name} {size_mb:.1f} MB {mtime.isoformat()}")
|
||||||
|
|
||||||
|
|
||||||
|
def verify_snapshot(snapshot_dir: Path):
|
||||||
|
"""Проверить целостность снапшота."""
|
||||||
|
manifest_path = snapshot_dir / "manifest.json"
|
||||||
|
if not manifest_path.exists():
|
||||||
|
print("ERROR: manifest.json not found")
|
||||||
|
return False
|
||||||
|
|
||||||
|
manifest = json.loads(manifest_path.read_text())
|
||||||
|
all_ok = True
|
||||||
|
|
||||||
|
for fspec in manifest.get("files", []):
|
||||||
|
fpath = snapshot_dir / fspec["path"]
|
||||||
|
if not fpath.exists():
|
||||||
|
print(f" MISSING: {fspec['path']}")
|
||||||
|
all_ok = False
|
||||||
|
continue
|
||||||
|
|
||||||
|
actual_hash = hashlib.sha256(fpath.read_bytes()).hexdigest()
|
||||||
|
if actual_hash != fspec["hash"]:
|
||||||
|
print(f" HASH MISMATCH: {fspec['path']}")
|
||||||
|
all_ok = False
|
||||||
|
|
||||||
|
if all_ok:
|
||||||
|
print(f"VERIFIED: {len(manifest['files'])} files OK")
|
||||||
|
return all_ok
|
||||||
|
|
||||||
|
|
||||||
|
def restore_snapshot(snapshot_dir: Path, dry_run: bool = False):
|
||||||
|
"""Восстановить конфигурацию из снапшота."""
|
||||||
|
manifest_path = snapshot_dir / "manifest.json"
|
||||||
|
manifest = json.loads(manifest_path.read_text())
|
||||||
|
|
||||||
|
print(f"Restoring from: {snapshot_dir}")
|
||||||
|
print(f"Created: {manifest.get('created_at', 'unknown')}")
|
||||||
|
print(f"Files: {len(manifest['files'])}")
|
||||||
|
print(f"{'[DRY RUN] ' if dry_run else ''}")
|
||||||
|
|
||||||
|
conflicts = []
|
||||||
|
|
||||||
|
for fspec in manifest.get("files", []):
|
||||||
|
src = snapshot_dir / fspec["path"]
|
||||||
|
dst = HERMES_HOME / fspec["path"]
|
||||||
|
|
||||||
|
if not src.exists():
|
||||||
|
print(f" SKIP (missing): {fspec['path']}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Проверить конфликт
|
||||||
|
if dst.exists() and not dry_run:
|
||||||
|
dst_hash = hashlib.sha256(dst.read_bytes()).hexdigest()
|
||||||
|
if dst_hash != fspec["hash"]:
|
||||||
|
conflicts.append(fspec["path"])
|
||||||
|
|
||||||
|
if dry_run:
|
||||||
|
action = "CONFLICT" if dst.exists() else "CREATE"
|
||||||
|
print(f" [{action}] {fspec['path']}")
|
||||||
|
else:
|
||||||
|
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
shutil.copy2(src, dst)
|
||||||
|
print(f" RESTORED: {fspec['path']}")
|
||||||
|
|
||||||
|
if conflicts:
|
||||||
|
print(f"\nWARNING: {len(conflicts)} files had local modifications (overwritten):")
|
||||||
|
for p in conflicts:
|
||||||
|
print(f" - {p}")
|
||||||
|
|
||||||
|
if dry_run:
|
||||||
|
print(f"\nDry run complete. {len(manifest['files'])} files would be restored.")
|
||||||
|
|
||||||
|
print("Restore complete.")
|
||||||
|
print("NOTE: .env files were backed up with redacted secrets.")
|
||||||
|
print(" Manually restore API keys and secrets after restore.")
|
||||||
|
|
||||||
|
|
||||||
|
def restore_from_archive(archive_path: Path):
|
||||||
|
"""Восстановить из tar.gz архива."""
|
||||||
|
import tarfile
|
||||||
|
|
||||||
|
extract_dir = RESTORE_DIR / archive_path.stem
|
||||||
|
extract_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
with tarfile.open(archive_path, "r:gz") as tar:
|
||||||
|
tar.extractall(extract_dir)
|
||||||
|
|
||||||
|
# Найти директорию снапшота внутри архива
|
||||||
|
snapshots = list(extract_dir.glob("*/manifest.json"))
|
||||||
|
if not snapshots:
|
||||||
|
print("ERROR: No manifest.json found in archive")
|
||||||
|
return
|
||||||
|
|
||||||
|
snapshot_dir = snapshots[0].parent
|
||||||
|
restore_snapshot(snapshot_dir)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import argparse
|
||||||
|
parser = argparse.ArgumentParser(description="Hermes config restore")
|
||||||
|
parser.add_argument("--snapshot", type=Path,
|
||||||
|
help="Snapshot directory to restore from")
|
||||||
|
parser.add_argument("--latest", action="store_true",
|
||||||
|
help="Restore from latest snapshot")
|
||||||
|
parser.add_argument("--list", action="store_true",
|
||||||
|
help="List available snapshots")
|
||||||
|
parser.add_argument("--verify", type=Path,
|
||||||
|
help="Verify snapshot integrity")
|
||||||
|
parser.add_argument("--dry-run", action="store_true",
|
||||||
|
help="Show what would be restored")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.list:
|
||||||
|
list_snapshots()
|
||||||
|
elif args.verify:
|
||||||
|
verify_snapshot(Path(args.verify))
|
||||||
|
elif args.latest:
|
||||||
|
if not LATEST_DIR.exists():
|
||||||
|
print("ERROR: No latest snapshot found")
|
||||||
|
exit(1)
|
||||||
|
if args.dry_run:
|
||||||
|
restore_snapshot(LATEST_DIR, dry_run=True)
|
||||||
|
else:
|
||||||
|
verify_snapshot(LATEST_DIR)
|
||||||
|
restore_snapshot(LATEST_DIR)
|
||||||
|
elif args.snapshot:
|
||||||
|
snapshot = Path(args.snapshot)
|
||||||
|
if snapshot.suffix == ".gz":
|
||||||
|
restore_from_archive(snapshot)
|
||||||
|
else:
|
||||||
|
if args.dry_run:
|
||||||
|
restore_snapshot(snapshot, dry_run=True)
|
||||||
|
else:
|
||||||
|
verify_snapshot(snapshot)
|
||||||
|
restore_snapshot(snapshot)
|
||||||
|
else:
|
||||||
|
parser.print_help()
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 Ручное восстановление
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Список доступных снапшотов
|
||||||
|
python scripts/restore_backup.py --list
|
||||||
|
|
||||||
|
# Восстановить последний снапшот
|
||||||
|
python scripts/restore_backup.py --latest
|
||||||
|
|
||||||
|
# Восстановить конкретный снапшот
|
||||||
|
python scripts/restore_backup.py --snapshot ~/.hermes/backups/2026-07-20T14-00-00Z
|
||||||
|
|
||||||
|
# Восстановить из архива
|
||||||
|
python scripts/restore_backup.py --snapshot ~/.hermes/backups/archive/2026-07-20T14-00-00Z.tar.gz
|
||||||
|
|
||||||
|
# Проверить целостность без восстановления
|
||||||
|
python scripts/restore_backup.py --verify ~/.hermes/backups/latest
|
||||||
|
|
||||||
|
# Предварительный просмотр
|
||||||
|
python scripts/restore_backup.py --latest --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Git-репозиторий как хранилище
|
||||||
|
|
||||||
|
### 5.1 Структура репозитория `ochenstarik-ui/hermes-config-backup`
|
||||||
|
|
||||||
|
```
|
||||||
|
hermes-config-backup/
|
||||||
|
├── README.md
|
||||||
|
├── config/
|
||||||
|
│ ├── default.yaml # Без секретов
|
||||||
|
│ ├── worker-code.yaml
|
||||||
|
│ ├── worker-fast.yaml
|
||||||
|
│ ├── worker-research.yaml
|
||||||
|
│ └── worker-review.yaml
|
||||||
|
├── skills/ # Пользовательские навыки
|
||||||
|
│ └── *.md
|
||||||
|
├── memories/ # Память (без чувствительных данных)
|
||||||
|
│ ├── MEMORY.md
|
||||||
|
│ └── USER.md
|
||||||
|
├── scripts/
|
||||||
|
│ ├── update_backup.py
|
||||||
|
│ ├── restore_backup.py
|
||||||
|
│ └── sync_to_git.sh
|
||||||
|
└── .gitignore
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 `.gitignore`
|
||||||
|
|
||||||
|
```gitignore
|
||||||
|
# Исключить файлы с секретами
|
||||||
|
.env
|
||||||
|
*.env
|
||||||
|
auth.json
|
||||||
|
credentials/
|
||||||
|
secrets/
|
||||||
|
|
||||||
|
# Исключить чувствительные данные сессий
|
||||||
|
sessions/
|
||||||
|
state.db
|
||||||
|
|
||||||
|
# Исключить временные файлы
|
||||||
|
*.pyc
|
||||||
|
__pycache__/
|
||||||
|
backups/archive/
|
||||||
|
backups/latest/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 Синхронизация: `scripts/sync_to_git.sh`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# sync_to_git.sh — синхронизация снапшота в Git-репозиторий
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPO_DIR="$HOME/hermes-config-backup"
|
||||||
|
SNAPSHOT_DIR="$HOME/.hermes/backups/latest"
|
||||||
|
|
||||||
|
cd "$REPO_DIR"
|
||||||
|
|
||||||
|
# Копировать файлы из снапшота (без секретов)
|
||||||
|
if [ -d "$SNAPSHOT_DIR" ]; then
|
||||||
|
# config.yaml профилей
|
||||||
|
cp "$SNAPSHOT_DIR/config.yaml" config/default.yaml 2>/dev/null || true
|
||||||
|
cp "$SNAPSHOT_DIR/profiles/worker-code/config.yaml" config/worker-code.yaml 2>/dev/null || true
|
||||||
|
cp "$SNAPSHOT_DIR/profiles/worker-fast/config.yaml" config/worker-fast.yaml 2>/dev/null || true
|
||||||
|
cp "$SNAPSHOT_DIR/profiles/worker-research/config.yaml" config/worker-research.yaml 2>/dev/null || true
|
||||||
|
cp "$SNAPSHOT_DIR/profiles/worker-review/config.yaml" config/worker-review.yaml 2>/dev/null || true
|
||||||
|
|
||||||
|
# Навыки
|
||||||
|
rm -rf skills/
|
||||||
|
cp -r "$SNAPSHOT_DIR/skills/" skills/ 2>/dev/null || true
|
||||||
|
|
||||||
|
# Память
|
||||||
|
mkdir -p memories/
|
||||||
|
cp "$SNAPSHOT_DIR/profiles/default/memories/MEMORY.md" memories/ 2>/dev/null || true
|
||||||
|
cp "$SNAPSHOT_DIR/profiles/default/memories/USER.md" memories/ 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Commit и push
|
||||||
|
git add -A
|
||||||
|
if git diff --cached --quiet; then
|
||||||
|
echo "No changes to commit."
|
||||||
|
else
|
||||||
|
git commit -m "backup: $(date -u +'%Y-%m-%dT%H:%M:%SZ')"
|
||||||
|
git push origin main
|
||||||
|
echo "Backup synced to GitHub."
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Исключение секретов
|
||||||
|
|
||||||
|
| Компонент | Статус секретов | Действие при бэкапе |
|
||||||
|
|---|---|---|
|
||||||
|
| `config.yaml` | Может содержать `api_key` references | Проверить на прямые ключи; использовать `${ENV_VAR}` синтаксис |
|
||||||
|
| `.env` | Содержит API-ключи | REDACT при `--exclude-secrets`; NEVER commit в Git |
|
||||||
|
| `auth.json` | OAuth-токены | Исключён из бэкапа полностью |
|
||||||
|
| `state.db` | Может содержать ключи в сессиях | Исключён из бэкапа |
|
||||||
|
| `skills/` | Обычно без секретов | Бэкапируется; проверить на embedded ключи |
|
||||||
|
| `memories/` | Может содержать косвенные ссылки | Бэкапируется; проверить на чувствительные данные |
|
||||||
|
|
||||||
|
### 6.1 Проверка на секреты перед commit
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Проверить снапшот на наличие паттернов секретов
|
||||||
|
grep -rE "(sk-|api_key|token|secret|password|-----BEGIN)" ~/.hermes/backups/latest/ \
|
||||||
|
--exclude-dir=sessions --exclude="*.pyc" && echo "WARNING: Potential secrets found!"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. Процедуры
|
||||||
|
|
||||||
|
### 7.1 Плановый бэкап (ежедневно)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# daily_backup.sh
|
||||||
|
|
||||||
|
echo "=== Daily backup $(date) ==="
|
||||||
|
|
||||||
|
# 1. Создать снапшот
|
||||||
|
python scripts/update_backup.py --exclude-secrets
|
||||||
|
echo "Snapshot created."
|
||||||
|
|
||||||
|
# 2. Проверить целостность
|
||||||
|
python scripts/restore_backup.py --verify ~/.hermes/backups/latest
|
||||||
|
echo "Integrity verified."
|
||||||
|
|
||||||
|
# 3. Синхронизировать в Git
|
||||||
|
bash scripts/sync_to_git.sh
|
||||||
|
echo "Git sync complete."
|
||||||
|
|
||||||
|
# 4. Очистить старые архивы (> 30 дней)
|
||||||
|
find ~/.hermes/backups/archive/ -name "*.tar.gz" -mtime +30 -delete
|
||||||
|
echo "Old archives cleaned."
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 Восстановление после сбоя
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# disaster_recovery.sh
|
||||||
|
|
||||||
|
echo "=== Disaster Recovery $(date) ==="
|
||||||
|
echo "WARNING: This will overwrite current Hermes configuration!"
|
||||||
|
read -p "Continue? (yes/no): " confirm
|
||||||
|
[[ "$confirm" != "yes" ]] && exit 0
|
||||||
|
|
||||||
|
# 1. Создать аварийный снапшот текущего состояния
|
||||||
|
python scripts/update_backup.py --output ~/.hermes/backups/pre_recovery
|
||||||
|
echo "Pre-recovery snapshot created."
|
||||||
|
|
||||||
|
# 2. Восстановить из последнего снапшота
|
||||||
|
python scripts/restore_backup.py --latest
|
||||||
|
echo "Config restored."
|
||||||
|
|
||||||
|
# 3. Проверить конфигурацию
|
||||||
|
hermes config check
|
||||||
|
echo "Config check complete."
|
||||||
|
|
||||||
|
# 4. Проверить health
|
||||||
|
hermes doctor
|
||||||
|
echo "Health check complete."
|
||||||
|
|
||||||
|
# 5. Перезапустить gateway
|
||||||
|
hermes gateway restart
|
||||||
|
echo "Gateway restarted."
|
||||||
|
|
||||||
|
echo "=== Recovery complete ==="
|
||||||
|
echo "NOTE: API keys (.env) were redacted in backup."
|
||||||
|
echo " Manually restore API keys if needed."
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. RPO и RTO
|
||||||
|
|
||||||
|
| Сценарий | RPO | RTO | Процедура |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Повреждение `config.yaml` | ≤ 1 час | ≤ 5 мин | `restore_backup.py --latest` |
|
||||||
|
| Потеря навыков | ≤ 6 часов | ≤ 10 мин | `restore_backup.py --latest` |
|
||||||
|
| Полная потеря `~/.hermes/` | ≤ 1 час (config) / ≤ 24 часа (sessions) | ≤ 30 мин | `disaster_recovery.sh` |
|
||||||
|
| Миграция на новый сервер | ≤ 1 час | ≤ 1 час | Клонировать Git-репозиторий + восстановить секреты |
|
||||||
|
|
||||||
|
## 9. Ссылки
|
||||||
|
|
||||||
|
- [HERMES.md](../adapters/HERMES.md) — спецификация Hermes-адаптера (конфигурация)
|
||||||
|
- [MONITORING.md](MONITORING.md) — мониторинг и алерты (включая ALT-BKP-001)
|
||||||
|
- [CREDENTIAL_POOLS.md](../patterns/CREDENTIAL_POOLS.md) — управление ключами
|
||||||
|
- [SPECIFICATION.md](../SPECIFICATION.md) §10 — Data lifecycle
|
||||||
|
- [SPECIFICATION.md](../SPECIFICATION.md) §7.2 — Reliability, consistency and recovery
|
||||||
340
docs/operations/MONITORING.md
Normal file
340
docs/operations/MONITORING.md
Normal file
|
|
@ -0,0 +1,340 @@
|
||||||
|
# Monitoring & Health-check
|
||||||
|
|
||||||
|
**Версия:** 0.1.0-draft
|
||||||
|
**Дата:** 2026-07-20
|
||||||
|
**Статус:** Draft
|
||||||
|
**Зависит от:** `SPECIFICATION.md` (§7.5 Operations and observability), `docs/adapters/HERMES.md`, `docs/patterns/WORKER_ORCHESTRATION.md`
|
||||||
|
|
||||||
|
> Этот документ описывает систему мониторинга и health-check'ов для Hermes Agent в составе Agent Control Center. Реализация запрещена до G0/G1 и утверждения оператором.
|
||||||
|
|
||||||
|
## 1. Обзор
|
||||||
|
|
||||||
|
Система мониторинга ACC охватывает три слоя:
|
||||||
|
1. **Инфраструктурный** — Connector, Control Plane, базы данных, сеть
|
||||||
|
2. **Агентский** — Hermes профили, провайдеры, credential pools
|
||||||
|
3. **Интеграционный** — Gateway (Telegram polling), 9Router, внешние API
|
||||||
|
|
||||||
|
## 2. Health-check компонентов
|
||||||
|
|
||||||
|
### 2.1 Матрица health-check
|
||||||
|
|
||||||
|
| Компонент | Метод проверки | Периодичность | Критичность |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **Hermes default профиль** | `hermes --profile default doctor` | 5 мин | CRITICAL |
|
||||||
|
| **Worker-code** | `hermes --profile worker-code doctor` | 5 мин | HIGH |
|
||||||
|
| **Worker-fast** | `hermes --profile worker-fast doctor` | 5 мин | HIGH |
|
||||||
|
| **Worker-research** | `hermes --profile worker-research doctor` | 5 мин | MEDIUM |
|
||||||
|
| **Worker-review** | `hermes --profile worker-review doctor` | 5 мин | MEDIUM |
|
||||||
|
| **Gateway (Telegram)** | `hermes gateway status` | 1 мин | CRITICAL |
|
||||||
|
| **9Router** | `curl https://9router.example.com/health` | 1 мин | HIGH |
|
||||||
|
| **OpenCode Go API** | `curl -I https://api.opencode.ai/health` | 5 мин | HIGH |
|
||||||
|
| **Fireworks API** | `curl -I https://api.fireworks.ai/health` | 5 мин | HIGH |
|
||||||
|
| **Gemini API** | `curl -I https://generativelanguage.googleapis.com` | 5 мин | HIGH |
|
||||||
|
| **NVIDIA API** | `curl -I https://integrate.api.nvidia.com/v1` | 5 мин | HIGH |
|
||||||
|
| **Дисковое пространство** | `df -h` | 15 мин | MEDIUM |
|
||||||
|
| **Память** | `free -m` | 15 мин | MEDIUM |
|
||||||
|
| **CPU** | `top -bn1` | 15 мин | LOW |
|
||||||
|
|
||||||
|
### 2.2 Hermes Health-check
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Комплексная проверка
|
||||||
|
hermes doctor --fix
|
||||||
|
|
||||||
|
# Проверка конфигурации
|
||||||
|
hermes config check
|
||||||
|
|
||||||
|
# Статус компонентов
|
||||||
|
hermes status --all
|
||||||
|
|
||||||
|
# Проверка конкретного профиля
|
||||||
|
hermes --profile worker-code doctor
|
||||||
|
|
||||||
|
# Проверка gateway
|
||||||
|
hermes gateway status
|
||||||
|
|
||||||
|
# Проверка cron
|
||||||
|
hermes cron status
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 Интерпретация статусов
|
||||||
|
|
||||||
|
| Статус | Описание | SLA Impact |
|
||||||
|
|---|---|---|
|
||||||
|
| `healthy` | Все проверки пройдены | — |
|
||||||
|
| `degraded` | Часть функций работает с ограничениями | WARNING, частичная доступность |
|
||||||
|
| `unhealthy` | Критическая функция недоступна | CRITICAL, требуется intervention |
|
||||||
|
| `unknown` | Health-check не выполнен / нет данных | WARNING, проверка мониторинга |
|
||||||
|
|
||||||
|
## 3. Ключевые метрики
|
||||||
|
|
||||||
|
### 3.1 Latency
|
||||||
|
|
||||||
|
| Метрика | Цель | WARNING | CRITICAL |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `hermes_response_time_p95` | ≤ 5s | > 10s | > 30s |
|
||||||
|
| `provider_latency_p95` (openrouter) | ≤ 3s | > 8s | > 20s |
|
||||||
|
| `delegate_task_startup_time_p95` | ≤ 10s | > 20s | > 60s |
|
||||||
|
| `telegram_polling_latency_p95` | ≤ 2s | > 5s | > 15s |
|
||||||
|
| `9router_proxy_latency_p95` | ≤ 500ms | > 2s | > 5s |
|
||||||
|
|
||||||
|
### 3.2 Token usage
|
||||||
|
|
||||||
|
| Метрика | Обновление | WARNING | CRITICAL |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `tokens_used_daily` | Каждый запрос | > 80% дневной квоты | > 95% |
|
||||||
|
| `tokens_used_monthly` | Каждый запрос | > 80% месячной квоты | > 95% |
|
||||||
|
| `tokens_per_request_avg` | Скользящее окно 1 час | > 100K в среднем (аномалия) | > 500K (возможна утечка) |
|
||||||
|
| `cost_estimate_daily` | Ежечасно | > бюджета | > бюджета × 1.5 |
|
||||||
|
|
||||||
|
### 3.3 Error rates
|
||||||
|
|
||||||
|
| Метрика | Окно | WARNING | CRITICAL |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `provider_4xx_rate` | 5 мин | > 5% | > 15% |
|
||||||
|
| `provider_5xx_rate` | 5 мин | > 2% | > 10% |
|
||||||
|
| `delegation_failure_rate` | 15 мин | > 10% | > 25% |
|
||||||
|
| `gateway_message_delivery_failure` | 15 мин | > 5% | > 15% |
|
||||||
|
| `telegram_polling_errors` | 5 мин | > 3 ошибок | > 10 ошибок |
|
||||||
|
|
||||||
|
### 3.4 Gateway метрики
|
||||||
|
|
||||||
|
| Метрика | Описание | WARNING | CRITICAL |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `telegram_polling_loop_healthy` | polling loop активен | false в течение 2 мин | false в течение 5 мин |
|
||||||
|
| `gateway_websocket_connected` | mTLS WSS к Control Plane | disconnected > 30s | disconnected > 5 мин |
|
||||||
|
| `active_user_sessions` | Количество активных пользователей | = 0 (нет активности) | — |
|
||||||
|
| `message_queue_depth` | Глубина очереди недоставленных сообщений | > 100 | > 1000 |
|
||||||
|
|
||||||
|
## 4. Алерты
|
||||||
|
|
||||||
|
### 4.1 Классификация
|
||||||
|
|
||||||
|
| Severity | Описание | Время реакции | Эскалация |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **CRITICAL** | Сервис недоступен, пользователи затронуты | 5 мин | Немедленно → Operator |
|
||||||
|
| **WARNING** | Деградация, возможны проблемы | 30 мин | В течение часа |
|
||||||
|
| **INFO** | Информационное событие | — | В рабочее время |
|
||||||
|
|
||||||
|
### 4.2 Перечень алертов
|
||||||
|
|
||||||
|
| Alert ID | Название | Severity | Условие | Runbook |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `ALT-GTW-001` | Gateway down | CRITICAL | `hermes gateway status` != running | [§6.1](#61-gateway-down) |
|
||||||
|
| `ALT-GTW-002` | Telegram polling lag | WARNING | Polling latency > 5s за 5 мин | [§6.2](#62-telegram-polling-lag) |
|
||||||
|
| `ALT-PRV-001` | Provider quota exhausted | CRITICAL | Все ключи провайдера exhausted | [§6.3](#63-provider-quota-exhausted) |
|
||||||
|
| `ALT-PRV-002` | Provider unavailable | CRITICAL | Все провайдеры для профиля unhealthy | [§6.4](#64-provider-unavailable) |
|
||||||
|
| `ALT-PRV-003` | Quota warning | WARNING | > 80% квоты использовано | [§6.3](#63-provider-quota-exhausted) |
|
||||||
|
| `ALT-WRK-001` | Worker profile offline | WARNING | Профиль unhealthy > 10 мин | [§6.5](#65-worker-profile-offline) |
|
||||||
|
| `ALT-WRK-002` | All workers offline | CRITICAL | Все worker-профили unhealthy | [§6.5](#65-worker-profile-offline) |
|
||||||
|
| `ALT-CFG-001` | Config invalid | WARNING | `hermes config check` failed | [§6.6](#66-config-invalid) |
|
||||||
|
| `ALT-BKP-001` | Backup failed | WARNING | Последний снапшот старше 24ч | [BACKUP.md](BACKUP.md) |
|
||||||
|
| `ALT-DSK-001` | Low disk space | CRITICAL | < 5 GB свободно | [§6.7](#67-low-disk-space) |
|
||||||
|
| `ALT-9RT-001` | 9Router unreachable | WARNING | Health-check 9Router failed > 5 мин | [§6.8](#68-9router-unreachable) |
|
||||||
|
| `ALT-CRN-001` | Cron scheduler stalled | WARNING | Последний tick > 2× интервала | `hermes cron status` |
|
||||||
|
|
||||||
|
## 5. Мониторинговые инструменты
|
||||||
|
|
||||||
|
### 5.1 Встроенные
|
||||||
|
|
||||||
|
| Инструмент | Команда | Назначение |
|
||||||
|
|---|---|---|
|
||||||
|
| `hermes doctor` | `hermes doctor [--fix]` | Проверка зависимостей и конфигурации |
|
||||||
|
| `hermes status` | `hermes status --all` | Статус компонентов |
|
||||||
|
| `hermes config check` | `hermes config check` | Проверка конфигурации |
|
||||||
|
| `hermes gateway status` | `hermes gateway status` | Статус gateway |
|
||||||
|
| `hermes cron status` | `hermes cron status` | Статус планировщика |
|
||||||
|
| `hermes insights` | `hermes insights --days 7` | Аналитика использования |
|
||||||
|
| `hermes debug` | `hermes debug` (или `/debug`) | Отчёт для отладки |
|
||||||
|
|
||||||
|
### 5.2 Внешние (рекомендуемые)
|
||||||
|
|
||||||
|
- **Prometheus + Grafana** — сбор и визуализация метрик
|
||||||
|
- **Healthchecks.io / Uptime Kuma** — внешний мониторинг доступности
|
||||||
|
- **Sentry / Datadog** — отслеживание ошибок
|
||||||
|
- **W&B** — логирование ML-экспериментов и usage
|
||||||
|
|
||||||
|
## 6. Процедуры восстановления
|
||||||
|
|
||||||
|
### 6.1 Gateway down
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Проверить статус
|
||||||
|
hermes gateway status
|
||||||
|
|
||||||
|
# 2. Проверить логи
|
||||||
|
tail -100 ~/.hermes/logs/gateway.log | grep -i "error\|failed"
|
||||||
|
|
||||||
|
# 3. Перезапустить
|
||||||
|
hermes gateway restart
|
||||||
|
|
||||||
|
# 4. Если не помогло — проверить systemd (Linux)
|
||||||
|
systemctl --user status hermes-gateway
|
||||||
|
systemctl --user reset-failed hermes-gateway
|
||||||
|
systemctl --user restart hermes-gateway
|
||||||
|
|
||||||
|
# 5. Если crash loop — проверить конфигурацию платформ
|
||||||
|
hermes config check
|
||||||
|
# Убедиться, что токены платформ валидны
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 Telegram polling lag
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Проверить статус polling
|
||||||
|
hermes gateway status | grep telegram
|
||||||
|
|
||||||
|
# 2. Переключить fallback IP (если блокировка)
|
||||||
|
# В config.yaml:
|
||||||
|
# gateway.platforms.telegram.fallback_ips добавить/изменить IP
|
||||||
|
|
||||||
|
# 3. Перезапустить gateway
|
||||||
|
hermes gateway restart
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.3 Provider quota exhausted
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Проверить статус ключей
|
||||||
|
hermes auth list opencode-go
|
||||||
|
hermes auth list fireworks
|
||||||
|
hermes auth list gemini
|
||||||
|
hermes auth list nvidia
|
||||||
|
|
||||||
|
# 2. Если есть активные ключи — проверить стратегию
|
||||||
|
# Убедиться, что exhausted ключи корректно исключены
|
||||||
|
|
||||||
|
# 3. Если все ключи exhausted:
|
||||||
|
# a. Проверить наличие резервных ключей
|
||||||
|
# b. Добавить новый ключ: hermes auth add <provider>
|
||||||
|
# c. Или ждать сброса квоты
|
||||||
|
|
||||||
|
# 4. Для срочных задач — переключить на другого провайдера
|
||||||
|
# worker-code (opencode-go) → worker-code (nvidia / deepseek-v4-pro)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.4 Provider unavailable
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Проверить доступность API
|
||||||
|
curl -I https://api.opencode.ai/health
|
||||||
|
curl -I https://api.fireworks.ai/health
|
||||||
|
curl -I https://generativelanguage.googleapis.com
|
||||||
|
|
||||||
|
# 2. Если API недоступен — ждать восстановления
|
||||||
|
# 3. Переключить профили на альтернативных провайдеров
|
||||||
|
# (см. WORKER_ORCHESTRATION.md §3 Fallback-цепочки)
|
||||||
|
|
||||||
|
# 4. Проверить статус 9Router
|
||||||
|
curl https://9router.example.com/health
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.5 Worker profile offline
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Проверить статус профиля
|
||||||
|
hermes --profile worker-code doctor
|
||||||
|
|
||||||
|
# 2. Проверить конфигурацию
|
||||||
|
hermes --profile worker-code config check
|
||||||
|
|
||||||
|
# 3. Проверить .env профиля
|
||||||
|
hermes --profile worker-code config env-path
|
||||||
|
# Убедиться, что API ключи на месте и валидны
|
||||||
|
|
||||||
|
# 4. Проверить доступность провайдера (см. §6.4)
|
||||||
|
|
||||||
|
# 5. Если проблема в модели — переключить модель
|
||||||
|
hermes --profile worker-code config set model.default <alternative_model>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.6 Config invalid
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Проверить синтаксис
|
||||||
|
hermes config check
|
||||||
|
|
||||||
|
# 2. Сравнить с последним бэкапом
|
||||||
|
diff ~/.hermes/config.yaml ~/.hermes/backups/latest/config.yaml
|
||||||
|
|
||||||
|
# 3. Восстановить из бэкапа (см. BACKUP.md)
|
||||||
|
python scripts/restore_backup.py --latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.7 Low disk space
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Определить, что занимает место
|
||||||
|
du -sh ~/.hermes/sessions/
|
||||||
|
du -sh ~/.hermes/logs/
|
||||||
|
du -sh ~/.hermes/audio_cache/
|
||||||
|
|
||||||
|
# 2. Очистить старые сессии
|
||||||
|
hermes sessions prune --older-than 30
|
||||||
|
|
||||||
|
# 3. Очистить старые логи
|
||||||
|
find ~/.hermes/logs/ -name "*.log" -mtime +7 -delete
|
||||||
|
|
||||||
|
# 4. Очистить audio cache
|
||||||
|
find ~/.hermes/audio_cache/ -mtime +7 -delete
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.8 9Router unreachable
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Проверить доступность
|
||||||
|
curl -v https://9router.example.com/health
|
||||||
|
|
||||||
|
# 2. Переключить Hermes на прямой endpoint
|
||||||
|
# В config.yaml изменить base_url на прямой URL провайдера
|
||||||
|
hermes config set model.base_url "https://api.opencode.ai/v1"
|
||||||
|
|
||||||
|
# 3. После восстановления 9Router — вернуть конфигурацию
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. Панель мониторинга
|
||||||
|
|
||||||
|
Рекомендуемая структура дашборда:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ AGENT CONTROL CENTER — MONITORING v0.1.0-draft │
|
||||||
|
├────────────┬────────────┬────────────┬──────────────────┤
|
||||||
|
│ PROFILES │ PROVIDERS │ GATEWAY │ INFRASTRUCTURE │
|
||||||
|
│ ──────── │ ──────── │ ──────── │ ────────────── │
|
||||||
|
│ default │ opencode │ telegram │ CPU: 34% │
|
||||||
|
│ ● ONLINE │ ● HEALTHY │ ● ONLINE │ RAM: 62% │
|
||||||
|
│ │ │ │ DISK: 41% │
|
||||||
|
│ worker-c │ fireworks │ discord │ │
|
||||||
|
│ ● ONLINE │ ● DEGRADED │ ○ OFFLINE │ UPTIME: 14d 3h │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ worker-f │ gemini │ slack │ CONNECTOR │
|
||||||
|
│ ● ONLINE │ ● HEALTHY │ ○ OFFLINE │ ● CONNECTED │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ worker-r │ nvidia │ │ BACKUP │
|
||||||
|
│ ● ONLINE │ ● HEALTHY │ │ ✓ 2h ago │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ worker-rv │ │ │ CRON │
|
||||||
|
│ ● ONLINE │ │ │ ✓ running │
|
||||||
|
├────────────┴────────────┴────────────┴──────────────────┤
|
||||||
|
│ USAGE TODAY ERRORS (LAST HOUR) │
|
||||||
|
│ ───────── ────────────────── │
|
||||||
|
│ Tokens: 1.2M / 5M (24%) Total: 12 │
|
||||||
|
│ Cost: $4.20 / $25.00 (17%) 4xx: 8 | 5xx: 4 │
|
||||||
|
│ Rate: 0.3% │
|
||||||
|
├──────────────────────────────────────────────────────────┤
|
||||||
|
│ RECENT EVENTS │
|
||||||
|
│ 14:32 INFO worker-code: task completed (45s) │
|
||||||
|
│ 14:30 WARN fireworks: quota 82% │
|
||||||
|
│ 14:28 INFO telegram: message delivered │
|
||||||
|
│ 14:25 WARN worker-fast: latency spike (8.2s p95) │
|
||||||
|
└──────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. Ссылки
|
||||||
|
|
||||||
|
- [HERMES.md](../adapters/HERMES.md) — спецификация Hermes-адаптера
|
||||||
|
- [WORKER_ORCHESTRATION.md](../patterns/WORKER_ORCHESTRATION.md) — health-check worker-профилей
|
||||||
|
- [CREDENTIAL_POOLS.md](../patterns/CREDENTIAL_POOLS.md) — мониторинг квот
|
||||||
|
- [BACKUP.md](BACKUP.md) — бэкап и восстановление
|
||||||
|
- [SPECIFICATION.md](../SPECIFICATION.md) §7.5 — Operations and observability
|
||||||
282
docs/patterns/CREDENTIAL_POOLS.md
Normal file
282
docs/patterns/CREDENTIAL_POOLS.md
Normal file
|
|
@ -0,0 +1,282 @@
|
||||||
|
# Credential Pools & Multi-Account Management
|
||||||
|
|
||||||
|
**Версия:** 0.1.0-draft
|
||||||
|
**Дата:** 2026-07-20
|
||||||
|
**Статус:** Draft
|
||||||
|
**Зависит от:** `SPECIFICATION.md`, `ARCHITECTURE.md`, `docs/adapters/HERMES.md`
|
||||||
|
|
||||||
|
> Этот документ описывает паттерны управления пулами API-ключей для провайдеров ИИ-моделей. Реализация запрещена до G0/G1 и утверждения оператором.
|
||||||
|
|
||||||
|
## 1. Обзор
|
||||||
|
|
||||||
|
Credential Pool — механизм ротации нескольких API-ключей в рамках одного провайдера, обеспечивающий:
|
||||||
|
- Автоматический обход исчерпанных квот
|
||||||
|
- Round-robin распределение нагрузки
|
||||||
|
- Приоритетное использование ключей
|
||||||
|
- Прозрачный failover без ручного вмешательства
|
||||||
|
|
||||||
|
## 2. Архитектура credential pool
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────┐
|
||||||
|
│ Hermes Agent │
|
||||||
|
│ ┌───────────────────────────────────────┐ │
|
||||||
|
│ │ Credential Pool Manager │ │
|
||||||
|
│ │ ┌─────────┐ ┌─────────┐ ┌───────┐ │ │
|
||||||
|
│ │ │ Key #1 │ │ Key #2 │ │ Key N │ │ │
|
||||||
|
│ │ │ (active)│ │ (active)│ │(spare)│ │ │
|
||||||
|
│ │ └────┬────┘ └────┬────┘ └───┬───┘ │ │
|
||||||
|
│ │ │ │ │ │ │
|
||||||
|
│ │ ▼ ▼ ▼ │ │
|
||||||
|
│ │ ┌──────────────────────────────┐ │ │
|
||||||
|
│ │ │ Provider API Backend │ │ │
|
||||||
|
│ │ └──────────────────────────────┘ │ │
|
||||||
|
│ └───────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌───────────────────────────────────────┐ │
|
||||||
|
│ │ 9Router (опционально) │ │
|
||||||
|
│ │ • Round-robin распределение │ │
|
||||||
|
│ │ • Quota tracking │ │
|
||||||
|
│ │ • OpenAI-формат трансляции │ │
|
||||||
|
│ └───────────────────────────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Hermes Auth Pools
|
||||||
|
|
||||||
|
### 3.1 Конфигурация
|
||||||
|
|
||||||
|
Hermes поддерживает несколько ключей на одного провайдера через `hermes auth`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Добавление ключа
|
||||||
|
hermes auth add opencode-go
|
||||||
|
hermes auth add fireworks
|
||||||
|
hermes auth add gemini
|
||||||
|
hermes auth add nvidia
|
||||||
|
|
||||||
|
# Просмотр пула
|
||||||
|
hermes auth list opencode-go
|
||||||
|
# Вывод:
|
||||||
|
# [0] sk-abc...xyz active quota: 85%
|
||||||
|
# [1] sk-def...uvw active quota: 42%
|
||||||
|
# [2] sk-ghi...rst active quota: 12%
|
||||||
|
|
||||||
|
# Ручной сброс exhausted-статуса
|
||||||
|
hermes auth reset opencode-go 2
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Стратегии выбора ключа
|
||||||
|
|
||||||
|
| Стратегия | Алгоритм | Применение |
|
||||||
|
|---|---|---|
|
||||||
|
| **Round-robin** | Последовательный перебор активных ключей | Равномерное распределение нагрузки |
|
||||||
|
| **Priority-based** | Ключи с более высоким приоритетом используются первыми | primary/fallback сценарий |
|
||||||
|
| **Least-loaded** | Ключ с наименьшим процентом использованной квоты | Оптимизация использования квот |
|
||||||
|
| **Failover-only** | Второй ключ активируется только при отказе первого | Минимизация случайного использования дорогих ключей |
|
||||||
|
|
||||||
|
### 3.3 Primary/Fallback паттерн
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# ~/.hermes/auth.json (концептуально)
|
||||||
|
{
|
||||||
|
"pools": {
|
||||||
|
"opencode-go": {
|
||||||
|
"keys": [
|
||||||
|
{
|
||||||
|
"id": "primary",
|
||||||
|
"key": "sk-primary-...",
|
||||||
|
"priority": 1,
|
||||||
|
"quota_limit": 1000000,
|
||||||
|
"status": "active"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fallback",
|
||||||
|
"key": "sk-fallback-...",
|
||||||
|
"priority": 2,
|
||||||
|
"quota_limit": 500000,
|
||||||
|
"status": "active"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"strategy": "priority"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 Статусы ключей
|
||||||
|
|
||||||
|
| Статус | Описание | Поведение |
|
||||||
|
|---|---|---|
|
||||||
|
| `active` | Ключ работает | Используется согласно стратегии |
|
||||||
|
| `exhausted` | Ключ исчерпал квоту | Автоматически исключается из ротации |
|
||||||
|
| `rate_limited` | Временный rate limit | Исключается на cooldown-период (60s) |
|
||||||
|
| `error` | Ошибка аутентификации | Исключается до ручного сброса |
|
||||||
|
| `disabled` | Ручное отключение | Никогда не используется |
|
||||||
|
|
||||||
|
### 3.5 Обработка ошибок
|
||||||
|
|
||||||
|
| HTTP-код | Интерпретация | Действие |
|
||||||
|
|---|---|---|
|
||||||
|
| 401 | Неверный ключ | `status=error`; alert |
|
||||||
|
| 403 | Доступ запрещён | `status=error`; alert |
|
||||||
|
| 429 | Rate limit | `status=rate_limited`; retry через cooldown |
|
||||||
|
| 429 + `x-ratelimit-remaining: 0` | Квота исчерпана | `status=exhausted`; переключение на следующий ключ |
|
||||||
|
| 500/502/503 | Серверная ошибка | Retry до 3 раз; затем fallback ключ |
|
||||||
|
| Timeout | Сетевой сбой | Retry 1 раз; затем fallback ключ |
|
||||||
|
|
||||||
|
## 4. Интеграция с 9Router
|
||||||
|
|
||||||
|
### 4.1 Назначение
|
||||||
|
|
||||||
|
9Router — внешний агрегатор API-ключей, обеспечивающий:
|
||||||
|
- Централизованное управление пулом ключей от разных аккаунтов
|
||||||
|
- Round-robin / weighted распределение запросов
|
||||||
|
- Отслеживание квот в реальном времени
|
||||||
|
- Трансляцию форматов запросов (не-OpenAI → OpenAI-совместимый)
|
||||||
|
- Единый endpoint для всех моделей
|
||||||
|
|
||||||
|
### 4.2 Конфигурация Hermes → 9Router
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# config.yaml
|
||||||
|
model:
|
||||||
|
provider: opencode-go
|
||||||
|
base_url: "https://9router.example.com/v1/opencode-go" # Проксируется через 9Router
|
||||||
|
api_key: "${NINEROUTER_API_KEY}"
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# .env
|
||||||
|
NINEROUTER_API_KEY=nr-...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 Паттерны использования
|
||||||
|
|
||||||
|
#### Round-robin через 9Router
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# 9Router конфигурация (на стороне 9Router)
|
||||||
|
pools:
|
||||||
|
opencode-go:
|
||||||
|
strategy: round-robin
|
||||||
|
keys:
|
||||||
|
- account: personal
|
||||||
|
key: sk-personal-...
|
||||||
|
weight: 1
|
||||||
|
- account: team
|
||||||
|
key: sk-team-...
|
||||||
|
weight: 2
|
||||||
|
- account: backup
|
||||||
|
key: sk-backup-...
|
||||||
|
weight: 1
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Quota tracking
|
||||||
|
|
||||||
|
9Router отслеживает использование через:
|
||||||
|
- Response headers (`x-ratelimit-remaining`, `x-ratelimit-reset`)
|
||||||
|
- Собственный счётчик токенов из тела ответа
|
||||||
|
- Периодический опрос `/v1/usage` (если доступен)
|
||||||
|
|
||||||
|
При достижении 95% квоты ключ помечается `low`; при 100% — `exhausted` и исключается из ротации.
|
||||||
|
|
||||||
|
### 4.4 Отказоустойчивость
|
||||||
|
|
||||||
|
Если 9Router недоступен, Hermes MUST fallback на прямое подключение к провайдеру с локальным credential pool:
|
||||||
|
|
||||||
|
```
|
||||||
|
Hermes → [попытка] 9Router → [отказ]
|
||||||
|
→ [fallback] прямой endpoint провайдера + локальный пул ключей
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Мониторинг квот
|
||||||
|
|
||||||
|
### 5.1 Метрики
|
||||||
|
|
||||||
|
| Метрика | Источник | Обновление |
|
||||||
|
|---|---|---|
|
||||||
|
| `tokens_used` | Response body `usage.total_tokens` | Каждый запрос |
|
||||||
|
| `tokens_remaining` | Response header `x-ratelimit-remaining` или локальный счётчик | Каждый запрос |
|
||||||
|
| `quota_percent` | `tokens_used / quota_limit * 100` | Каждый запрос |
|
||||||
|
| `request_count` | Локальный счётчик | Каждый запрос |
|
||||||
|
| `error_count` | HTTP status != 200 | Каждый запрос |
|
||||||
|
| `latency_p95` | Локальный замер | Скользящее окно 5 мин |
|
||||||
|
|
||||||
|
### 5.2 Пороги и алерты
|
||||||
|
|
||||||
|
| Порог | Условие | Действие |
|
||||||
|
|---|---|---|
|
||||||
|
| `WARNING` | `quota_percent > 80%` | Уведомление оператору |
|
||||||
|
| `CRITICAL` | `quota_percent > 95%` | Автоматический failover на следующий ключ + алерт |
|
||||||
|
| `EXHAUSTED` | `quota_percent >= 100%` | Исключение ключа из ротации + инцидент |
|
||||||
|
| `ERROR_BURST` | > 5 ошибок за 1 мин | Отключение ключа на 5 мин + алерт |
|
||||||
|
|
||||||
|
### 5.3 Автоматический failover
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Текущий ключ получает 429 с x-ratelimit-remaining: 0
|
||||||
|
2. Pool Manager помечает ключ exhausted
|
||||||
|
3. Выбирается следующий ключ согласно стратегии
|
||||||
|
4. Запрос повторяется с новым ключом
|
||||||
|
5. Если все ключи exhausted → возврат ошибки оператору
|
||||||
|
6. Exhausted-ключи сбрасываются при наступлении reset time
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Безопасность
|
||||||
|
|
||||||
|
| Требование | Реализация |
|
||||||
|
|---|---|
|
||||||
|
| Ключи NEVER в логах | Secret redaction (`security.redact_secrets`) включён по умолчанию |
|
||||||
|
| Ключи NEVER в коде | Хранятся только в `.env` или `auth.json` |
|
||||||
|
| Ключи NEVER в Git | `.gitignore` исключает `.env`, `auth.json` |
|
||||||
|
| Ротация без простоя | Добавление нового ключа в пул + сброс старого после grace period |
|
||||||
|
| Аудит использования | Все операции с ключами логируются через `audit` tool |
|
||||||
|
|
||||||
|
## 7. Процедуры
|
||||||
|
|
||||||
|
### 7.1 Добавление нового ключа
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Добавить ключ в пул
|
||||||
|
hermes auth add opencode-go
|
||||||
|
|
||||||
|
# 2. Проверить статус
|
||||||
|
hermes auth list opencode-go
|
||||||
|
|
||||||
|
# 3. Проверить health с новым ключом
|
||||||
|
hermes --profile worker-code doctor
|
||||||
|
|
||||||
|
# 4. При необходимости настроить 9Router
|
||||||
|
# Обновить конфигурацию 9Router с новым ключом
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 Замена скомпрометированного ключа
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Немедленно отозвать ключ на стороне провайдера
|
||||||
|
# 2. Пометить ключ как error в Hermes
|
||||||
|
hermes auth remove opencode-go 1 # индекс скомпрометированного ключа
|
||||||
|
|
||||||
|
# 3. Добавить новый ключ
|
||||||
|
hermes auth add opencode-go
|
||||||
|
|
||||||
|
# 4. Проверить, что старый ключ не используется
|
||||||
|
# Проверить логи аудита
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 Сброс exhausted-статуса
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# После сброса квоты (начало нового billing period)
|
||||||
|
hermes auth reset opencode-go 0
|
||||||
|
hermes auth reset opencode-go 1
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. Ссылки
|
||||||
|
|
||||||
|
- [HERMES.md](../adapters/HERMES.md) — спецификация Hermes-адаптера
|
||||||
|
- [WORKER_ORCHESTRATION.md](WORKER_ORCHESTRATION.md) — оркестрация worker-профилей
|
||||||
|
- [MONITORING.md](../operations/MONITORING.md) — мониторинг квот и алерты
|
||||||
|
- [BACKUP.md](../operations/BACKUP.md) — бэкап конфигурации credential pools
|
||||||
|
- [SPECIFICATION.md](../SPECIFICATION.md) §6.8 — Usage, notifications и audit
|
||||||
207
docs/patterns/WORKER_ORCHESTRATION.md
Normal file
207
docs/patterns/WORKER_ORCHESTRATION.md
Normal file
|
|
@ -0,0 +1,207 @@
|
||||||
|
# Worker Orchestration Patterns
|
||||||
|
|
||||||
|
**Версия:** 0.1.0-draft
|
||||||
|
**Дата:** 2026-07-20
|
||||||
|
**Статус:** Draft
|
||||||
|
**Зависит от:** `SPECIFICATION.md`, `ARCHITECTURE.md`, `docs/adapters/HERMES.md`
|
||||||
|
|
||||||
|
> Этот документ описывает паттерны оркестрации через worker-профили Hermes Agent. Реализация запрещена до G0/G1 и утверждения оператором.
|
||||||
|
|
||||||
|
## 1. Обзор
|
||||||
|
|
||||||
|
Worker-профили Hermes Agent (`worker-code`, `worker-fast`, `worker-research`, `worker-review`) обеспечивают изолированное выполнение специализированных задач. Оркестратор (`default` профиль) маршрутизирует подзадачи, управляет fallback'ами и агрегирует результаты.
|
||||||
|
|
||||||
|
Архитектура оркестрации:
|
||||||
|
```
|
||||||
|
Пользователь → default (оркестратор)
|
||||||
|
├──→ worker-code (opencode-go / kimi-k2.7-code)
|
||||||
|
├──→ worker-fast (fireworks / grok-4.5)
|
||||||
|
├──→ worker-research (gemini / gemini-3-flash-preview)
|
||||||
|
└──→ worker-review (nvidia / deepseek-v4-pro)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Маршрутизация задач
|
||||||
|
|
||||||
|
### 2.1 Матрица маршрутизации
|
||||||
|
|
||||||
|
| Тип задачи | Первичный профиль | Вторичный (fallback) | Критерий выбора |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **Генерация кода** | `worker-code` | `worker-fast` | Задача содержит "напиши код", "реализуй", "создай файл" |
|
||||||
|
| **Рефакторинг** | `worker-code` | `worker-review` | Задача требует изменения существующего кода с сохранением поведения |
|
||||||
|
| **Отладка** | `worker-code` | `worker-research` | Задача содержит трассировку, ошибку, stack trace |
|
||||||
|
| **Быстрый ответ** | `worker-fast` | `worker-code` | Задача простая, не требует сложного рассуждения |
|
||||||
|
| **Исследование** | `worker-research` | `worker-code` | Задача требует анализа документации, поиска, чтения |
|
||||||
|
| **Code Review** | `worker-review` | `worker-code` | Задача проверки качества/безопасности кода |
|
||||||
|
| **Аудит безопасности** | `worker-review` | `worker-research` | Задача анализа уязвимостей, проверки политик |
|
||||||
|
| **Планирование** | `default` (in-process) | `worker-research` | Задача требует стратегического рассуждения |
|
||||||
|
| **Сложный анализ** | `worker-research` | `worker-review` | Задача с большим контекстом, требует synthesis |
|
||||||
|
|
||||||
|
### 2.2 Алгоритм маршрутизации
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Классифицировать задачу по типу (NL-эвристика)
|
||||||
|
2. Определить первичный профиль из матрицы
|
||||||
|
3. Проверить health первичного профиля
|
||||||
|
4. Если health OK → delegate_task(profile=primary, ...)
|
||||||
|
5. Если health DEGRADED/OFFLINE → проверить fallback
|
||||||
|
6. Если fallback OK → delegate_task(profile=secondary, ...)
|
||||||
|
7. Если оба недоступны → выполнить задачу в default с предупреждением
|
||||||
|
```
|
||||||
|
|
||||||
|
Маршрутизация MUST учитывать capabilities профиля: если задача требует `browser`, а `worker-fast` не имеет этого инструмента, маршрутизация не должна выбирать этот профиль.
|
||||||
|
|
||||||
|
## 3. Fallback-цепочки
|
||||||
|
|
||||||
|
### 3.1 Модель fallback'а
|
||||||
|
|
||||||
|
При отказе провайдера (rate limit, timeout, model unavailable) Hermes использует внутренний механизм credential pools (см. `CREDENTIAL_POOLS.md`). На уровне оркестратора реализуется дополнительный fallback:
|
||||||
|
|
||||||
|
```
|
||||||
|
worker-code (kimi-k2.7-code) → [ОТКАЗ]
|
||||||
|
├──→ worker-fast (grok-4.5) — быстрая альтернатива для кода
|
||||||
|
├──→ worker-code (deepseek-v4-pro, nvidia) — та же роль, другой провайдер
|
||||||
|
└──→ default (deepseek-v4-pro) — выполнить in-process
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Стратегии fallback'а
|
||||||
|
|
||||||
|
| Стратегия | Описание | Применение |
|
||||||
|
|---|---|---|
|
||||||
|
| **Role-preserving** | Та же роль, другой провайдер | `worker-code` + `nvidia` вместо `opencode-go` |
|
||||||
|
| **Downgrade** | Более простой профиль | `worker-code` → `worker-fast` |
|
||||||
|
| **Escalate** | Более мощный профиль | `worker-fast` → `worker-code` |
|
||||||
|
| **In-process** | Выполнение в оркестраторе | Когда все worker'ы недоступны |
|
||||||
|
|
||||||
|
### 3.3 Политика повторных попыток
|
||||||
|
|
||||||
|
| Ошибка | Действие | Макс. повторов | Задержка |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Rate limit (429) | Ждать + повторить тот же профиль | 3 | Exponential: 1s, 4s, 16s |
|
||||||
|
| Timeout (504) | Fallback на вторичный профиль | 1 | — |
|
||||||
|
| Model unavailable (503) | Fallback на вторичный профиль | 0 | — |
|
||||||
|
| Auth error (401/403) | Не повторять; alert оператору | 0 | — |
|
||||||
|
| Unknown error | Fallback → in-process | 1 | 5s |
|
||||||
|
|
||||||
|
## 4. Параллельное делегирование (Fan-out)
|
||||||
|
|
||||||
|
### 4.1 Паттерн
|
||||||
|
|
||||||
|
`delegate_task(tasks=[...])` запускает дочерние задачи параллельно:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tasks": [
|
||||||
|
{"goal": "Реализовать API endpoint /users", "profile": "worker-code"},
|
||||||
|
{"goal": "Написать тесты для /users endpoint", "profile": "worker-code"},
|
||||||
|
{"goal": "Обновить документацию API", "profile": "worker-fast"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 Ограничения
|
||||||
|
|
||||||
|
| Параметр | Значение по умолчанию | Описание |
|
||||||
|
|---|---|---|
|
||||||
|
| `max_concurrent_children` | 3 | Максимум параллельных дочерних задач |
|
||||||
|
| `max_spawn_depth` | 2 | Максимальная глубина вложенности оркестраторов |
|
||||||
|
| `max_iterations` | 50 | Максимум итераций на дочернюю задачу |
|
||||||
|
|
||||||
|
### 4.3 Сбор результатов
|
||||||
|
|
||||||
|
Оркестратор SHOULD:
|
||||||
|
1. Дождаться завершения всех дочерних задач (или таймаута)
|
||||||
|
2. Агрегировать результаты в порядке исходного списка
|
||||||
|
3. При частичном отказе — вернуть успешные результаты + ошибки для упавших
|
||||||
|
4. Предложить пользователю retry для упавших задач
|
||||||
|
|
||||||
|
## 5. Передача контекста
|
||||||
|
|
||||||
|
### 5.1 Механизмы
|
||||||
|
|
||||||
|
| Механизм | Формат | Применение |
|
||||||
|
|---|---|---|
|
||||||
|
| `goal` + `context` | Markdown/JSON | Основной механизм: цель + структурированный контекст |
|
||||||
|
| Файловая система | Файлы в рабочей директории | Общие артефакты (спецификации, результаты) |
|
||||||
|
| `memory` tool | MEMORY.md | Долгоживущие факты/решения между сессиями |
|
||||||
|
| `session_search` | FTS5-поиск | Поиск релевантной истории |
|
||||||
|
|
||||||
|
### 5.2 Формат контекста
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"goal": "Реализовать модуль аутентификации",
|
||||||
|
"context": {
|
||||||
|
"objective": "JWT-based authentication module",
|
||||||
|
"acceptance_criteria": ["Login endpoint", "Token refresh", "Password reset"],
|
||||||
|
"constraints": {"framework": "FastAPI", "database": "PostgreSQL"},
|
||||||
|
"decisions": ["Use python-jose for JWT", "Use passlib for hashing"],
|
||||||
|
"artifact_refs": ["specs/auth-spec.md", "schemas/user.sql"],
|
||||||
|
"completed_work": ["Database schema created", "User model defined"],
|
||||||
|
"pending_work": ["Implement /login", "Implement /refresh"],
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Health-check worker-профилей
|
||||||
|
|
||||||
|
### 6.1 Процедура
|
||||||
|
|
||||||
|
Health-check MUST выполняться:
|
||||||
|
- Перед каждой делегацией задачи
|
||||||
|
- Периодически (раз в 5 минут) для фонового мониторинга
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Проверка конкретного профиля
|
||||||
|
hermes --profile worker-code chat -q "respond with 'OK'" --quiet
|
||||||
|
|
||||||
|
# Проверка статуса провайдера
|
||||||
|
hermes --profile worker-code doctor
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 Состояния
|
||||||
|
|
||||||
|
| Состояние | Критерий | Действие |
|
||||||
|
|---|---|---|
|
||||||
|
| `ONLINE` | Успешный ping < 5s | Нормальная маршрутизация |
|
||||||
|
| `DEGRADED` | Успешный ping, но > 5s или retries | Маршрутизация с предупреждением, приоритет fallback |
|
||||||
|
| `OFFLINE` | Ping failed / timeout 30s | Исключить из маршрутизации, алерт оператору |
|
||||||
|
| `INCOMPATIBLE` | Adapter version mismatch | Заблокировать запуск, требуется upgrade |
|
||||||
|
|
||||||
|
### 6.3 Метрики health-check
|
||||||
|
|
||||||
|
| Метрика | Порог WARNING | Порог CRITICAL |
|
||||||
|
|---|---|---|
|
||||||
|
| Ping latency | > 5s | > 30s (timeout) |
|
||||||
|
| Error rate (5 min) | > 10% | > 25% |
|
||||||
|
| Token usage rate | > 80% квоты | > 95% квоты |
|
||||||
|
| Active runs | > 5 | > 10 |
|
||||||
|
|
||||||
|
## 7. Паттерны обработки ошибок
|
||||||
|
|
||||||
|
### 7.1 Классификация ошибок делегирования
|
||||||
|
|
||||||
|
| Код | Ошибка | Действие |
|
||||||
|
|---|---|---|
|
||||||
|
| `DELEGATION_TIMEOUT` | Дочерняя задача превысила `max_iterations` | Возврат частичного результата; предложить разбить задачу |
|
||||||
|
| `DELEGATION_PROFILE_OFFLINE` | Профиль недоступен | Fallback на альтернативный профиль |
|
||||||
|
| `DELEGATION_RATE_LIMITED` | Исчерпана квота провайдера | Fallback на профиль с другим провайдером |
|
||||||
|
| `DELEGATION_TOOL_ERROR` | Ошибка инструмента в дочерней задаче | Повтор с уточнённым контекстом |
|
||||||
|
| `DELEGATION_DEPTH_EXCEEDED` | Превышена `max_spawn_depth` | Выполнить задачу in-process |
|
||||||
|
|
||||||
|
### 7.2 Таймауты
|
||||||
|
|
||||||
|
| Стадия | Таймаут по умолчанию | Описание |
|
||||||
|
|---|---|---|
|
||||||
|
| Запуск профиля | 10s | Время на инициализацию Hermes |
|
||||||
|
| Ответ на goal | 60s | Время до первого осмысленного ответа |
|
||||||
|
| Полное выполнение | 300s (5 min) | Общее время на задачу |
|
||||||
|
| Сбор результатов | 30s | Таймаут после завершения задачи |
|
||||||
|
|
||||||
|
## 8. Ссылки
|
||||||
|
|
||||||
|
- [HERMES.md](../adapters/HERMES.md) — спецификация Hermes-адаптера
|
||||||
|
- [SPECIFICATION.md](../SPECIFICATION.md) §6.4 — Runs, approvals и handoff
|
||||||
|
- [ARCHITECTURE.md](../ARCHITECTURE.md) §5 — Run и handoff state machine
|
||||||
|
- [CREDENTIAL_POOLS.md](CREDENTIAL_POOLS.md) — управление пулами ключей
|
||||||
|
- [MONITORING.md](../operations/MONITORING.md) — мониторинг health-check'ов
|
||||||
Loading…
Reference in a new issue