Каждый, кто хоть раз работал в одиночку, знает этот сценарий: открываешь вкладку браузера уточнить документацию, а через сорок минут ловишь себя на чтении ленты новостей. Классические таймеры Pomodoro или жесткие блокировщики сайтов работают топорно: они либо прерывают глубокий поток, либо блокируют нужные рабочие страницы.
Разработчик Райли Браун (Riley Brown) предложил изящный выход, опубликовав открытый проект bluey-by-riley. Он создал настольного физического компаньона Bluey, который следит за контекстом рабочего стола и возвращает к делу при отвлечениях. Главный секрет спрятан под капотом: вместо тяжелых генеративных моделей Браун задействовал специализированный OpenAI Decisions API. Я изучил устройство системы и спешу показать, как быстрые классификаторы меняют архитектуру персональных ассистентов.
От забавной анимации к ассистенту против СДВГ
Изначально персонаж Bluey задумывался как забавный декоративный эксперимент. Райли взял старый ненужный iPhone, закрепил его на магнитной подставке прямо под монитором своего Mac и вывел на экран милую синюю ягодку с британским акцентом. Персонаж просто следил глазами за курсором мыши на мониторе, создавая забавный визуальный эффект живого присутствия.
Однако в сообществе людей с синдромом дефицита внимания (СДВГ) давно известен психологический феномен «удвоения тела» (body doubling): когда рядом с тобой молча работает другой человек, фокусироваться на рутине становится в разы проще. Браун решил превратить милую игрушку в полноценного цифрового напарника по концентрации.
Идея проста: ты зажимаешь персонажа на экране, произносишь вслух текущую задачу (например: «Пишу статью про архитектуру микросервисов, сессия на 45 минут»), после чего на смартфоне запускается таймер, а фоновый агент на компьютере начинает периодически оценивать открытые окна. Если ты залип на видеороликах или маркетплейсах, персонаж сначала хмурится, а затем вежливо привлекает внимание и предлагает вернуться к делу.
Анатомия Decisions API: почему классификация дешевле чата
Главный тупик прежних попыток — задержки и цена традиционных LLM. Чтобы оценивать экран хотя бы раз в 30 секунд, нужно каждые полминуты отправлять снимок в GPT-4o. При чат-инференсе это дает задержку в 3–5 секунд, поток лишних текстовых рассуждений и огромный счет за токены.
Специализированный эндпоинт OpenAI Decisions API на базе модели GPT-6 Luna работает иначе: модель возвращает типизированный объект решения (Decision Object) без генерации текста.
Интерфейс поддерживает три режима:
predicate: быстрая проверка гипотезы с вероятностью от 0.0 до 1.0;choice: выбор одного варианта из массива категорий (on_task,off_task,uncertain) со скором уверенности;score: численная оценка соответствия по шкале.
Исходящие токены не тарифицируются: оплачивается только входной контекст по ставке $0.10 за 1 миллион токенов. В итоге 100 проверок обходятся в 1 цент ($0.01), а 8-часовой рабочий день стоит около 9 центов при задержке всего 150–300 мс.
Как устроен пайплайн Bluey: скриншоты, сокеты и эвристика напоминаний
Архитектурно Bluey представляет собой монорепозиторий на Swift, состоящий из двух независимых приложений: десктопного menu bar приложения для macOS и клиента для iOS.
Они общаются по локальной сети через WebSocket:
- При первом запуске десктопный клиент генерирует QR-код с локальным IP-адресом и одноразовым токеном.
- Ты наводишь камеру iPhone, происходит мгновенное сопряжение, и десктоп начинает стримить на мобильный экран координаты курсора и статус таймера.
- Раз в 30 секунд фоновый демон macOS с помощью системного фреймворка
ScreenCaptureKitделает снимок рабочего стола, пережимает его в JPEG и отправляет в Decisions API.
Но самое важное в создании ненавязчивого ассистента — эвристический алгоритм напоминаний (Nudge Engine). Если бы Bluey дергал пользователя при каждом открытии вкладки YouTube, приложение удалили бы через десять минут: ведь разработчик может искать там доклад с конференции!
Браун заложил гибкую логику задержки:
- Единичное попадание в статус
off_taskне вызывает звуков или уведомлений — персонаж на экране iPhone лишь меняет анимацию на озадаченную. - Если уверенность модели высокая (confidence > 0.9), напоминание срабатывает только при непрерывном отвлечении в течение 2 минут.
- Если контекст пограничный (поиск в браузере, чтение документации с оценкой confidence 0.5–0.8), таймер ожидания увеличивается до 4 минут.
- Напоминание выводится в виде мягкого системного баннера на Mac и анимации персонажа, после чего активируется период охлаждения (cooldown) на 3 минуты.
Код интеграции: от JSON-запроса к Swift-контроллеру
Давай заглянем внутрь реализации. Вот как выглядит структура полезной нагрузки, отправляемой в эндпоинт POST https://api.openai.com/v1/decisions:
{
"model": "gpt-6-luna",
"decision_type": "choice",
"options": [
{ "id": "on_task", "description": "Пользователь активно работает над заявленной задачей" },
{ "id": "off_task", "description": "Пользователь отвлекся на социальные сети, игры или развлекательные видео" },
{ "id": "uncertain", "description": "Смешанный контекст: поиск в поисковике или чтение документации" }
],
"input": {
"task_goal": "Написание главы отчета в текстовом редакторе",
"elapsed_minutes": 15,
"screenshot_base64": "iVBORw0KGgoAAAANSUhEUgAA..."
},
"instructions": "Изучи активные окна. Если открыта документация по теме отчета, классифицируй как on_task."
}
А вот ключевой фрагмент контроллера на Swift, управляющий отправкой снимка и накоплением счетчика отвлечений:
import Foundation
import ScreenCaptureKit
struct DecisionResponse: Decodable {
let selectedOption: String
let confidence: Double
let probabilities: [String: Double]
}
final class FocusMonitor {
private var offTaskStreakSeconds = 0
private let cooldownDuration: TimeInterval = 180
private var lastNudgeTime: Date = .distantPast
func evaluateScreen(screenshot: Data, currentTask: String) async throws {
var request = URLRequest(url: URL(string: "https://api.openai.com/v1/decisions")!)
request.httpMethod = "POST"
request.setValue("Bearer \(ProcessInfo.processInfo.environment["OPENAI_API_KEY"] ?? "")", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let payload: [String: Any] = [
"model": "gpt-6-luna",
"decision_type": "choice",
"options": [
["id": "on_task", "description": "Работа над целью"],
["id": "off_task", "description": "Отвлечение на развлечения"],
["id": "uncertain", "description": "Смешанный контекст"]
],
"input": [
"task": currentTask,
"screenshot_base64": screenshot.base64EncodedString()
]
]
request.httpBody = try JSONSerialization.data(withJSONObject: payload)
let (data, _) = try await URLSession.shared.data(for: request)
let decision = try JSONDecoder().decode(DecisionResponse.self, from: data)
await handleDecision(decision)
}
@MainActor
private func handleDecision(_ decision: DecisionResponse) {
if decision.selectedOption == "off_task" {
offTaskStreakSeconds += 30
let threshold = decision.confidence > 0.9 ? 120 : 240
if offTaskStreakSeconds >= threshold && Date().timeIntervalSince(lastNudgeTime) > cooldownDuration {
NotificationCenter.default.post(name: .showBlueyNudge, object: "Кажется, пора вернуться к задаче!")
lastNudgeTime = Date()
offTaskStreakSeconds = 0
}
} else if decision.selectedOption == "on_task" {
offTaskStreakSeconds = max(0, offTaskStreakSeconds - 30)
}
}
}
Код предельно лаконичен: никаких сложных регулярок для разбора Markdown, никакой валидации битого JSON. Модель возвращает строго типизированный ответ, который моментально мапится в Swift-структуру.
Экономика фонового контроля и приватность данных
Ключ API хранится исключительно в локальной системной связке ключей macOS Keychain и не передается на телефон. Однако отправка скриншотов в облако требует осторожности: в настройках Bluey обязательно укажите список исключений (банковские клиенты, менеджеры паролей), при фокусе на которых захват экрана автоматически приостанавливается.
Кейс Bluey наглядно показывает, куда движется персональный ИИ. Вместо тяжелых чат-ботов, требующих ручного ввода длинных инструкций, мы получаем фоновых стражей контекста («System One Gates»). Они работают бесшумно, стоят копейки и активируются только тогда, когда это действительно нужно. А старый смартфон на магнитной подставке обретает вторую жизнь, становясь идеальным рабочим напарником.
