Дайджесты новостей
Рабочее место с маленьким настольным экраном-компаньоном концентрации

Riley Brown создал трекер концентрации Bluey на базе OpenAI Decisions API

Каждый, кто хоть раз работал в одиночку, знает этот сценарий: открываешь вкладку браузера уточнить документацию, а через сорок минут ловишь себя на чтении ленты новостей. Классические таймеры 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:

  1. При первом запуске десктопный клиент генерирует QR-код с локальным IP-адресом и одноразовым токеном.
  2. Ты наводишь камеру iPhone, происходит мгновенное сопряжение, и десктоп начинает стримить на мобильный экран координаты курсора и статус таймера.
  3. Раз в 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»). Они работают бесшумно, стоят копейки и активируются только тогда, когда это действительно нужно. А старый смартфон на магнитной подставке обретает вторую жизнь, становясь идеальным рабочим напарником.