PROJECT RETROSPECTIVE · ИЮЛЬ 2026

История разработки: от одного Python-скрипта до агентской платформы с памятью, десятью инструментами, веб-интерфейсом и собственной иконкой в Dock.

ВЕРСИЯv1.2
ИНСТРУМЕНТОВ10
ДВИЖОКqwen3:8b
ЖЕЛЕЗОM4 · 16 ГБ
01 · Философия

Два принципа, из которых следует всё остальное

ПРИНЦИП 01 «Интеллект, память и инструменты принадлежат платформе, а не модели.»
ПРИНЦИП 02 «Полная локальность. Единственное исключение — web_search.»

Модель — сменная деталь: сегодня qwen3:8b через Ollama, завтра любая другая. Долговременная память, характер и набор инструментов живут в коде и в базе, а не в весах модели. Заменить движок — не значит потерять ассистента.

02 · Хронология

Как проект рос

Две недели: от структуры папок до приложения в Dock.

Начальная архитектура: main.py, models/qwen.py

Память: SQLite + факты (core/assistant.py)

Cursor → Claude Code (лимит free-плана)

Личность: personality/system.md

Инструменты: 3 → 6 → 10

Планировщик: задачи и напоминания

Исходный план закрыт полностью — веха

Обслуживание: v0.4 → v1.0

Веб-интерфейс: FastAPI + SSE

Запуск одной иконкой: TrifonAI.app

03 · Архитектура

Слои платформы

Клиенты взаимозаменяемы; ядро одно.

КЛИЕНТЫ
main.pyтерминал
ui/server.pyFastAPI + SSE
ЯДРО →
core/assistant.pyоркестрация
core/text · confirmчистка · подтверждение
СЛОИ ПЛАТФОРМЫ →
tools/10 инструментов
memory/диалоги · факты · задачи
personality/system.md
ВНЕШНИЕ РЕСУРСЫ →
SQLitetrifonai.db
Ollamaqwen3:8b
DuckDuckGoединственный выход в сеть

Рис. 1 — Слои платформы. Клиенты взаимозаменяемы; ядро одно.

Карта файлов
ПУТЬОТВЕТСТВЕННОСТЬ
main.pyТерминальная версия — тонкий UI-цикл
core/assistant.pyКласс Assistant: оркестрация модели, памяти и инструментов
core/text.pyclean_model_output() — чистка ANSI-кодов Ollama
core/memory_evaluator.pyИзвлечение фактов и смысловая дедупликация через модель
core/reminders.pyПоказ актуальных напоминаний при старте
core/confirm.pyАбстракция подтверждения записи: терминал и веб
core/version.pyЕдиный источник версии (v1.2)
memory/database.pySQLite: таблицы conversations, facts, tasks
models/qwen.pyВызов Ollama; поиск бинарника по PATH с фолбэками
tools/registry.pyРеестр инструментов и протокол вызова
personality/system.mdХарактер, идентичность, правила работы с памятью
ui/static/index.htmlВесь веб-фронтенд одним файлом
scripts/*.shЗапуск и остановка веб-версии, сборка .app
CLAUDE.mdКонтекст проекта для Claude Code
04 · Инструменты

Десять инструментов

Момент, когда ассистент перестал быть говорящей головой.

ИНСТРУМЕНТМОДУЛЬЧТО ДЕЛАЕТ
list_filestools/basic.pyСодержимое папки
read_filetools/basic.pyЧтение файла
write_filetools/basic.pyЗапись файла — только после подтверждения человеком
search_filestools/basic.pyПоиск по файлам
recall_memorytools/basic.pyДоступ к собственной памяти — фактам и истории
add_tasktools/tasks.pyНовая задача или напоминание
list_taskstools/tasks.pyСписок активных задач
complete_tasktools/tasks.pyЗакрыть задачу
complete_all_taskstools/tasks.pyБатч-закрытие — обход слабости модели в длинных цепочках
web_searchtools/web.pyПоиск в интернете через ddgs (DuckDuckGo)

get_current_time был одиннадцатым и удалён осознанно: текущее время подаётся в контекст всегда, отдельный вызов оказался лишним звеном и источником ошибок в датах.

05 · Инженерные истории

Баги, где симптом врал о причине

Четыре случая, где путь от симптома до причины был неочевиден.

3.1 · ANSI escape-коды Ollama в сохранённом тексте
СИМПТОМ
В базе и в интерфейсе в тексте ответов попадался мусор вокруг нормальных слов.
ПРИЧИНА
Ollama в CLI-режиме подмешивает ANSI escape-коды. В терминале мусор был невиден — терминал его исполнял, а не печатал. В базу он попадал как есть.
РЕШЕНИЕ
Отдельный слой очистки — core/text.py, функция clean_model_output(). Единая точка: всё от модели проходит через неё.
3.2 · SQLite LOWER() не работает с кириллицей
СИМПТОМ
Поиск по памяти находил не всё: запрос «москва» не находил факт со словом «Москва».
ПРИЧИНА
Встроенный LOWER() в SQLite работает только с ASCII. SQL был корректен; неверным было предположение о его семантике.
РЕШЕНИЕ
Приведение регистра перенесено в Python, где Unicode работает как ожидается — на стороне запроса, а не движка базы.
3.5 · macOS TCC: права привязаны к подписи процесса
СИМПТОМ
После сборки .app инструменты работы с файлами перестали видеть содержимое папок, хотя «Полный доступ к диску» был выдан.
ПРИЧИНА
TCC привязывает разрешения к подписи процесса, а не к папке .app. Обёртка и интерпретатор — разные субъекты. Право было выдано не тому, кто обращался к диску.
РЕШЕНИЕ
Разбираться с моделью безопасности системы, а не с кодом. Ни одной строкой Python это не лечилось.
3.7 · threading.local в пуле потоков Starlette
СИМПТОМ
События терялись: часть вызовов инструментов не доходила до браузера. Непредсказуемо — то доходили, то нет.
ПРИЧИНА
Канал событий хранился в threading.local. Starlette выполняет синхронный код в пуле потоков — код, публикующий событие, видел пустое хранилище вместо канала.
РЕШЕНИЕ
Отказ от привязки контекста к потоку — контекст передаётся явно, по границе запроса. Симптом указывал на транспорт, причина была в модели исполнения фреймворка.

ещё 4 разобранных бага — в полной ретроспективе (PDF ниже).

06 · Ограничения

Честно о компромиссах

07 · Что дальше

Toward a weak local Claude Code

Платформа к этому готова архитектурно: реестр инструментов расширяется одним файлом, подтверждение абстрагировано под два интерфейса, память и личность от модели не зависят.

PDF · РЕТРОСПЕКТИВА

Скачать полную ретроспективу

9 глав · 8 разобранных багов · архитектура и карта файлов

↓ СКАЧАТЬ PDF