惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

云风的 BLOG
云风的 BLOG
Security Archives - TechRepublic
Security Archives - TechRepublic
V
Vulnerabilities – Threatpost
C
CXSECURITY Database RSS Feed - CXSecurity.com
P
Proofpoint News Feed
G
GRAHAM CLULEY
P
Privacy International News Feed
The Hacker News
The Hacker News
Forbes - Security
Forbes - Security
U
Unit 42
N
News and Events Feed by Topic
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
Cyber Attacks, Cyber Crime and Cyber Security
C
Cisco Blogs
A
About on SuperTechFans
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
D
Docker
I
Intezer
Spread Privacy
Spread Privacy
The Last Watchdog
The Last Watchdog
V2EX - 技术
V2EX - 技术
S
Security @ Cisco Blogs
F
Full Disclosure
S
Secure Thoughts
M
MIT News - Artificial intelligence
Microsoft Security Blog
Microsoft Security Blog
G
Google Developers Blog
aimingoo的专栏
aimingoo的专栏
W
WeLiveSecurity
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Project Zero
Project Zero
Recorded Future
Recorded Future
Cyberwarzone
Cyberwarzone
S
Security Affairs
AWS News Blog
AWS News Blog
H
Help Net Security
The GitHub Blog
The GitHub Blog
Hacker News: Ask HN
Hacker News: Ask HN
Vercel News
Vercel News
P
Proofpoint News Feed
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
The Register - Security
The Register - Security
S
Schneier on Security
F
Fortinet All Blogs
C
CERT Recently Published Vulnerability Notes
L
LINUX DO - 最新话题
T
Tor Project blog
T
The Exploit Database - CXSecurity.com
MongoDB | Blog
MongoDB | Blog
Webroot Blog
Webroot Blog

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Как я сделал AI‑директора для малого бизнеса и почему отказался от RAG
Виктор · 2026-05-12 · via Все публикации подряд на Хабре

Уровень: senior backend, AI/ML Стек: FastAPI, SQLite, Claude Haiku 4.5, кастомный scheduler Что внутри: архитектура AI‑агента для команды 5–50 человек, типизированная память вместо vector RAG, граф знаний организации, ежедневный reflection

Что такое Лира на пальцах

Маленькая компания, человек 20. Гендир тонет в задачах. Помнить кто что обещал, отслеживать движение по целям, держать в голове десяток проектов одновременно. У больших корпораций для этого есть штат руководителей среднего звена и проджектов. У малых есть один директор, который пытается быть всем сразу.

Лира берёт на себя часть этой работы. Это не корпоративный чат‑бот, не ChatGPT с настройками компании. Конкретный продукт с конкретными функциями:

  • Знает каждого сотрудника. Должность, чем занимался последний месяц, какие предпочтения, сильные стороны.

  • Помнит обещания. Если сотрудник в чате сказал «доделаю auth до пятницы», Лира это запоминает и в четверг напоминает.

  • Отслеживает цели. У компании есть бизнес‑цели, Лира видит как задачи команды на них работают, кто реально движет, а кто буксует.

  • Пишет гендиру сводки. Каждый день короткая «что было у команды», раз в неделю развёрнутый дайджест с движением по целям.

  • Отвечает в чате обеим сторонам. И гендиру («кто из команды простаивает?»), и сотрудникам («какие у меня сейчас приоритеты?»). С учётом контекста того кто спросил.

Это не теоретический проект. Лира делается под конкретного заказчика, производственное предприятие в России, сейчас в активной разработке. Архитектурное ядро уже работает, проверено на десятках сценариев. UI намеренно не показываю, продукт коммерческий, статья про технические решения внутри.

В этой статье разберу самый интересный кусок: как устроена память агента. Это нетривиальная история, потому что я отказался от стандартного подхода с векторной базой и RAG, и сделал по‑своему. Расскажу почему и что получилось.

Сценарий, через который понятна вся архитектура

Чтобы было понятно зачем все технические решения ниже, представьте конкретную сцену.

Понедельник, утро. Гендир открывает Лиру в чате и спрашивает: «Что у Дмитрия в работе?». Дмитрий, старший разработчик в команде из 20 человек.

Что должно произойти чтобы Лира ответила нормально?

Она должна знать кто такой Дмитрий. Не просто «пользователь с id X», а контекст: должность, за что отвечает, какой стиль работы, какие сейчас цели и задачи.

Она должна помнить недавние обещания. Если на прошлой неделе Дмитрий писал в чат «доделаю рефакторинг auth до пятницы», Лира должна это вытащить и сказать «обещал auth, дедлайн в пятницу, посмотрим».

Она должна различать актуальное и устаревшее. Если Дмитрий вчера сдвинул дедлайн на следующую неделю, Лира не должна продолжать рапортовать про пятницу.

Она должна различать уверенность в фактах. «Дмитрий старший разработчик» это факт из его профиля. «Дмитрий любит работать в наушниках» это вывод из косвенных признаков. В ответе эти вещи должны звучать с разной степенью уверенности.

Стандартный путь решения такой задачи в 2026 году это RAG (Retrieval‑Augmented Generation). Все разговоры, документы и заметки про Дмитрия эмбеддятся в векторную базу, при запросе релевантные чанки достаются по cosine similarity и пихаются в контекст LLM. Это работает для документации и FAQ. Для корпоративного агента который знает людей и события работает хуже. Я попробовал и через две недели упёрся в стену.

Почему я отказался от RAG

Четыре причины. Каждая по отдельности обходится. Все четыре вместе делают RAG неудачным выбором.

Первая. Сходство по эмбеддингу это не релевантность для агента. Запрос «что у Дмитрия в работе?» и запись «Дмитрий обещал доделать рефакторинг auth до пятницы» имеют vector similarity возможно высокий, возможно нет. Зависит от модели эмбеддингов, от соседних чанков, от формулировки. Рулетка. Особенно на русском, где русскоязычные эмбеддинги отстают от английских на одно‑два поколения качества. А когда мне нужна предсказуемость, рулетка не подходит.

Вторая. В векторной базе все чанки равны. Если Дмитрий вчера обещал доделать рефакторинг до пятницы, а сегодня сказал «сдвигаю на следующую неделю», оба факта попадут в RAG. И LLM может вытащить старый и сказать «обещал до пятницы». Можно делать timestamp и сортировать, но это уже не чистый RAG, это начало структурированного хранилища.

Третья. В векторной базе нет метаданных типа «этот факт мы прямо подтвердили, а этот выведен LLM‑ом из косвенных признаков». А для агента это критично. Он не должен говорить «Дмитрий не любит созвоны после 16:00» с тем же тоном что «Дмитрий старший разработчик». Первое наблюдение с confidence=0.5, второе факт с confidence=1.0. RAG это различие стирает.

Четвёртая. Нет авторства. Кто записал? Сам пользователь, другой сотрудник, LLM вывел из переписки? У этих источников разная достоверность, и для агента это критически важная информация. RAG её просто не имеет.

Всё это можно добавить поверх RAG. Но в какой‑то момент становится понятно что vector search это уже не помощь, а помеха. Ты делаешь структурированный поиск с фильтрами и сортировкой, а embedding‑similarity это просто ещё один шумный сигнал. Я отказался от RAG как от основного механизма и сделал по‑другому.

Что вместо

Типизированная память. Таблица в SQLite со схемой:

VALID_KINDS = {"fact", "trait", "commitment", "context", "preference"}

Каждая запись о сотруднике имеет один из пяти типов:

  • fact — объективный факт («работает с 2024 года», «закончил Бауманку»)

  • trait — черта характера или стиля работы («внимательный к деталям»)

  • commitment — обещание которое надо отслеживать («обещал доделать рефакторинг auth до пятницы»)

  • context — контекст разговоров («на прошлой встрече обсуждали миграцию на Postgres»)

  • preference — предпочтения («не любит созвоны после 16:00»)

Плюс метаданные: confidence (0..1), tags, evidence (откуда инфа), author (либо user_id ручного автора, либо agent:reflection / agent:conversation / agent:observation).

class MemoryCreate(BaseModel):
    subject_id: str
    kind: str = Field("fact", pattern="^(fact|trait|commitment|context|preference)$")
    content: str = Field(..., min_length=2, max_length=2000)
    evidence: Optional[str] = None
    confidence: float = Field(0.7, ge=0, le=1)
    tags: list[str] = Field(default_factory=list)

Почему именно эти типы? Не из теории, а из практики. После трёх месяцев итераций я заметил что любая полезная информация про человека ложится в один из этих пяти ящиков. Если новый факт не вписывается, обычно я просто не подумал и его можно переформулировать. Шестого типа никогда не понадобилось.

Архивирование вместо удаления, is_archived=1. Если Дмитрий «не любил созвоны после 16:00», а полгода спустя адаптировался, старая запись архивируется, новая создаётся. История остаётся для контекста.

Граф знаний организации

Параллельно с памятью о людях, память о структуре бизнеса. Это уже не записи, а граф:

VALID_NODE_TYPES = {
    "product", "process", "regulation", "role", "location",
    "customer", "supplier", "equipment", "department", "concept",
}
VALID_RELATIONS = {
    "responsible_for", "depends_on", "part_of", "related_to",
    "supplies", "regulates", "located_at", "owns",
}

Узлы это сущности компании: продукты, процессы, регламенты, ключевые клиенты, оборудование. Связи это кто за что отвечает, что от чего зависит, что чем регулируется.

Самый частый узел product плюс связь responsible_for с пользователем даёт агенту знание «этим продуктом занимается такой‑то человек». Когда пользователь спрашивает «что у нас по линейке X?», агент сразу знает к кому из сотрудников эта тема относится. Без векторного поиска по миллиону документов.

Каждый узел имеет slug который генерируется через транслит русских имён:

def _slugify(text: str) -> str:
    translit = {
        'а':'a','б':'b','в':'v','г':'g','д':'d','е':'e','ё':'yo','ж':'zh',
        # ... полный набор
    }
    text = text.lower().strip()
    out = []
    for ch in text:
        if ch in translit:
            out.append(translit[ch])
        elif ch.isalnum() or ch in '-_':
            out.append(ch)
        elif ch == ' ':
            out.append('-')
    s = ''.join(out)
    s = re.sub(r'-+', '-', s).strip('-')
    return s[:80] or 'node'

Slug нужен для URL и человекочитаемых ссылок. Это мелочь, но она имеет значение. Когда агент в ответах ссылается на узлы графа, он использует slug как ID. Пользователь видит produkt-toplivnye-baki-serii-K1 вместо UUID. Разница между «система для людей» и «система для логов».

Эндпоинт /api/memory/context/{user_id} — главная штука

Главный API метод всей системы. Когда агент собирается ответить пользователю, первым делом он запрашивает контекст:

@router.get("/context/{target_id}")
def get_user_context(target_id: str, user: dict = Depends(require_auth)):
    """
    Собранный контекст про сотрудника для использования в промпте агента
    или для отображения в карточке сотрудника.
    """
    with get_db() as db:
        # ... проверки прав
        cur = db.cursor()

        # Профиль и членство в орг
        cur.execute("""SELECT u.id, u.full_name, u.name, m.role, m.position
                       FROM users u
                       JOIN org_members m ON m.user_id = u.id ...""", ...)
        profile = cur.fetchone()

        # Командная память
        cur.execute("""SELECT id, kind, content, confidence, created_at, author
                       FROM team_memory
                       WHERE org_id = ? AND subject_id = ? AND is_archived = 0
                       ORDER BY kind, confidence DESC, id DESC LIMIT 100""", ...)
        memory = [dict(r) for r in cur.fetchall()]

        # Активные обещания (commitment kind, не выполнены)
        commitments = [m for m in memory if m["kind"] == "commitment"]

        # Узлы графа где он owner
        cur.execute("""SELECT id, node_type, title, slug
                       FROM knowledge_nodes
                       WHERE org_id = ? AND owner_user_id = ? AND is_archived = 0""", ...)
        owned_nodes = [dict(r) for r in cur.fetchall()]

        # Активные задачи (с приоритетной сортировкой)
        cur.execute("""SELECT id, title, status, priority, due_at
                       FROM user_todos
                       WHERE assignee_id = ? AND status IN ('open', 'in_progress')
                       ORDER BY CASE priority WHEN 'urgent' THEN 0 WHEN 'high' THEN 1
                                              WHEN 'normal' THEN 2 ELSE 3 END,
                                created_at DESC LIMIT 20""", ...)
        active_tasks = [dict(r) for r in cur.fetchall()]

        # Последние 5 reflection (см. ниже)
        # ... + baseline + ещё несколько источников

        return { "profile": ..., "memory": memory, "commitments": commitments,
                 "owned_nodes": owned_nodes, "active_tasks": active_tasks,
                 "recent_reflections": ..., "baseline": ... }

Это детерминированный запрос. Никакого vector search, никаких embeddings. Один SQL на несколько таблиц, всё с правильными индексами и сортировкой. На любом разумном объёме данных (миллионы записей в одной организации) это работает за единицы миллисекунд.

Эту структуру я потом склеиваю в текстовый блок и кладу в системный промпт агента. Грубо так:

Профиль: Дмитрий Кузнецов, старший разработчик
Активные обещания:
- Доделать рефакторинг auth до пятницы (confidence: 0.9)
- Подготовить план миграции на Postgres (confidence: 0.7)
Известные предпочтения:
- Не любит созвоны после 16:00
Отвечает за:
- Продукт: Внутренний CRM
- Процесс: Деплой production
Текущие задачи (топ-5 по приоритету):
- [URGENT] Починить очередь email-уведомлений
- [HIGH] Обновить зависимости в auth-service
- ...

И только теперь агент пишет ответ. Контекст детерминированный, проверяемый, структурированный. Если в ответе что‑то не так, я могу зайти в БД и посмотреть. Вот память, вот граф, вот задачи. Откуда взялась галлюцинация? Часто из того что какой‑то факт записан некорректно, и это можно исправить точечно, а не «переэмбедить весь корпус».

Планировщик: LLM декомпозирует цель в pipeline

Второй кусок системы это автоматическое разбиение крупной цели на конкретные шаги. Когда директор создаёт цель «обновить главный сайт за месяц», Лира декомпозирует её в 3–10 шагов и кладёт в agent_pipelines.

async def decompose_goal(
    user_id: str, org_id: str, name: str, goal: str,
    context_hint: Optional[str] = None,
) -> dict:
    # Собираем контекст исполнителя
    with get_db() as db:
        # 1. Профиль и должность
        # 2. Узлы графа где он owner (за что отвечает)
        # 3. Топ-8 заметок team_memory с самым высоким confidence
        ...

    parts = ["Контекст исполнителя:"]
    if profile_str:
        parts.append(f"- {profile_str}")
    if owned:
        parts.append("- отвечает за: " + ", ".join(
            f"{o['title']} ({o['node_type']})" for o in owned[:6]))
    if memory:
        parts.append("- известно: " + "; ".join(m["content"][:80] for m in memory[:5]))

    context = "\n".join(parts)

    instruction = (
        "Ты Лира, корпоративный ИИ-директор. Разложи цель на 3-10 конкретных "
        "шагов. Каждый шаг должен быть выполнимым самостоятельно и иметь ясный "
        "результат. Не пиши очевидных шагов вроде «обдумать», «обсудить»."
        f"\n\n{context}\n\n"
        f"Название: {name}\n"
        f"Цель: {goal}\n\n"
        "Верни строго JSON (без обёрток):\n"
        '{ "steps": [{"title": "...", "description": "..."}] }'
    )

    # Вызов Claude Haiku 4.5
    async with httpx.AsyncClient(timeout=60) as client:
        r = await client.post("https://api.anthropic.com/v1/messages", json={
            "model": "claude-haiku-4-5-20251001",
            "max_tokens": 2000,
            "messages": [{"role": "user", "content": instruction}],
        }, headers=...)
        text = r.json().get("content", [{}])[0].get("text", "").strip()
        parsed = json.loads(text)
        steps = parsed.get("steps") or []

    # Fallback если LLM не сработал
    if not steps:
        steps = [{"title": name, "description": goal}]

    # Записать pipeline + steps в БД
    ...

Важная штука здесь: контекст исполнителя в промпте. Я не просто прошу «разложи цель на шаги». Я прошу «разложи цель на шаги учитывая что исполнитель это Дмитрий Кузнецов, старший разработчик, отвечает за CRM и деплой, и про него известно что он не любит созвоны после 16:00».

Разница в качестве шагов огромная. Без контекста модель пишет шаблон. С контекстом конкретику с учётом роли. «Согласовать архитектуру с командой CRM» вместо «Согласовать архитектуру». Шаг сразу выполним, потому что понятно с кем согласовывать.

claude-haiku-4-5-20251001 тут выбран не случайно. Полноценный Opus стоит дорого, а задача декомпозиции это простое структурное мышление, не требующее глубокого reasoning. Haiku справляется почти всегда. Когда не справляется, fallback в один шаг = вся цель, чтобы система не падала.

Reflection: ежедневная самооценка агента

Третий ключевой кусок: автоматический агент который пишет сводки.

Каждый вечер (запускается через scheduler в 19:00) для каждого активного сотрудника генерируется reflection:

async def collect_user_day(user_id: str, org_id: str, day: str) -> dict:
    """Собирает все события дня для одного пользователя."""
    day_start = f"{day}T00:00:00+00:00"
    next_day = (datetime.fromisoformat(day_start) + timedelta(days=1)).isoformat()

    with get_db() as db:
        cur = db.cursor()
        # 1. Профиль (имя, должность)
        # 2. Задачи: завершённые сегодня, новые сегодня, открытые на конец дня
        # 3. Комментарии в задачах за день
        # 4. Pipeline-шаги отмеченные сегодня
        # 5. Движение по целям где он owner или contributor
        # 6. Замеры по целям сделанные сегодня
        ...

Это всё детерминированная сборка из БД, никаких LLM‑вызовов на этом этапе. Потом всё идёт в Haiku с инструкцией:

«Собери структурированный отчёт за день. Верни JSON с полями: summary (1–2 предложения), highlights (что важного), commitments (что обещано), risks (проблемы), observations (нейтральные наблюдения).»

LLM возвращает структурированный JSON. Сохраняется в agent_reflections с UNIQUE constraint по (user_id, day). Нельзя случайно создать два отчёта за один день.

И вот тут происходит главная магия. Из поля commitments LLM‑ответа я автоматически создаю записи в team_memory:

# Псевдокод (реальная логика разнесена по нескольким функциям)
for commitment_text in reflection_json.get("commitments", []):
    create_memory(
        subject_id=user_id,
        kind="commitment",
        content=commitment_text,
        confidence=0.6,  # LLM-извлечённое, средняя уверенность
        author="agent:reflection",
        evidence=f"Reflection {day}"
    )

Confidence=0.6 потому что это LLM‑извлечённый факт, не подтверждённый. Author='agent:reflection' чтобы потом можно было отличить от ручных записей. Evidence указывает на источник.

Завтра когда тот же пользователь напишет в чат «что мне сегодня нужно сделать?», Лира в контексте увидит обещания которые он сам себе вчера дал. И напомнит. Не как «я тебя не отпущу пока не сделаешь», а как нормальный коллега: «помнишь обещал доделать auth до пятницы, сегодня уже четверг».

Это пример того как типизация памяти превращается в продукт. Если бы это был RAG, «обещания» были бы просто чанками текста среди тысяч других. С типизацией это запросимый класс данных с понятной семантикой.

Что бы я сделал по‑другому

Меньше типов с самого начала. В первой версии я сделал семь типов памяти, потом сократил до пяти. Думаю можно было и до трёх: fact, commitment, observation. Меньше типов = легче решать «куда положить это», и LLM путается меньше. Но переход уже сделан, переименовывать сейчас дорого.

Confidence‑decay по времени. Сейчас confidence это статическое поле. Записал и оно навсегда. Для большинства фактов confidence должен падать со временем. Если факт о человеке записан два года назад и с тех пор не подтверждался, он уже не такой надёжный. Это в roadmap.

Embeddings всё‑таки нужны, но не как primary. Я отказался от RAG как от основного механизма, но vector similarity полезна для поиска похожих записей при создании, чтобы не дублировать. Сейчас этого нет, система создаёт дубликаты, потом я руками архивирую. Embeddings как secondary index это следующая большая фича.

Не Haiku, а маршрутизация моделей. Для декомпозиции целей Haiku хватает. Для reflection на грани, особенно когда событий много. Для финального ответа агента в чат иногда нужен Sonnet, иногда хватает Haiku, иногда вообще можно дать ответ без LLM (если запрос явно структурный). Хочу сделать routing, но это новый класс сложности.

Заключение, возвращаясь к Лире

В начале статьи я описал сцену. Гендир спрашивает «что у Дмитрия в работе?», и Лира должна ответить нормально. Теперь весь стек ясен.

Когда такой запрос приходит, агент дёргает /api/memory/context/{dmitry_id}. Один SQL на несколько таблиц возвращает: профиль Дмитрия, его активные обещания (отфильтрованные по kind='commitment'), узлы графа знаний где он owner, активные задачи с приоритетной сортировкой, последние 5 reflection‑сводок, baseline от его руководителя. Всё собирается в текстовый блок, кладётся в системный промпт, и только после этого агент формулирует ответ.

Ответ получается не «по чанкам из векторной базы», а по детерминированной выборке структурированных данных. Если в ответе что‑то странно, я открываю БД и вижу почему. Если факт устарел, он архивирован, не в выборке. Если что‑то выведено LLM с низкой уверенностью, это видно по полю confidence и author='agent:reflection'.

Главный тезис. Для агентов которые работают с людьми и событиями типизированная память сильнее vector search. Это не «RAG плохой, я придумал лучше». RAG отлично работает когда у тебя гигабайты документации и пользователь задаёт открытый вопрос. Когда у тебя сотни структурированных событий про десятки людей, структурированное хранилище с типами и метаданными работает быстрее, предсказуемее и легче отлаживается.

Лира как продукт ещё в активной разработке, есть много чего что нужно дополнительно. Но архитектурное ядро (memory + planner + reflection) уже стабильное. И от того насколько правильно оно построено, зависит реально ли агент будет полезен или превратится в очередной «корпоративный ChatGPT с лого компании». Надеюсь этот опыт пригодится тем кто строит что‑то похожее. Для команды, для семьи, для себя.


Это шестая статья из моей серии. В предыдущих было про мобильный мессенджер ONEMIX: трёхуровневый кэш, Double Ratchet E2E, WebRTC звонки, vanilla Electron, и мнение про вайб‑кодинг. Эта про другой проект, Лиру, AI‑агента для бизнеса.

Если интересна реализация конкретных кусков (как именно склеивается контекст для промпта, как устроен scheduler, как обходится отсутствие LLM‑ключа в reflection), пишите, разберём в комментариях или следующей статье.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

21.57%Да, если бы был готовый продукт11

37.25%Скорее да, но только под собственный самохостинг19

9.8%Скорее нет — не доверяю AI принимать решения о людях5

11.76%Нет, это лишний слой между мной и командой6

19.61%У меня нет команды10

Проголосовал 51 пользователь. Воздержались 6 пользователей.