Отдельный организм: тело — отсканированная нервная система (коннектом червя, позже мухи), мозг — LLM (сейчас qwen2.5-0.5b q4_k_m, старше — 1.5b/12b по замеру).
Не часть AI_UI и не зависит от него: своя программа запуска, своя панель-окно,
свой llama-server, свой мозг-модель внутри проекта, свои логи. Из AI_UI пришло
только тело (worm.json) — копией, в свой дом, как и модель.
- Понять на практике, что даёт настоящая нервная система, отточенная эволюцией, когда к ней подключают языковую модель. Игрушка — первая фаза, а не приговор.
- Дать оператору живой инструмент: он ставит миссии через панель, организм их исполняет и отчитывается. Первая миссия — аудит своей сети («где у меня что открыто и куда я вообще дотягиваюсь»).
| слой | что это | чего стоит |
|---|---|---|
| 1. рефлексы | импульс по коннектому, body.step() — тело живёт само |
бесплатно, модель не нужна |
| 2. мозг 0.5b q4_k_m | заметить, назвать, напомнить, коротко решить | локальный GPU/CPU |
| 3. старший | внешний вызов, когда 0.5b не тянет (12b, позже) | дорого, по нужде |
Честная оговорка: 0.5b — «дежурный», а не «решающий». Его сила — быть всегда на месте и не выдумывать лишнего. Рассуждать за старшего он не станет: на прямой вопрос «кто ты» отвечает «Я — Synapse, червя» — это и есть его потолок, и место для 12b уже подготовлено тем же самым кодом.
Апгрейд мозга проверен, а не обещан (26.09.2026): q2_k заменён на q4_k_m (тот же размер,
качество выше), а 1.5B q4_k_m лежит рядом в models/brain/ и просто выбирается в окне.
Честные замеры (llama-bench на 940MX; pp128 — разбег промпта, tg32 — ответ):
| модель | -ngl |
разбег, ток/с | ответ, ток/с |
|---|---|---|---|
| 0.5b q4_k_m | 10 | 217 | 15.5 |
| 0.5b q4_k_m | 99 | 340 | 33.8 |
| 1.5b q4_k_m | 10 | 83 | 3.0 |
| 1.5b q4_k_m | 99 | 129 | 15.7 |
Отсюда две вещи, которые важнее выбора модели. Первая: -ngl 10 — ловушка для 1.5b
(3.0 ток/с против 15.7 при полной загрузке); для 0.5b та же граница мягче. Вторая: моё прежнее
число «2.3 t/s живьём» было ошибкой замера — я сравнила чистую генерацию (llama-bench) с
целым ходом через сервер, то есть с загрузкой модели и разбегом промпта. Живой замер через
настоящий сервер сейчас: разбег 122 токена на 153 ток/с, ответ 64 токена на 13.3 ток/с.
Значит «он овощ» — не про скорость, а про качество 0.5b: сам промпт думания сложнее, чем малый
мозг умеет (0.5b зацикливается на 💭 или эхом возвращает промпт, хотя на прямой вопрос
отвечает по делу). Поэтому следующий шаг — короткий промпт для малого мозга, а не «модель
побольше».
data/body/worm.json — формат connectome-graph/1: 302 узла, 3004 связи,
у каждого нейрона имя (ADAL, AVAR…), координаты, вес и нейромедиатор.
Это C. elegans — единственный организм, чья нервная система отсканирована целиком.
Импульс живёт по закону
act ← eff·act + gain·Σ(ЗНАК источника · доля связи · act источника),
где eff = min(0.95, decay + sustain·энергия), а всё, что ниже порога, гаснет.
Знак источника — из его медиатора: тормозной нейрон ВЫЧИТАЕТ, а не «возбуждает
слабо» (см. «Физиология передачи» ниже). Умолчание body.nt.enabled=true; с
false тело ведёт себя ровно как до физиологии — это и есть контроль в тестах.
Числа подобраны замером, а не на глаз. Метрика всюду одна: пик — сколько
узлов было выше порога 0.02 в самый яркий такт, «гаснет» — на каком такте не
осталось ни одного. Текст стимула влияет (seed = crc32 текста: разные нейроны —
разная волна), поэтому в таблице пять текстов, а точные точки закреплены тестом
tests/test_qa_fixes.py. Замеры обновлены 25.09.2026: прежние числа не
воспроизводились — это нашёл независимый QA (Орест), и был прав.
| decay | gain | порог | что вышло |
|---|---|---|---|
| 0.70 | 0.30 | 0.02 | рабочее: k=3 — пик 9–18, гаснет за 26–47 тактов; k=8 — пик 30–46, гаснет за 48–110 |
| 0.70 | 0.30 | 0.02 | k=30 (в окне недостижимо — там максимум 6) — пик 222–231 и не гаснет: срывается и рабочее |
| 0.75 | 0.35 | 0.02 | припадок: пик 16–200 и не гаснет (на k=3 — 19, никогда) |
| 0.72 | 0.55 | 0.02 | припадок: 208–226 зажжены, не гаснет |
| 0.75 | 0.35 | 0.005 | не гаснет вообще: порог и есть смертность |
Вывод, который стоит помнить: тело срывается, когда decay + gain > 1
(0.70 + 0.30 = ровно 1.0 — это граница, и на ней тело живёт, пока вход не слишком
сильный). Живое ≠ возбуждённое до конца.
Честно про sustain (нашла 25.09.2026, разбирая QA-отчёт). В формуле
eff = min(0.95, decay + sustain·энергия) это коэффициент СОХРАНЕНИЯ
(act ← eff·act), а не скорость затухания. Значит sustain сохранение
увеличивает: это САМОПОДДЕРЖКА — чем активнее нейрон, тем дольше он себя держит.
Замер на восьми стимулированных нейронах: sustain 0 — гаснет за 43 такта,
sustain 1.2 (умолчание) — за 48–110, sustain 4 — припадок (пик 219, не гаснет).
Настоящая страховка — decay и порог. Переименовано в sustain по слову Стаса.
Мой первый заход (v0.1) зажимал тормоз
в 1.0, а eff = 1.0 значит «не гаснет вообще», то есть тормоз работал ровно
наоборот; зажим теперь 0.95.
Слово Стаса: «конечно делай всё по настоящему, мы же не для декораций делаем».
До этого медиатор влиял только на краску: волна шла по весам связей, и
тормозной нейрон вёл себя как возбуждающий. Теперь знак берётся из ДАННЫХ — из
медиатора самого нейрона (synapse/pharmacology.py), а не из таблицы «на глаз».
| кто | сколько | что делает | откуда известно |
|---|---|---|---|
| GABA — тормозит | 26 | ВЫЧИТАЕТ из возбуждения целей (гиперполяризация) | DD1–DD6 и VD1–VD13 — тормозные мотонейроны мускулатуры тела, RME — мышц головы, AVL/DVB — кишки, RIS — сна |
| ацетилхолин | 129 | возбуждает: моторные нейроны → мышцы | основной возбуждающий медиатор C. elegans |
| глутамат | 71 | в графе нейрон-нейрон возбуждает; у нервно-мышечного соединения тормозит через GluCl, но мышц в нашем графе нет | переключается настройкой body.nt.glutamate_inhibitory |
| моноамины: серотонин 7, дофамин 8, октопамин 2 | 17 | не проводят быстрый ток: меняют ВОЗБУДИМОСТЬ тех же целей (тонус) | объёмная передача, метаботропные рецепторы |
| медиатор не назван атласом | 59 | нейтральный знак возбуждения, честно серые | неизвестность не повод выдумывать торможение |
Числа — замер, а не вкус (метрика та же: пик и «гаснет»):
| что мерили | физиология ВЫКЛ (контроль) | физиология ВКЛ |
|---|---|---|
рабочая точка, k=3 (текст alpha) |
пик 16, гаснет 29 | пик 16, гаснет 28 |
| рабочая точка, k=8 | пик 46, гаснет 48 | пик 45, гаснет 45 |
| импульс только в 26 GABA-узлов | цели возбуждены: 11.64 | цели почти молчат: 1.99 |
| порог срыва (максимальный k, что ещё гаснет) | 14 | 16 с одним торможением · 14 с торможением и тонусом |
| k=6 после серотонина / после дофамина | 44 / 44 (медиатор ни при чём) | 46 (бодрость держит) / 41 (замедление гасит) |
| залили тонус 0.4 всем узлам | — | тело молчит через 30 тактов (тонус не держит его живым) |
Три вывода, которые стоит помнить.
- Торможение тело УКРЕПЛЯЕТ. Одна настоящая GABA поднимает порог срыва с 14 до 16: то, что раньше уходило в припадок, теперь само себя останавливает.
- Тонус работает не током, а возбудимостью. Первая версия ДОБАВЛЯЛА тонус как ток — и тело залипало: серотониновые NSM держали петлю вечно (замер: k=3 не гас НИКОГДА). Моноамины действуют через метаботропные рецепторы: сами канал не открывают, а меняют усиление. Поэтому тонус множит СОХРАНЕНИЕ, а оно остаётся < 0.95 — и структурно не может держать тело живым без импульса.
- Цена. Такт тела: 1.79 мс без физиологии, 2.63 мс с полной (+0.84 мс), а одно торможение даже дешевле контроля (1.59 мс) — 17 модуляторов перестали гонять быстрый ток по 325 связям.
Настройки (вкладка «Настройки» их не трогает — это data/config.json, раздел
body.nt): enabled (весь знак), modulation (тонус моноаминов),
inhibitory_gain (во сколько раз торможение сильнее единицы связи),
glutamate_inhibitory, tone_strength (0.10), tone_decay (0.92), tone_cap
(0.20). Видно это в окне: в раскраске по медиаторам легенда пишет не только цвет,
но и что медиатор делает («GABA · тормозной 26», «Dopamine · модулирующий 8»),
а под легендой стоит строка передачи; в карточке узла — «передача: тормозной».
Ролей больше не «эвристика по именам». tools/build_roles.py собирает карту
data/body/neuron_roles.json из выгрузки OpenWorm (owmeta v0.12.3, файл
ConnectomeLab/data/owmeta_cache.json): типы, медиаторы, рецепторы — на все 302.
| роль | сколько | кто это |
|---|---|---|
| motor | 120 | VA/VB/VC/VD/DA/DB/DD, AS, RMD/RME/RMF/RMH, SMD/SMB, M1–M5… |
| sensory | 84 | ADF/ADL/AFD/ASE/ASG, AWA/AWB/AWC, ALM/AVM/PLM/PVM, PVD, BAG, IL2, CEP… |
| inter | 69 | AV*, PV*, RI*, AI*, DVA?, RIM/RIV/RID… |
| polymodal | 27 | IL1, M3, MC, NSM, URA, VB — атлас говорит «motor + sensory» |
| other | 2 | CANL/CANR — клетки без синапсов, честно помечены, а не выдуманы |
Зоны тела (проверено по атласу, а не на глаз): голова 211, середина 32, хвост 59.
Грабля знака: в этой выгрузке голова — там, где отрицательный y (амфидные
ADLL/ASEL/AWAL при y≈−0.44, хвостовые PHCL/PLML при y≈+1.43). Первый заход взял
ось наоборот и выдал «210 хвостовых» — ошибка знака, а не биология.
Хабы — командные вставочные, и они же самые связные узлы коннектома: AVAL (79 связей), AVAR (72), RIAL (63), RIAR (62), AVDR (60), DVA (60).
Полимодальные узлы (IL1/M3/MC/NSM/URA/VB) входят и в сенсорный набор, и в «руки» —
поэтому стимул бьёт и по ним, а body.pool("motor") = 147 узлов, "sensory" = 111.
python run.py # или: python run.py panel
Synapse.pyw # двойной клик — окно без чёрной консоли
Что в окне: живое тело (крутится мышью, приближается колесом, клик по узлу — его карточка, и выбранный узел при этом вспыхивает белым), кнопки жизни (Жить / Пауза / Такт / Импульс / Сброс вида), раскраска по ролям, по медиаторам или по зонам тела, вкладки Миссии, Журнал (аудит), Мысли (внутренняя речь 💭), Отчёты, Мозг (включить свой сервер, скопировать модель к себе, спросить), Настройки.
Под телом две статусные строки (FixedLine, высота задана раз и навсегда): строка ответа —
что вышло от последнего нажатия, и цифры тела. Место под карточку узла зарезервировано, но
до клика она ПУСТА: подсказок-заглушек под кнопками нет (слово 26.09.2026: «убери кучу переносов и
ненужных подсказок под кнопками и графикой — оставить только 2 статусные строки»). Карточка
переносит текст (FixedLine(wrap=True)), поэтому её читаешь целиком, а не по одной обрезанной
строке. Повод у фиксированной высоты — жалоба 26.09.2026: «постоянно в каждый тик что то моргает в
статусной строке под графическим экраном, смещая все строки ломая на секунду весь интерфейс»:
подписи были с переносом слов, длина менялась каждый такт, и Qt пересобирал всю левую колонку.
Легенда цветов переехала во вкладку «Настройки» (группа «Цвета тела»): «оно не нужно постоянно смотреть на неё», и под телом она ещё и смещала вид.
Стас сказал: «как давать миссии я не понял — писать вручную?». Поэтому форма говорит сама за себя:
- Что сделать — инструмент выбирается КОМАНДОЙ, как он и записан в очереди:
net_audit,ask,remote,stimulus,note,write_probe,certs,diff(рядом — короткое пояснение по-английски). - Поля включаются по делу: аудиту нужна подсеть, остальным — текст задания; лишнее поле гаснет, а не притворяется нужным.
- Под формой — подсказка, что инструмент делает, и проверка ДО постановки: «ready to queue» или «subnet required / the door says no: …». Отказ виден ДО нажатия, а не в журнале после.
- Run now — поставит в очередь и сразу сделает такт; Queue — оставит ждать ближайшего такта; My network — подставит подсеть этой машины; Delete selected — убрать выбранную.
- Таблица внизу — очередь и история (статусы
queued / running / done / failed / refused), а под ней полный итог выбранной миссии.
Из консоли то же самое: python run.py mission add --kind net_audit --cidr ....
Стас: «отчёты не читаемы — я ничего в них не понимаю». Сырой JSON остаётся для
кода, а рядом ложится <то же имя>.txt, который читается глазами: когда и что
сканировали, сводка по службам («445 SMB — общие папки Windows: 4 хоста»), каждый
хост по одному с переводом баннеров («SSH назвался: OpenSSH_7.4», «ответ: 302
Moved Temporarily»), общие папки именами и раздел «что это значит».
Честность зашита в текст, а не подразумевается: «права НЕ проверялись» и «проба
„куда есть доступ на запись“ НЕ запускалась — нужно твоё отдельное слово». В списке
отчётов — подпись («аудит сети 192.168.1.0/24 · живых 7 · 24.09 16:26»), а не имя
файла; кнопка Открыть файлом показывает .txt системным просмотрщиком.
Из консоли: python run.py reports, reports --show 1, reports --rebuild.
Панель управления живёт на видеокарте: synapse/qt_panel.py — Qt + OpenGL (PySide6).
Линии связей и точки нейронов рисует GPU, цвет и размер приходят на каждую вершину.
Двойной клик по Synapse.pyw идёт через C:\WINDOWS\pyw.exe и запускал окно на
C:\Python314\pythonw.exe, где нет ни PySide6, ни Pillow. Панель молча уходила
на tkinter-путь и рисовала тело canvas-ом — то есть Qt-панель на видеокарте в запуске
оператора вообще не участвовала. Отсюда и «тормоза такие же» (замер 25.09).
Теперь synapse/launcher.py спрашивает интерпретатор, есть ли в нём PySide6, и если
нет — ищет такой (конфиг → PATH → локальные установки) и перезапускает окно им. Выбор
помнит в конфиге. Нет Qt нигде — окно откроется запасным путём, но в журнале будет
panel.fallback с причиной, а не тишина. Раздел конфига:
"ui": {"prefer": "qt", "python": "", "python_candidates": []}prefer: tk — не искать Qt вовсе; python — готовый путь к интерпретатору с Qt;
python_candidates — где ещё искать (сейчас там venv соседнего проекта, это мостик).
Замер, который определил устройство (24.09.2026):
| что меряем | было (tkinter) | стало (Qt + OpenGL) |
|---|---|---|
| кадр при 1366x768 | 20 мс на кадр, 27–28 кадр/с | 4–7 мс, 43 кадр/с |
| кадр при 1920x1080 | 56 мс (рисование 29 + передача картинки 11 + перерисовка 16) | 40 кадр/с, отрисовка на GPU 0.32–0.47 мс |
| цена от размера окна | растёт линейно по пикселям | почти не зависит: пиксели платит GPU |
Главная находка замера: дорогая была не видеокарта, а цикл Python. Первый заход дал
build 16.3 мс / draw 0.3 мс. Поэтому положения вершин собираются РЕДКО (поворот, зум,
ресайз), а каждый кадр точечно обновляется только цвет и яркость — и только у изменившихся
связей. Цвет нагрева — таблица из 33 ступеней вместо разбора hex-строк 900 раз за кадр.
Кнопки: Live / Pause / Step / Pulse / Reset view, раскраска по ролям, трансмиттерам, зонам. Вкладки: Missions / Journal / Reports / Brain / Settings. Клик по нейрону — карточка узла. Такт с миссией и мозгом идёт в фоновом потоке: окно дышит, пока модель думает. У каждой кнопки есть подсказка при наведении.
Причина — слово Стаса: «сделай темную тему… сделай уже с локалями, как обычно i18n… какой то хаус у нас с языком вышел». Так и было: русские строки лежали прямо в коде панели, отчётов и речи существа, поэтому одна переведённая подпись не делала погоды.
Правило языка, железное: техническое — ВСЕГДА английское и через словарь не проходит;
интерфейс — переводится. К первому относятся имена команд (net_audit, write_probe,
certs, diff, stimulus, note, ask), состояния миссий (queued, running, done,
failed, refused), события журнала и его поля, ключи настроек, режимы мозга (off,
server), имена файлов. Оператор видит ровно то слово, которое лежит в очереди миссий, и
может искать по нему. Ко второму — заголовки, кнопки, подписи полей, подсказки, текст
отчётов, «что это значит» и речь самого существа.
Где живёт язык: synapse/i18n.py — один словарь на весь проект (ключ → английский,
русский). Нет ключа — t() возвращает САМ КЛЮЧ (на кнопке будет btn.live), а не пустоту:
ошибку видно глазами, а не прячем. Английский — язык по умолчанию, русский переключается
во вкладке настроек или строкой ui.lang в data/config.json.
Отчёты и речь следуют языку интерфейса. JSON — истина, .txt — снимок для глаз; поэтому
панель рисует отчёт ЗАНОВО из JSON на текущем языке, а готовый .txt открывает только если
JSON уже нет. Иначе после смены языка старые отчёты остались бы на прежнем — тот самый хаус.
Слово Стаса: «ты ерунду какую-то сделала - теперь у меня окно в экран не влазит. при том что FullHD - сделай нормально универсально под любой экран».
Причина — замер, а не догадка: окно требовало минимум 2246x765 при FullHD 1920. Виноват
не «сложный интерфейс», а закон Qt: у QLabel БЕЗ переноса слов minimumSizeHint() равен ширине
ВСЕГО текста. Подпись тела просила 1488 px, строка ответа — 960, а с полотном тела (430) выходило
2246: окно физически не могло стать уже экрана. (Строки, сделанные «ровными по высоте» в v0.20.0,
распёрли окно по ШИРИНЕ.)
Что теперь:
- подписи просят 72 px и забирают свободную ширину целиком (
QSizePolicy.Ignored), лишнее режется многоточием, полный текст — в подсказке при наведении; synapse/window_fit.py— вписание в доступную площадь экрана (за вычетом панели задач), окно ставится по центру; аргумент--sizeтоже вписывается, а не приказывает;- каждая страница вкладок в прокрутке: «Настройки» требовали 737 px по высоте САМИ ПО СЕБЕ и становились минимумом всего окна (на 1366x768 доступно ~728);
- запасная Tk-панель по тому же правилу (было жёсткое 1180x760).
Минимум окна: 2246x765 → 467x358 — влезает и в FullHD, и в 1024x768 (проверено на четырёх
сочетаниях языка и темы). Проверяет
tests/test_window_fits.py, контроль первым: обычный QLabel на том же тексте по-прежнему просит
888 px, поэтому «стало 589» не зелено по умолчанию.
Причина — слово Стаса: «что делают кнопки под графическим окном? я на них нажимаю и ничего не происходит. что за роли цвета? ничего не меняется. Жить — значит включить и потом пауза остановить?».
Кнопки работали МОЛЧА. В запасной tkinter-панели каждое действие писалось в лог, а в Qt-панели (её Стас и видит) ответа в окне не было НИ ОДНОГО: такт правда брал миссию и писал в журнал, но на экране не менялось ничего. Теперь на каждое нажатие есть строка ответа:
| кнопка | что она делает | что появляется в строке |
|---|---|---|
| Жить / Пауза | включает и выключает ТАКТ по расписанию (миссии и думание) | «живу: такт каждые 60 с — беру миссии и думаю. Тело дышит всё время» / «пауза: тактов и миссий нет. Тело продолжает дышать» |
| Такт | один шаг прямо сейчас | «tick 187: …» или «mission m-…: done — …» |
| Импульс | впрыснуть импульс в сенсорные нейроны | «импульс телу: зажжено 6/302…; громче всех ASGR(1.0), BAGL(1.0)…» |
| Сброс вида | вернуть угол и масштаб | «вид сброшен» |
| цвет: роли / медиаторы / зоны | чем красить нейроны | «раскраска: зоны» + легенда меняется |
Ответ на его вопрос про Жить: да — кнопка включает такт, а «Пауза» его останавливает. Тело при этом живёт ВСЕГДА: дыхание — это рефлексы коннектома, а не расписание. Так и написано в подсказке и в строке ответа, чтобы не приходилось догадываться.
Импульс был невидим — замер: вдох тела шёл каждые 10 кадров (16 мс → 0.16 с), тело мерцало без
пауз (в среднем 18 зажжено из 302, до 31), а импульс поднимал всего +10 нейронов и гас за 0.16 с.
Теперь вдохи редкие и слабые (body.breathe_every = 90 кадров, breathe_amp = 0.55): за 400
кадров спокойны 330, максимум 9 зажжено. А задетые импульсом узлы помечаются вспышкой
(body.flash_seconds = 1.6): белые и вдвое крупнее (радиус 6.2 против 3.0).
Легенда цветов (PanelCore.legend()) живёт во вкладке «Настройки» (с 26.09.2026, v0.20.0):
плашка того самого цвета, которым
нарисованы узлы, подпись и их число. Меняется вместе с раскраской, переводится вместе с языком,
и строится по данным, а не по палитре файла тела — иначе 59 нейронов без медиатора (серые)
оставались без объяснения, а медиатор с нулём узлов занимал строку.
Тема — одна палитра на всё окно (synapse/theme.py): тёмная (по умолчанию) и светлая.
Раньше окно Qt было светлым, а полотно тела тёмным — панель выглядела сборной из двух разных
программ. Теперь палитра одна и на виджеты (QSS), и на тело: фон полотна, цвет связей, цвета
ролей. Палитра медиаторов приходит ИЗ ФАЙЛА тела и настроена на тёмный фон — на светлой теме
theme.adapt() её приглушает.
Вкладка «Настройки» — то, что раньше приходилось править руками в config.json: язык,
тема, темп кадра тела, такт (0 = поток: мысль идёт за мыслью в темпе самой модели; число =
такт, и он же темп мыслей), «думать вслух», разрешение тесту записи, диапазон портов
двумя числами (от / до), мозг (модель, счёт, слоёв на GPU, порт) и
цвета тела (легенда). Язык и тема применяются СРАЗУ, не закрывая окно (все подписи ставятся
из словаря заново); числа проверяются, и о плохом сказано с сохранением прежнего значения, а не
«упало».
Мозг выбирается в окне (v0.20.0). Модели читаются из собственной папки существа
(models/brain/*.gguf): положил туда файл — он появился в списке (так и лежат рядом 0.5b и 1.5b).
Счёт — «видеокарта» или «процессор» (brain.device), слои на GPU (brain.ngl), порт. Команда
сервера собирается одним местом (Brain.argv()), поэтому её видно тестом, не поднимая сервер.
Раньше -ngl и -c жили только внутри server_args, и оператор правил их руками; теперь это
свои ключи, а старые значения переносятся при чтении (config._migrate_brain_args) —
поведение не меняется, меняется только способ правки. Смена модели гасит свой сервер и прямо
говорит, что поднять его надо заново: модель и счёт читаются при запуске.
Вкладка «Мысли» наполняется (v0.20.0). Повод — «сделала вкладку Мысли — но там пусто».
Причина: канал 💭 наполнялся ТОЛЬКО метками самой модели, а 0.5b их ставить не умеет — файла
thoughts.log не было вовсе. Но в потоке ответ модели И ЕСТЬ её думание вслух: теперь он идёт в
канал мыслей (audit.thought), копии-эхо — нет. Живой прогон: три такта → две строки в канале.
Поля вкладки показывают ФАЙЛ, а не умолчания (v0.15.0, 25.09.2026). Раньше они
заполнялись только кнопкой «Перечитать» и после сохранения, а при рождении окна в них стояли
умолчания (язык en, «думать вслух» выключено) — оператор жал «Сохранить» и записывал
умолчания как свой выбор. В живом журнале это видно: settings.saved changed=2 в 16:28:56,
язык en при русском окне. Теперь настройки читаются при старте и при каждом открытии вкладки.
Повод — жалоба «"такого_нет" ещё глаз мозолит». Это оказалась НЕ строка языка, а МУСОР В ЖИВЫХ
ДАННЫХ: 39 записей в рабочей очереди с придуманным именем команды, которое я сама гоняла,
проверяя отказ. Причина — missions.add() не проверял имя вообще (панель проверяла, консоль
нет), а список инструментов жил в ДВУХ местах — в агенте и в ядре панели.
- Один список инструментов —
missions.KINDS. Агент, панель и консоль берут его оттуда же, поэтому новый инструмент появляется везде сам, а «забыть» его в панели больше нельзя. - Проверка ДО записи — как
scope.pyдля сети: незнакомое имя в очередь не ложится, консоль отвечает отказом и выходит с кодом 2. Строку с чужим именем, оставшуюся от прежней версии, агент по-прежнему честно помечаетrefused— путь отказа закрыт тестом. - Итог миссии — на языке окна. В очереди лежит строка, написанная в тот день и на том языке;
для миссий с ОТЧЁТОМ панель собирает её заново из JSON (
reports.find_for_mission) — как перерисовывает человеческий.txt. Что пересобрать нельзя (стимул телу, ошибка инструмента), показывается как записано: не выдумываем. - Проза инструментов ушла в словарь. «дверь не пустила», «нет аудита сети — сначала
net_audit» и ещё полтора десятка фраз были вписаны прямо в код
certs/diff/write_probe/net_scan: панель переведёшь, а отчёт всё равно русский. Теперь это ключиerr.*,src.*,wp.*. Русским в инструментах осталось только то, что и должно: разбор русского выводаipconfigиnet view. - Тесты больше не трогают живые данные. Найдено тем же поиском: тесты писали в рабочие
data/— мусор в очередь, «test.event» и настоящие такты в журнал, и даже крутилиstate.jsonживого организма (счётчик тактов рос от прогонов). Теперьtests/live_data.pyуводит очередь, оба журнала, ОТЧЁТЫ и состояние во временную папку, аtests/conftest.pyделает это АВТОМАТИЧЕСКИ для каждого теста — чтобы новый тест не мог забыть про песочницу (именно так и вышлоtest_tools_v07: он писалbrain.thought text=мысльв живой журнал, 6 записей). Проверено замером: после полного прогона живые файлы не меняются ни на байт.
| команда | что делает | пишет на чужую машину? |
|---|---|---|
net_audit |
обойти свою подсеть: живые хосты, порты, баннеры, имена общих папок | нет, только смотрит |
certs |
рукопожатие TLS и срок сертификата: кому выдан, кем, сколько дней осталось | нет, только чтение |
write_probe |
тест «куда есть доступ на запись»: один файл-маркер, прочитать обратно, убрать | да — единственный такой |
diff |
что изменилось с прошлого аудита: новые и пропавшие хосты, порты, общие папки | нет, читает два отчёта |
remote |
найти способ выполнить команду на указанном адресе и выполнить её: WinRM (5985/5986), WMI/DCOM (135+445 — путь psexec), SSH (22) | да — выполняет команду |
ask, stimulus, note |
вопрос своему мозгу, импульс телу, заметка в истории | нет |
Плюс net_audit и certs идут по своему же последнему аудиту, если хост не указан:
python run.py mission add --kind certs # возьмёт службы 443 из последнего аудита
python run.py mission add --kind certs --host 192.168.1.88
python run.py mission add --kind write_probe # папки из последнего аудита
python run.py mission add --kind diff # сравнить два последних аудита
python run.py mission add --kind remote --host 192.168.1.88 # только найти пути управления
python run.py mission add --kind remote --host 192.168.1.88 \
--user stas --password ... --command whoami # и выполнить
У write_probe правила в коде, а не в обещаниях: дверь спрашивается до касания, служебные
папки (IPC$, ADMIN$, C$, D$) не трогаются, выключатель net.allow_write_probe,
каждая попытка (и успех, и отказ) ложится в журнал с полным путём.
У remote правила такие же строгие, и по той же причине — он единственный ВЫПОЛНЯЕТ
команду на чужой машине: дверь (scope) спрашивается до касания адреса, выключатель
net.allow_remote, способы пробуются по одному с записью в журнал, а БЕЗ логина и
пароля инструмент ничего не подбирает — он только отвечает, какие пути открыты.
Пароль уходит в командную строку PowerShell (так работает WinRM): это надо знать, а
не обнаружить — помни об этом, если на машине есть чужие глаза.
Слово оператора 24.09.2026: потолок пробуждений (было 96 в сутки) снят — почему вообще
живое существо должно переставать жить из-за счётчика? max_wakeups_per_day: 0 = без
потолка; счётчик остался в state.json как история, а не как поводок.
ОДИН ТЕМП — ТАКТ (v0.22.0). Отдельного ключа «пауза до мысли» больше нет ВООБЩЕ:
tick_seconds и есть темп мыслей. tick_seconds: 0 — поток: пауз нет, мысль идёт за мыслью
в темпе самой модели; число — мысль не чаще, чем раз в это число секунд. Слово оператора
26.09.2026: «время такта и есть количество мыслей — ведь если мозг выключен, то не будет
мыслей… время такта 0 — поток. всё». Прежние «раз в N тактов» и «раз в N секунд» были ШВОМ:
просили поток — получали паузу, только измеренную в других единицах. Число мыслей в секунду
теперь задаёт модель: сколько она отвечает — столько и длится такт.
Порты — ДИАПАЗОНОМ (v0.22.0). Список служб заменён парой чисел net.port_from /
net.port_to (умолчание 1-1024), в окне — два поля. «Все порты» выразимо (1-65535), а вместо
потолка в 4096 записей — честная цена обхода фразой в окне: замер на своей сети дал 1.7 мс
на порт при 256 потоках (при 16 потоках — 22 мс, то есть полный диапазон занял бы на один
хост часы). Поэтому net.port_workers = 256.
Тем же правилом живут рефлекс и журнал (живой замер, из-за которого это и сделано):
Копия промпта — не мысль (v0.17.0, найдено по живому журналу 25.09.2026). Оператор видел в потоке
одну и ту же фразу «Заметь, сколько прошло времени.» — и это была моя же строка промпта (ракурс
наблюдения): поток спрашивает мозг каждый такт, а 0.5b в q2_k склонна ПРОДОЛЖАТЬ поданный текст вместо
ответа. Лечение: ракурс стал короткой меткой (ракурс — время), в промпт добавлен прямой запрет
копирования, а сама копия ловится механически — Synapse._echo_of() ищет в ответе длинный кусок слов
подряд из промпта (3 слова и больше) или ответ целиком внутри промпта. Копия не выдаётся за мысль
(журнал brain.echo с полем copied_from, строка ответа «модель скопировала мои слова»), а три копии
подряд — мозг замолкает на brain.echo_cooldown (60 с, одна строка brain.muted с причиной): существо
живёт телом, а не молотит одно и то же. Одно-два случайно совпавших слова копией НЕ считаются — иначе
детектор задушил бы живую речь. Живьём это видно и сейчас: 0.5b в q4_k_m на длинном промпте выдаёт
«💭 Извините за путаницу, но я не хочу быть скромным» и уходит в потолок токенов (первый такт — 50 с,
следующие — по 1.8 с, потому что ответ короткий). Это потолок модели, а не механизма.
| что | было | стало |
|---|---|---|
голод (instinct.feed) |
каждый такт: 40 записей за 70 с, все energy=0.0, lit=0 |
не чаще раза в instinct_min_seconds (20), импульс заметный (amp 0.9) |
строка tick … status=idle |
каждый такт, ~5 раз в секунду | раз в tick_log_seconds (30); случившееся — всегда и со своим статусом (fed, thinking, repeating) |
мысль (brain.thought) |
повтор гасился в тишину: 6 запросов → 1 запись в журнал | повтор СКАЗАН и помечен (repeat=True + streak), а промпт просит конкретнее: назвать нейрон |
| вопрос «жив ли мозг» | на каждом такте: проба молчащего сервера стоит 2112 мс → 2 такта за 3 с | сначала темп, потом вопрос: 138 тактов за 3 с, в журнале 3 строки вместо сотни |
Brain.ensure() |
проба до разрежения | сначала retry_after, потом проба |
Разбор жалобы «абсолютно ничерта не работает, ничего не пишет в логи и не отвечает» нашёл четыре настоящие поломки, все наши:
- Запись журнала обязана быть ОДНОЙ строкой. Мозг ответил 2000 знаков разделителей «---», и
поле
text=...разорвало человеческий журнал: из 1264 строк 641 оказалась мусором (посчитано). Теперь все поля проходятaudit._flat()(перевод строки → « ⏎ », потолок 160 знаков), мысль пишется одной строкой,thoughts_tail()не показывает пустое и разделители. Живые файлы починены: журнал 1265 → 609 строк, канал мыслей 1602 → 1 настоящая мысль. - Мусорный ответ не идёт в нить. Живая проба на настоящем сервере: с мусорной нитью модель
отвечала «---\n\n---», без нити — внятную фразу. Теперь строки-разделители выкидываются
(
_clean_answer), ответ без слов считается мусором (brain.junk), в нить и в канал мыслей он НЕ попадает, а послеbrain.echo_limit(3) копий или мусора мозг замолкает на cooldown. - Модель берётся ТА, ЧТО ВЫБРАНА. Замер
/propsживого сервера: на порту 11435 сидел СИРОТА 0.5b (pid без pid-файла, прошлая сессия), а в настройках стояла 1.5b — существо отвечало сиротой. Теперьadopt()сверяет модель сервера с настройками,stop_port_listeners()гасит сироту без pid-файла (проверяя имя процесса), поэтому «выключить мозг» и смена модели наконец действуют. - Проба мозга дешевле секунды. Дамп стеков (faulthandler) показал: проверка «жив ли мозг» шла
из ГЛАВНОГО потока окна и стоила 2.05 с — на этой машине закрытый порт молчит до конца
таймаута. Теперь это один connect с
PROBE_TIMEOUT = 0.25и минимальный GET. Замер: файлtests/test_window_fits— 13 тестов за 14.3 с (было зависание), полный прогон — 351 тест за 2:11.
Вывод сервера больше не уходит в пустоту: data/logs/brain-server.log, а при смерти последние
строки попадают в журнал (brain.server.died reason=...) — «умер код 1» без причины больше нет.
ЧЕСТНО ПРО -ngl 99 С 1.5b: сервер иногда падает при загрузке (код 1, сообщения нет) — на 2 ГБ
видеопамяти вместе с телом AI_UI запаса нет; при -ngl 10 он поднимается стабильно (11.4 с).
Параметр — выбор оператора, поэтому не менялся.
Слово оператора 30.09.2026: «я хочу сотворить живой киберорганизм ещё и с пользой. зачем по твоему мы ему давали инстинкт самовыживания?»
До этого у организма был только метаболический рефлекс: body.energy() — среднее
возбуждение импульса по коннектому, её нельзя потратить и нечем пополнить. Инстинкт
оживлял гаснущее тело, но повода жить не давал.
Теперь есть ЗАПАС (synapse/life.py, файл data/life.json):
| ключ | умолчание | что значит |
|---|---|---|
life.enabled |
true | рубильник жизни: выключен — ни расхода, ни голода |
life.reserve |
0.5 | стартовый запас нового файла (0..1) |
life.spend_per_hour |
0.12 | трата временем: полного запаса ~на восемь часов |
life.max_gap_seconds |
900 | окно было закрыто — жизнь стояла, а не гасла |
life.feed_per_fact |
0.03 | одна НОВАЯ находка |
life.feed_max |
0.35 | потолок еды за одну миссию |
life.hungry_at / starving_at |
0.30 / 0.05 | где начинаются голод и истощение |
Еда — только за новое. Факты миссии сравниваются с памятью по ОБЛАСТИ того, что
смотрели (net:<cidr>, diff:<path>, certs:<host:port>, remote:<host>). Повторный
обход той же сети даёт new=0 и не кормит: иначе еда росла бы от безделья. Итог
миссии говорит об этом прямо: «нашла нового: 4 — сыта 0.120, запас 0.54 (ok)».
Голод — первый рефлекс (agent.instinct): истощение — тело гаснет (soothe),
голод — беспокойный импульс; оба называются словами (событие instinct.hungry). Расход
временем в журнал не пишется каждый такт (урок tick_log_seconds): событием
становится смена уровня — life.level was=ok level=hungry. Плюс life.fed,
life.quiet, life.error.
Живой замер 30.09.2026 (на настоящем отчёте миссии remote 203.0.113.10,
m-20260930-144227-19): запас 0.42 → разбор отчёта дал 4 новых факта → сыта 0.12
→ запас 0.54 (ok); повторный разбор того же отчёта — new=0, еды нет.
План развития — PLAN.md: этапы M1…M6 (самозадание при голоде, зрение соединений
роутера и VPS только для чтения, память о делах, мозг как голос, который предлагает
миссию на подтверждение оператору).
Слово оператора 30.09.2026: «я хочу сотворить живой киберорганизм ещё и с пользой». Польза видна только на сравнении: порт, открывшийся ночью, выглядит как «как всегда», пока не с чем сравнить.
synapse/world.py (файл data/world.json, дневник data/diary.md) держит снимок фактов
на каждую ОБЛАСТЬ — по тому же правилу «что смотрели», что и запас (одна функция
life.scope_key, не копия) — и на каждом отчёте считает разницу:
| вердикт | что значит |
|---|---|
first |
первый взгляд: сравнивать не с чем, изменение НЕ объявляется |
same |
как было |
grew / shrank |
только пришло / только ушло |
shift |
и пришло, и ушло |
Итог миссии несёт это строкой: «мир изменился: появилось 2, ушло 1 (+10.0.0.1 open=22,3389)».
Каждая перемена ложится строкой в дневник, и дневник виден в окне (вкладка «Мысли»):
- 30.09 15:12 · peers · появилось 2, ушло 1 · +router: listen tcp 58018.
Мозгу есть о чём говорить. В промпт мысли добавлена строка «Мой мир»: когда смотрела, что изменилось, сколько открытых целей и своих. До этого мысль кормилась только состоянием нейронов, и малая модель возвращала мою же строку — «распределено 6 нейронов из 302».
Слово оператора: «сначала посмотрим что он будет предлагать (и как я это апрувить? кнопка какая то что ли) — а потом … если его этим не тормозить, он будет что то делать сам просто давая отчеты. Можно это сделать галкой в настройках — типа подтверждение действий».
Мозг может написать строку 🎯 инструмент поля:
🎯 peers
🎯 remote host=203.0.113.10
🎯 net_audit cidr=192.168.1.0/24
Метка вынимается из ответа до проверок на копию промпта и мусор — в речь она не утекает, а становится миссией в общей очереди. Дальше всё решает код, а не модель:
- список железный и только читающий:
peers,net_audit,remote(без логина и команды),certs,diff. Ниwrite_probe, ниstimulus, ниnoteот мозга не принимаются; поля тоже по списку —🎯 peers cmd=rm -rf /теряет команду, а не исполняет; - дверь спрашивается до очереди: предложить то, что
scopeне пустит, нельзя; - статус
proposed:missions.take_nextберёт толькоqueued, поэтому без слова оператора предложенная работа не идёт НИКОГДА. Подтверждение — кнопка «Подтвердить» рядом с «Удалить»; снятие — обычное удаление; - потолки:
brain.propose_max(6) за сутки,brain.propose_seconds(1800 с) за то же место, и не повторять то, что уже в очереди.
| ключ | умолчание | что значит |
|---|---|---|
brain.propose |
true | мозг вообще может предлагать работу |
brain.propose_confirm |
true | …и ждать кнопки «Подтвердить» (сняли — берёт сразу и только отчитывается) |
brain.propose_max |
6 | потолок предложений за сутки |
brain.propose_seconds |
1800 | не чаще этого за то же место |
Журнал: brain.proposed (что предложено и в каком статусе), brain.propose_blocked (почему
не взяли: off / cap / again / inflight / door / bad_tool / bad_param),
mission.approved (подтвердил оператор), world.changed (мир изменился).
Закон Стаса (24.09.2026): «модель должна быть с телом вместе — сама себе сервер,
ai_ui не при чём». Поэтому models/brain/ внутри проекта держит свою копию
рантайма llama.cpp и модели (provision — 1.57 ГБ, копируется один раз, повтор
пропускает уже скопированное), а Synapse сам поднимает свой llama-server
на своём порту (11435 — чтобы не спорить с AI_UI на 11434) и сам его гасит.
Малому мозгу нужен КОРОТКИЙ системный промпт (v0.16.0, найдено живым замером). С полным
modelfile.md 0.5b принимала список правил за текст, который надо ПРОДОЛЖИТЬ, и вместо ответа
выдавала его же пункты: «Я сейчас делаю следующее: 1. Отвечать на вопросы пользователя по делу,
коротко и без выдумок. 2. Говорить на языке, на котором к тебе обратились…». Дело не в
чат-шаблоне: с коротким промптом та же модель отвечает по делу. Поэтому в modelfile.md есть
раздел ## System Prompt (188 знаков против 1879 в целом файле), и Brain.system_prompt() по
умолчанию берёт именно его; brain.prompt_mode: full возвращает весь файл — это для старшего
мозга 12b (он уже лежит на диске: Gemma4-12B-QAT…Q4_K_M.gguf, 7.4 ГБ).
Мозг включён по умолчанию — и теперь это правда в самом коде: в config.py
умолчания brain.mode: server и idle_thinking: true. До 25.09 умолчание было
off, а живой config.json стоял на server: «по умолчанию» и «как у оператора»
говорили разное — расхождение нашёл QA Орест. Такт сам зовёт
brain.ensure() — если мозг разрешён, сервер обязан быть жив. Подъём пробуем не чаще
раза в 5 минут (попытка стоит до 90 с ожидания — в каждом такте ей делать нечего).
Поднятый сервер пишет свой pid в data/brain.pid, поэтому его можно погасить
откуда угодно — в том числе если он поднят из консоли, а панель его подхватила:
по протухшему pid чужой процесс не убиваем (сверяем имя — llama-server).
Закрытие окна гасит свой сервер само (и в Qt-панели, и в tkinter): раньше закрытие оставляло его сиротой — строку про это найдёшь в CHANGELOG за 25.09 (6), это находка независимого QA. Если сервер в этот момент ещё поднимался в фоне, окно гасит и его: такт после закрытия ничего не запускает.
python run.py brain --action status # что с мозгом
python run.py brain --action provision # скопировать рантайм и модель к себе
python run.py brain --action on|off # включить/выключить (поднимет/погасит сервер)
В панели то же самое кнопками; долгие дела (копирование, старт сервера) идут в отдельном потоке, а окно трогаем только из главного — иначе панель замирает.
Слово Стаса 30.09.2026: «давай делать подключение облачной модели, начнём с gemini —
причём давай сделаем выбор режимов — только облако, только локальный и локальный +
облако… сделать нужно чтоб OpenAI совместимые в будущем можно было тот же groq к
примеру подключить». Причина простая и названа им же: «эта 1.5b вобще не понятно что в
этой роли» — живой журнал 30.09 дал 148 строк thoughts.log, почти каждая была копией
моего же промпта («распределено 6 нейронов из 302»).
Ключа действительно достаточно. Живой замер 30.09.2026 ключом из
ai_ui/config/providers.json: GET …/v1beta/openai/models → 200, 61 модель;
POST …/openai/chat/completions на gemini-3.5-flash-lite → 200 за 0.85 с, usage
46/28. На gemini-2.5-flash-lite — 404 «no longer available to new users», поэтому имя
модели в окне — редактируемое поле, а не жёсткий список.
Один код на всех провайдеров. У Gemini, Groq, OpenRouter, NVIDIA, DeepSeek и Z.AI
один протокол (POST {base_url}/chat/completions, ключ заголовком Bearer), поэтому
провайдер — строчка в настройках (brain.online.provider), а не ветка кода; custom
принимает любой свой адрес. Ключ живёт в data/creds.json по имени провайдера — там же,
где логины узлов, и по тому же правилу: нет ключа — «доступа нет», а не выдуманный запрос.
Три режима (brain.online.mode, «Настройки → Откуда мозг берёт мысль»):
| режим | что делает |
|---|---|
local |
только свой сервер, как было. Умолчание |
online |
только облако; свой сервер не нужен; облако молчит — существо молчит ТОЖЕ |
hybrid |
облако, когда готово и потолок не выбран; иначе свой сервер (откат в журнал) |
Развод путей — Brain.route(), то есть код, а не модель: мозг не выбирает, где он живёт.
Бюджет — наш, а не чужой тариф. data/budget.json: вход+выход и число обращений за
сутки; сутки сменились — счёт с нуля. Оценка запроса идёт ДО отправки (2.9 знака на токен —
наш замер), поэтому на потолке запрос не уходит вовсе: событие brain.online.limit и
фраза «суточный потолок выбран». Тарифа Groq/Google мы не знаем и не выдумываем — это наш
потолок, brain.online.daily_tokens и max_calls.
Маска адресов. Публичный IPv4 уходит как «внешний-IP» (второй — «внешний-IP-2», повтор
того же — то же имя), своя сеть остаётся как есть: без неё разбор «что это, откуда, вредное
ли» слепнет. Галка brain.online.mask (включена по умолчанию) выключается осознанно.
Маска закрывает публичные IPv4; имена узлов и URL она не трогает.
Проверить живьём. На вкладке «Мозг» — кнопка «Проверить облако»: короткий настоящий
запрос и ответ словами («облако отвечает: gemini · gemini-3.5-flash-lite · 850 мс · …»).
«Готов по настройкам» ещё не значит «отвечает»: ключ могут отозвать, модель — снять с
обслуживания. В журнале это brain.online.ask (tokens_in/tokens_out/masked),
brain.online.fallback, brain.online.limit, brain.online.error.
Граница, названная вслух. Бесплатный тариф Google учится на том, что ему послали — их
собственная строка на странице цен: «Used to improve our products: Yes» у free, «No» у
платного. Поэтому ключ в Synapse положен, а режим оставлен local: наружу не уходит
ничего, пока оператор сам не переключит. Отчёты peers — это карта сети, и такое решение
принимает человек, а не программа.
Список сетей, по которым организму можно ходить, задаёт оператор:
net.allowed_cidrs (по умолчанию 0.0.0.0/0 — любые сети, слово Стаса 30.09.2026).
Это единственный рубильник двери: проверка идёт до касания хоста, а отказ
ложится в аудит (scope.denied, причина — кодом, слова берёт словарь).
Отдельно стоит потолок адресов на подсеть (net.max_hosts, 256) — он про размер
обхода, а не про то, чья сеть.
python run.py init # первая сборка: конфиг + тело + карта ролей
python run.py status # что организм чувствует сейчас
python run.py roles [--build] # точная карта нейронов
python run.py tick --n 3 # прожить три такта
python run.py run --seconds 60 # жить автономно (Ctrl+C — стоп)
python run.py mission add --kind net_audit --cidr 192.168.1.0/24
python run.py mission list
python run.py scan --cidr 192.168.1.0/24
python run.py reports [--show 1] # отчёты человеческим языком (--rebuild — пересобрать .txt)
python run.py brain --action tidy # убрать лишний свой сервер, вставший на тот же порт
python run.py brain --action cloud # живая проба облака: что ответило, за сколько и сколько съело (v0.30.0)
python tests/test_roles_panel.py # 33: карта, проекция, ядро панели, рендер, мозг
python tests/test_i18n_theme.py # 26: словарь, темы, отчёты, настройки
python tests/test_qa_fixes.py # 25: находки QA Ореста (замок тела, отмена, окно), сироты словаря
python tests/test_stream.py # 25: поток без пауз, повтор СКАЗАН, короткий промпт для 0.5b
python tests/test_reports.py # 22: человеческие отчёты и форма миссий
python tests/test_panel_actions.py # 20: кнопки отвечают, импульс виден, строки молчат до нажатия
python tests/test_synapse.py # 20: конфиг, тело, дверь, миссии, сеть, слушатели порта
python tests/test_mission_kinds.py # 19: дверь очереди, язык итогов, проза инструментов
python tests/test_pharmacology.py # 18: знак синапса из данных, GABA тормозит, контроль
python tests/test_settings_live.py # 14: вкладка читает файл, такт вместо двух темпов, молчащий мозг не тормозит
python tests/test_thoughts_settings.py # 14: канал 💭, настройки без противоречий, порты диапазоном
python tests/test_ui_v020.py # 14: мозг в настройках, легенда там же, ровные строки, КАРТОЧКА после клика
python tests/test_brain_adopt.py # 13: сирота с чужой моделью, проба мозга дешевле секунды
python tests/test_echo.py # 13: копия промпта — не мысль (контроль первым), молчание после трёх копий
python tests/test_window_fits.py # 13: окно влезает в экран (минимум окна, прокрутка вкладок, вписание)
python tests/test_log_hygiene.py # 12: журнал не рвётся, мусор не заражает поток
python tests/test_live_body.py # 11: гибридное тело (запечённое + предметы)
python tests/test_panel_refresh.py # 11: окно не пересобирает неизменный вид
python tests/test_tools_v07.py # 9: потолок снят, write_probe, certs, diff
python tests/test_launcher.py # 8: правильный интерпретатор ищется сам
python tests/test_subproc.py # 8: сторож консольных окон
python tests/test_neuron_card.py # 7: карточка узла — поля по факту, контроль первым (v0.22.1)
python tests/test_flow.py # 6: нить, склейка, канал 💭 (v0.18.0)
python tests/test_seamless.py # 6: живой поток мыслей, непрерывный такт
python tests/test_instinct.py # 5: инстинкт самосохранения, нумерация миссий
python tests/test_life.py # 15: запас сил — расход временем, еда за находки (v0.27.0)
python tests/test_no_flash.py # 3: свидетель консольных окон (тест не показывает окно)
python tests/test_world.py # 11: память о делах — «стало иначе» или «как было» (v0.29.0)
python tests/test_propose.py # 20: мозг предлагает работу, но не исполняет её (v0.29.0)
python tests/test_online.py # 36: облачный мозг — три режима, бюджет, маска, честный отказ (v0.30.0)
# итого 543 теста, 0 провалов — чистый прогон 30.09.2026 (239.9 с), а не по памяти.
# В прогоне, который шёл ОДНОВРЕМЕННО с правкой файлов, было 8 отказов класса «среда
# замера» (4 test_subproc, 2 test_no_flash, 1 test_launcher — поднимают дочерний
# процесс с проверкой консоли: WinError 50 без консольных дескрипторов) и 1 флак
# test_synapse::TestExtraListeners (без -s падает через раз, с -s 4/4 зелёные).
# tests/conftest.py — автофикстура: КАЖДЫЙ тест живёт во временной папке,
# tests/live_data.py — помощник: очередь, журналы, отчёты, state, запас, МИР, счётчик
# предложений мозга и БЮДЖЕТ ОБЛАКА уводятся в temp,
# гонять ИМЕННО python.exe: в pythonw.exe нет консольных дескрипторов,
# и тесты, поднимающие процессы, падают с «WinError 6: неверный дескриптор»
Вид миссий: net_audit (аудит сети), write_probe (тест записи в шары), certs (сроки
сертификатов), diff (что изменилось), remote (удалённое управление адресом),
stimulus (стимул телу), note (заметка), ask (вопрос своему мозгу).
| путь | что |
|---|---|
synapse/config.py |
настройки: память главнее умолчаний, незнакомый ключ не трогаем, выведенные ключи убираем |
synapse/i18n.py |
ОДИН словарь строк на весь проект: техническое английское, интерфейс переводится |
synapse/theme.py |
тема: одна палитра на виджеты (QSS) и на тело (фон полотна, связи, роли) |
synapse/body.py |
тело-коннектом, импульс, роли из карты, стимул и намерение |
synapse/roles.py |
правила ролей и сборка точной карты (чистые функции — их и проверяют тесты) |
tools/build_roles.py |
разовая сборка neuron_roles.json из выгрузки атласа |
synapse/brain.py |
мозг: off/server, своя копия рантайма и модели, свой llama-server, развод путей route() |
synapse/online.py |
ОБЛАЧНЫЙ мозг: OpenAI-совместимые провайдеры, три режима, бюджет, маска адресов |
data/creds.json |
секреты: логины узлов И ключи провайдеров облака (по имени) — не в коде и не в логах |
data/budget.json |
суточный расход облака: вход+выход и число обращений (наш потолок, не тариф) |
synapse/qt_panel.py |
окно управления на видеокарте (Qt + OpenGL) — основное |
synapse/launcher.py |
выбор ПРАВИЛЬНОГО интерпретатора: где есть PySide6, там и окно |
synapse/panel.py |
окно-дашборд (tkinter) — запасной путь |
synapse/panel_core.py |
ядро панели без tkinter: проекция, цвета, миссии, отчёты, мозг |
synapse/render.py |
отрисовка кадра: быстрый путь Pillow + запасной canvas |
synapse/missions.py |
очередь миссий (её читает панель) |
synapse/reports.py |
отчёты человеческим языком: подпись, разбор баннера, .txt рядом с JSON |
synapse/scope.py |
дверь: список сетей оператора (net.allowed_cidrs) |
synapse/tools/net_scan.py |
инструмент №1: аудит сети |
synapse/agent.py |
такт жизни: тело → миссия → реакция → журнал |
synapse/life.py |
ЖИЗНЬ: запас сил — расход временем, еда за новые находки, голод и истощение |
PLAN.md |
план развития: замысел, слои памяти, этапы M1…M6 с приёмкой, границы |
synapse/audit.py |
аудит (JSONL + текст), ротация |
synapse/subproc.py |
один сторож консольных окон: флаг в единственной точке запуска (ставится при импорте пакета) |
run.py |
панель управления: окно или командная строка |
Замер на живой миссии: ping по 127.0.0.2..6 молчит, а TCP-проба эти адреса
видит (порт 445 открыт). Поэтому живость = «ping ИЛИ открытый порт»: ICMP режут
фаервол и VPN, и аудит не должен врать «хост мёртв». Плюс первая версия сканера
брала ping под 64 потоками и теряла хосты под нагрузкой.
2026-09-24, первая сборка (v0.1): 16 тестов из 16; миссия net_audit по
127.0.0.0/29 — 6 живых адресов, порт 445, отчёт на диске; миссия stimulus
зажгла ASGR/AWAL/PLML, импульс разошёлся на 34 нейрона и погас сам.
2026-09-24, вторая сборка (v0.2): 49 тестов (16 + 33); карта 302/302 без
unknown; свой llama-server из models/brain поднялся и ответил; настоящий
аудит своей сети 192.168.1.0/24 через очередь миссий — 7 живых хостов,
отчёт в data/reports/:
| хост | порты | шары |
|---|---|---|
| 192.168.1.1 | 22, 80 | — (роутер) |
| 192.168.1.15 | 139, 445 | — (эта машина) |
| 192.168.1.18 | 80, 139 | — |
| 192.168.1.43 | 139, 445, 3389 | — |
| 192.168.1.78 | 80 | — |
| 192.168.1.88 | 22, 80, 139, 443, 445 | Install, Juli, outdoor_cam, Stas |
| 192.168.1.90 | 139, 445 | — |
Окно проверено живьём: python run.py panel --selftest — 302 узла, 900 связей,
кадры идут, ни одного исключения; тем же прогоном проверен и запасной canvas-путь.
2026-09-24, третья сборка (v0.3–0.4): 75 тестов (20 + 33 + 22) — добавлены
человеческие отчёты, форма миссий с проверкой двери и сторож кадра; такт больше не
держит окно (мозг думает в фоне), а сервер при старте убирает лишних слушателей
своего порта. Окно: --selftest → 302 узла, 900 связей, 51–56 кадров за 3 с, без
исключений; форма миссий проверена на скрытом окне (4 инструмента, гаснущие поля,
отказ двери, «Моя сеть»); run.py reports --rebuild пересобрал человеческие .txt
для всех старых отчётов. Найдено и убрано лишнее: на 11435 стояли ДВА своих
llama-server (лишние ~600 МБ) — Windows пускает двоих на один порт.
2026-09-26, «ничего не работает» — журнал, поток, сирота и проба (v0.20.2): 351 тест, 0 провалов,
полный прогон 2:11. Живой сервер поднялся за 9.3 с и /props подтвердил qwen2.5-1.5b; живая мысль
с настоящим мозгом — 24.5 с, ответ по смыслу; журнал после прогона 629 строк, мусорных 0 (было 641
из 1264); сирота 0.5b на порту 11435 убран живьём (brain.server.orphan_killed pids=[5412]).
2026-09-25, отзывчивость окна и правильный запуск (v0.9): 113 тестов, 0 провалов.
Причина «ничего не изменилось» найдена замером: окно запускалось интерпретатором без
PySide6 и Pillow (см. раздел про запуск). После правки запуск двойным кликом даёт
panel.relaunch → panel.open engine=qt-ogl; панель на видеокарте — 42 кадра в секунду
(212 кадров за 5 с, 6.4 мс/кадр). Обновление окна без изменений: 3.0 мс вместо 15-23 мс,
выделение миссии и прокрутка журнала больше не сбрасываются.
Живой вечер 30.09.2026 дал жалобу «я не вижу ничего интересного» — и три причины в нашем же коде, а не в модели:
- запас стоял 0.99 при расходе 0.12/час — от полного до голода было 5.8 часов ОТКРЫТОГО
окна, то есть самозадание («живёт и работает само») не включилось бы и за вечер. Теперь
находок нет дольше
life.bored_after(10 мин) — ставка растёт наlife.bored_spend(0.9/час), полный запас голодает через ~50 минут, а переход пишется событиемlife.bored. Скука — это НУЖДА: польза закрывает её, а не украшает жизнь; - в промпт мысли уходил снимок мгновения — «зажжено 0/302 нейрона, энергия 0.0000; громче
всех тихо», потому что такт раз в 30 секунд ловил паузу между импульсами (тело при этом
дышит: вдох каждые 90 кадров плюс рефлекс). Модель читала это как «я умираю». Теперь тело
отчитывается за ОТРЕЗОК:
body.liveliness()— сколько импульсов прошло и до скольких нейронов зажигалось (body.live), а «сейчас» называется отдельно (body.now); - предложения мозга были выдумкой — дверь разрешает любые сети (
net.allowed_cidrs=0.0.0.0/0, слово оператора), поэтомуnet_audit cidr=192.168.1.0/24иremote host=127.0.0.1формально проходили. Теперь у предложения второе, узкое сито (propose.known_target,known_net, код отказаalien): можно СВОЁ (узлы и сети из настроек, петля) и УЖЕ ВИДЕННОЕ (зацепкиdata/leads.jsonиз своих отчётов) — ровно то, что оператор просил уметь «брать хост как цель». А окно повтора (brain.propose_seconds) уступает новому материалу: если мир изменился (world.last), то же место можно предложить снова.
Рычаги — числа в data/config.json (life.bored_after, life.bored_spend), как доля схем и
тариф облака: по слову оператора двигаются без перезапуска (Synapse.reload).
- маскировки процесса, «спор» в скрытых папках, самокопирования на другие машины — эти приёмы ловит антивирус/EDR на тех же серверах, где организм живёт; решили «на потом», сначала практика и поведение;
- скачивания чужих данных и работы вне своих сетей — дверь закрыта;
- слепой записи на чужие шары: сканер только читает (порты, баннеры, имена шар), проверку «есть ли доступ на запись» делаем отдельным словом оператора.
- Живой киберорганизм с пользой — по
PLAN.md: M3 самозадание при голоде (работа только для чтения, с потолками и журналомself.work), M4peers— соединения своего роутера и нашего VPS, встречный разбор «что это, откуда, вредное ли» только чтением, M5 «стало хуже/лучше» (world.py, дневник), M6 мозг предлагает работу на подтверждение (propose.py, статусproposed). Еда организму нужна свежая: статичный мир он быстро объедает и перестаёт находить новое. Сделано 30.09.2026 (v0.27.0–v0.29.0); осталось ЖИВОЕ: нажать «Жить» и посмотреть, как голод ставит работу, что скажет дневник и что предложит мозг. Плюс M7 того же дня (v0.30.0): облачный мозг — три режима, бюджет, маска адресов и кнопка «Проверить облако». Живое: переключить источник налокальный + облакои посмотреть, что скажет облако на РАЗБОР отчётаpeers, — там, где 1.5b беспомощна. - Тело мухи (
fly_core.json, ~7000 нейронов) как второй вид. - Роли для мозга осмысленно: карта уже есть — связать её с решениями (стимул → сенсорные, действие → мотонейроны + полимодальные).
- Промпт думания под малый мозг (короткий и конкретный) — это важнее «модели побольше», потолок 0.5b виден живьём. Старший мозг 12b на 940MX снят с повестки словом оператора («куда её на нашем железе вставлять, 40 минут один ответ»): код к нему готов, железо — нет. Середина (1.5b q4_k_m) лежит рядом и выбирается в окне.
- Проверка «куда есть доступ на запись» — отдельным словом оператора, с записью в аудит каждой пробы.