Добор дневного минимума: добор дневного минимума в разные компании.
job-agent — автономный модульный монолит для безопасного поиска вакансий,
сопоставления с подтверждённым профилем и подготовки/отправки откликов. Celery
worker и Beat работают на сервере независимо от ChatGPT, Gemini, Codex или
телефона пользователя; REST, мобильная server-rendered панель и удалённый MCP
служат интерфейсами управления.
Реальный crawl Rabota.md и реальная Gmail-отправка по умолчанию выключены. Репозиторий не запускает массовый live scan и не отправляет реальные письма во время тестов. Перед live crawl оператор обязан проверить условия сайта и юридическое основание, а затем явно зафиксировать review.
Основной поток не зависит от конкретного job board:
source adapter → discovery/crawl → normalization → SourceJob
→ reversible deduplication → CanonicalJob
→ deterministic filters → strict LLM evaluation
→ verified contact → verified resume → application generator
→ deterministic policy → Gmail sender(application_id)
app/crawlers/— registry, Rabota.md, Delucru.md, Generic HTML, fixture, API/RSS/sitemap adapters, checkpoints, recheck и degradation circuit breaker;app/models/,app/database/,migrations/— SQLAlchemy 2, PostgreSQL и Alembic;app/deduplication/— исходные публикации остаются сохранёнными, canonical merge можно разъединить;app/matching/,app/contacts/,app/applications/,app/policies/— границы недоверенных данных и детерминированное решение;app/email/— OAuth 2.0/PKCE, зашифрованный refresh token, Gmail и test-only fake provider;app/scheduler/— Celery Beat, очереди, Redis locks и идемпотентность;app/api/,app/admin/,app/mcp/— REST, мобильная панель и MCP Streamable HTTP;app/audit/,app/observability/— audit events, JSON logs, health/readiness и Prometheus metrics.
Схема создаёт UserProfile, Resume, JobPreference, JobSource,
SourceCategory, SourceJob, CanonicalJob, JobSnapshot, ScanRun,
BatchScanRun, MatchEvaluation, EmployerContact, Application,
EmailDelivery, OAuthCredential, AuditEvent, Alert и DailyReport.
Начальная миграция находится в migrations/versions/.
call-agent — отдельный процесс job-agent phone-agent, который получает события
PhoneGate REST API, сопоставляет входящие звонки с откликами и сохраняет
CommunicationSession / CommunicationTurn. По умолчанию процесс выключен
(PHONE_AGENT_ENABLED=false); для включения нужны PHONEGATE_URL и
PHONEGATE_AUTH_TOKEN.
Автоответ включается отдельно через PHONE_AUTO_ANSWER_ENABLED=true (по умолчанию
false). Агент проверяет состояние звонка, блок-лист и остановку оператором,
затем через PhoneGate отвечает, произносит раскрытие роли, слушает собеседника и
ведёт детерминированный сценарий с ограничением длительности. Оператор управляет
автоответом и текущим звонком в панели Звонки (/admin?view=calls); там же видно
здоровье канала. API статуса — GET /api/v1/phone/status. Деградация телефона не
влияет на /ready. Подробности — телефонный агент.
Автоотвеченный звонок закрывается детерминированным сценарием, после чего Celery
сохраняет короткие WAV-доказательства и запускает три строгих русскоязычных прохода
llmRouter. Факты проходят состояния candidate, confirmed, conflict и
unknown; производное состояние сессии — high_confidence, confirmed или
needs_review. candidate становится confirmed только после связанного SMS или
аудируемого действия оператора. Все SMS читаются идемпотентно из PhoneGate, а
Telegram уведомления имеют bounded retry и revision.
DEV-настройки выключены безопасно по умолчанию: включайте их через
PHONE_SUMMARY_LLM_ENABLED, TELEGRAM_ENABLED и соответствующие параметры
PHONE_VERIFICATION_*, PHONE_SMS_*, PHONE_EVIDENCE_*, PHONE_TELEGRAM_*.
Проверка реального GSM и внешних сервисов выполняется только явно:
ENABLE_REALCALL_TESTS=true uv run pytest tests/realcall/test_realcall_phase_2b.py -vvПроверка очереди выполняется в панели Звонки: оператор сверяет транскрипт и
доказательство, затем подтверждает или исправляет факт. Контур не ведёт realtime
LLM-диалог, не записывает InterviewAppointment, не синхронизирует календарь и не
делает исходящие звонки или SMS.
Требуются Docker Compose v2 и, для host-проверок, Python 3.12+ с uv.
cp .env.example .env
chmod 600 .env
uv sync --extra dev
uv run job-agent hash-password
uv run job-agent hash-api-keyОбе команды без аргумента читают секрет без echo. Сохраните Argon2 hash как
ADMIN_PASSWORD_HASH в одинарных кавычках, а SHA-256 bearer key — как JSON-массив
MCP_API_KEYS_HASHED=["..."]. Исходный bearer token храните отдельно и задайте
его клиенту как JOB_AGENT_MCP_TOKEN; сервер его не хранит. Также замените
SECRET_KEY. Не передавайте секреты в shell argv на общем хосте.
docker compose up --build -d postgres redis
docker compose run --rm migrate
docker compose run --rm --no-deps api job-agent seed
docker compose up --build -d api worker beat caddy
docker compose psПосле запуска доступны:
- панель:
http://localhost/; - OpenAPI:
http://localhost/api/docs; - health/readiness:
http://localhost/healthи/ready; - MCP Streamable HTTP:
http://localhost/mcp.
В production используются только HTTPS и уникальные secrets. Чистая локальная БД также может быть создана напрямую:
uv run alembic upgrade head
uv run job-agent seed
uv run uvicorn app.main:app --reloadWorker и Beat запускаются отдельными процессами; точные команды есть в
docker-compose.yml.
Обязательные production-настройки:
ENVIRONMENT=production;- согласованные
POSTGRES_*иDATABASE_URL=postgresql+asyncpg://...без example-пароля; REDIS_PASSWORDи пароль вREDIS_URL;PUBLIC_BASE_URL=https://jobs.example.comиCADDY_ADDRESS=jobs.example.com;- случайный
SECRET_KEY,ADMIN_PASSWORD_HASH, хотя бы один hash вMCP_API_KEYS_HASHED; - идентифицирующий
CRAWLER_USER_AGENTс адресом оператора.
Условные переменные:
LLM_PROVIDER=mock|openai|gemini, соответствующий API key и явный model;TOKEN_ENCRYPTION_KEY,GMAIL_CLIENT_ID,GMAIL_CLIENT_SECRET— для Gmail и Google OIDC-входа;GOOGLE_ADMIN_EMAILS=["operator@example.com"]— точный allowlist Google-аккаунтов, которым разрешён вход в панель;EMAIL_PROVIDER=gmailиREAL_EMAIL_DELIVERY_ENABLED=true— только после отдельной приёмки;EMERGENCY_EMAIL_KILL_SWITCH=trueнемедленно блокирует provider path после recreation API/worker.
.env.example содержит development placeholders, а не production-секреты.
Production validation намеренно отклоняет известный placeholder secret,
не-HTTPS origin и example database password.
Войдите в панель и используйте разделы Профиль, Резюме и Предпочтения.
Полный профиль, включая опыт, образование, права, языки и confirmed_facts, можно
заполнить через PUT /api/v1/profile или MCP update_user_profile; пример полей
есть в config/preferences.example.yaml. Новый профиль можно создать сразу с
первым резюме одной формой в разделе Профиль (POST /admin/profiles); PDF
резюме в этой форме необязателен.
PDF загружается через панель или POST /api/v1/resumes. Сервер проверяет размер,
расширение, MIME/magic bytes, безопасное имя, SHA-256 и путь. После ручной сверки
нажмите Проверено в панели: только active+verified resume может участвовать в
автоотправке. MCP принимает лишь metadata и никогда не принимает filesystem path.
В списке резюме доступны Открыть (просмотр PDF в новой вкладке),
Деактивировать/Активировать и Удалить (последнее — только если резюме не
использовано ни в одном отклике или оценке). Те же действия есть в REST
(DELETE /api/v1/resumes/{id}, POST /api/v1/resumes/{id}/activate и
/deactivate) и MCP (delete_resume, activate_resume, deactivate_resume).
Контактные email и телефон профиля попадают в подпись отправляемого письма
(строки Тел.: и Email: на языке письма); пустые поля строк не добавляют.
Заявки, уже стоящие в очереди «Требуют решения», сохраняют прежнюю подпись до
следующей полной переподготовки.
Настройте отдельно:
- разрешённые и запрещённые категории;
- категории, разрешённые именно для auto-send;
- города, remote, графики, зарплату, языки и работу без опыта;
consider_outside_primary_resume=trueдля явно разрешённой работы вне основной профессии;- score и дневной лимит.
Безопасные значения по умолчанию: auto_send_enabled=false,
global_pause=true, пустой auto-send allowlist.
REST использует тот же bearer key:
export JOB_AGENT_BASE_URL=http://localhost
curl --fail --silent --show-error \
-H "Authorization: Bearer ${JOB_AGENT_MCP_TOKEN}" \
"${JOB_AGENT_BASE_URL}/api/v1/status"job-agent seed создаёт основной профиль с безопасными остановленными настройками,
а Rabota.md — как enabled=false, PAUSED и automatic_actions_paused=true.
Не считайте этот README юридическим разрешением.
Первый live scan выполняйте только после явного разрешения владельца установки и
актуального review, описанного в docs/sources/rabota-md.md:
- вручную проверьте публичные условия, ограничения и дату review;
- через аутентифицированный REST
PATCH /api/v1/sources/{id}или MCPupdate_sourceпередайте полный существующий config, добавивlive_mode=true,policy_review_acknowledged=trueи непустойpolicy_review_referenceс датой/основанием/версией проверенных документов; - вызовите
validate_source(source_id)— это минимальная публичная проверка, не full scan; - вызовите
enable_source(source_id); это включает только crawling и сохраняетautomatic_actions_paused=true, а успешный scan ещё должен вернуть source вHEALTHY; - при необходимости проверьте
discover_categories(source_id); - только затем вызовите
start_full_scan(source_id)и сразу получитеscan_id; - следите через
get_scan_status(scan_id), панель, alerts и audit.
Планировщик продолжает incremental/full/recheck для включённого источника при
automatic_actions_paused=true, но после таких scan не запускает matching и
application pipeline. Downstream снимается с паузы отдельным явным изменением и
всё равно подчиняется global_pause, auto-send allowlist и delivery kill switch.
Поле configuration заменяется целиком: перед update сохраните schedules и
лимиты. live_mode=false является только тестовым seam с внедрённым fixture
transport и не может включить persisted Rabota.md source. CAPTCHA, login, 403,
429, изменение структуры или access policy приостанавливают источник; система не
закрывает вакансии массово.
Скопируйте config/sources/generic-example.yaml, оставьте только реальный
разрешённый домен и заполните селекторы после исследования публичной структуры.
Шаблон намеренно содержит null, а не выдуманные селекторы.
uv run job-agent validate-source-config config/sources/my-source.yamlДобавьте конфигурацию через POST /api/v1/sources или MCP add_source, затем
выполните validate_source, discovery и fixture/incremental scan. Для API, RSS,
sitemap и employer careers используйте зарегистрированные типы generic_api,
rss, sitemap, company_careers; matching, policy, Gmail и Applications менять
не нужно. Полный checklist находится в docs/adding-source.md.
- Создайте Google OAuth Web client и разрешите точный callback
https://DOMAIN/api/v1/oauth/gmail/callback. - Настройте client ID/secret, отдельный случайный
TOKEN_ENCRYPTION_KEYи точныйGOOGLE_ADMIN_EMAILSallowlist. - Оставьте
REAL_EMAIL_DELIVERY_ENABLED=falseи глобальную паузу включённой. - Откройте панель и нажмите
Войти через Google. Один server-side callback проверит ID token, email allowlist, state и PKCE, сохранит refresh token только зашифрованно и выдаст отдельную admin-session cookie. - Проверьте Google identity и Gmail token в разделах
ОбзориСистема.
Запрашиваются OIDC openid email и Gmail scopes gmail.send/gmail.readonly.
Read-only scope нужен для DSN/bounce и ответов в известных thread; mailbox не
изменяется. Sender принимает только
application_id; recipient, MIME, текст и verified resume выбирает сервер.
Подробности и процедура revoke: docs/gmail-oauth.md.
Endpoint: https://DOMAIN/mcp, transport: Streamable HTTP, header:
Authorization: Bearer <исходный JOB_AGENT_MCP_TOKEN>
Все tools требуют bearer auth; встроенные ключи пока не имеют отдельных ролей.
start_full_scan возвращает ID и работает асинхронно, а send_application
принимает только application_id и повторно проверяет policy/idempotency.
Настройки MCP Inspector, Codex и протокольные примеры для ChatGPT/Gemini-
совместимых клиентов приведены в docs/mcp.md; доступность UI конкретного клиента
не утверждается.
Сначала прогоните E2E с mock LLM/fake Gmail, проверьте профиль, confirmed facts, verified resumes, contacts, несколько писем, малый дневной лимит и узкий список категорий. Затем нужны оба независимых разрешения:
- deployment:
EMAIL_PROVIDER=gmailиREAL_EMAIL_DELIVERY_ENABLED=true, после чего recreate API/worker; - user policy: категории auto-send, порог, лимит,
global_pause=falseи отдельное явное действиеresume_auto_send/переключатель панели.
После этого только auto_approved не требует подтверждения каждого письма. LLM
не может дать это решение самостоятельно.
При инциденте сначала вызовите MCP pause_auto_send или переключатель панели.
Затем задайте EMERGENCY_EMAIL_KILL_SWITCH=true и
REAL_EMAIL_DELIVERY_ENABLED=false, пересоздайте как минимум API и worker и
проверьте sending/delivery_unknown. Пауза проверяется непосредственно перед
provider call.
./scripts/verify.sh
./scripts/demo-e2e.sh
uv run alembic upgrade head
uv run alembic check
docker compose config --quiet
docker compose buildverify.sh устанавливает зависимости из lock-файла и Chromium, затем выполняет
ruff, mypy и весь pytest, включая браузерные проверки панели. CI запускает эти
проверки при push и pull request, а также проверяет миграции на PostgreSQL.
Обычные тесты не зависят от Rabota.md и не отправляют письма. PostgreSQL/Redis/ Celery service-backed проверки запускаются отдельно (CI делает это автоматически):
RUN_SERVICE_INTEGRATION_TESTS=1 \
DATABASE_URL=postgresql+asyncpg://job_agent:test-password@127.0.0.1:5432/job_agent_test \
REDIS_URL=redis://127.0.0.1:6379/15 \
uv run pytest tests/integration/test_infrastructure.pyOpt-in live smoke выключен по умолчанию, делает малый bounded crawl и не отправляет отклики:
ENABLE_LIVE_RABOTA_SMOKE_TEST=true uv run pytest -m liveЗапускайте его только после собственного актуального policy review.
Изолированная Docker-среда на rooted A51 управляется из DEV checkout:
scripts/dev-a51.sh deploy, verify, status, logs, console, down.
Сборка выполняется на телефоне, перед заменой API применяются DEV миграции;
после запуска проверяются fixture-обход, worker и три чистых браузерных прохода.
Интерфейсы показывают метку DEV и ревизию. Доступ через ADB: http://127.0.0.1:18881.
Подробности, ограничения Android, доступ к primary proxy на соседнем A14 и обязательный
gate перед PROD: runbook DEV A51.
После заполнения production .env и secret manager используйте только
deploy/prod-compose.sh: wrapper привязывает image к текущему PROD Git SHA и
не допускает общий DEV/PROD tag. migrate использует защищённый
/etc/jobhunter/migrator.env и запускается только как jobhunter_migrator.
./deploy/prod-compose.sh config --quiet
./deploy/prod-compose.sh build --pull
./deploy/prod-compose.sh up -d postgres redis
./deploy/prod-compose.sh run --rm migrate
./deploy/prod-compose.sh \
run --rm --no-deps api job-agent seed
./deploy/prod-compose.sh \
up -d api worker beat caddy
./deploy/prod-compose.sh psПеред upgrade поставьте auto-send на паузу, дождитесь/остановите writers, создайте
backup и используйте локальный build --pull либо отдельно настройте immutable
registry image — не смешивайте эти два workflow. Канонические команды backup и
restore находятся в docs/operations.md.
- Сервис получает только публично доступные активные страницы; удалённые страницы, закрытые архивы и скрытые контакты недоступны.
- Исследование на 2026-08-03 не обнаружило разрешённого публичного API/RSS/sitemap Rabota.md и не установило однозначного разрешения на массовый crawl; live source поэтому fail-closed до review. Публичная глубина сайта может ограничить полноту.
- HTML меняется; degradation detector ставит источник на паузу, но адаптер иногда требует обновления fixtures/selectors вручную.
- Встроенный bearer auth однопользовательский и без scoped roles; для нескольких операторов нужен внешний OIDC/OAuth gateway.
- Alertmanager/канал уведомлений и автоматический off-host backup scheduler не
входят в Compose; metrics доступны внутреннему scraper, Caddy закрывает
публичный
/metrics. - Текущий token encryption использует один active key; штатного key-ring/ re-encryption command нет. Для ротации поставьте отправку на паузу, отзовите OAuth и переподключите аккаунт с новым ключом.
- Playwright — только опциональный безопасный fallback и требует установки extra и browser binaries; основной crawler использует HTTP/HTML.
- Реальный LLM/Gmail и live Rabota.md не участвуют в CI. Mock/fake внешних систем не подменяет основную бизнес-логику.
- Архитектура
- Безопасность и модель угроз
- Краулеры и добавление источника
- Исследование Rabota.md
- Политика auto-send
- Gmail OAuth
- Память отношений с работодателем
- MCP
- Обучение на решениях review
- Развёртывание и операции
Система не проходит CAPTCHA или авторизацию, не снимает собственные rate limits и не использует приватные API. Техническая доступность URL сама по себе не является разрешением на массовое использование. Оператор отвечает за условия каждого источника, персональные данные, частоту запросов и правила коммуникации с работодателями.