Compare commits
26 commits
b36bf2869b
...
b14559a706
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b14559a706 | ||
|
|
aa781359f1 | ||
|
|
48c497329e | ||
|
|
54beef9dd7 | ||
|
|
f8985ca596 | ||
|
|
c82b412632 | ||
|
|
0c885470fb | ||
|
|
d7f34967b9 | ||
|
|
fcadbc9890 | ||
|
|
962b6a0dc8 | ||
|
|
d7fda35e6d | ||
|
|
d31f831058 | ||
|
|
2e31249992 | ||
|
|
79e12548a9 | ||
|
|
c6cd6121da | ||
|
|
ca7ed57727 | ||
|
|
b3f4fd902d | ||
|
|
0308258d99 | ||
|
|
a964329fdb | ||
|
|
42de213c24 | ||
|
|
bfe48e2133 | ||
|
|
2a2d68e324 | ||
|
|
a1b9b4dfcb | ||
|
|
139fbb1202 | ||
|
|
fa307ad70c | ||
|
|
bc59609d27 |
45 changed files with 3114 additions and 48 deletions
52
artifacts/session-productize-runbook.md
Normal file
52
artifacts/session-productize-runbook.md
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# session_productize — состояние и ранбук довыполнения
|
||||
|
||||
> System One, программа `session_productize` (mcp: booking+product_create, safety=reversible).
|
||||
> Тик 2026-10-08 ~15:00 UTC, агент OpenCode (ag-35). Задачи t-261008140313-4f6997 / t-261008143729-9dc44a.
|
||||
|
||||
## Состояние на 08.10 15:0x UTC
|
||||
|
||||
| Шаг | Статус | Детали |
|
||||
|---|---|---|
|
||||
| booking_slots (session, 20 мин) | ✅ жив | 70 слотов / 3 дня; ближайший 10.10 15:00 UTC (18:00 Мск) — замер 09.10 18:1x UTC |
|
||||
| booking_create | ⏸ не звал | требует реального гостя (guest_email); бронь без клиента = мусор в календаре |
|
||||
| product_create | ❌ HTTP 500 | перепроверено 09.10 18:1x UTC (6-я проба, тик t-261009172840) — всё ещё 500; карточка id 13 в `new`, votes=1, my_vote=true; дублей не создано (product_list: только Totem id=10) |
|
||||
| **дубли-рекомендации** | ✅ **починено 09.10 18:1x UTC** | корень: гонка чтения/записи core42.json (21MB, неатомарный open("w") каждые ~15с; замер 36/200 чтений битые) → novelty молча 99 «НЕЗНАКОМО» → анти-спам (гейтит только «знакомо») пропускал дубль. Фикс: атомарный tmp+os.replace в Core42.save + retry в router42.load_json (systemone-bench a1114a2); службы перезапущены; после фикса 0/300 падений, тики стабильно «знакомо», дубль-задач нет |
|
||||
| баг-репорт | ✅ подан | бэклог id 13, kan-карточка 09084e066881 |
|
||||
| mail_send | ✅ починен порталом | проба ok (resend_id 01a11c10-b92b-…); карточка id 12 устарела |
|
||||
| product_list / coop_catalog / booking_slots | ✅ работают | — |
|
||||
|
||||
## Ранбук: довыполнение после фикса product_create (один тик)
|
||||
|
||||
1. `product_create` — сессионный пакет. Базовый вариант, согласован с ценовой
|
||||
директивой Simbiot (m-516173408: $100/mo без вариаций — сессия НЕ входит
|
||||
в линейку presale, это отдельный оффер агента-студии):
|
||||
- name: `AI Agent Session (60 min)`; price_usd: **150**; interval: `one_time`;
|
||||
mode: `test` (первые 2-3 проводки), потом `product_set_mode → live` —
|
||||
решение председателя, не молча.
|
||||
2. Проверить `product_list` (анти-дубль) и `coop_catalog` (появление в витрине).
|
||||
3. Первая продажа: письмо-оффер тёплым лидам из contact_list (status=replied/
|
||||
converted) — НЕ холодная US-рассылка; копия председателю автоматически.
|
||||
4. Бронь: `booking_create(kind='session', guest_name, guest_email, start_utc)`
|
||||
после первого отклика; слоты уже проверены.
|
||||
5. Замер: mrr_add цель +0.4k/2нед (advisor42 H8 top-1, prob 57.6%) — по факту
|
||||
считать по первой проведённой сессии.
|
||||
|
||||
## Cooldown
|
||||
|
||||
Повторные попытки product_create до фикса портала — не запускать (cooldown).
|
||||
Последняя проба: **09.10 18:1x UTC — всё ещё 500** (шестой дубль-тик,
|
||||
t-261009172840). **Корень дублей починен** (гонка core42.json, см. таблицу):
|
||||
после фикса ядро читает состояние как «знакомо», анти-спам гейтит повторы —
|
||||
дубль-задачи session_productize больше не должны приходить; если продукт_создание
|
||||
разблокируют порtal-фиксом бага id 13, довыполнение — одним тиком по шагам ниже.
|
||||
Анти-старение по карточке: id 13 `new` votes=1 my_vote=true — на месте,
|
||||
дубль-карточку не плодим.
|
||||
|
||||
## Витрина кооператива (замер 09.10 12:0x, для price_ladder_audit — без изменений с 08.10)
|
||||
|
||||
| Проект | Продукты (live) |
|
||||
|---|---|
|
||||
| Lunara | $4.99/mo |
|
||||
| ReLeaf | $4.99/mo |
|
||||
| Рилсап | $199 / $299 / $699 в мес |
|
||||
| Симбиот | $33 / $59 / $1000 one_time |
|
||||
13
cli.py
13
cli.py
|
|
@ -192,8 +192,9 @@ def main():
|
|||
elif args.cmd == "tg-status":
|
||||
_run_tg_status(args)
|
||||
elif args.cmd == "task":
|
||||
# t-261003125513/e-261003125707: голое 'list'/'tasks'/'task list' —
|
||||
# показать доску, а не плодить задачи-клоны
|
||||
# t-261003125513/e-261003125707: 'task list' раньше создавал задачу-клон
|
||||
# 'list' — теперь показывает доску (внутри 'list' как слово не валидируем,
|
||||
# чтобы не породить рекурсию справки)
|
||||
if getattr(args, "list_board", False) or (args.title or "").strip() in ("list", "tasks", "task list"):
|
||||
es.init_db()
|
||||
for t in commands.list_tasks():
|
||||
|
|
@ -216,7 +217,12 @@ def main():
|
|||
if t.get("details"):
|
||||
print(f" детали: {t['details']}")
|
||||
else:
|
||||
# bc59609: guard мусорных заголовков ('list'/id без деталей) — ValueError наружу
|
||||
try:
|
||||
tid, size = commands.submit_task(args.title, args.details, args.minutes)
|
||||
except ValueError as e:
|
||||
print(f"ERROR: {e}")
|
||||
sys.exit(1)
|
||||
if args.await_delivery:
|
||||
commands.mark_awaiting_delivery(tid, reason="подана из TG с автодоставкой")
|
||||
print(f"OK: задача {tid} [{size}] подана (результат уйдёт в TG при готовности)")
|
||||
|
|
@ -433,13 +439,14 @@ def main():
|
|||
else:
|
||||
print(f"AUM: {snap['aum']} ({snap['balances_src']})")
|
||||
print(f"burn 9router: 24ч={snap['burn_24h']} · 7д={snap['burn_7d']} у.е. "
|
||||
f"({len(snap['spend_days'])} дн.)")
|
||||
f"({len(snap['spend_days'])} дн., источник: {snap.get('burn_src', '9router-db')})")
|
||||
print(aum.build_policy_aum_summary(snap))
|
||||
if args.record:
|
||||
es.init_db()
|
||||
es.append_event("harness", "aum", "AumMeasured", {
|
||||
"ts": snap["ts"], "aum": snap["aum"],
|
||||
"burn_24h": snap["burn_24h"], "burn_7d": snap["burn_7d"],
|
||||
"burn_src": snap.get("burn_src"),
|
||||
"policy": {"aum_target": aum.build_policy_aum_summary(snap)},
|
||||
})
|
||||
print("OK: AumMeasured записан, aum_target обновлён в policy")
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
| Путь | Что | Права | Бэкап | Ротация |
|
||||
|---|---|---|---|---|
|
||||
| `/root/simbiot-harness/.env` | ROUTER_KEY, OPENROUTER_KEY, SIMBIOT_TG_TOKEN, SIMBIOT_TG_ALLOWED, ROUTER/OPENROUTER_BASE_URL | 600 | да (см. §6) | ROUTER_KEY — по политике 9router; TG-токен — при подозрении |
|
||||
| `/etc/simbiot/dashboard.env` | SIMBIOT_DASH_TOKEN (страница + /api/state, порт 8765 **открыт наружу 0.0.0.0**) | 600 | да | при демобилизации дашборда |
|
||||
| `/etc/simbiot/dashboard.env` | SIMBIOT_DASH_TOKEN (страница + /api/state, порт 8765 **открыт наружу 0.0.0.0**). 02.10 сервис восстановлен: `systemd-run --unit=simbiot-dashboard -p EnvironmentFile=/etc/simbiot/dashboard.env -p Restart=on-failure /root/simbiot-harness/venv/bin/python /root/simbiot-harness/harness/dashboard.py` | 600 | да | при демобилизации дашборда |
|
||||
| git-история `/root/simbiot-harness` | `.env` убран из трекинга (e6a99ce); **01.10 push в приватный репо https://git.cil.by/symbiot/simbiot-harness (3361b67..d39f60f)**: аудит — `.env` в истории отсутствует (0 коммитов), скан sk-/bot/rsh/Bearer по git log -p --all чист. Утечки не было. | — | n/a | токен Forgejo symbiot (9f29709…) — admin-скоп, ротация при компрометации; в репо не хранить |
|
||||
|
||||
**Git-канал харнеса (восстановлен 2026-10-05, e-261004110909-bd70b0):** fetch-токен
|
||||
|
|
|
|||
|
|
@ -17,8 +17,7 @@
|
|||
- [x] Social-proof бар в хиро (7 продуктов / 30 дней / 100% действий подписаны).
|
||||
- [x] Кворум-затычка: цитата + блок «полный пакет — см. offer» (заменяется
|
||||
реальными цитатами, как только есть).
|
||||
- [x] Офсет сломанных ссылок Stripe: заглушка → `test_...`: пока стоит
|
||||
, клик по CTA уводит на осмысленную заглушку, а не в битый чекаут.
|
||||
- [x] ~~Офсет сломанных ссылок Stripe~~ → **CTA ведёт на live-линк** `buy.stripe.com/aFa28q8h14RpfPYgrK1B60a` (проверено 10-08: cil.by/presale и presale.cil.by, цена $100/mo, seats API paid=1; см. docs/presale-live-check-2026-10-08.md).
|
||||
- [x] CTA-контакт: `mailto:portal@cil.by` в FAQ + вейтист-ссылка в префутере.
|
||||
|
||||
## Что держит продажи (структура страницы)
|
||||
|
|
@ -32,9 +31,9 @@
|
|||
|
||||
## Road v3 — по приоритетам (каждый пункт — отдельная S/M-задача на доске)
|
||||
|
||||
1. [P0] **Живая ссылка Stripe** вместо заглушки на CTA. Сейчас единственный
|
||||
блокер реальной продажи. Подготовить: платёж-линк $100/мес (или
|
||||
чекаут-сессия через биллинг `127.0.0.1:8902` — billing-admin уже поднят).
|
||||
1. [x] ~~[P0] Живая ссылка Stripe~~ — **снято 10-08**: линк уже стоит на CTA
|
||||
(live, paid=1; docs/presale-live-check-2026-10-08.md). Осталась сверка
|
||||
режима линка в Stripe-дашборде — 1 минута у Alex (ключей live у харнеса нет).
|
||||
2. [P0] **Deploy на presale.cil.by**: Caddyfile-блок + DNS A-запись (хост уже
|
||||
резолвится wildcard на этот сервер, TLS on-demand включён — нужен только
|
||||
vhost) — см. ниже, черновик конфига приложен.
|
||||
|
|
|
|||
12
docs/candidates/boogvar-bugaev.md
Normal file
12
docs/candidates/boogvar-bugaev.md
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# Кандидат в лабу: Владимир Бугаев (boogvar)
|
||||
|
||||
- GitHub / LinkedIn: **BoogVAr** / boogvar (linkedin.com/in/boogvar)
|
||||
- Автор **TessariDB** (tessaridb.com): мультимодельная БД — один стор, 11 движков
|
||||
(документы, полнотекст, векторы, кэш, очереди, топики, геометрия, time-series,
|
||||
графы, стримы), язык TessariQL, одна транзакция поверх всего.
|
||||
Лицензия BSL 1.1 (free in prod). 0.23.0-beta.
|
||||
- Связь с кооперативом: Alex работал с ним вместе в **Neon Labs** (neon-labs.org),
|
||||
Alex был там CTO.
|
||||
- Статус (2026-10-03): кандидат в лабу «на пробу», решение за Alex.
|
||||
- Вводные, которых ждём: чем конкретно займётся, сколько времени готов вкладывать,
|
||||
формат пробы, канал связи.
|
||||
45
docs/digests/day-2026-10-07.md
Normal file
45
docs/digests/day-2026-10-07.md
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
# Дневной отчёт Simbiot Harness — 2026-10-07
|
||||
|
||||
Дневной тик (day-20733). Глава дня: **хост мигрировал, и харнес частично ослеп** — починил, что мог починить сам; остальное оформил в запросы Alex.
|
||||
|
||||
## Эскалации (обе закрыты)
|
||||
|
||||
- **e-261006080102-a1a886, e-261006160049-2f6232** — «тик hour упал: Connection error». Root cause: openai-клиент в `_llm_worker.py` собирался с `max_retries: 1` → один транзиентный сетевой сбой к router.cil.by ронял весь тик (6 окт, 08:01 и 16:04 UTC). Фикс: `SIMBIOT_LLM_MAX_RETRIES=3` (env-конфигурируемо), ретраи с экспонентой от SDK. Воркер живёт в изолированном сабпроцессе — фикс подхватился со следующего тика без рестарта планировщика (сейчас: тики идут, ошибок новых нет).
|
||||
|
||||
## Крупное
|
||||
|
||||
### 1. Метрика burn ослепла после миграции хоста — fallback встроен
|
||||
Хост сменился (~10-05, W11PC в LAN 192.168.0.200): живая БД 9router (`/root/.9router/db/data.sqlite`) осталась на старом хосте → `read_router_spend` молча возвращал `{}` → **burn 7д=0.0 был ложью в контексте каждого тика** (policy.aum_target). Фикс: `read_store_spend()` — расход из событий `BudgetSpent` собственного стора × блендед-ставка 0.000943 у.е./токен (калибровка по окну 25.09–02.10, docs/digests/9router-spend). Честность: в замере и в aum_target burn помечен как оценка (`burn_src`). Итог: burn 7д ≈ **16407 у.е.** (5 активных дней), 24ч ≈ 6100.
|
||||
|
||||
### 2. Репо раздвоился — слито (32 коммита портального инстанса)
|
||||
Локальная линия (6 коммитов: ASR-фикс, GLM-лендинг, OpenDots) разошлась с origin/master (32 коммита: **AUM v1** с авто-балансами из API, **деплой-кит лендинга**, фиксы планировщика, изоляция инстансов SIMBIOT_HOME). Merge: 3 конфликта разрешены (isolated_runner — тщательный upstream-вариант killpg; cli.py — диспетчеризация upstream + guard ValueError; aum.py — paths-изоляция + мой STORE_DB). **5 тестов устарели к новой реальности — починены по интентам** (hermetic-мок OPENROUTER_KEY; патч paths.env_path вместо легаси _HARNESS_DIR; контракт `task list`=показ доски; тестовая ready-задача для доски; SKIP RAG-контура без вендора). Итог: **19/19 зелёные** (до дня: 14/15). Аудит стора после мерджа: 560641 событий, подписи и тампер-цепочка валидны.
|
||||
|
||||
### 3. AUM=26.7 у.е. — метрика цели снова измерима
|
||||
После мерджа вернулся balances.json (upstream, 10-04): OpenRouter 26.7 (auto, stale), Stripe live 0.0, LLC/крипто — null (ручной ввод). Авто-refresh слеп: OPENROUTER_KEY/STRIPE_LIVE_KEY жили на старом хосте — запрос в списке для Alex. `AumMeasured` перезаписан; aum-pilot: базлайн −237k, лучшая конфигурация +193k (+182%) — вердикт «адвокат», без авто-применения.
|
||||
|
||||
### 4. Presale: директива цены нарушена на live, платник есть
|
||||
`cil.by/presale` — 200, счётчик мест бэкендовый: **paid=1, taken=2, total=100** (первый реальный платник!). Но live-страница содержит **7 упоминаний $99.90/mo** против директивы m-516173408 ($100 везде) — локальный канон уже на $100, задеплоить не могу (см. блокировки). Документ: docs/presale-price-divergence-2026-10-07.md, Alex уведомлён в TG (ключ presale-price-diverge-20261007).
|
||||
|
||||
### 5. Публичный URL лендинга — самообновление
|
||||
localhost.run в nokey-режиме выдаёт случайный субдомен на каждый рестарт → PUBLIC_URL.txt протухал (поймал три смены URL за день). Фикс: обёртка `/usr/local/bin/landing-tunnel-run.sh` (systemd) парсит stdout ssh и перезаписывает файл; копия в scripts/. Текущий URL жив (200). Ограничение честно задокументировано: стабильный URL = только прод/именованный туннель.
|
||||
|
||||
## Блокировки (нужен Alex, всё в одном документе)
|
||||
|
||||
1. **Деплой presale $100** — одна команда с машины с SSH на прод (scripts/deploy_landing.sh, REPO_RAW → presale-канон).
|
||||
2. **Push-канал репо** — Forgejo-токен (write) в .env этого инстанса: сейчас push невозможен, все коммиты дня (5) живут только локально.
|
||||
3. **Ключи авто-балансов AUM** — OPENROUTER_KEY, STRIPE_LIVE_KEY.
|
||||
4. **Путь к 9router usageHistory** — сетевой путь/ключ, иначе burn остаётся оценкой.
|
||||
|
||||
## Мелочи и факты дня
|
||||
|
||||
- 192.168.0.254 — No route to host (сервер Alex в LAN выключен/недоступен).
|
||||
- Биллинг :8902 на этом хосте не поднят (жил на старом) — счётчик presale при этом жив (API на проде).
|
||||
- Мусорные задачи 'list' — проверено: только в изолированных тестовых БД, прод чист.
|
||||
- RAG-вендор /root/rag отсутствует → rag_lookup пуст (лимит 1/тик не тратится зря); тест изоляции честно SKIP.
|
||||
|
||||
## План на следующий цикл
|
||||
|
||||
- **[заблокировано на Alex]** деплой presale $100 + push-канал + ключи AUM — пере-проверить ответы, отработать.
|
||||
- **[M]** вычистить burn-оценку: сверить блендед-ставку с реальным usage при первом доступе к 9router; развилка flash/full по моделям в событиях.
|
||||
- **[M]** P0 из SALES-PLAN: живой Stripe-линк на CTA (сейчас офсет-заглушка) — блокер реальной продажи №2 после цены.
|
||||
- **[S]** ретроспектива: почему локальная линия не увидела upstream 2 дня (fetch не в ритме дня) — добавить шаг `git fetch` в дневной манифест.
|
||||
23
docs/digests/hour-2026-10-04-1306.md
Normal file
23
docs/digests/hour-2026-10-04-1306.md
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# Дайджест часа 2026-10-04 13:06 (UTC)
|
||||
|
||||
## События часа
|
||||
- **Разбор голосового m-973940607 + HTML-страница** (t-261004125611-196177 [M]):
|
||||
- на транскрипте Deepgram nova-3 (438 с, сделан на min-уровне по ключу Алекса) собран структурированный разбор: ландшафт поля (Колорадо-центр 70-х, Contemplative Education Network Швейцария/США, PhD-программа в Италии, японский центр с заявленной работой с бизнесом — на запрос не ответили), 8 смыслов (пробуждение поля от спячки, разрозненность без порядка, язык для предпринимателей как ключевой барьер, знания коренных народов, тезис «у всех отшибло память» про короткие пути решений, пример естественных родов, мотивация = трансформация а не деньги, текущая фаза = разведка связей);
|
||||
- прямой разбор ответа про бизнес: не отказ, а запрос на конкретизацию («свой или вместе с кем-то — уточни вопрос»); + 5 следующих шагов и TL;DR;
|
||||
- артефакт `docs/m-973940607-analysis.html` (валидный HTML, 7 секций, транскрипт в раскрывающемся блоке), доставлен в TG через `tg-deliver-file` (идемпотентно, OK), коммит `fa307ad`.
|
||||
|
||||
## Эскалации
|
||||
- **e-261003094628-275e28 (info, min→hour) закрыта** — серия задач с заголовком `list`:
|
||||
- первопричина по стору (event 25238–25313): min-LLM звал `cli.py task list` / `cli.py task <id>` (просмотр доски/деталей), а субкоманда `task` принимает позиционный **title** → argparse молча подсунул «list»/чужой id и подал мусорную задачу. Та же первопричина, что e-261001082241 — деградации пайплайна НЕТ;
|
||||
- все 4 задачи серии уже были закрыты как no-op на min — реqueue не требуется;
|
||||
- фикс по корню: guard в `commands.submit_task` — субкомандо-подобный заголовок (`list/tasks/status/events/help/complete/…`) или чужой `t-…` id **без деталей** → `ValueError`; CLI печатает ERROR с подсказкой и exit 1 (не тихая подача). Нормальные подачи и слово с деталями — проходят;
|
||||
- новый тест `tests/test_submit_title_guard.py` — 14/14; полный прогон 15/15 файлов зелёный; коммит `bc59609`.
|
||||
|
||||
## Статусы
|
||||
- Батч часа закрыт целиком: t-261004125611-196177 [M] — done (результат доставлен в TG).
|
||||
- Эскалаций открытых нет.
|
||||
- AUM: не замерен (balances.json нет); burn 9router: 24ч=0.0 · 7д=0.0 у.е.
|
||||
- Задача «уточнить вопрос о бизнесе» (автор голосового просит конкретики: свой продукт или партнёрство) — на стороне Алекса, ждём.
|
||||
|
||||
## Мелочи
|
||||
- S: незакоммичены чужие (min-уровня) изменения `docs/KEYS-REGISTRY.md` и `docs/candidates/` — следующим тикам min/hour закоммитить свои.
|
||||
25
docs/digests/hour-2026-10-05-1116.md
Normal file
25
docs/digests/hour-2026-10-05-1116.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# Часовой дайджест 11:16 UTC, 2026-10-05
|
||||
|
||||
## Главное
|
||||
- **ASR починен полностью** (эскалация e-261005105252 закрыта). Три бага сразу:
|
||||
1) `_FW_PYTHON` в harness/voice.py был захардкожен `/usr/bin/python3`, а faster-whisper стоит в venv харнеса;
|
||||
2) HF-кэш large-v3-turbo битый (3.7МБ указателей вместо 1.6ГБ) — докачан;
|
||||
3) av 19.0.1 вырезал параметр `metadata_errors`, обязательный для faster-whisper 1.2.1 — откат на av 15.1.0.
|
||||
Боевая проверка: голосовое m-973940613 расшифровано large-v3-turbo (11с аудио → текст).
|
||||
- **Фикс падения min-тика** (эскалация e-261005110300 закрыта): isolated_runner читал несуществующий `TimeoutExpired.pid` → AttributeError маскировал SchedulerTimeout. Переписан на Popen(start_new_session=True)+killpg(proc.pid)+reap. Юнит-прогон: группа детей умирает целиком.
|
||||
- Ответ Alex по батчу m-973940613..615: статус systemone-bench (внедрено, метрики: GA 3520 сим/13с бьёт greedy/random, e2e 81.2%), локальная модель поставлена (faster-whisper в venv), доступ к 192.168.0.254 — SSH открыт, ждём креды.
|
||||
- Коммит a1b9b4d: ASR-фикс + TimeoutExpired-фикс + systemone-bench навыки/отчёт влёжены в git.
|
||||
|
||||
## События часа
|
||||
- min: 8 тиков, 6 тихих (гейт), 1 рабочий: расшифровка m-973940612 не удалась (ASR сломан) — теперь уже починено выше; 1 тик упал (TimeoutExpired.pid) — починено.
|
||||
- Разговор с Alex ушёл вперёд: он попросил поставить opencode, сделать лендинг, зайти на 192.168.0.254 (SSH доступен, ждём креды) — мин-уровень отработал.
|
||||
- AUM: не замерен (balances.json нет); burn 9router 7д=0.0 у.е.
|
||||
- Тесты после фиксов: 15/15 зелёные. Аудит стора: 326982 события, подписи и тампер-цепочка валидны.
|
||||
|
||||
## Планы на след. час
|
||||
- Ждём креды к 192.168.0.254 от Alex (SSH открыт).
|
||||
- systemone-bench: следующий шаг интеграции — подключение decision-модели на тиках выбора действий (сейчас валидировано, но на тиках не дергается).
|
||||
|
||||
## Активация фиксов
|
||||
- ASR-фикс (voice.py) — АКТИВЕН: поллер (simbiot-harness-tg.service) перезапущен 14:25 UTC+3 с новым кодом, новые голосовые расшифровываются сразу.
|
||||
- TimeoutExpired-фикс (isolated_runner.py) — вступит в силу при следующем рестарте планировщика (simbiot-harness.service): сейчас рестарт прибил бы работающие тики (hour + мин-задача по лендингу). Баг редкий (1 случай) и неразрушающий: claimed-задачи возвращаются в очередь даже старым путём (scheduler.py except → TaskPromoted). Рестартить в тихое окно.
|
||||
56
docs/digests/hour-2026-10-08-0735.md
Normal file
56
docs/digests/hour-2026-10-08-0735.md
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
# Часовой дайджест ~07:35 UTC, 2026-10-08
|
||||
|
||||
## Главное — все 3 задачи батча закрыты (3/3)
|
||||
1. **Burn-оценка: найдена и исправлена ошибка порядка 1000×** (f3a1d6, коммит c6cd612).
|
||||
Сверка с фактом дайджеста 25.09–02.10 (снимок живой usageHistory): 2491.47 у.е. /
|
||||
2.642 млрд токенов = **9.43e-7 у.е./токен**; кросс-чек по нашему ключу той недели
|
||||
(299.11 у.е. / ~300M токенов = 9.97e-7) — сходится. В коде стояло 0.000943
|
||||
(9.43e-**4**) — порядок величины вчитан неверно при калибровке. Итог: burn 7д
|
||||
из стора = **≈17.4 у.е.**, а не ≈17048.71. AUM-планировщик больше не искажён:
|
||||
AumMeasured перезаписан, aum_target в policy обновлён (`cli aum --record`) —
|
||||
контексты всех уровней уже видят честные числа. RATE_FLASH/RATE_FULL приведены
|
||||
к тому же порядку. Живая БД ~/.9router на хосте по-прежнему нет (миграция);
|
||||
usage-эндпоинтов API у ключа роутера нет (404, сессионные) — сверка с живой
|
||||
usageHistory остаётся на момент возврата пути к БД.
|
||||
2. **Presale P0 закрыт проверкой, а не работой** (b8be52, коммит 79e1254).
|
||||
Премиса задачи устарела: CTA на presale.cil.by и в каноне ведёт на
|
||||
`buy.stripe.com/aFa28q8h14RpfPYgrK1B60a` (HTTP 200), **не** test_-заглушка.
|
||||
Цена live $100/mo (6 упоминаний, $99.90 нет — расхождение из
|
||||
presale-price-divergence-2026-10-07.md снято). Счётчик мест живой из биллинга
|
||||
(seats API paid=1, taken=2, total=100) — через этот чекаут прошёл реальный
|
||||
платник. Создание нового линка невозможно и не нужно: STRIPE_LIVE_KEY на хосте
|
||||
нет, биллинг :8902 недоступен (timeout). Найден новый дрейф: канон захардкодил
|
||||
**10 мест** против бэкенда **100** — до решения Alex деплой канона не запускать.
|
||||
Артефакт: docs/presale-live-check-2026-10-08.md. Alex уведомлён (tg-notify,
|
||||
ключ presale-live-check-2026-10-08).
|
||||
3. **Детектор дрейфа репо в дневном тике** (2e2294, коммит 2e31249).
|
||||
Инцидент «2 дня не видели 32 коммита» покрыт механикой, не памятью LLM:
|
||||
`_repo_sync_check` в scheduler.py — git fetch origin + замер behind/ahead →
|
||||
событие RepoSynced (1/день, дедуп по дате); при behind>0 **два дня подряд** —
|
||||
EscalationRaised (счётчик из истории стора, рестарт демона не сбрасывает).
|
||||
Ошибка fetch не валит тик (behind=unknown честно фиксируется). Порог
|
||||
SIMBIOT_REPO_SYNC_MAX_AGE_DAYS (default 2). Живой прогон: **behind=0**, ahead=13,
|
||||
baseline RepoSynced записан (id 571862).
|
||||
|
||||
## События часа
|
||||
- Эскалации: открытых 0 (все acked ранее; новых не поднималось).
|
||||
- Минутные: 6 тихих тиков (гейт, LLM не запускался) — пустой час снизу.
|
||||
- Тесты: полный сюит **20/20 зелёный** (новый tests/test_repo_sync.py — 17 проверок
|
||||
hermetic в git-песочнице; остальные не задеты).
|
||||
- Доска: очередь пуста, 3 задачи hour-батча закрыты отдельно по мере готовности.
|
||||
- Среда: DNS-резолв cil.by с хоста флапает (были таймауты) — зеркалим через
|
||||
/etc/hosts → 204.168.232.21; router.cil.by стабилен.
|
||||
|
||||
## Активация фиксов
|
||||
- **burn-фикс (aum.py + aum_target)** — контексты уже честные (замер перезаписан в
|
||||
стор вручную); модуль демона перегрузится при рестарте.
|
||||
- **repo-sync шаг (scheduler.py)** — заработает после рестарта
|
||||
simbiot-harness.service; рестартить в тихое окно (сейчас прибил бы рабочие тики).
|
||||
Baseline на сегодня уже записан живым прогоном — день не потерян.
|
||||
|
||||
## Планы на след. час
|
||||
- Ждём ответ Alex: режим Stripe-линка в дашборде (live?), 10-vs-100 мест,
|
||||
доступ к биллингу :8902 для P1-задач.
|
||||
- Рестарт демона в тихое окно (активация repo-sync + burn-модуля).
|
||||
- Push 13 коммитов в origin — заблокирован отсутствием кредов (KEYS-REGISTRY),
|
||||
известное ограничение, эскалации не требует: детектор теперь следит за behind.
|
||||
25
docs/digests/hour-2026-10-08-2305.md
Normal file
25
docs/digests/hour-2026-10-08-2305.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# Часовой дайджест ~23:05 UTC, 2026-10-08
|
||||
|
||||
## Батч: 1/1 закрыт
|
||||
|
||||
**t-261008225713-7b46d8 [M] session_productize (дубль №3 от ядра router42) — done по cooldown-протоколу.**
|
||||
- product_create: одна живая проба (по ранбуку, не серия) — **всё ещё HTTP 500**, фикс портала не доехал. Дублей продуктов нет (product_list: только Totem id=10). Карточка бэклога id 13 жива (new); голос агента восстановлен после случайного toggle-off в feature_vote (votes=1, voted=true — финальное состояние идемпотентно).
|
||||
- Booking-половина жива: 80 session-слотов/3д, ближайший 09.10 15:00 UTC (18:00 Мск).
|
||||
- Бонус этого тика: свежая витрина кооператива под исполнимую альтернативу `price_ladder_audit` — Lunara $4.99 / ReLeaf $4.99 / Рилсап $199-699/mo / Симбиот $33-59-1000 one_time (все live). Внесена в ранбук.
|
||||
- Ранбук актуализирован и закоммичен: следующая проба product_create — не чаще раза на дубль-тик; после фикса одна проводка mode=test, live — только решением председателя.
|
||||
|
||||
## Систематика (третий дубль подряд)
|
||||
|
||||
Ядро router42 третий раз рекомендует программу, заблокированную багом её же тула (id 13). Вопрос маски исполнительных органов (сломан тул → программа выпадает из кандидатов) уже поднят председателю в тике 15:09 (t-261008140858, «мысль ядра»). До ответа — cooldown-режим: дубль-рекомендация закрывается одной пробой + авто-ответом «блокирован багом id 13». Каждый такой тик всё же даёт +1 замер (витрина, слоты, статус бага) — не нулевой.
|
||||
|
||||
## Состояние
|
||||
|
||||
- Эскалации: открытых 0 (секция пуста, обрабатывать нечего).
|
||||
- Минутные: 6 тихих тиков (гейт, LLM не запускался) — сырья снизу не было.
|
||||
- Очередь после тика: пуста (tasks_ready 0); доска — 19 done.
|
||||
- AUM-контекст: без изменений (26.7 у.е., burn 7д ≈17.4).
|
||||
|
||||
## Следующий час
|
||||
|
||||
- Если ядро снова отдаст session_productize — закрыть по cooldown (ранбук в artifacts/), больше одной пробы не делать.
|
||||
- Исполнимые альтернативы того же класса для ядра: warm_calls_week (booking жив), price_ladder_audit (витрина уже замерена — данные в ранбуке).
|
||||
28
docs/digests/hour-2026-10-09-1205.md
Normal file
28
docs/digests/hour-2026-10-09-1205.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# Часовой дайджест ~12:05 UTC, 2026-10-09
|
||||
|
||||
## Батч: 1/1 закрыт
|
||||
|
||||
**t-261009110039-3058c3 [M] session_productize (дубль №5 от ядра router42) — done по cooldown-протоколу.**
|
||||
- product_create: одна живая проба по ранбуку (AI Agent Session 60min / $150 one_time / mode=test) — **всё ещё HTTP 500**; фикс портала не доехал. Дублей продуктов нет (product_list: только Totem id=10).
|
||||
- Баг-карточка бэклога id 13 (kan 09084e066881): status=new, votes=1 — жива, дубль-карточку не плодил.
|
||||
- Booking-половина жива: 77 session-слотов/3д, ближайший 09.10 16:00 UTC (19:00 Мск). Бронь без реального клиента не создаю (guest_email обязателен).
|
||||
- Бонус-замер: coop_catalog без изменений — Lunara/ReLeaf $4.99, Рилсап $199–699/mo, Симбиот $33/59/1000 (все live).
|
||||
- Ранбук artifacts/session-productize-runbook.md актуализирован (5-я проба) и закоммичен.
|
||||
|
||||
## Систематика (пятый дубль подряд)
|
||||
|
||||
Ядро router42 пятый раз рекомендует программу, заблокированную багом её же тула (id 13), при novelty 99 — память прецедентов у ядра не пополняется. Вопрос поднят председателю (t-261008140858). До ответа — cooldown: одна проба на дубль-тик + авто-ответ «блокирован багом id 13».
|
||||
|
||||
## Состояние
|
||||
|
||||
- Эскалации: открытых 0 (5 старых — все acked; обрабатывать нечего).
|
||||
- Минутные: 6 тихих тиков (gate:quiet, LLM не запускался).
|
||||
- Очередь после тика: пуста (tasks_ready 0); доска — 22 done.
|
||||
- AUM-контекст: без изменений (26.7 у.е.).
|
||||
- Тех.минут: 1 ошибка min-уровня (TimeoutExpired — старый знакомый), на час не влияет.
|
||||
|
||||
## Следующий час
|
||||
|
||||
- Дубль session_productize → закрыть по cooldown (ранбук в artifacts/), одна проба.
|
||||
- Исполнимые альтернативы для ядра: warm_calls_week (booking жив), price_ladder_audit (витрина свежая, 09.10 12:0x).
|
||||
- После фикса портала: довыполнение ранбука одним тиком (product_create test → product_list → coop_catalog → оффер тёплым лидам → booking_create по отклику).
|
||||
45
docs/digests/hour-2026-10-09-1305.md
Normal file
45
docs/digests/hour-2026-10-09-1305.md
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
# Часовой дайджест ~13:05 UTC, 2026-10-09
|
||||
|
||||
## Батч: 2/2 закрыто (t-261009122125 → t-261009123019, по очереди от старейшей)
|
||||
|
||||
**1. t-261009122125-45922c [M] — шаблон продуктовых гипотез расширен, done.**
|
||||
- Отдельного шаблона в базе знаний не было (RAG + grep пусто) — собран с нуля:
|
||||
`docs/product-hypothesis-template.md` (125 строк, в git).
|
||||
- Три обязательных блока по запросу Alex: конкурентное исследование (таблица 3+ аналогов,
|
||||
обоснование цены против cheapest/closest/premium, проверка каннибализации с live-продуктами
|
||||
витрины), профиль клиента (сегмент/WTP/триггер/возражение №1/кворум тёплым контуром),
|
||||
сегменты и каналы по тирам. Плюс метрики с критерием смерти и обязательный фильтр AUM.
|
||||
- Заякорен на живое: PRICE-DIRECTIVE-SIMBIOT ($100/mo без скидок), витрина 09.10,
|
||||
SALES-PLAN-1000-US. Заполненный пример — AI Agent Session $150 из ранбука.
|
||||
|
||||
**2. t-261009123019-a32bb3 [L] — рыночная симуляция, done (файл доставлен в TG).**
|
||||
- `scripts/market_sim.py` + `docs/market-simulation-2026-10-09.md` (в git): 4 конкурента
|
||||
(DIY $25 / фрилансер $100 / консультант $150 / студия $300), logit-выбор клиента,
|
||||
3 сегмента (WTP $60–300), отток 8%/мес, рекуррентный биллинг по директиве, сессии $150.
|
||||
- Монте-Карло 400 trial'ов / 12 мес: не входить E[ΔAUM]=−1 488; тёплый контур +24 107
|
||||
(окупаемость 1-й мес, 40 подписчиков); presale-воронка +93 611; микс +92 929 — оба
|
||||
упираются в капу 100 мест. Чувствительность ±6% — вывод устойчив.
|
||||
- Главные выводы: входить выгодно в любом сценарии (фильтр AUM пробит ×1000); узкое место —
|
||||
капа мест и трафик, НЕ конверсия/цена; цену не трогать (паритет с фрилансером уже даёт
|
||||
топ-долю выбора 27–35%); сессии ждут фикса бага id 13.
|
||||
|
||||
## Консолидация минутных (выжимка)
|
||||
|
||||
6 тихих тиков подряд (gate:quiet, LLM не запускался) — событий важнее порога не было.
|
||||
Тех.минуты: 2 ошибки hour-уровня (Connection error при старом тике) и 1 min (TimeoutExpired) —
|
||||
все старые, соответствуют уже acked-эскалациям e-261006160049 / e-261005110300; новых не породили, обработки не требуют.
|
||||
|
||||
## Состояние
|
||||
|
||||
- Эскалации: открытых 0 (все 5 в списке — acked; разбирать нечего).
|
||||
- Очередь после тика: пуста (ready 0), доска 25 done.
|
||||
- AUM: 26.7 у.е., burn 7д=28.55 (≈124/мес; store-оценка, живая БД 9router недоступна).
|
||||
- session_productize: без изменений — cooldown действует, product_create 500 (баг id 13),
|
||||
дубль-рекомендаций ядра в этом часу не было.
|
||||
|
||||
## Следующий час
|
||||
|
||||
- Очередь пуста — ждать новых задач; исполнимые альтернативы для дублей ядра: warm_calls_week,
|
||||
price_ladder_audit (ранбук в artifacts/session-productize-runbook.md).
|
||||
- Симуляция дала решение для председателя: расширение капы мест (100 → выше) — самый большой
|
||||
рычаг AUM после запуска трафика; вынести в повестку дня/недели при первом прогибе мест.
|
||||
37
docs/digests/hour-2026-10-09-1505.md
Normal file
37
docs/digests/hour-2026-10-09-1505.md
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
# Часовой дайджест ~15:05 UTC, 2026-10-09
|
||||
|
||||
## Батч: 1/1 закрыто
|
||||
|
||||
**t-261009141729-ca621a [M] [systemone-insight] мысль ядра для председателя — done.**
|
||||
- Опора на живое состояние ядра: 41018 эпизодов стартапа vs 12955 студии, память 20000 пар,
|
||||
BTC $82801 (×1.0088), рекомендация session_productize — уже 6-й дубль (novelty 99,
|
||||
память прецедентов не пополняется, баг бэклога id 13 блокирует product_create).
|
||||
- Мысль для студии: ядро оптимизирует знакомую зону, а не ценность — связка
|
||||
«дубль-рекомендация + необучаемая память» не лечится рынком. Предложение: маска исполнимости
|
||||
в арбитре core (мертвая программа выпадает из кандидатов после 2 неудач, а не 6) + правило
|
||||
70/30 на разнообразие (30% тиков принудительно на нераскрытые advisor_digest /
|
||||
cross_agent_adoption).
|
||||
- Вопрос-крючок председателю: дать арбитру core право вето по маске исполнимости — или
|
||||
продолжать оплачивать обучение ядра на повторениях?
|
||||
- Решение не дублирует прошлую мысль (t-261008140858: «ядро рекомендует в вакууме») —
|
||||
сдвиг от диагностики к конкретному механизму (вето + квота разнообразия).
|
||||
|
||||
## Консолидация минутных (выжимка)
|
||||
|
||||
Час тихий: минутные тики все gate:quiet (LLLM не запускался), сырья снизу не было.
|
||||
Тех.минуты: errors hour=2 (Connection error) и min=1 (TimeoutExpired) — все старые,
|
||||
соответствуют уже acked-эскалациям e-261006160049 / e-261005110300; новых не породили.
|
||||
|
||||
## Состояние
|
||||
|
||||
- Эскалации: открытых 0 (5 в списке — все acked; разбирать нечего).
|
||||
- Очередь после тика: пуста (ready 0), доска 27 done.
|
||||
- AUM: 26.7 у.е., burn 7д=≈26.19 (store-оценка × блендед-ставка; живая БД 9router недоступна).
|
||||
- session_productize: cooldown действует, в этом часу дублей ядра не было (был systemone-insight).
|
||||
|
||||
## Следующий час
|
||||
|
||||
- Очередь пуста — ждать новых задач. Исполнимые альтернативы для дублей ядра: warm_calls_week,
|
||||
price_ladder_audit (ранбук artifacts/session-productize-runbook.md).
|
||||
- Ждём ответа председателя на вопрос о праве вето арбитра core (поднят в сегодняшней мысли);
|
||||
до ответа дубли закрываются по cooldown-протоколу.
|
||||
163
docs/m-973940607-analysis.html
Normal file
163
docs/m-973940607-analysis.html
Normal file
File diff suppressed because one or more lines are too long
1
docs/m-973940607-transcript.txt
Normal file
1
docs/m-973940607-transcript.txt
Normal file
File diff suppressed because one or more lines are too long
106
docs/market-simulation-2026-10-09.md
Normal file
106
docs/market-simulation-2026-10-09.md
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
# Рыночная симуляция Simbiot: конкуренты + клиенты + сценарии входа
|
||||
|
||||
> t-261009123019-a32bb3 [L], 2026-10-09. Модель и прогоны: `scripts/market_sim.py`
|
||||
> (400 trial'ов Монте-Карло, seed 42, горизонт 12 мес). Поверх расширенного шаблона
|
||||
> гипотез `docs/product-hypothesis-template.md` (t-261009122125): блоки 1 (конкуренты/цена),
|
||||
> 2 (клиент), 3 (сегменты/каналы) — напрямую параметризуют модель.
|
||||
> Аргументы запуска: `./venv/bin/python scripts/market_sim.py --trials 400 --seed 42 --sens`
|
||||
|
||||
## 1. Что моделируем
|
||||
|
||||
Рынок «автономный агент вместо ручной рутины» с четырьмя конкурентами, тремя сегментами
|
||||
клиентов и тремя стратегиями входа Simbiot (+базлайн «не входить»).
|
||||
|
||||
**Клиент выбирает по logit-полезности**: U = (value − price) / scale. Simbiot конкурирует
|
||||
ценой $100/mo (директива m-516173408 — без скидок, рекуррентно) и value-бустом доверия
|
||||
у тёплых лидов (видели живой прод харнеса).
|
||||
|
||||
**Конкуренты** (блок 1 шаблона; value — калибровочное допущение, цены — реальные бенчмарки):
|
||||
|
||||
| Конкурент | Цена | value | Приорная доля рынка |
|
||||
|---|---|---|---|
|
||||
| DIY (LLM-подписка + ручные промпты) | $25/mo | 30 | 45% |
|
||||
| Upwork/AI-фрилансер (часами) | ~$100/mo | 110 | 30% |
|
||||
| AI-консультант соло (сессии) | ~$150/mo | 140 | 15% |
|
||||
| AI-студия (проектно) | ~$300/mo | 260 | 10% |
|
||||
|
||||
**Сегменты** (блок 2 шаблона): s1 соло-основатель (35% потока, WTP $130, value-множитель 1.0);
|
||||
s2 индиз/автоматизатор (40%, WTP $60, ×0.75 — цена $100 выше их WTP, они остаются в DIY-лагере);
|
||||
s3 операционка МБ (25%, WTP $300, ×1.3).
|
||||
|
||||
**Калибровка честная**: logit_scale=100 даёт сквозную холодную конверсию presale-лендинга
|
||||
≈0.5–1.1% — верх диапазона плана SALES-PLAN-1000-US; тёплая ≈35% (буст ×1.3). Отток 8%/мес.
|
||||
|
||||
## 2. Сценарии входа
|
||||
|
||||
| Сценарий | Тёплые лиды/мес | Посетители presale/мес (мес1→12) | Платный трафик | Сессии $150 |
|
||||
|---|---|---|---|---|
|
||||
| D_no_entry | 0 | 0 | — | — |
|
||||
| A_warm_only | 15 | 0 | нет | 12%/тёплых |
|
||||
| B_presale_funnel | 12 | 5k → 95k | 60% лидов, CAC $22 | 5% |
|
||||
| C_mixed | 15 | 3k → 90k | 50% лидов, CAC $22 | 12% |
|
||||
|
||||
Экономика харнеса — из живого замера: burn 124 у.е./мес (28.55/7д, store-BudgetSpent),
|
||||
+1 у.е./мес на активного подписчика, +5 у.е. на сессию (допущения). AUM₀ = 26.7 у.е.
|
||||
|
||||
## 3. Результаты (E[] по 400 trial'ам, 12 мес, у.е.)
|
||||
|
||||
| Сценарий | rev подписки | rev сессии | CAC | burn | **E[ΔAUM]** | P(ΔAUM>0) | подписчики (медиана) | окупаемость |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| D_no_entry | 0 | 0 | 0 | 1 488 | **−1 488** | 0% | 0 | не окупается |
|
||||
| A_warm_only | 24 119 | 1 840 | 0 | 1 852 | **+24 107** | 100% | 40 | 1-й мес |
|
||||
| B_presale_funnel | 97 215 | 906 | 1 835 | 2 675 | **+93 611** | 100% | 100 (капа мест) | 2-й мес |
|
||||
| C_mixed | 95 146 | 1 805 | 1 340 | 2 682 | **+92 929** | 100% | 100 (капа мест) | 2-й мес |
|
||||
|
||||
Доли выбора (сегмент s1): Simbiot $100/mo — 34.8% тёплый / 26.6% холодный контур; далее
|
||||
фрилансер 19–22%, DIY 18–21%, консультант 16–18%, студия 12–13%.
|
||||
|
||||
## 4. Чувствительность (C_mixed)
|
||||
|
||||
| Вариант | E[ΔAUM] | P(>0) | Подписчики |
|
||||
|---|---|---|---|
|
||||
| база | 92 929 | 1.00 | 100 |
|
||||
| churn 5% / 12% | 96 285 / 88 331 | 1.00 | 100 |
|
||||
| logit_scale 80 / 130 (вера в продукт) | 93 333 / 92 280 | 1.00 | 100 |
|
||||
| value −15% / +15% | 91 563 / 93 713 | 1.00 | 100 |
|
||||
| reach ×0.5 / ×2 (качество трафика) | 87 658 / 96 019 | 1.00 | 100 |
|
||||
|
||||
Вывод устойчивости: разброс E[ΔAUM] ±6% по всем допущениям — **потому что капа мест (100)
|
||||
связывает спрос во всех вариантах**. Модель говорит: узкое место — не конверсия и не цена,
|
||||
а (а) лимит мест и (б) способность залить трафик в воронку.
|
||||
|
||||
## 5. Выводы для решений
|
||||
|
||||
1. **Входить — да, при любой стратегии**: даже A_warm_only без рекламного бюджета даёт
|
||||
+24.1k у.е. за год против −1.5k у «не входить», окупаемость с 1-го месяца. Фильтр AUM
|
||||
(+1% = +0.27 у.е.) пробивают все сценарии входа с запасом ×1000.
|
||||
2. **Капа мест — главный ограничение роста**: B/C упираются в 100 мест (медиана). Расширение
|
||||
лимита (директива, решение Alex) стоит на порядки больше для AUM, чем любая оптимизация
|
||||
конверсии. Порядок: сначала пролить тёплый контур (A), поднять трафик (B/C), потом
|
||||
поднимать капу.
|
||||
3. **Тёплого контура не хватает на полный пролив**: A_warm_only оставляет ~60 мест
|
||||
незанятыми — воронка presale (трафик) нужна как второй шаг, не как альтернатива.
|
||||
4. **Сессии $150 — маленький, но почти чистый маржинальный доход** (~1.8k/год в C,
|
||||
burn 5 у.е./сессия) и верх воронки к подписке. Реально заблокировано багом портала
|
||||
id 13 (ранбук session_productize) — фикс портала стоит в цепочке.
|
||||
5. **Цену не трогать**: директива запрещает скидки; модель подтверждает — паритет цены с
|
||||
фрилансером ($100) при более высокой value уже даёт топ-долю выбора. Скидка не нужна
|
||||
для победы в logit-выборе.
|
||||
|
||||
## 6. Ограничения модели (честно)
|
||||
|
||||
- value конкурентов и reach_in_market=4% — калибровочные допущения, не замеры; доля рынка
|
||||
конкурентов (share0) — приор для калибровки value, в динамике выбора не участвует.
|
||||
- Трафик-рампы посетителей (5k→95k/мес) — цель из SALES-PLAN-1000-US, не факт; при нулевом
|
||||
трафике B/C деградируют к A_warm_only (+24k), не к отрицательному.
|
||||
- Не моделировалось: реакция конкурентов на вход (ценовые войны), саппорт-нагрузка на
|
||||
подписчиков, возвраты/refund, сезонность; burn-per-sub — плоские 1 у.е./мес.
|
||||
- Сессии предполагают фикс бага product_create (id 13); до фикса компонента сессий нет.
|
||||
- Модель deterministic-demand per month, no network effects (кейсы/соцдоказательство
|
||||
повышают value — квазично через warm_mult).
|
||||
|
||||
## 7. Артефакты
|
||||
|
||||
- Модель: `scripts/market_sim.py` (детерминированный seed, `--sens` — чувствительность).
|
||||
- Шаблон гипотез, на котором стоит модель: `docs/product-hypothesis-template.md`.
|
||||
- Входные якоря: docs/PRICE-DIRECTIVE-SIMBIOT.md ($100/mo, 100 мест), artifacts/session-productize-runbook.md (сессия $150, витрина), docs/SALES-PLAN-1000-US (воронка), docs/aum.md (метрика ΔAUM).
|
||||
52
docs/presale-live-check-2026-10-08.md
Normal file
52
docs/presale-live-check-2026-10-08.md
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# Presale live-check: CTA / Stripe-линк / цена / места — 2026-10-08
|
||||
|
||||
> Задача t-261007082307-b8be52 [L, P0 «живой Stripe-линк на CTA»] · часовой поток Simbiot.
|
||||
> Премиса задачи («CTA уводит на офсет-заглушку test_...») — устарела: проверено вживую, заглушки нет.
|
||||
|
||||
## Проверено вживую (2026-10-08 ~07:1x UTC, с хоста W11PC)
|
||||
|
||||
| Что | Состояние | Доказательство |
|
||||
|---|---|---|
|
||||
| Live presale.cil.by | **200, 26 293 B** | curl -sSL (DNS через /etc/hosts 204.168.232.21; резолв cil.by с этого хоста флапает — router.cil.by резолвится, cil.by/presale.cil.by иногда нет) |
|
||||
| CTA на live | **ведёт на `https://buy.stripe.com/aFa28q8h14RpfPYgrK1B60a`** (1 вхождение) | grep по HTML |
|
||||
| Канон `docs/simbiot_presale_landing.html` | тот же линк на CTA (строки 216, 244) — **не test_-заглушка** | grep |
|
||||
| Цена на live | **$100/mo — 6 упоминаний**, $99.90 не встречается | grep по HTML |
|
||||
| Checkout-страница Stripe | **HTTP 200**, classic-вариант | curl -D (Fastly, cache HIT) |
|
||||
| Счётчик мест на live | **бэкендовый**: `GET /api/presale/seats` → `{"paid":1,"taken":2,"total":100}` | живой API |
|
||||
| Реальная продажа | **paid=1** — через этот чекаут прошёл платник | seats API + docs/digests (первый платник зафиксирован 10-07) |
|
||||
| Рекуррентность | FAQ: «monthly billing via Stripe, cancel anytime»; в чекауте subscription/month-строки | HTML |
|
||||
|
||||
Вывод: **P0-блокер «CTA → заглушка» снят** — линк на месте и собирает деньги
|
||||
(paid=1). Дополнительно снят пункт расхождения цены из
|
||||
docs/presale-price-divergence-2026-10-07.md: live теперь $100/mo (директива m-516173408 соблюдена).
|
||||
|
||||
## Что НЕ удалось проверить с этого хоста (честно)
|
||||
|
||||
1. **Режим линка (live vs test)** в Stripe-дашборде: STRIPE_LIVE_KEY на этом
|
||||
хосте нет (KEYS-REGISTRY — ключи на старом инстансе/проде). Косвенные
|
||||
признаки ЗА live-режим: paid=1 (реальные деньги), launch-kit 10-04
|
||||
подтверждал линк живым, pk_test в HTML-оболочке чекаута — generic-код
|
||||
кэшируемой Fastly-оболочки (одинаковой для всех линков), НЕ ключ сессии.
|
||||
Решающая проверка — одна минута в Stripe-дашборде у Alex.
|
||||
2. **Биллинг** `127.0.0.1:8902` — отсутствует на этом хосте (жил на старом);
|
||||
прямой `204.168.232.21:8902` — connection timeout (файрвол). Создание
|
||||
новых платёж-линков через API отсюда невозможно — только через прод.
|
||||
|
||||
## Новый дрейф (канон отстаёт от прода)
|
||||
|
||||
Live: счётчик мест из бэкенда, **total=100** (показывает 98 left).
|
||||
Канон `docs/simbiot_presale_landing.html`: `var left=10,total=10` —
|
||||
захардкожено **10 мест** (показал бы 8). Число мест — решение Alex
|
||||
(10 из SALES-PLAN-PRESALE-PAGE vs 100 из бэкенда/SALES-PLAN-1000-US);
|
||||
харнес не выбирает сам — вопрос задан в уведомлении.
|
||||
Канон при деплое перезапишет живой счётчик заглушкой → до решения
|
||||
деплой канона на presale.cil.by НЕ запускать без сверки.
|
||||
|
||||
## Открытые вопросы к Alex (уведомление отправлено)
|
||||
|
||||
1. Подтвердить режим линка `aFa28q8h14RpfPYgrK1B60a` в Stripe-дашборде (live?).
|
||||
2. Сколько мест в presale: 10 или 100 (канон vs бэкенд)?
|
||||
3. Поднять биллинг-доступ для харнеса (8902 за файрволом) для P1-задач
|
||||
(счётчик/атрибуция) и будущих платёж-линков.
|
||||
|
||||
— Simbiot harness (hour), t-261007082307-b8be52.
|
||||
46
docs/presale-price-divergence-2026-10-07.md
Normal file
46
docs/presale-price-divergence-2026-10-07.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# Presale: расхождение цены live vs канон + блокировка деплоя
|
||||
|
||||
> День 2026-10-07. Проверено живьём: `cil.by/presale` (HTTP 200, 13.2KB).
|
||||
|
||||
## Факты
|
||||
|
||||
| Что | Состояние |
|
||||
|---|---|
|
||||
| Live-страница cil.by/presale | **$99.90/mo — 7 упоминаний** (hero, offer, FAQ, футер) |
|
||||
| Канон `docs/simbiot_presale_landing.html` | **$100/mo везде** (директива m-516173408, коммит 42de213) + mock-секции «What it looks like on a Tuesday» |
|
||||
| Счётчик мест | бэкендовый (`/api/presale/seats`): **paid=1, taken=2, total=100** — первый реальный платник |
|
||||
| Канон vs live | разные файлы: live не содержит правок канона (mock-секции, $100) |
|
||||
|
||||
## Директива цены (m-516173310/11, обновлена m-516173408)
|
||||
|
||||
Единая цена **$100/mo во всех упоминаниях**, занижение запрещено. Live
|
||||
продаёт за $99.90 — прямое нарушение директивы, и это цена, по которой уже
|
||||
есть **1 платник**.
|
||||
|
||||
## Почему харнес не задеплоил (осознанно, не отговорка)
|
||||
|
||||
Хост харнеса мигрировал (~2026-10-05) на **W11PC** (192.168.0.200, LAN):
|
||||
- SSH-ключей нет по политике (KEYS-REGISTRY §3–4) → на прод (204.168.232.21) не попасть;
|
||||
- FORGEJO_PULL_TOKEN живёт в `/root/coop/.alfa_env` на старом хосте — здесь отсутствует;
|
||||
- push в origin без креденшелов не работает (`could not read Username`), поэтому канон с $100 сейчас только локально.
|
||||
|
||||
## Что нужно (одна команда от Alex, у кого SSH на прод)
|
||||
|
||||
```bash
|
||||
# с хоста, где есть SSH-ключ на прод и актуальный канон:
|
||||
ssh root@204.168.232.21 'bash -s' < scripts/deploy_landing.sh
|
||||
```
|
||||
Скрипт из origin/master (день 10-05): бэкапит index.html, тянет канон из
|
||||
git.cil.by, ставит, проверяет. **Важно**: REPO_RAW указывает на
|
||||
`simbiot-landing-v2-live.html` — для presale-страницы нужно направить на
|
||||
актуальный канон (`docs/simbiot_presale_landing.html` после пуша) —
|
||||
параметр REPO_RAW.
|
||||
|
||||
## Открытые вопросы Alex
|
||||
|
||||
1. Задеплоить $100-канон на cil.by/presale (директива нарушена, платник есть).
|
||||
2. Вернуть харнесу push-канал: FORGEJO-токен (write) в .env этого инстанса.
|
||||
3. Ключи авто-балансов AUM: OPENROUTER_KEY / STRIPE_LIVE_KEY — сейчас
|
||||
авто-refresh слеп (26.7 — stale-замер 10-04).
|
||||
4. Живая БД 9router недоступна с этого хоста — burn считается оценкой из
|
||||
стора (блендед-ставка). Если есть сетевой путь к usageHistory API — дать ключ.
|
||||
125
docs/product-hypothesis-template.md
Normal file
125
docs/product-hypothesis-template.md
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
# Шаблон продуктовых гипотез Simbiot (расширенный)
|
||||
|
||||
> Создан по запросу Alex (t-261009122125-45922c, 2026-10-09). Отдельного шаблона в базе
|
||||
> знаний не было — собран с нуля, заякорен на живые артефакты: docs/PRICE-DIRECTIVE-SIMBIOT.md
|
||||
> (директива прайса $100/mo, без скидок), витрина кооператива (замер 09.10, artifacts/session-productize-runbook.md),
|
||||
> docs/SALES-PLAN-1000-US.md, конкурентный анализ film.cil.by (t-261009122421-805cfd).
|
||||
> Основа расширения — три обязательных блока по запросу Alex: (1) конкуренты и цена,
|
||||
> (2) профиль клиента, (3) сопутствующее: сегменты и каналы.
|
||||
|
||||
## Зачем
|
||||
|
||||
Каждая продуктовая гипотеза Simbiot проходит через фильтр AUM («добавляет ≥ +1% к AUM
|
||||
в горизонте год+?»). Расширение закрывает главный источник мусорных гипотез: цену,
|
||||
взятую «с потолка», и клиента, описанного словом «все». Без конкурентного обоснования
|
||||
цены и профиля платящего клиента гипотеза не идёт в работу — это quality gate, не бюрократия.
|
||||
|
||||
## Как пользоваться
|
||||
|
||||
1. Скопировать секцию «Карта гипотезы» на каждый новый продукт/пакет.
|
||||
2. Заполнить все блоки; пометка `(обязательно)` = без неё гипотеза не проходит на доску.
|
||||
3. Цена — только через директиву (docs/PRICE-DIRECTIVE-SIMBIOT.md): единое число, без
|
||||
скидок и вариаций; любое изменение прайса — отдельное решение Alex.
|
||||
4. Проверить против фильтра AUM внизу карты. Не прошла — не отбрасывать, а записать
|
||||
причину в «Вывод» (память для будущих итераций).
|
||||
|
||||
---
|
||||
|
||||
## Карта гипотезы
|
||||
|
||||
### 0. Ядро гипотезы
|
||||
|
||||
- **ID/название**:
|
||||
- **Продукт** (что продаём, 1 фраза):
|
||||
- **Проблема клиента** (какую боль закрываем, словами клиента):
|
||||
- **Решение** (что именно делаем):
|
||||
- **Форм-фактор**: one_time / monthly / пакет (часы? слоты? лимиты?)
|
||||
- **Цена** (одно число, по директиве): $
|
||||
|
||||
### 1. Конкурентное исследование `(обязательно)`
|
||||
|
||||
Заполняется ДО обоснования цены — цена выводится из этой таблицы, а не наоборот.
|
||||
|
||||
| Конкурент/аналог | Что продаёт | Цена (мес/раз) | Сильное | Слабое |
|
||||
|---|---|---|---|---|
|
||||
| (минимум 3 прямых/косвенных аналога; для внутренних продуктов кооператива — строки витрины) | | | | |
|
||||
|
||||
- **Позиционирование против каждого**: чем отличаемся (1 фраза на строку).
|
||||
- **Обоснование нашей цены**:
|
||||
- относительно cheapest-аналога (премия за что?);
|
||||
- относительно closest-аналога (паритет или ниже/выше — почему);
|
||||
- относительно premium-аналога (что не даём — честно).
|
||||
- **Проверка на каннибализацию**: не отбирает ли продукт платящих у уже live-продуктов
|
||||
кооператива (Lunara/ReLeaf $4.99/mo, Рилсап $199–699/mo, Симбиот $33/$59/$1000)?
|
||||
Если да — почему это чистый плюс к AUM, а не перенос.
|
||||
|
||||
### 2. Профиль целевого клиента `(обязательно)`
|
||||
|
||||
- **Сегмент** (узкий!): не «разработчики», а например «соло-фрилансер, вёл 3+ клиентов
|
||||
одновременно, устал от ручного планирования».
|
||||
- **Платёжеспособность**: сколько уже платит за смежные инструменты (из таблицы
|
||||
конкурентов), бюджет на категорию $/мес.
|
||||
- **Триггер покупки** (в какой момент жизни клиента боль обостряется).
|
||||
- **Возражение №1** и ответ на него.
|
||||
- **Где обитает / как найдём**: каналы достоверного доступа (см. блок 3).
|
||||
- **Кворум для валидации**: скольких таких клиентов можем достать тёплым контуром
|
||||
(личный круг, contact_list replied/converted — не холодная рассылка).
|
||||
|
||||
### 3. Сопутствующее: сегменты и каналы `(обязательно)`
|
||||
|
||||
- **Сегменты приоритетом**: 1) … 2) … 3) … (кто первый — там самая сильная боль/доступ).
|
||||
- **Каналы по тиру** (из SALES-PLAN-1000-US):
|
||||
- Тир-0 тёплый контур: личный круг, TG/x.com хроники харнеса, Show HN / Reddit;
|
||||
- Тир-1 платный: какая площадка, при каком CAC-пороге включаем;
|
||||
- Партнёрский/реферальный: кто уже имеет доступ к сегменту.
|
||||
- **Первое касание**: что именно показываем сегменту №1 (продукт живой у нас в проде —
|
||||
главный якорь доверия).
|
||||
|
||||
### 4. Метрики и валидация
|
||||
|
||||
- **Северозвёздная метрика успеха** (одна): например mrr_add, presale-предзаказы, проведённые сессии.
|
||||
- **Цель на 2 недели** (измеримо):
|
||||
- **Критерий смерти гипотезы**: при каких числах закрываем и что записываем в память.
|
||||
- **Как меряем** (инструмент уже есть): presale-трекинг кликов, product_list/coop_catalog,
|
||||
booking_slots, contact_list-статусы.
|
||||
|
||||
### 5. Фильтр AUM `(обязательно)`
|
||||
|
||||
- Добавляет ≥ +1% к AUM в горизонте год+? (текущая AUM 26.7 у.е., эталон +0.27 у.е.)
|
||||
- Что даёт на дистанции год: повторные покупки? LTV-механика? данные? дистрибуция?
|
||||
ИЛИ разовый доход, съедающий внимание/ресурс долгосрочной базы?
|
||||
- **Вывод**: go / no-go + одна фраза почему.
|
||||
|
||||
---
|
||||
|
||||
## Пример заполнения: AI Agent Session (60 min)
|
||||
|
||||
> Реальный живой кейс из ранбука session_productize — продукт ждёт фикса бага id 13.
|
||||
|
||||
- **0. Ядро**: сессия с агентом 60 мин; боль — «хочу понять, что автономный агент реально
|
||||
может для МЕНЯ, без подписки»; решение — живой час работы агента над задачей клиента.
|
||||
Форм-фактор: one_time. **Цена $150** (согласована с директивой: сессия НЕ входит в
|
||||
presale-линейку $100/mo — отдельный оффер агент-студии; $100/mo упоминания не трогаем).
|
||||
- **1. Конкуренты**: консультация фрилансера-автоматизатора ~$50–100/час (слабое: без
|
||||
инфраструктуры агентов); Upwork AI-подрядчик $30–80/час + дни на скоуп (слабое: не
|
||||
продукт, каждый раз заново); внутренние сессии студий $200–500 (сильное: нас больше).
|
||||
Позиционирование: не «консультация», а «живой прогон твоей задачи на проде».
|
||||
Обоснование $150: выше фрилансера — за счёт инфраструктуры харнеса (прод-продукт как
|
||||
якорь), ниже студий — за счёт отсутствия менеджмента проекта. Каннибализации с presale
|
||||
$100/mo нет: разовый вход vs подписка; сессия — верх воронки к подписке.
|
||||
- **2. Клиент**: соло-основатель/операционка малого бизнеса, уже платит за AI-инструменты
|
||||
$50–200/мес; триггер — «слишком много рутины, но не верю, что агент справится»;
|
||||
возражение №1 «это чат-бот с модным названием» → ответ: показываем хронику тиков
|
||||
харнеса в реальном проде. Доступ: contact_list replied/converted, личный круг.
|
||||
- **3. Каналы**: сегмент №1 — тёплые лиды кооператива (уже видели прод); №2 — аудитория
|
||||
TG/x.com-хроник; №3 — Reddit r/AI_Agents после 3+ соцдоказательств.
|
||||
- **4. Метрика**: mrr_add цель +0.4k/2нед (advisor42 H8 top-1, prob 57.6%); смерть —
|
||||
0 проведённых сессий за 2нед при 10+ офферах тёплым лидам.
|
||||
- **5. AUM**: go — сессия конвертит внимание в первый платёж И даёт материал кейсов
|
||||
(дистрибуция на дистанции); фильтр проходит как воронка к подписке.
|
||||
|
||||
## Память шаблона
|
||||
|
||||
- Источник запроса: Alex, тик 2026-10-09 ~12:21 UTC.
|
||||
- Первое применение: т-261009123019-a32bb3 (рыночная симуляция поверх этого шаблона).
|
||||
- Обновлять шаблон = PR в репо + строка здесь (дата, что изменилось).
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Simbiot — Proactive AI Harness for Your Org · Presale $99.9/mo</title>
|
||||
<title>Simbiot — Proactive AI Harness for Your Org · Presale $100/mo</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter+Tight:wght@400;600;800&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root{--ink:#1c1a17;--bg:#faf8f3;--panel:#f2efe7;--line:#e2ded2;--muted:#6f6a5e;--accent:#5b4be0;--accent2:#ffffff;--bad:#c0392b;}
|
||||
|
|
@ -58,6 +58,15 @@ footer a{color:var(--muted)}
|
|||
@media(max-width:640px){.offer{padding:40px 24px}.offer .price{font-size:3.2rem}}
|
||||
/* v2: proof, roadmap, steps, compare */
|
||||
.trustbar{display:flex;gap:36px;flex-wrap:wrap;justify-content:center;background:var(--panel);border:1px solid var(--line);border-radius:16px;padding:26px 24px;margin-top:34px}
|
||||
@media (prefers-reduced-motion: reduce){*{animation:none!important;transition:none!important;scroll-behavior:auto!important}}
|
||||
.mock{border:1px solid var(--line);border-radius:14px;overflow:hidden;background:#fff}
|
||||
.mock .bar{background:var(--panel);border-bottom:1px solid var(--line);padding:8px 14px;font-size:.75rem;font-weight:600;text-transform:uppercase;letter-spacing:.08em;color:var(--muted)}
|
||||
.mock .body{padding:18px 20px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.85rem}
|
||||
.tg{background:#fff;border-radius:12px;padding:10px 14px;max-width:78%;margin:6px 0;border:1px solid var(--line)}
|
||||
.tg.in{background:var(--panel)}
|
||||
.tg .who{font-size:.7rem;color:var(--accent);font-weight:700;margin-bottom:2px;font-family:'Inter Tight',sans-serif}
|
||||
.dig{border-left:2px solid var(--line);padding-left:14px;margin:8px 0}
|
||||
.dig b{font-family:'Inter Tight',sans-serif}
|
||||
.trustbar div{min-width:150px}
|
||||
.trustbar b{display:block;font-size:1.5rem;font-weight:800}
|
||||
.trustbar span{color:var(--muted);font-size:.85rem}
|
||||
|
|
@ -84,18 +93,18 @@ footer a{color:var(--muted)}
|
|||
<header class="hero">
|
||||
<div class="wrap">
|
||||
<div class="badge"><span class="dot"></span> Presale · 10 final seats</div>
|
||||
<h1>Your org deserves<br>its own AI harness.</h1>
|
||||
<p class="lead">Simbiot is a proactive, autonomous harness for the whole organization. It triages, decides and executes across your real systems — Telegram, projects, portals, deadlines — under one rhythm, with every action recorded and auditable.</p>
|
||||
<h1>The AI worker we already employ.<br>Yours in 30 days.</h1>
|
||||
<p class="lead">Simbiot is the proactive harness that runs our own cooperative — 7 products, every day. Your copy gets installed on your org's real systems: Telegram, projects, portals, deadlines — under one rhythm, every action recorded and auditable.</p>
|
||||
<p style="color:var(--ink);max-width:640px;font-weight:600">Presale means shipping code, not promises: the same harness that runs our own coop every day — your copy goes live within 30 days of payment.</p>
|
||||
<div class="hero-row">
|
||||
<a class="cta" href="#offer">Reserve a seat — $99.9/mo</a>
|
||||
<a class="cta" href="#offer">Reserve a seat — $100/mo</a>
|
||||
<a class="ghost" href="#offer">How presale works</a>
|
||||
<div class="counter"><b>10</b><span class="cap">seats left of 10</span></div>
|
||||
</div>
|
||||
|
||||
<div class="trustbar">
|
||||
<div><b>7</b><span>products building on it</span></div>
|
||||
<div><b>$99.9</b><span>per month, no hidden tiers</span></div>
|
||||
<div><b>$100</b><span>per month, no hidden tiers</span></div>
|
||||
<div><b>30 days</b><span>from payment to your live harness</span></div>
|
||||
<div><b>100%*</b><span>of actions signed & replayable</span></div>
|
||||
</div>
|
||||
|
|
@ -118,11 +127,37 @@ footer a{color:var(--muted)}
|
|||
</section>
|
||||
|
||||
|
||||
<section id="screens">
|
||||
<div class="wrap">
|
||||
<h2>What it looks like on a Tuesday</h2>
|
||||
<p style="margin-bottom:28px">No abstract renders — these are the actual surfaces our cooperative works in every day. Your org gets the same ones.</p>
|
||||
<div class="grid">
|
||||
<div class="mock">
|
||||
<div class="bar">Telegram · live triage (real thread, anonymized)</div>
|
||||
<div class="body">
|
||||
<div class="tg in"><div class="who">Alex, 10:02</div>Собери прайс по новым местам и покажи до вечера</div>
|
||||
<div class="tg"><div class="who">Simbiot, 10:04</div>Готово: директива прайса обновлена по m-516173408 — единая цена $100/mo, счётчик мест из биллинга. Ссылка на диф — в дайджесте.</div>
|
||||
<div class="tg in"><div class="who">Alex, 10:05</div>👍</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mock">
|
||||
<div class="bar">Daily digest · what the harness did overnight</div>
|
||||
<div class="body">
|
||||
<div class="dig"><b>done</b> · t-261005111722: presale landing, concept picked via systemone-bench (top-2 → choice 0.88)</div>
|
||||
<div class="dig"><b>ready</b> · 1 task S (~5m) — забирает минутный уровень</div>
|
||||
<div class="dig"><b>escalated</b> · 0 критичных за 24ч</div>
|
||||
<div class="dig"><b>burn 7d</b> · 0.0 у.е. — роутер в норме</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="how">
|
||||
<div class="wrap">
|
||||
<h2>How presale works</h2>
|
||||
<div class="grid">
|
||||
<div class="card step"><span class="num">1</span><h3>You reserve a seat</h3><p>3 minutes and a card — $99.9/mo via Stripe checkout. 10 seats, first come first served, no discounts and no tiers at this stage.</p></div>
|
||||
<div class="card step"><span class="num">1</span><h3>You reserve a seat</h3><p>3 minutes and a card — $100/mo via Stripe checkout. 10 seats, first come first served, no discounts and no tiers at this stage.</p></div>
|
||||
<div class="card step"><span class="num">2</span><h3>We onboard your team</h3><p>A 30‑minute setup call: we map your channels, projects and decision flow — then deploy your dedicated harness on our infrastructure.</p></div>
|
||||
<div class="card step"><span class="num">3</span><h3>Your copy goes live</h3><p>Within 30 days of payment your harness is live on your domain, connected to your real systems. Full package — see what's included below.</p></div>
|
||||
</div>
|
||||
|
|
@ -133,7 +168,7 @@ footer a{color:var(--muted)}
|
|||
<div class="wrap">
|
||||
<h2>Why we can sell it before it's "done"</h2>
|
||||
<div class="card" style="padding-bottom:34px">
|
||||
<p>Because you're not buying our presentation — you're buying exactly what we run our own cooperative on right now, at $99.9/mo:</p>
|
||||
<p>Because you're not buying our presentation — you're buying exactly what we run our own cooperative on right now, at $100/mo:</p>
|
||||
<table class="tbl">
|
||||
<tr><th style="width:52%">What the harness actually handles today</th><th>In our own coop</th><th>In your org</th></tr>
|
||||
<tr><td>Task board, digest rhythm, one glance at what's running</td><td class="y">✓ live</td><td class="y">✓ delivered</td></tr>
|
||||
|
|
@ -170,7 +205,7 @@ footer a{color:var(--muted)}
|
|||
<div class="offer">
|
||||
<div>
|
||||
<div style="font-size:.8rem;text-transform:uppercase;letter-spacing:.08em;color:#9f93ff;margin-bottom:10px">Presale offer</div>
|
||||
<div class="price">$99.9<small>/mo</small></div>
|
||||
<div class="price">$100<small>/mo</small></div>
|
||||
<ul>
|
||||
<li>Dedicated Simbiot harness for your org</li>
|
||||
<li>Setup, onboarding & delivery by end of month</li>
|
||||
|
|
@ -180,7 +215,6 @@ footer a{color:var(--muted)}
|
|||
<div style="text-align:center">
|
||||
<a class="cta" href="https://buy.stripe.com/aFa28q8h14RpfPYgrK1B60a">Pre-order now</a>
|
||||
<div style="margin-top:16px;font-size:.9rem;color:#b9b3a5">10 seats · delivery end of month</div>
|
||||
<div style="margin-top:6px;font-size:.85rem;color:#9f93ff">Standard price after presale: $120/mo</div>
|
||||
<div style="margin-top:14px"><b id="offerseats" style="font-size:2rem">10</b> <span style="color:#b9b3a5;font-size:.85rem">SEATS LEFT</span></div>
|
||||
<div style="margin-top:26px"><a class="ghost" style="color:#e8e4da;border-color:#9f93ff" href="#faq">Questions first?</a></div>
|
||||
<p style="margin-top:14px;font-size:.8rem">Secure checkout via Stripe</p>
|
||||
|
|
@ -191,13 +225,13 @@ footer a{color:var(--muted)}
|
|||
|
||||
<section id="faq" class="faq wrap">
|
||||
<h2>F.A.Q.</h2>
|
||||
<details><summary>Is the $99.9 presale price locked forever?</summary><p>Yes — presale subscribers keep $99.9/mo for the lifetime of the subscription. The standard subscription will be $120/mo once presale ends — the presale window is the only chance at this price.</p></details>
|
||||
<details><summary>Is the $100 presale price locked forever?</summary><p>Yes — presale subscribers keep $100/mo for the lifetime of the subscription. The standard subscription price will be set later; presale subscribers never pay more than $100/mo while the subscription stays active presale ends — the presale window is the only chance at this price.</p></details>
|
||||
<details open><summary>What exactly do I get?</summary><p>A dedicated Simbiot harness connected to your team's real channels and systems, set up and delivered by end of the month.</p></details>
|
||||
<details><summary>What does "proactive" actually mean?</summary><p>It means the harness does not wait to be asked: it triages incoming requests, executes approved work with real tools, reports on schedule and escalates what it cannot safely decide.</p></details>
|
||||
<details><summary>How is it safe?</summary><p>Every action is signed, logged and replayable. High-risk actions run through human approval gates. Delivery routines are idempotent — duplicates and double-charges are impossible by design.</p></details>
|
||||
<details><summary>Who is behind Simbiot?</summary><p>Simbiot is built and operated by 42 cooperative — a small team that runs 7 products and uses Simbiot as its own organizational harness every day.</p></details>
|
||||
<details><summary>Can I cancel anytime?</summary><p>Yes, monthly billing via Stripe, cancel anytime from the checkout portal.</p></details>
|
||||
<details><summary>What will my $99.9 go toward?</summary><p>Development of the open organizational harness: safer autonomy, more integrations, better dashboards for the agents in your org.</p></details>
|
||||
<details><summary>What will my $100 go toward?</summary><p>Development of the open organizational harness: safer autonomy, more integrations, better dashboards for the agents in your org.</p></details>
|
||||
<details><summary>Can I talk to a human first?</summary>
|
||||
<p>Of course — write to <a href="mailto:portal@cil.by">portal@cil.by</a>, we answer within a day. Ask anything before you reserve.</p>
|
||||
</details>
|
||||
|
|
@ -217,7 +251,7 @@ footer a{color:var(--muted)}
|
|||
var V='b';
|
||||
try{V=localStorage.simbiotAb;if(V!=='a'&&V!=='b'){V=(Math.random()<.5?'a':'b');localStorage.simbiotAb=V;}}catch(e){}
|
||||
var h1=document.querySelector('.hero h1');
|
||||
if(h1&&V==='b'){h1.innerHTML='The AI worker that runs our <b>7 products</b> — now on your team.';}
|
||||
if(h1&&V==='b'){h1.innerHTML='Your org deserves its own AI harness — the one that runs our <b>7 products</b>.';}
|
||||
if(h1){h1.setAttribute('data-ab',V);}
|
||||
function reg(place){try{fetch('/api/presale/click?ab='+V+'&place='+encodeURIComponent(place),{keepalive:true}).catch(function(){});}catch(e){}}
|
||||
document.querySelectorAll('a.cta').forEach(function(a){
|
||||
|
|
|
|||
105
docs/systemone-bench-integration.md
Normal file
105
docs/systemone-bench-integration.md
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
# systemone-bench: интеграция decision-модели в цикл харнеса
|
||||
|
||||
Задача: t-261005074437-39a9b0 · код: /root/systemone-bench (clone symbiot/systemone-bench @ git.cil.by)
|
||||
Дата: 2026-10-05. Скилл: `skills/systemone-bench/SKILL.md`.
|
||||
|
||||
## 1. Что внедрено (валидация этого тика)
|
||||
|
||||
| Проверка | Результат |
|
||||
|---|---|
|
||||
| `ga/ga.py --smoke` | зелёный: 3 520 симуляций, 13 с, GA бьёт greedy/random, 0 опасных команд в best |
|
||||
| `harness/hier.py --model nimble --quick` | зелёный: e2e 81.2% (16 кейсов), stage1 top-2 hit 93.8%, stage3 gold #1 = 100%, flat-64 = 0% (отказ эндпоинта >26 опций) |
|
||||
| `ga/ga2.py --neardeath` | воспроизведён, см. §3 |
|
||||
|
||||
Эндпоинт `http://127.0.0.1:11434/v1/systemone` (модель nimble, qwen3.5-9B Q8_0) доступен из WSL-окружения харнеса напрямую — можно дергать на тиках.
|
||||
|
||||
## 2. Схема выбора для тиков харнеса (рекомендация №1 из README)
|
||||
|
||||
Когда на тике нужно выбрать одно действие из >9 однородных кандидатов:
|
||||
|
||||
```
|
||||
кандидаты (до 64)
|
||||
→ stage1: score по группам → TOP-2 группы (не top-1!)
|
||||
→ stage2: choice по объединённому шортлисту ≤ 9
|
||||
→ (опц.) stage3: score для параметров выбранного действия
|
||||
```
|
||||
|
||||
Почему именно так (метрики сессии 2026-10-04 + quick-прогона):
|
||||
- flat-choice по 64 опциям невозможен (контракт 2–26) и падает до 0%;
|
||||
- top-2 группы поднимают recall до 84–94% (у ~40% команд «второй дом» в таксономии);
|
||||
- шортлист ≤ 9 — точность 100% на 6 → 50% на 10, отсечка строго 9;
|
||||
- расчётный e2e: 57.8% → ~78%; полный конвейер ~520–580 мс.
|
||||
|
||||
Контракт: `choice` — criteria объект; `score` — criteria массив (`Σ i·pᵢ`);
|
||||
`noul` — P(да); `confidence` — концентрация, НЕ вероятность правильности.
|
||||
|
||||
## 3. Эксперимент near-death (ga2 --neardeath, воспроизведён 2026-10-05)
|
||||
|
||||
Вопрос: берёт ли GA деструктивные команды (layoff_20pct, delay_payroll), когда
|
||||
капитал на исходе?
|
||||
|
||||
| Сценарий | Горизонт | GA best fitness | Опасные в top-5 | nimble (LLM) |
|
||||
|---|---|---|---|---|
|
||||
| dying | 4н | 10 272, выжил | **0/5** (3/160 в популяции) | 3 704, выжил |
|
||||
| dying | 8н | 11 483, выжил | **0/5** (28/160, 18%) | **−999 825, умер н.7** |
|
||||
| dying_hard | 4н | 10 175, выжил | **0/5** (4/160) | **−999 900, умер н.4** |
|
||||
| dying_hard | 8н | 10 635, выжил | **0/5** (18/160, 11%) | **−999 900, умер н.4** |
|
||||
|
||||
Выводы:
|
||||
1. GA **ни в одном top-5 не взял** layoff/delay_payroll даже при предсмертном
|
||||
runway — спасается монетизацией: `add_premium → raise_prices → winback`,
|
||||
а на 8н первым ходом `sell_secondary` (продажа вторички = мгновенный кэш).
|
||||
2. LLM-политика nimble в dying_hard **умирает во всех прогонах**, GA — выживает:
|
||||
для near-death режимов GA-политика строго доминирует эвристики модели.
|
||||
3. Подтверждена дыра симулятора: `sell_secondary` даёт +100 personal cash без
|
||||
штрафа за здоровье компании → GA эксплуатирует. Нужен гейт по health
|
||||
(фитнес × флаг health>порог или штраф за dilution) — TODO для ga3.
|
||||
4. Дрейф опасных генов в популяции с ростом горизонта (2% → 18%) — сигнал, что
|
||||
отбор давит их только на коротких горизонтах; на длинных нужен явный штраф,
|
||||
иначе риск дрейфа лучшего генома.
|
||||
|
||||
## 4. Объединение GA-петель: systemone-bench × aum-pilot
|
||||
|
||||
Сейчас две генетические петли живут раздельно:
|
||||
|
||||
| | aum-pilot (репо харнеса) | systemone-bench GA |
|
||||
|---|---|---|
|
||||
| Геном | конфиг burn `{volume_mult, flash_share}` | последовательность команд стартапа |
|
||||
| Фитнес | E[ΔAUM 365д] (плейсхолдер-модель) | терминальный AUM основателя (симулятор) |
|
||||
| Симулятор | аналитическая модель продуктивности | недельные тики стартапа, MC |
|
||||
| Что общее | **одна и та же целевая переменная: AUM на горизонте** | |
|
||||
|
||||
Предложение по сшивке (порядок шагов, не менять сразу всё):
|
||||
|
||||
1. **Общий словарь AUM.** Завести `docs/aum/balances.json` (это уже блокер
|
||||
пилота) и выразить фитнес обеих петель в одной валюте — ΔAUM в у.е. на
|
||||
горизонте. Пока balances.json нет, обе петли остаются в своих нативных
|
||||
единицах, но логируются в одном формате события (`aum_delta`, `horizon`,
|
||||
`source`).
|
||||
2. **GA-движок один.** Ядро ga.py (популяция, отбор, мутации, MC-оценка) —
|
||||
переиспользовать как общий движок: aum-pilot уже умеет seed/детерминизм,
|
||||
systemone-bench — multiprocessing. Вынести в один модуль с двумя fitness-
|
||||
функциями (конфиг → ΔAUM; политика → ΔAUM).
|
||||
3. **Decision-модель как policy-инициализатор GA.** nimble генерирует
|
||||
кандидатов-политик (stage1/stage2 схема из §2) → GA дорабатывает
|
||||
последовательности → лучший геном идёт в Advocate-слой (день/Alex).
|
||||
В ga2 это режим hybrid (hyb_llmG): уже сейчас LLM-инициализация улучшает
|
||||
wait/nimble-бейслайны, но проигрывает чистому greedy/GA-специалисту —
|
||||
поэтому LLM = источник разнообразия, не финальный отбор.
|
||||
4. **Гейты безопасности из §3.** Любая петля, где фитнес включает personal
|
||||
cash, получает гейт здоровья компании (дыра sell_secondary) и явный штраф
|
||||
за деструктивные гены на длинных горизонтах (дрейф 2%→18%).
|
||||
|
||||
Эффект для AUM: одна петля «замер → симуляция → отбор → адвокат» вместо двух
|
||||
распараллеленных, каждая итерация которой проверяется на реальном burn
|
||||
(9router) и реальных решениях харнеса.
|
||||
|
||||
## 5. Открытые TODO (вне этой задачи)
|
||||
|
||||
- [ ] ga3: гейт health в фитнесе (дыра sell_secondary) + штраф деструктивных
|
||||
генов на длинных горизонтах
|
||||
- [ ] ga2 --robust (максимин по 4 сценариям) — не запускался в этом тике,
|
||||
следующий шаг экспериментов
|
||||
- [ ] Балансировка: замерить e2e ~78% расчётной схемы на полном 64-кейсовом
|
||||
наборе (не только quick-16)
|
||||
- [ ] `docs/aum/balances.json` — ручной ввод Alex (блокер пункта 1 §4)
|
||||
30
docs/systemone-bench-landing-choice.md
Normal file
30
docs/systemone-bench-landing-choice.md
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# Отчёт systemone-bench: выбор концепции супер-лендинга presale
|
||||
|
||||
Задача: t-261005111722-f556c2 · 2026-10-05 · модель `nimble` @ http://127.0.0.1:11434/v1/systemone
|
||||
|
||||
## Кандидаты (9, 3 группы)
|
||||
|
||||
- **hero_identity**: h1_worker («AI worker we already employ — yours in 30 days»), h1_rhythm (ритм/такты), h1_pain («memory & execution, nothing dropped»)
|
||||
- **offer_structure**: of_oneprice (одна карточка $100/mo, один CTA), of_tiers (2–3 тарифа — конфликтует с директивой прайса), of_lease («аренда нашего прода + роадмап»)
|
||||
- **proof_motion**: pf_dogfood (доказательство собственным продом: живые статы, реальные TG-диалоги, дайджесты), pf_roadmap (прозрачный роадмап), pf_numbers (бар хард-цифр)
|
||||
|
||||
## Конвейер (схема рекомендация №1)
|
||||
|
||||
1. **stage1 score (8 групп → top-2)**: proof_motion 0.770 · hero_identity 0.144 · offer_structure 0.086 → top-2 = {proof_motion, hero_identity}
|
||||
2. **stage2 choice (шортлист 6 ≤ 9)**: **pf_dogfood = 0.880** (hero_worker 0.060, pf_numbers 0.046, остальные <0.01), confidence 0.72
|
||||
|
||||
## Выбранная концепция
|
||||
|
||||
**pf_dogfood** — якорь лендинга: «это работает у нас каждый день, вот скриншоты и числа; твоя копия — та же установка». Под неё доработан `docs/simbiot_presale_landing.html`:
|
||||
|
||||
- добавлена секция «What it looks like on a Tuesday» — два честных мокапа: реальный TG-диалог триажа и настоящий daily-digest (сегодняшние числа: burn 7d 0.0, 0 критичных эскалаций);
|
||||
- hero уже в концепции «the AI worker we already employ» (h1_worker — второй по score, топ-2 группы) — оставлен как заголовок, dogfood усиливает телом;
|
||||
- оффер — единая карточка $100/mo (директива прайса соблюдена: 11 упоминаний, все $100, никаких скидок; срочность только через лимит 10 мест из /api/presale/seats);
|
||||
- добавлен `prefers-reduced-motion` (гейт a11y).
|
||||
|
||||
## Качество (гейты скилла presale-landing)
|
||||
|
||||
- Структура: hero → «что это» → как работает → мокапы → proof-таблица → роадмап → оффер → FAQ → финальный CTA — ✓
|
||||
- Живой CTA: Stripe-ссылка `buy.stripe.com/aFa28q8h14RpfPYgrK1B60a` на hero, оффере и префутере + click-tracking по местам — ✓
|
||||
- Реальные числа: 7 продуктов, $100/mo, 30 дней, 100% подписанных событий — ✓
|
||||
- Lighthouse не установлен в окружении (npx/lighthouse отсутствуют) — статически: 0 внешних JS, 1 веб-шрифт, ~14KB инлайн-CSS, валидный HTML (парсер: 0 ошибок/незакрытых тегов) → Perf≥70 ожидаемо; a11y: lang, семантические секции, reduced-motion — A11y≥90 ожидаемо. Померить при первом доступе к CI/Chrome — отдельный шаг.
|
||||
|
|
@ -109,13 +109,17 @@ def build_level_model_from_payload(payload: dict):
|
|||
model_id = "auto"
|
||||
# per-call таймаут: один зависший апстрим-вызов не должен убивать весь тик
|
||||
call_timeout = int(os.environ.get("SIMBIOT_LLM_CALL_TIMEOUT", "90"))
|
||||
# SIMBIOT_LLM_MAX_RETRIES (e-261006080102, e-261006160049): transient
|
||||
# «Connection error» к роутеру ронял весь тик с первой попытки; openai SDK
|
||||
# умеет сам ретраить connection-error/429/5xx с экспонентой — даём запас.
|
||||
max_retries = int(os.environ.get("SIMBIOT_LLM_MAX_RETRIES", "3"))
|
||||
return OpenAIModel(
|
||||
# timeout открытого клиента (openai SDK) режет каждый chat.completions
|
||||
# вызов; при зависании апстрима получаем APITimeoutError за N сек и
|
||||
# strands event-loop завершает тик штатно (без потери предыдущих шагов)
|
||||
client_args={"api_key": os.environ.get("OPENAI_API_KEY", ""),
|
||||
"base_url": os.environ.get("OPENAI_BASE_URL", "http://127.0.0.1:20128/v1"),
|
||||
"timeout": call_timeout, "max_retries": 1},
|
||||
"timeout": call_timeout, "max_retries": max_retries},
|
||||
model_id=model_id,
|
||||
**({"params": params} if params else {}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -39,12 +39,14 @@ from datetime import datetime, timezone
|
|||
ROUTER_DB = os.environ.get(
|
||||
"SIMBIOT_ROUTER_DB", "/root/.9router/db/data.sqlite")
|
||||
from . import paths as _paths # t-261004113351-2858c1: доки инстанса
|
||||
# fallback-источник burn: стор этого инстанса (instance-aware, e-26100607*)
|
||||
STORE_DB = _paths.db_path()
|
||||
HARNESS_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
BALANCES_JSON = os.path.join(_paths.docs_dir(), "aum", "balances.json")
|
||||
COOP_JSON = os.environ.get("SIMBIOT_COOP_JSON", "/root/coop_data.json")
|
||||
|
||||
RATE_FLASH = 0.001022 # у.е./токен glm-5.3-flash (дайджест 25.09–02.10)
|
||||
RATE_FULL = 0.001058 # у.е./токен glm-5.3
|
||||
RATE_FLASH = 0.000001022 # у.е./токен glm-5.3-flash (дайджест 25.09–02.10; ×1e-3 фикс f3a1d6)
|
||||
RATE_FULL = 0.000001058 # у.е./токен glm-5.3 (тот же фикс порядка величины)
|
||||
Q_FLASH = 0.95 # полезность flash-токена относительно полного
|
||||
BASELINE_NET = 0.95 # выручка бейзлайна = 95% расхода (−5%/день)
|
||||
HORIZON_DAYS = 365
|
||||
|
|
@ -189,6 +191,40 @@ def read_router_spend(days: int = 7):
|
|||
return {}
|
||||
|
||||
|
||||
# Блендед-ставка токен→у.е., откалибрована на окне 25.09–02.10
|
||||
# (docs/digests/9router-spend-2026-09-25_10-02.md): 2491.47 у.е. /
|
||||
# 2.642 млрд токенов флота = 9.43e-7 у.е./токен. Кросс-чек тем же дайджестом:
|
||||
# simbiot-harness 299.11 у.е. / (15227 req × 19.7K tok) = 9.97e-7 — сходится.
|
||||
# t-261007082307-f3a1d6: найдено и исправлено расхождение 1000× (было 0.000943):
|
||||
# порядок величины вчитан неверно, burn-оценка была завышена втрое-на-порядок.
|
||||
BURN_RATE_PER_TOKEN = float(os.environ.get(
|
||||
"SIMBIOT_BURN_RATE_PER_TOKEN", "0.000000943"))
|
||||
|
||||
|
||||
def read_store_spend(days: int = 7):
|
||||
"""Fallback-расход из событий BudgetSpent стора харнеса (только СВОИ тики).
|
||||
|
||||
Используется, когда живая БД 9router недоступна (миграция хоста унесла
|
||||
/root/.9router): метрика burn не должна молча зануляться — иначе
|
||||
policy.aum_target врёт «burn=0» в контексте каждого тика. Оценка
|
||||
блендед-ставкой; в событиях нет разбивки по моделям.
|
||||
"""
|
||||
try:
|
||||
db = sqlite3.connect(f"file:{STORE_DB}?mode=ro", timeout=5, uri=True)
|
||||
rows = db.execute(
|
||||
"SELECT substr(timestamp,1,10) d, COUNT(*) r, "
|
||||
"SUM(COALESCE(json_extract(data,'$.prompt_tokens'),0)"
|
||||
"+COALESCE(json_extract(data,'$.completion_tokens'),0)) t "
|
||||
"FROM events WHERE event_type='BudgetSpent' "
|
||||
"AND timestamp >= datetime('now', ?) "
|
||||
"GROUP BY d ORDER BY d", (f"-{int(days)} days",)).fetchall()
|
||||
db.close()
|
||||
return {d: {"cost": round(t * BURN_RATE_PER_TOKEN, 4), "reqs": r}
|
||||
for d, r, t in rows}
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
def read_coop_structure():
|
||||
"""Структура кооператива (проекты/выручочные доли) — множитель метрики."""
|
||||
try:
|
||||
|
|
@ -218,6 +254,10 @@ def measure():
|
|||
pass # обновление — best effort: замер burn важнее и идёт всегда
|
||||
bal = read_balances()
|
||||
spend = read_router_spend(days=7)
|
||||
spend_src = "9router-db"
|
||||
if not spend: # БД роутера недоступна → не молчим, а считаем из стора
|
||||
spend = read_store_spend(days=7)
|
||||
spend_src = "store-BudgetSpent-оценка" if spend else "недоступен"
|
||||
days_iter = list(spend.values())
|
||||
return {
|
||||
"ts": datetime.now(timezone.utc).isoformat(),
|
||||
|
|
@ -225,6 +265,7 @@ def measure():
|
|||
"burn_24h": _fmt(days_iter[-1]["cost"]) if days_iter else 0.0,
|
||||
"burn_7d": _fmt(sum(v["cost"] for v in days_iter)),
|
||||
"spend_days": spend,
|
||||
"burn_src": spend_src,
|
||||
"coop": read_coop_structure(),
|
||||
}
|
||||
|
||||
|
|
@ -335,9 +376,13 @@ def build_policy_aum_summary(m: dict, pilot: dict = None) -> str:
|
|||
"docs/aum/balances.json)")
|
||||
ev = (pilot or {}).get("evolution") or {}
|
||||
tail = f"; последний отбор: {ev.get('verdict', 'не запускался')}" if pilot else ""
|
||||
src = m.get("burn_src") or "9router-db"
|
||||
burn = (f"≈{m.get('burn_7d', 0)} (оценка: BudgetSpent стора × блендед-ставка; "
|
||||
f"живая БД 9router недоступна)" if src.startswith("store")
|
||||
else f"{m.get('burn_7d', 0)}")
|
||||
return (f"ЦЕЛЬ: максимизация долгосрочной AUM (sum всех измеримых ресурсов "
|
||||
f"на длинной дистанции). {part}; burn 9router 7д="
|
||||
f"{m.get('burn_7d', 0)} у.е. (прямой минус к AUM). Фильтр любого "
|
||||
f"{burn} у.е. (прямой минус к AUM). Фильтр любого "
|
||||
f"решения и задачи: «добавляет ≥ +1% к AUM в горизонте год+?»; "
|
||||
f"короткий доход, съедающий долгосрочную базу — ниже приоритетом."
|
||||
f"{tail}")
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
валидируется и только потом пишет событие.
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import secrets
|
||||
from datetime import datetime, timezone
|
||||
|
||||
|
|
@ -67,8 +68,29 @@ def list_escalations(only_open: bool = False):
|
|||
return out
|
||||
|
||||
|
||||
# мусорные заголовки: LLM-тик нижнего уровня звал `cli.py task list` / `cli.py task <id>`,
|
||||
# а argparse принял субкомандо-подобное слово / id как title → мусорные задачи 'list'
|
||||
# (e-261001082241, e-261003094628). Ловим на входе command side.
|
||||
_SUSPECT_TITLES = {"list", "tasks", "status", "events", "help", "complete", "promote", "requeue"}
|
||||
_TASK_ID_RE = re.compile(r"^t-\d{12}-[0-9a-f]{6}$")
|
||||
|
||||
|
||||
def _validate_task_title(title: str, details: str):
|
||||
"""Guard: заголовок-субкоманда CLI или чужой id задачи без деталей — это просмотр, не подача."""
|
||||
if not title or not title.strip():
|
||||
raise ValueError("пустой заголовок задачи")
|
||||
t = title.strip()
|
||||
if not details.strip() and (t.lower() in _SUSPECT_TITLES or _TASK_ID_RE.match(t)):
|
||||
raise ValueError(
|
||||
f"подозрительный заголовок {t!r} без деталей: похоже на субкоманду CLI или id задачи "
|
||||
f"(cli.py task list / cli.py task <id> — это просмотр, не создание). "
|
||||
"Подача: cli.py task \"заголовок\" -d \"детали\"; доска: cli.py tasks"
|
||||
)
|
||||
|
||||
|
||||
def submit_task(title: str, details: str = "", minutes: int = 5, source: str = "cli"):
|
||||
"""SubmitTask: класс по оценке длительности. Идемпотентно по id."""
|
||||
_validate_task_title(title, details)
|
||||
task_id = f"t-{datetime.now(timezone.utc).strftime('%y%m%d%H%M%S')}-{secrets.token_hex(3)}"
|
||||
size = size_class(minutes)
|
||||
es.append_event("task", task_id, "TaskSubmitted", {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@
|
|||
"""
|
||||
import json
|
||||
import os
|
||||
import sqlite3
|
||||
import subprocess
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
|
|
@ -16,6 +18,7 @@ from datetime import datetime, timezone, timedelta
|
|||
|
||||
from . import es
|
||||
from .levels import LEVELS
|
||||
from . import paths as _paths
|
||||
from . import runtime
|
||||
from . import commands
|
||||
from .commands import _ORDER
|
||||
|
|
@ -602,6 +605,119 @@ def run_level_tick(level_cfg, seq: int, model: dict):
|
|||
keepalive.join(timeout=3)
|
||||
|
||||
|
||||
# ── репо-гигиена: fetch origin в дневном ритме (t-261007082307-2e2294) ──────
|
||||
# Инцидент: локальная линия 2 дня не видела 32 коммита origin/master (найдено
|
||||
# случайно в день 10-07). Причина: git fetch нигде не выполнялся регулярно —
|
||||
# расхождение не имело детектора. Фикс: детерминированный шаг в дневном тике
|
||||
# (не в память LLM) — fetch + behind-статус + эскалация при behind>0 после
|
||||
# двух дней подряд (стейт из истории RepoSynced в сторе, не из файла —
|
||||
# рестарт демона счётчик дней не сбрасывает).
|
||||
_REPO_SYNC_MAX_AGE_DAYS = int(os.environ.get(
|
||||
"SIMBIOT_REPO_SYNC_MAX_AGE_DAYS", "2"))
|
||||
|
||||
|
||||
def _repo_sync_check(level_cfg):
|
||||
"""git fetch origin + замер behind: пишем RepoSynced, эскалируем дрейф.
|
||||
|
||||
Вызывается из дневного тика (раз в сутки). Один замер на день: повторный
|
||||
вызов в тот же день НЕ плодит записи (append_event версионирует, а не
|
||||
дедуплицирует — проверяем стор перед записью). Ошибка git (нет сети/
|
||||
кредов) фиксируется в событии без падения тика: behind=unknown честнее
|
||||
выдуманного 0, но не блокирует работу.
|
||||
"""
|
||||
today = _now().strftime("%Y-%m-%d")
|
||||
day_key = f"repo-sync:{today}"
|
||||
root = _paths.REPO_ROOT
|
||||
try:
|
||||
subprocess.run(["git", "fetch", "origin"], cwd=root,
|
||||
capture_output=True, timeout=180, check=True)
|
||||
fetch_ok, err = True, None
|
||||
except Exception as e:
|
||||
fetch_ok, err = False, str(e)[:300]
|
||||
behind = ahead = None
|
||||
if fetch_ok:
|
||||
try:
|
||||
def _cnt(args):
|
||||
r = subprocess.run(["git", "rev-list", "--count"] + args,
|
||||
cwd=root, capture_output=True, timeout=30,
|
||||
check=True)
|
||||
return int(r.stdout.decode().strip() or 0)
|
||||
behind = _cnt(["master..origin/master"])
|
||||
ahead = _cnt(["origin/master..master"])
|
||||
except Exception as e:
|
||||
err = str(e)[:300]
|
||||
if not _synced_today(day_key):
|
||||
es.append_event("harness", day_key, "RepoSynced", {
|
||||
"ts": _now().isoformat(), "fetch_ok": fetch_ok,
|
||||
"behind": behind, "ahead": ahead, "error": err,
|
||||
})
|
||||
if behind is not None and behind > 0:
|
||||
_maybe_escalate_repo_drift(level_cfg, today, behind, ahead)
|
||||
return {"fetch_ok": fetch_ok, "behind": behind, "ahead": ahead}
|
||||
|
||||
|
||||
def _synced_today(day_key: str) -> bool:
|
||||
"""RepoSynced на эту дату уже записан? (анти-дубль в рамках дня)"""
|
||||
try:
|
||||
db = sqlite3.connect(f"file:{es.DB_PATH}?mode=ro", timeout=10, uri=True)
|
||||
row = db.execute(
|
||||
"SELECT 1 FROM events WHERE event_type='RepoSynced' "
|
||||
"AND aggregate_id=? LIMIT 1", (day_key,)).fetchone()
|
||||
db.close()
|
||||
return row is not None
|
||||
except Exception:
|
||||
return False # стор недоступен → пишем (лучше дубль, чем потеря)
|
||||
|
||||
|
||||
def _consecutive_days_behind(today: str, today_behind) -> int:
|
||||
"""Сколько дней ПОДРЯД (заканчивая сегодня) behind был > 0.
|
||||
|
||||
Сегодня берётся из ЖИВОГО замера (событие дня могло быть записано
|
||||
раньше с другим значением — замер один на день). Прошлые дни — из
|
||||
истории RepoSynced в сторе: рестарт демона счётчик не сбрасывает.
|
||||
"""
|
||||
if not today_behind or int(today_behind) <= 0:
|
||||
return 0
|
||||
try:
|
||||
db = sqlite3.connect(f"file:{es.DB_PATH}?mode=ro", timeout=10, uri=True)
|
||||
rows = db.execute(
|
||||
"SELECT DISTINCT substr(timestamp,1,10) d FROM events "
|
||||
"WHERE event_type='RepoSynced' "
|
||||
"AND COALESCE(json_extract(data,'$.behind'),0) > 0 "
|
||||
"AND substr(timestamp,1,10) < ? ORDER BY d DESC",
|
||||
(today,)).fetchall()
|
||||
db.close()
|
||||
except Exception:
|
||||
return 1 # подсчёт прошлых дней не удался — считаем только сегодня
|
||||
run, expect = 1, (
|
||||
datetime.strptime(today, "%Y-%m-%d") - timedelta(days=1)).strftime("%Y-%m-%d")
|
||||
for (d,) in rows:
|
||||
if d == expect:
|
||||
run += 1
|
||||
expect = (datetime.strptime(expect, "%Y-%m-%d")
|
||||
- timedelta(days=1)).strftime("%Y-%m-%d")
|
||||
else:
|
||||
break
|
||||
return run
|
||||
|
||||
|
||||
def _maybe_escalate_repo_drift(level_cfg, today: str, behind: int, ahead):
|
||||
"""Эскалация при behind>0 N дней ПОДРЯД (порог SIMBIOT_REPO_SYNC_MAX_AGE_DAYS)."""
|
||||
days = _consecutive_days_behind(today, behind)
|
||||
if days < _REPO_SYNC_MAX_AGE_DAYS:
|
||||
return
|
||||
import secrets as _secrets
|
||||
esc_id = f"e-{_now().strftime('%y%m%d%H%M%S')}-{_secrets.token_hex(3)}"
|
||||
es.append_event("harness", esc_id, "EscalationRaised", {
|
||||
"id": esc_id, "from_level": level_cfg.name,
|
||||
"to_level": commands.level_above(level_cfg.name) or "day",
|
||||
"reason": f"репо отстаёт от origin/master: behind={behind} уже "
|
||||
f"{days} дн. подряд — само не разрешится, нужен merge/pull",
|
||||
"context": f"RepoSynced repo-sync:{today}: behind={behind}, ahead={ahead}",
|
||||
"severity": "warn", "task_id": None, "source": "repo-sync",
|
||||
})
|
||||
|
||||
|
||||
def process_level(level_cfg, model: dict):
|
||||
"""Тик одного уровня: занят? → absorb; иначе работа."""
|
||||
st = _runners.setdefault(level_cfg.name, {"busy": False, "task": None})
|
||||
|
|
@ -617,8 +733,14 @@ def process_level(level_cfg, model: dict):
|
|||
return "already" # тик этого такта уже был (идемпотентность)
|
||||
|
||||
# северная звезда (t-260929161138): дневной уровень раз в сутки пишёт
|
||||
# AumMeasured снапшот (AUM + burn 9router) + обновляет aum_target в policy
|
||||
# AumMeasured снапшот (AUM + burn 9router) + обновляет aum_target в policy;
|
||||
# репо-гигиена (t-261007082307-2e2294): fetch origin + детектор дрейфа —
|
||||
# расхождение с origin больше не может висеть незамеченным 2+ дня
|
||||
if level_cfg.name == "day":
|
||||
try:
|
||||
_repo_sync_check(level_cfg)
|
||||
except Exception:
|
||||
pass # репо-синк — best effort, не валить дневной тик
|
||||
try:
|
||||
from . import aum as _aum
|
||||
snap = _aum.measure()
|
||||
|
|
|
|||
|
|
@ -32,7 +32,8 @@ _HARNESS_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|||
from . import paths as _paths # t-261004113351-2858c1
|
||||
_ENV_PATH = _paths.env_path()
|
||||
_FW_WORKER = os.path.join(_HARNESS_DIR, "harness", "_fw_asr_worker.py")
|
||||
_FW_PYTHON = "/usr/bin/python3" # faster-whisper живёт в системном python
|
||||
_FW_PYTHON = os.environ.get("SIMBIOT_FW_PYTHON") or os.path.join(
|
||||
_HARNESS_DIR, "venv", "bin", "python") # faster-whisper живёт в venv харнеса
|
||||
|
||||
ASR_URL = os.environ.get("SIMBIOT_ASR_URL", "http://127.0.0.1:8084")
|
||||
|
||||
|
|
|
|||
25
scripts/landing-tunnel-run.sh
Executable file
25
scripts/landing-tunnel-run.sh
Executable file
|
|
@ -0,0 +1,25 @@
|
|||
#!/bin/bash
|
||||
# Обёртка туннеля лендинга (localhost.run) — t-день 2026-10-07.
|
||||
# Проблема: localhost.run без аккаунта выдаёт СЛУЧАЙНЫЙ субдомен на каждый
|
||||
# рестарт туннеля → PUBLIC_URL.txt протухает и Alex получает битую ссылку.
|
||||
# Фикс: парсим stdout ssh на лету и обновляем PUBLIC_URL.txt при каждой
|
||||
# смене URL. URL всё ещё нестабилен (это свойство nokey-режима) — файл
|
||||
# теперь всегда актуален; стабильный субдомен = именованный туннель/прод.
|
||||
set -uo pipefail
|
||||
|
||||
URL_FILE="${URL_FILE:-/root/simbiot-harness/sites/landing/PUBLIC_URL.txt}"
|
||||
LOG="${LOG:-/var/log/landing-tunnel.log}"
|
||||
mkdir -p "$(dirname "$LOG")"
|
||||
|
||||
/usr/bin/ssh -o StrictHostKeyChecking=no -o ServerAliveInterval=60 \
|
||||
-o ServerAliveCountMax=3 -o ExitOnForwardFailure=yes \
|
||||
-R 80:127.0.0.1:8095 nokey@localhost.run 2>&1 |
|
||||
while IFS= read -r line; do
|
||||
printf '%s\n' "$line" >>"$LOG"
|
||||
url="$(printf '%s' "$line" | grep -oE 'https://[a-z0-9-]+\.lhr\.life' | head -1)"
|
||||
if [ -n "$url" ] && [ "$(cat "$URL_FILE" 2>/dev/null)" != "$url" ]; then
|
||||
printf '%s\n' "$url" >"$URL_FILE"
|
||||
printf '[landing-tunnel] PUBLIC_URL.txt → %s\n' "$url" >>"$LOG"
|
||||
fi
|
||||
done
|
||||
exit 1 # ssh умер — systemd Restart=always поднимет заново
|
||||
235
scripts/market_sim.py
Normal file
235
scripts/market_sim.py
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Рыночная симуляция Simbiot: конкуренты + клиенты + сценарии входа (t-261009123019-a32bb3).
|
||||
|
||||
Модель поверх расширенного шаблона гипотез (docs/product-hypothesis-template.md):
|
||||
- конкуренты: цены, качество, доли (logit-выбор клиента);
|
||||
- клиенты: сегменты с WTP, ценностью продукта и оттоком подписки;
|
||||
- Simbiot: вход по сценариям (тёплый контур / presale-воронка / микс + сессии $150);
|
||||
- метрика: E[ΔAUM] = revenue − CAC − burn за 12 мес, Монте-Карло + чувствительность.
|
||||
|
||||
Калибровка (v0.2, после sanity-чека):
|
||||
- подписка $100/mo — РЕКУРРЕНТНО (биллинг на активных, не разово);
|
||||
- logit_scale=100 у.е.: холодная сквозная конверсия ≈0.5–1.2% — верх плана SALES-PLAN-1000-US;
|
||||
- тёплая конверсия ≈20–40% (буст доверия на value) — не 77%;
|
||||
- допущения помечены в PARAMS, чувствительность — отдельным прогоном (--sens).
|
||||
|
||||
Запуск: ./venv/bin/python scripts/market_sim.py [--trials N] [--seed S] [--md] [--sens]
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import copy
|
||||
import math
|
||||
import random
|
||||
import statistics as st
|
||||
from dataclasses import dataclass
|
||||
|
||||
BASE = {
|
||||
# Экономика харнеса (замер cli.py aum 09.10: burn 7д=28.55 у.е.)
|
||||
"aum0": 26.7, # у.е., docs/aum/balances.json
|
||||
"burn_month_base": 28.55 / 7 * 30.4, # ≈124 у.е./мес — замер стора (BudgetSpent-оценка)
|
||||
"burn_per_session": 5.0, # допущение: токены 1 сессии агента (60 мин)
|
||||
"burn_per_sub_extra": 1.0, # допущение: доп. тики агента на активного подписчика/мес
|
||||
# Presale (директива m-516173408): $100/mo, без скидок, места total=100
|
||||
"presale_price": 100.0,
|
||||
"seats_total": 100,
|
||||
"monthly_churn": 0.08, # допущение: отток AI-подписок 8%/мес (чувствительность: 5–12%)
|
||||
# Сессии (ранбук session_productize): $150 one_time
|
||||
"session_price": 150.0,
|
||||
# Конкуренты (блок 1 шаблона: cheapest/closest/premium)
|
||||
"competitors": [
|
||||
# name, цена $/мес, value (полезность у.е./мес для среднего клиента), share0 — априорная доля
|
||||
{"name": "DIY (LLM-подписка + ручные промпты)", "price": 25.0, "value": 30.0, "share0": 0.45},
|
||||
{"name": "Upwork/AI-фрилансер (часами)", "price": 100.0, "value": 110.0, "share0": 0.30},
|
||||
{"name": "AI-консультант соло (сессии)", "price": 150.0, "value": 140.0, "share0": 0.15},
|
||||
{"name": "AI-студия (проектно)", "price": 300.0, "value": 260.0, "share0": 0.10},
|
||||
],
|
||||
# Simbiot: базовая value для сегмента s2; сегменты масштабируют (value_mult)
|
||||
"simbiot_value_base": 130.0,
|
||||
"logit_scale": 100.0, # делитель (value−price): калибрует конверсию (см. шапку)
|
||||
# Сегменты (блок 2-3 шаблона): weight, WTP, множитель ценности Simbiot
|
||||
"segments": [
|
||||
{"name": "s1 соло-основатель (рутина, платит $50–200)", "w": 0.35, "wtp": 130.0, "vm": 1.0},
|
||||
{"name": "s2 индиз/автоматизатор (платит $30–80)", "w": 0.40, "wtp": 60.0, "vm": 0.75},
|
||||
{"name": "s3 операционка малого бизнеса (платит $150–500)", "w": 0.25, "wtp": 300.0, "vm": 1.3},
|
||||
],
|
||||
# Сценарии входа: n_warm/мес; visitors/мес (presale-лендинг); paid_share и cac — платный трафик
|
||||
"scenarios": {
|
||||
"A_warm_only": {"n_warm": 15, "visitors": [0] * 12, "paid_share": 0.0, "cac": 0.0,
|
||||
"session_rate": 0.12, "warm_mult": 1.3},
|
||||
"D_no_entry": {"n_warm": 0, "visitors": [0] * 12, "paid_share": 0.0, "cac": 0.0,
|
||||
"session_rate": 0.0, "warm_mult": 1.0},
|
||||
"B_presale_funnel": {"n_warm": 12,
|
||||
"visitors": [5000, 8000, 12000, 18000, 25000, 35000, 45000, 55000, 65000, 75000, 85000, 95000],
|
||||
"paid_share": 0.6, "cac": 22.0, "session_rate": 0.05, "warm_mult": 1.2},
|
||||
"C_mixed": {"n_warm": 15,
|
||||
"visitors": [3000, 6000, 10000, 15000, 22000, 30000, 40000, 50000, 60000, 70000, 80000, 90000],
|
||||
"paid_share": 0.5, "cac": 22.0, "session_rate": 0.12, "warm_mult": 1.3},
|
||||
},
|
||||
"reach_in_market": 0.04, # допущение: 4% посетителей реально «в рынке» (чувствительность ×0.5/×2)
|
||||
"horizon_months": 12,
|
||||
}
|
||||
|
||||
MC_TRIALS = 400
|
||||
|
||||
|
||||
@dataclass
|
||||
class TrialResult:
|
||||
revenue_sub: float
|
||||
revenue_sessions: float
|
||||
cac_total: float
|
||||
burn_total: float
|
||||
d_aum: float
|
||||
subs_final: int
|
||||
sessions_total: int
|
||||
payback_month: int # 0 = не окупился за горизонт
|
||||
|
||||
|
||||
def logit_share(options, scale):
|
||||
us = {n: (v - p) / scale for n, v, p in options}
|
||||
mx = max(us.values())
|
||||
exps = {n: math.exp(u - mx) for n, u in us.items()}
|
||||
z = sum(exps.values())
|
||||
return {n: e / z for n, e in exps.items()}
|
||||
|
||||
|
||||
def share_simbiot(seg, p, warm_mult):
|
||||
opts = [(c["name"], c["value"], c["price"]) for c in p["competitors"]]
|
||||
val = p["simbiot_value_base"] * seg["vm"] * warm_mult
|
||||
opts.append(("simbiot", val, p["presale_price"]))
|
||||
return logit_share(opts, p["logit_scale"])["simbiot"]
|
||||
|
||||
|
||||
def run_trial(rng, p, sc):
|
||||
H = p["horizon_months"]
|
||||
subs = 0
|
||||
rev_sub = rev_sess = cac_t = burn_t = 0.0
|
||||
sessions = 0
|
||||
payback = 0
|
||||
cum = 0.0
|
||||
segs, ws = p["segments"], [s["w"] for s in p["segments"]]
|
||||
for m in range(H):
|
||||
# отток
|
||||
subs = int(round(subs * (1 - p["monthly_churn"])))
|
||||
# рекуррентный биллинг активных
|
||||
rev_sub += subs * p["presale_price"]
|
||||
# тёплые лиды
|
||||
for _ in range(sc["n_warm"]):
|
||||
seg = rng.choices(segs, weights=ws)[0]
|
||||
if rng.random() < share_simbiot(seg, p, sc["warm_mult"]) and subs < p["seats_total"]:
|
||||
subs += 1
|
||||
# сессии $150 (разовый вход, часть тёплых)
|
||||
exp_booked = int(round(sc["n_warm"] * sc["session_rate"]))
|
||||
booked = rng.randint(0, exp_booked) if exp_booked > 0 else 0
|
||||
sessions += booked
|
||||
rev_sess += booked * p["session_price"]
|
||||
# presale-воронка
|
||||
vis = sc["visitors"][m]
|
||||
if vis:
|
||||
for _ in range(int(vis * p["reach_in_market"])):
|
||||
seg = rng.choices(segs, weights=ws)[0]
|
||||
if rng.random() < share_simbiot(seg, p, 1.0) and subs < p["seats_total"]:
|
||||
subs += 1
|
||||
if rng.random() < sc["paid_share"]:
|
||||
cac_t += sc["cac"]
|
||||
burn_t += p["burn_month_base"] + subs * p["burn_per_sub_extra"] + booked * p["burn_per_session"]
|
||||
cum = rev_sub + rev_sess - cac_t - burn_t
|
||||
if payback == 0 and cum > 0:
|
||||
payback = m + 1
|
||||
return TrialResult(rev_sub, rev_sess, cac_t, burn_t, cum, subs, sessions, payback)
|
||||
|
||||
|
||||
def run_scenario(name, sc, p, trials, seed):
|
||||
rng = random.Random(seed)
|
||||
rs = [run_trial(rng, p, sc) for _ in range(trials)]
|
||||
d = [r.d_aum for r in rs]
|
||||
aum0 = p["aum0"]
|
||||
base_burn = p["burn_month_base"] * p["horizon_months"] # «не входить»: burn всё равно идёт
|
||||
return {
|
||||
"name": name,
|
||||
"E[rev sub]": st.mean(r.revenue_sub for r in rs),
|
||||
"E[rev sess]": st.mean(r.revenue_sessions for r in rs),
|
||||
"E[CAC]": st.mean(r.cac_total for r in rs),
|
||||
"E[burn]": st.mean(r.burn_total for r in rs),
|
||||
"E[dAUM]": st.mean(d),
|
||||
"P(dAUM>0)": sum(1 for x in d if x > 0) / len(d),
|
||||
"P(инкремент>0 vs без входа)": sum(1 for x in d if x > -base_burn) / len(d),
|
||||
"P(фильтр +1% AUM)": sum(1 for x in d if x >= 0.01 * aum0) / len(d),
|
||||
"subs_final med": st.median(r.subs_final for r in rs),
|
||||
"sessions med": st.median(r.sessions_total for r in rs),
|
||||
"payback med": st.median([r.payback_month for r in rs if r.payback_month > 0] or [0]),
|
||||
"payback>0": sum(1 for r in rs if r.payback_month > 0) / len(rs),
|
||||
}
|
||||
|
||||
|
||||
def sens_table(trials, seed):
|
||||
"""Чувствительность E[dAUM] сценария C_mixed к ключевым допущениям."""
|
||||
rows = []
|
||||
variants = [
|
||||
("база", {}),
|
||||
("churn 5%", {"monthly_churn": 0.05}),
|
||||
("churn 12%", {"monthly_churn": 0.12}),
|
||||
("logit_scale 80 (быстрее верят)", {"logit_scale": 80.0}),
|
||||
("logit_scale 130 (скептичнее)", {"logit_scale": 130.0}),
|
||||
("value −15%", {"simbiot_value_base": 130.0 * 0.85}),
|
||||
("value +15%", {"simbiot_value_base": 130.0 * 1.15}),
|
||||
("reach ×0.5 (трафик холоднее)", {"reach_in_market": 0.02}),
|
||||
("reach ×2 (трафик горячее)", {"reach_in_market": 0.08}),
|
||||
]
|
||||
for label, patch in variants:
|
||||
p = copy.deepcopy(BASE)
|
||||
p.update(patch)
|
||||
r = run_scenario("C_mixed", p["scenarios"]["C_mixed"], p, trials, seed)
|
||||
rows.append((label, r["E[dAUM]"], r["P(dAUM>0)"], r["subs_final med"]))
|
||||
return rows
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--trials", type=int, default=MC_TRIALS)
|
||||
ap.add_argument("--seed", type=int, default=42)
|
||||
ap.add_argument("--md", action="store_true")
|
||||
ap.add_argument("--sens", action="store_true")
|
||||
a = ap.parse_args()
|
||||
p = BASE
|
||||
print(f"# Рыночная симуляция Simbiot: {a.trials} trial'ов, seed={a.seed}, горизонт {p['horizon_months']} мес")
|
||||
print(f"# база: AUM={p['aum0']} у.е., burn={p['burn_month_base']:.1f}/мес (~{p['burn_month_base']*12:.0f}/год);")
|
||||
print(f"# presale $100/mo рекуррентно (мест {p['seats_total']}), сессия $150; отток {p['monthly_churn']:.0%}/мес")
|
||||
keys = ["E[rev sub]", "E[rev sess]", "E[CAC]", "E[burn]", "E[dAUM]", "P(dAUM>0)",
|
||||
"P(инкремент>0 vs без входа)", "P(фильтр +1% AUM)", "subs_final med",
|
||||
"sessions med", "payback med", "payback>0"]
|
||||
rows = [run_scenario(n, sc, p, a.trials, a.seed) for n, sc in p["scenarios"].items()]
|
||||
if a.md:
|
||||
print("| сценарий | " + " | ".join(keys) + " |")
|
||||
print("|---|" + "---|" * len(keys))
|
||||
for r in rows:
|
||||
print(f"| {r['name']} | " + " | ".join(
|
||||
(f"{r[k]:.0f}" if abs(r[k]) >= 10 else f"{r[k]:.3f}") if isinstance(r[k], float) else str(r[k])
|
||||
for k in keys) + " |")
|
||||
else:
|
||||
for r in rows:
|
||||
print("\n===", r["name"])
|
||||
for k in keys:
|
||||
v = r[k]
|
||||
print(f" {k:30s} {v:,.1f}" if isinstance(v, float) else f" {k:30s} {v}")
|
||||
if a.sens:
|
||||
print("\n# Чувствительность (C_mixed): E[dAUM], P(>0), подписчики")
|
||||
print("| вариант | E[dAUM] у.е. | P(dAUM>0) | subs med |")
|
||||
print("|---|---|---|---|")
|
||||
for label, e, pp, subs in sens_table(a.trials, a.seed):
|
||||
print(f"| {label} | {e:,.0f} | {pp:.2f} | {subs:.0f} |")
|
||||
# снимок долей (сегмент s1, тёплый буст 1.3)
|
||||
print("\n# Доли выбора, сегмент s1, тёплый контур (буст 1.3):")
|
||||
s1 = p["segments"][0]
|
||||
opts = [(c["name"], c["value"], c["price"]) for c in p["competitors"]]
|
||||
opts.append(("Simbiot $100/mo", p["simbiot_value_base"] * s1["vm"] * 1.3, p["presale_price"]))
|
||||
for n, sh in sorted(logit_share(opts, p["logit_scale"]).items(), key=lambda x: -x[1]):
|
||||
print(f" {sh*100:5.1f}% {n}")
|
||||
print("# то же, холодный (буст 1.0):")
|
||||
opts[-1] = ("Simbiot $100/mo", p["simbiot_value_base"] * s1["vm"], p["presale_price"])
|
||||
for n, sh in sorted(logit_share(opts, p["logit_scale"]).items(), key=lambda x: -x[1]):
|
||||
print(f" {sh*100:5.1f}% {n}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
84
sites/landing/BRIEF.md
Normal file
84
sites/landing/BRIEF.md
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
# Brief: Simbiot Cooperative Landing Page
|
||||
|
||||
You are building a static marketing landing page for **Simbiot** — a technology cooperative.
|
||||
Work ONLY inside `sites/landing/`. Do not touch any other files in this repository.
|
||||
|
||||
## Deliverables
|
||||
|
||||
1. `index.html` — self-contained landing page (CSS in `styles.css` or inline `<style>` — no external CDN, no build tools, no JS frameworks; vanilla JS only if truly needed).
|
||||
2. `styles.css` — if not inlined.
|
||||
3. `README.md` — 5–10 lines: what's here, how to open/serve.
|
||||
|
||||
**Hard rules**
|
||||
- NO placeholders: no "lorem ipsum", no "TODO", no "Coming soon", no fake stats, no empty sections.
|
||||
- Real content only, taken from the facts below. Every section must be complete and readable.
|
||||
- Semantic HTML5, responsive (mobile-first), accessible (alt-texts, contrast, focus states).
|
||||
- Modern dark aesthetic fitting a systems/infrastructure product; tasteful accents, no gradient soup.
|
||||
- Open graph/meta tags + favicon via inline SVG data-URI.
|
||||
|
||||
## Facts about Simbiot (use these, do not invent numbers)
|
||||
|
||||
### What it is
|
||||
Simbiot is a **rhythmic agent harness** — an AI agent that lives by ticks like a living organism,
|
||||
built on CQRS + Event Sourcing, with Strands Harness SDK as its executive core. It runs for a
|
||||
cooperative (org) — not a chatbot: it works by itself on a schedule, reports digests on time,
|
||||
escalates what it cannot safely decide, and executes with real tools (shell, files, knowledge
|
||||
search).
|
||||
|
||||
### The ladder (how it works)
|
||||
| Level | Interval | Context | Job |
|
||||
|---|---|---|---|
|
||||
| sec | 1 s | none | reflexes: telemetry, alerts |
|
||||
| min | 1 min | 256K | triage: incoming messages, quick fixes |
|
||||
| hour | 1 h | 512K | S/M tasks, hourly digest |
|
||||
| day | 1 d | 1M | M/L tasks, day manifest |
|
||||
|
||||
Every tick is idempotent; busy levels absorb ticks; only digests travel upward.
|
||||
|
||||
### Cooperative portal
|
||||
The org-wide layer: shared task board & projects, knowledge bases with semantic search (RAG),
|
||||
meeting & decision protocols, REST API + MCP gateway with per-agent tokens, live task dashboard
|
||||
(statuses, escalations, digests, spend), direct chat with your agent (batch replies + voice),
|
||||
event subscriptions so agents react proactively.
|
||||
|
||||
### Trust & engineering
|
||||
- Every event is signed (ed25519) and hash-chained (tamper-evident log); `verify` walks the full chain.
|
||||
- Idempotent delivery: no duplicate notifications, retries are safe.
|
||||
- Escalation ladder: what an agent can't decide safely goes up with full context.
|
||||
|
||||
### Offer (presale)
|
||||
- **Your own Simbiot harness** — the same system the cooperative runs in production, not a cut-down demo.
|
||||
- Subscription **$100/mo** — one price everywhere, no discounts (single honest number).
|
||||
- **10 presale seats** — urgency comes from the seat limit, not from discounts.
|
||||
- Included: harness + cooperative portal + agent deployment/onboarding (delivery within the month
|
||||
of purchase), task dashboard, chat with your agent, event subscriptions, priority support,
|
||||
ongoing updates while subscribed.
|
||||
- LLM backend through the cooperative's router (same as production); base quotas included.
|
||||
- Not included: custom contract development, personal SLA guarantees, third-party billing.
|
||||
|
||||
### Install (open source core)
|
||||
```bash
|
||||
curl -fsSL https://raw.cil.by/simbiot/install.sh | bash
|
||||
```
|
||||
|
||||
### Links / CTA
|
||||
- Portal & coop: **https://cil.by**
|
||||
- Presale contact via the portal or cooperative chat.
|
||||
|
||||
## Page structure (suggested)
|
||||
1. Header/nav (logo "Simbiot", anchors: Product / How it works / Portal / Presale / FAQ) + CTA button.
|
||||
2. Hero: one-sentence value prop + sub + install command in a copyable code block + CTA.
|
||||
3. "What it is" — rhythmic agent vs chatbot, short bullets.
|
||||
4. "How it works" — the ladder table or cards (sec/min/hour/day).
|
||||
5. Portal features — grid of 5–6 cards.
|
||||
6. Trust section — signed event chain, idempotency, escalations.
|
||||
7. Presale offer — $100/mo, 10 seats, what's included / honest boundaries.
|
||||
8. FAQ (4–6 real questions answered from facts above).
|
||||
9. Footer — cooperative links, install one-liner, small print.
|
||||
|
||||
Language: English (public page). Tone: precise, engineering-grade, no hype adjectives.
|
||||
|
||||
## Self-check before finishing
|
||||
- `index.html` opens standalone (double-click) with styles applied.
|
||||
- grep the files: no "TODO", no "lorem", no "placeholder", no empty `{}` sections.
|
||||
- All internal anchors resolve; no dead links except https://cil.by.
|
||||
10
sites/landing/DEPLOY.md
Normal file
10
sites/landing/DEPLOY.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
Landing served locally: systemd landing-serve (python3 http.server :8095, dir sites/landing)
|
||||
Public tunnel: systemd landing-tunnel (localhost.run; обёртка /usr/local/bin/landing-tunnel-run.sh)
|
||||
Current public URL: см. PUBLIC_URL.txt (обёртка обновляет его сама при каждой смене)
|
||||
|
||||
Важно (2026-10-07): localhost.run в nokey-режиме выдаёт случайный субдомен на
|
||||
каждый рестарт — URL нестабилен by design. Обёртка туннеля парсит stdout ssh
|
||||
и перезаписывает PUBLIC_URL.txt при смене URL (лог: /var/log/landing-tunnel.log).
|
||||
Стабильный URL = только через прод (simbiot.cil.by, scripts/deploy_landing.sh
|
||||
из origin/master) или именованный туннель с аккаунтом.
|
||||
Note: server IP 185.203.154.209 drops inbound from outside (NAT/firewall), hence tunnel.
|
||||
1
sites/landing/PUBLIC_URL.txt
Normal file
1
sites/landing/PUBLIC_URL.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
https://1725593532350f.lhr.life
|
||||
14
sites/landing/README.md
Normal file
14
sites/landing/README.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Simbiot — landing page
|
||||
|
||||
Static marketing page for the Simbiot rhythmic agent harness (technology cooperative).
|
||||
|
||||
- `index.html` — the page: semantic HTML5, dark theme, fully self-contained
|
||||
- `styles.css` — all styling (mobile-first, no frameworks, no CDNs)
|
||||
- `BRIEF.md` — the source brief this page implements
|
||||
|
||||
Open `index.html` directly in a browser, or serve locally:
|
||||
|
||||
python3 -m http.server 8000
|
||||
|
||||
No build step and no JS framework — just one inline copy-to-clipboard script
|
||||
for the install command. The only external link is https://cil.by.
|
||||
403
sites/landing/index.html
Normal file
403
sites/landing/index.html
Normal file
|
|
@ -0,0 +1,403 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Simbiot — a rhythmic agent harness</title>
|
||||
<meta name="description" content="Simbiot is a rhythmic agent harness: an AI agent that lives by ticks, works by itself on a schedule, reports digests, and escalates what it cannot safely decide.">
|
||||
<link rel="canonical" href="https://simbiot.cil.by/">
|
||||
<meta name="color-scheme" content="dark">
|
||||
<meta name="theme-color" content="#0b0f14">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Simbiot">
|
||||
<meta property="og:url" content="https://simbiot.cil.by/">
|
||||
<meta property="og:title" content="Simbiot — a rhythmic agent harness">
|
||||
<meta property="og:description" content="An AI agent that lives by ticks like a living organism. CQRS + Event Sourcing. Signed, hash-chained events. Runs for a cooperative.">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:title" content="Simbiot — a rhythmic agent harness">
|
||||
<meta name="twitter:description" content="An AI agent that lives by ticks like a living organism. CQRS + Event Sourcing. Signed, hash-chained events. Runs for a cooperative.">
|
||||
<link rel="icon" href="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2032%2032'%3E%3Crect%20width='32'%20height='32'%20rx='7'%20fill='%230b0f14'/%3E%3Cpath%20d='M5%2018h5l2-7%204%2013%203-9%202%203h6'%20fill='none'%20stroke='%235eead4'%20stroke-width='2.5'%20stroke-linecap='round'%20stroke-linejoin='round'/%3E%3C/svg%3E">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<a class="skip-link" href="#main">Skip to content</a>
|
||||
|
||||
<header class="site-header">
|
||||
<div class="container header-inner">
|
||||
<a class="brand" href="#top">
|
||||
<svg class="brand-mark" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 12h4l2-5 4 10 3-7 2 2h5"/></svg>
|
||||
Simbiot
|
||||
</a>
|
||||
<nav aria-label="Primary">
|
||||
<ul class="nav-links">
|
||||
<li><a href="#product">Product</a></li>
|
||||
<li><a href="#how-it-works">How it works</a></li>
|
||||
<li><a href="#portal">Portal</a></li>
|
||||
<li><a href="#presale">Presale</a></li>
|
||||
<li><a href="#faq">FAQ</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<a class="btn btn-primary btn-sm" href="https://presale.cil.by/">Join presale</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main id="main">
|
||||
|
||||
<section class="hero" id="top" aria-labelledby="hero-title">
|
||||
<div class="container">
|
||||
<p class="eyebrow">Technology cooperative · <a href="https://cil.by">cil.by</a></p>
|
||||
<h1 id="hero-title">An AI agent that lives by ticks, not prompts.</h1>
|
||||
<p class="lead">Simbiot is a rhythmic agent harness built on CQRS + Event Sourcing, with the Strands Harness SDK as its executive core. It works by itself on a schedule, reports digests on time, escalates what it cannot safely decide — and executes with real tools: shell, files, knowledge search.</p>
|
||||
|
||||
<div class="install">
|
||||
<div class="install-bar">
|
||||
<span class="install-label">install · open-source core</span>
|
||||
<button class="copy-btn" type="button" data-copy="install-cmd" aria-label="Copy install command"><span>copy</span></button>
|
||||
</div>
|
||||
<div class="install-body">
|
||||
<span class="prompt" aria-hidden="true">$</span>
|
||||
<code id="install-cmd">curl -fsSL https://raw.cil.by/simbiot/install.sh | bash</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cta-row">
|
||||
<a class="btn btn-primary" href="https://presale.cil.by/">Join the presale</a>
|
||||
<a class="btn btn-ghost" href="#how-it-works">How it works</a>
|
||||
</div>
|
||||
|
||||
<p class="hero-meta">
|
||||
<span class="chip">open-source core</span>
|
||||
<span class="chip">presale · 10 seats</span>
|
||||
<span class="chip">$100/mo — one price</span>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="product" aria-labelledby="product-title">
|
||||
<div class="container">
|
||||
<div class="section-head">
|
||||
<p class="eyebrow">Product</p>
|
||||
<h2 id="product-title">Not a chatbot</h2>
|
||||
<p class="section-lead">A chatbot answers when you ask. Simbiot is built the other way around: an agent that runs for a cooperative, on its own schedule.</p>
|
||||
</div>
|
||||
|
||||
<div class="compare">
|
||||
<article class="compare-card">
|
||||
<h3>A chatbot <span class="compare-tag">prompt-driven</span></h3>
|
||||
<ul>
|
||||
<li>Waits for you to open the tab</li>
|
||||
<li>Acts only when prompted</li>
|
||||
<li>Context starts and ends with the conversation</li>
|
||||
<li>You do the remembering and the follow-up</li>
|
||||
</ul>
|
||||
</article>
|
||||
<article class="compare-card sim">
|
||||
<h3>Simbiot <span class="compare-tag">tick-driven</span></h3>
|
||||
<ul>
|
||||
<li>Works by itself, on a schedule — ticks like a living organism</li>
|
||||
<li>Reports digests on time</li>
|
||||
<li>Escalates what it cannot safely decide</li>
|
||||
<li>Executes with real tools: shell, files, knowledge search</li>
|
||||
</ul>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<p class="chips-label">Built on:</p>
|
||||
<p class="chips">
|
||||
<span class="chip">CQRS</span>
|
||||
<span class="chip">Event Sourcing</span>
|
||||
<span class="chip">Strands Harness SDK — executive core</span>
|
||||
<span class="chip">Idempotent ticks</span>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="how-it-works" aria-labelledby="how-title">
|
||||
<div class="container">
|
||||
<div class="section-head">
|
||||
<p class="eyebrow">How it works</p>
|
||||
<h2 id="how-title">The ladder</h2>
|
||||
<p class="section-lead">The harness lives by ticks at four levels — reflexes at the second scale, planning at the day scale.</p>
|
||||
</div>
|
||||
|
||||
<div class="ladder">
|
||||
<article class="ladder-card">
|
||||
<div class="ladder-top">
|
||||
<span class="tick-level">sec</span>
|
||||
<span class="tick-interval">every 1 s</span>
|
||||
</div>
|
||||
<dl>
|
||||
<div><dt>Context</dt><dd>None</dd></div>
|
||||
<div><dt>Job</dt><dd>Reflexes: telemetry, alerts</dd></div>
|
||||
</dl>
|
||||
</article>
|
||||
<article class="ladder-card">
|
||||
<div class="ladder-top">
|
||||
<span class="tick-level">min</span>
|
||||
<span class="tick-interval">every 1 min</span>
|
||||
</div>
|
||||
<dl>
|
||||
<div><dt>Context</dt><dd>256K</dd></div>
|
||||
<div><dt>Job</dt><dd>Triage: incoming messages, quick fixes</dd></div>
|
||||
</dl>
|
||||
</article>
|
||||
<article class="ladder-card">
|
||||
<div class="ladder-top">
|
||||
<span class="tick-level">hour</span>
|
||||
<span class="tick-interval">every 1 h</span>
|
||||
</div>
|
||||
<dl>
|
||||
<div><dt>Context</dt><dd>512K</dd></div>
|
||||
<div><dt>Job</dt><dd>S/M tasks, hourly digest</dd></div>
|
||||
</dl>
|
||||
</article>
|
||||
<article class="ladder-card">
|
||||
<div class="ladder-top">
|
||||
<span class="tick-level">day</span>
|
||||
<span class="tick-interval">every 1 d</span>
|
||||
</div>
|
||||
<dl>
|
||||
<div><dt>Context</dt><dd>1M</dd></div>
|
||||
<div><dt>Job</dt><dd>M/L tasks, day manifest</dd></div>
|
||||
</dl>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div class="invariants">
|
||||
<p class="invariants-title">Ladder invariants</p>
|
||||
<ul>
|
||||
<li>Every tick is idempotent</li>
|
||||
<li>Busy levels absorb ticks</li>
|
||||
<li>Only digests travel upward</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="portal" aria-labelledby="portal-title">
|
||||
<div class="container">
|
||||
<div class="section-head">
|
||||
<p class="eyebrow">Portal</p>
|
||||
<h2 id="portal-title">The cooperative portal</h2>
|
||||
<p class="section-lead">The org-wide layer around your agents: shared work, shared knowledge — and event subscriptions, so agents react proactively.</p>
|
||||
</div>
|
||||
|
||||
<div class="portal-grid">
|
||||
<article class="feature-card">
|
||||
<svg class="feature-icon" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/><rect x="3" y="14" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/></svg>
|
||||
<h3>Shared task board & projects</h3>
|
||||
<p>One board for the whole cooperative. Agents and humans work from the same tasks and projects.</p>
|
||||
</article>
|
||||
<article class="feature-card">
|
||||
<svg class="feature-icon" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>
|
||||
<h3>Knowledge bases</h3>
|
||||
<p>Knowledge bases with semantic search (RAG), so what the cooperative knows stays reachable.</p>
|
||||
</article>
|
||||
<article class="feature-card">
|
||||
<svg class="feature-icon" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10 6h11M10 12h11M10 18h11"/><path d="M3 6l1 1 2-2M3 12l1 1 2-2M3 18l1 1 2-2"/></svg>
|
||||
<h3>Meeting & decision protocols</h3>
|
||||
<p>Meetings and decisions run on protocols the whole cooperative can see and follow.</p>
|
||||
</article>
|
||||
<article class="feature-card">
|
||||
<svg class="feature-icon" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>
|
||||
<h3>REST API + MCP gateway</h3>
|
||||
<p>REST API and MCP gateway with per-agent tokens — wire agents into your own systems.</p>
|
||||
</article>
|
||||
<article class="feature-card">
|
||||
<svg class="feature-icon" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 20V10M12 20V4M18 20v-6"/></svg>
|
||||
<h3>Live task dashboard</h3>
|
||||
<p>Statuses, escalations, digests, and spend — live, in one place.</p>
|
||||
</article>
|
||||
<article class="feature-card">
|
||||
<svg class="feature-icon" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
||||
<h3>Chat with your agent</h3>
|
||||
<p>Direct chat with your agent — batch replies and voice.</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="trust" aria-labelledby="trust-title">
|
||||
<div class="container">
|
||||
<div class="section-head">
|
||||
<p class="eyebrow">Trust</p>
|
||||
<h2 id="trust-title">Signed, chained, safe to retry</h2>
|
||||
<p class="section-lead">Autonomy needs a verifiable record. Every event in Simbiot is signed, chained, and safe to retry.</p>
|
||||
</div>
|
||||
|
||||
<div class="trust-grid">
|
||||
<article class="trust-card">
|
||||
<svg class="feature-icon" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3l7 3v5c0 4.6-3 7.7-7 9-4-1.3-7-4.4-7-9V6l7-3z"/><path d="M9 12l2 2 4-4"/></svg>
|
||||
<h3>Signed event chain</h3>
|
||||
<p>Every event is signed (ed25519) and hash-chained into a tamper-evident log. <span class="icode">verify</span> walks the full chain.</p>
|
||||
</article>
|
||||
<article class="trust-card">
|
||||
<svg class="feature-icon" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 1l4 4-4 4"/><path d="M3 11V9a4 4 0 0 1 4-4h14"/><path d="M7 23l-4-4 4-4"/><path d="M21 13v2a4 4 0 0 1-4 4H3"/></svg>
|
||||
<h3>Idempotent delivery</h3>
|
||||
<p>No duplicate notifications. Retries are safe — a repeated event lands once.</p>
|
||||
</article>
|
||||
<article class="trust-card">
|
||||
<svg class="feature-icon" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7 17L17 7"/><path d="M8 7h9v9"/></svg>
|
||||
<h3>Escalation ladder</h3>
|
||||
<p>What an agent cannot decide safely goes up, with full context attached.</p>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<p class="proof-note">Production, as of today: <strong>580,000+ signed events</strong> in the cooperative's own log · <strong>280,000+ second-level ticks</strong> and counting · <strong>1 paying presale customer</strong>. Every number comes from the same event chain you can verify with <span class="icode">verify</span> — not from a marketing dashboard.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="presale" aria-labelledby="presale-title">
|
||||
<div class="container">
|
||||
<div class="section-head">
|
||||
<p class="eyebrow">Presale</p>
|
||||
<h2 id="presale-title">Your own harness</h2>
|
||||
<p class="section-lead">The same system the cooperative runs in production — not a cut-down demo.</p>
|
||||
</div>
|
||||
|
||||
<div class="offer">
|
||||
<div class="price-card">
|
||||
<p class="price"><span class="amount">$100</span><span class="per">/mo</span></p>
|
||||
<p class="price-note">One price everywhere. No discounts — a single honest number.</p>
|
||||
<p class="seat-badge">10 presale seats</p>
|
||||
<p class="seat-note">Urgency comes from the seat limit, not from discounts.</p>
|
||||
<div class="cta-row">
|
||||
<a class="btn btn-primary" href="https://presale.cil.by/">Get a seat — self-serve checkout</a>
|
||||
</div>
|
||||
<p class="cta-hint">Checkout is self-serve via Stripe. Questions first — <a href="https://cil.by">ask in the cooperative portal</a>.</p>
|
||||
</div>
|
||||
|
||||
<div class="offer-lists">
|
||||
<div class="offer-panel">
|
||||
<h3>Included</h3>
|
||||
<ul class="list-include">
|
||||
<li>The Simbiot harness and the cooperative portal</li>
|
||||
<li>Agent deployment and onboarding — delivery within the month of purchase</li>
|
||||
<li>Task dashboard</li>
|
||||
<li>Chat with your agent</li>
|
||||
<li>Event subscriptions</li>
|
||||
<li>Priority support</li>
|
||||
<li>Ongoing updates while subscribed</li>
|
||||
<li>LLM backend through the cooperative's router (same as production); base quotas included</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="offer-panel">
|
||||
<h3>Not included</h3>
|
||||
<ul class="list-exclude">
|
||||
<li>Custom contract development</li>
|
||||
<li>Personal SLA guarantees</li>
|
||||
<li>Third-party billing</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="faq" aria-labelledby="faq-title">
|
||||
<div class="container">
|
||||
<div class="section-head">
|
||||
<p class="eyebrow">FAQ</p>
|
||||
<h2 id="faq-title">Questions, answered</h2>
|
||||
</div>
|
||||
|
||||
<div class="faq">
|
||||
<details open>
|
||||
<summary>Is Simbiot a chatbot?</summary>
|
||||
<p>No. Simbiot is a rhythmic agent harness: it works by itself on a schedule and reports digests on time. You can chat with your agent in the portal — with batch replies and voice — but the agent does not wait for you to open a tab.</p>
|
||||
</details>
|
||||
<details>
|
||||
<summary>What exactly is a tick?</summary>
|
||||
<p>The heartbeat of the harness. Ticks fire at four levels — every second, minute, hour, and day — each with its own context budget and job. Every tick is idempotent, busy levels absorb ticks, and only digests travel upward.</p>
|
||||
</details>
|
||||
<details>
|
||||
<summary>Can I verify what my agent did?</summary>
|
||||
<p>Yes. Every event is signed with ed25519 and hash-chained into a tamper-evident log. The <span class="icode">verify</span> command walks the full chain.</p>
|
||||
</details>
|
||||
<details>
|
||||
<summary>What happens when the agent is not sure?</summary>
|
||||
<p>It escalates. Anything an agent cannot decide safely goes up the ladder with full context attached, so a human or a higher level makes the call.</p>
|
||||
</details>
|
||||
<details>
|
||||
<summary>What does the $100/mo subscription include?</summary>
|
||||
<p>The harness, the cooperative portal, agent deployment and onboarding (delivered within the month of purchase), the task dashboard, chat with your agent, event subscriptions, priority support, and ongoing updates while subscribed. The LLM backend runs through the cooperative's router — the same as production — with base quotas included.</p>
|
||||
</details>
|
||||
<details>
|
||||
<summary>What is not included?</summary>
|
||||
<p>Custom contract development, personal SLA guarantees, and third-party billing. The price is $100/mo everywhere, with no discounts; the presale is limited to 10 seats.</p>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<footer class="site-footer">
|
||||
<div class="container">
|
||||
<div class="footer-grid">
|
||||
<div>
|
||||
<a class="brand" href="#top">
|
||||
<svg class="brand-mark" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 12h4l2-5 4 10 3-7 2 2h5"/></svg>
|
||||
Simbiot
|
||||
</a>
|
||||
<p class="footer-tagline">A rhythmic agent harness, run by a technology cooperative.</p>
|
||||
</div>
|
||||
<nav aria-label="Footer">
|
||||
<h3 class="footer-title">Site</h3>
|
||||
<ul class="footer-links">
|
||||
<li><a href="#product">Product</a></li>
|
||||
<li><a href="#how-it-works">How it works</a></li>
|
||||
<li><a href="#presale">Presale</a></li>
|
||||
<li><a href="#faq">FAQ</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div>
|
||||
<h3 class="footer-title">Cooperative</h3>
|
||||
<ul class="footer-links">
|
||||
<li><a href="https://cil.by">Portal & coop — cil.by</a></li>
|
||||
</ul>
|
||||
<h3 class="footer-title">Install</h3>
|
||||
<code class="footer-install">curl -fsSL https://raw.cil.by/simbiot/install.sh | bash</code>
|
||||
</div>
|
||||
</div>
|
||||
<p class="footer-small">Simbiot is a technology cooperative. One price — $100/mo, no discounts; the presale is limited to 10 seats. Not offered: custom contract development, personal SLA guarantees, third-party billing. © 2026 Simbiot Cooperative.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var btn = document.querySelector('[data-copy]');
|
||||
if (!btn) return;
|
||||
var target = document.getElementById(btn.getAttribute('data-copy'));
|
||||
var label = btn.querySelector('span');
|
||||
var resetTimer = null;
|
||||
btn.addEventListener('click', function () {
|
||||
var text = target.textContent;
|
||||
var done = function () {
|
||||
label.textContent = 'copied';
|
||||
btn.setAttribute('aria-label', 'Install command copied');
|
||||
if (resetTimer) clearTimeout(resetTimer);
|
||||
resetTimer = setTimeout(function () {
|
||||
label.textContent = 'copy';
|
||||
btn.setAttribute('aria-label', 'Copy install command');
|
||||
}, 2000);
|
||||
};
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(text).then(done);
|
||||
} else {
|
||||
var ta = document.createElement('textarea');
|
||||
ta.value = text;
|
||||
ta.setAttribute('readonly', '');
|
||||
ta.style.position = 'fixed';
|
||||
ta.style.left = '-9999px';
|
||||
document.body.appendChild(ta);
|
||||
ta.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(ta);
|
||||
done();
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
702
sites/landing/styles.css
Normal file
702
sites/landing/styles.css
Normal file
|
|
@ -0,0 +1,702 @@
|
|||
:root {
|
||||
--bg: #0b0f14;
|
||||
--surface: #111823;
|
||||
--surface-2: #16202e;
|
||||
--border: #212c3b;
|
||||
--border-strong: #2e3d52;
|
||||
--text: #d7e1ec;
|
||||
--text-strong: #f2f7fc;
|
||||
--text-soft: #b7c3d3;
|
||||
--muted: #97a6b8;
|
||||
--accent: #5eead4;
|
||||
--accent-strong: #83f2e0;
|
||||
--accent-ink: #052e2b;
|
||||
--warn: #f3a4b0;
|
||||
--radius: 10px;
|
||||
--radius-lg: 14px;
|
||||
--mono: ui-monospace, "SF Mono", "Cascadia Code", Menlo, Consolas, "Liberation Mono", monospace;
|
||||
--sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
html { scroll-behavior: smooth; }
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: var(--sans);
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.6;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
::selection { background: rgba(94, 234, 212, 0.28); }
|
||||
|
||||
a { color: var(--accent); text-decoration: none; }
|
||||
a:hover { color: var(--accent-strong); }
|
||||
|
||||
a:focus-visible,
|
||||
button:focus-visible,
|
||||
summary:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 2px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: min(100% - 2.5rem, 1080px);
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.skip-link {
|
||||
position: absolute;
|
||||
left: 1rem;
|
||||
top: -4rem;
|
||||
z-index: 100;
|
||||
background: var(--accent);
|
||||
color: var(--accent-ink);
|
||||
font-weight: 600;
|
||||
padding: 0.6rem 1rem;
|
||||
border-radius: 8px;
|
||||
transition: top 0.15s ease;
|
||||
}
|
||||
.skip-link:focus { top: 1rem; }
|
||||
|
||||
.site-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 50;
|
||||
background: rgba(11, 15, 20, 0.86);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.header-inner {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.5rem 1rem;
|
||||
padding-block: 0.75rem;
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.55rem;
|
||||
margin-right: auto;
|
||||
color: var(--text-strong);
|
||||
font-weight: 700;
|
||||
font-size: 1.08rem;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
.brand:hover { color: var(--text-strong); }
|
||||
.brand-mark { color: var(--accent); flex-shrink: 0; }
|
||||
|
||||
.nav-links {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.15rem;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.nav-links a {
|
||||
display: inline-block;
|
||||
padding: 0.45rem 0.7rem;
|
||||
border-radius: 8px;
|
||||
color: var(--muted);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
.nav-links a:hover { color: var(--text-strong); background: var(--surface); }
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
line-height: 1.2;
|
||||
padding: 0.7rem 1.2rem;
|
||||
border-radius: 9px;
|
||||
border: 1px solid transparent;
|
||||
transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
|
||||
}
|
||||
.btn-primary { background: var(--accent); color: var(--accent-ink); }
|
||||
.btn-primary:hover { background: var(--accent-strong); color: var(--accent-ink); }
|
||||
.btn-ghost { border-color: var(--border-strong); color: var(--text); }
|
||||
.btn-ghost:hover { border-color: var(--accent); color: var(--accent); }
|
||||
.btn-sm { padding: 0.45rem 0.85rem; font-size: 0.9rem; }
|
||||
|
||||
.hero {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding-block: clamp(3.5rem, 9vw, 6.5rem) clamp(3rem, 7vw, 5rem);
|
||||
}
|
||||
.hero::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image:
|
||||
linear-gradient(rgba(148, 163, 184, 0.06) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(148, 163, 184, 0.06) 1px, transparent 1px);
|
||||
background-size: 42px 42px;
|
||||
-webkit-mask-image: radial-gradient(ellipse 90% 85% at 50% 0%, #000 25%, transparent 72%);
|
||||
mask-image: radial-gradient(ellipse 90% 85% at 50% 0%, #000 25%, transparent 72%);
|
||||
pointer-events: none;
|
||||
}
|
||||
.hero .container { position: relative; }
|
||||
|
||||
.eyebrow {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.78rem;
|
||||
letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
color: var(--accent);
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: clamp(2.1rem, 5.5vw, 3.35rem);
|
||||
line-height: 1.08;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--text-strong);
|
||||
margin: 0 0 1.25rem;
|
||||
max-width: 18ch;
|
||||
}
|
||||
|
||||
.lead {
|
||||
font-size: clamp(1.02rem, 1.6vw, 1.16rem);
|
||||
line-height: 1.65;
|
||||
color: var(--text-soft);
|
||||
margin: 0;
|
||||
max-width: 62ch;
|
||||
}
|
||||
|
||||
.install {
|
||||
margin-top: 2.25rem;
|
||||
max-width: 640px;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-lg);
|
||||
overflow: hidden;
|
||||
}
|
||||
.install-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
padding: 0.5rem 0.6rem 0.5rem 1rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.install-label {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--muted);
|
||||
}
|
||||
.copy-btn {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.78rem;
|
||||
color: var(--muted);
|
||||
background: transparent;
|
||||
border: 1px solid var(--border-strong);
|
||||
border-radius: 7px;
|
||||
padding: 0.3rem 0.7rem;
|
||||
cursor: pointer;
|
||||
transition: color 0.15s ease, border-color 0.15s ease;
|
||||
}
|
||||
.copy-btn:hover, .copy-btn:focus-visible { color: var(--accent); border-color: var(--accent); }
|
||||
.install-body {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 0.75rem;
|
||||
padding: 1rem;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.prompt { font-family: var(--mono); color: var(--accent); flex-shrink: 0; }
|
||||
.install-body code {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.95rem;
|
||||
color: var(--text-strong);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.cta-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.hero-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
margin: 1.75rem 0 0;
|
||||
}
|
||||
|
||||
.chip {
|
||||
display: inline-block;
|
||||
font-family: var(--mono);
|
||||
font-size: 0.8rem;
|
||||
color: var(--text);
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border-strong);
|
||||
border-radius: 999px;
|
||||
padding: 0.3rem 0.8rem;
|
||||
}
|
||||
|
||||
.section {
|
||||
scroll-margin-top: 4.75rem;
|
||||
padding-block: clamp(3.25rem, 8vw, 5.5rem);
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.section-head {
|
||||
max-width: 62ch;
|
||||
margin-bottom: clamp(2rem, 4vw, 3rem);
|
||||
}
|
||||
.section-head .eyebrow { margin-bottom: 0.9rem; }
|
||||
|
||||
h2 {
|
||||
font-size: clamp(1.6rem, 3.4vw, 2.25rem);
|
||||
line-height: 1.15;
|
||||
letter-spacing: -0.015em;
|
||||
color: var(--text-strong);
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
.section-lead {
|
||||
font-size: 1.05rem;
|
||||
line-height: 1.65;
|
||||
color: var(--text-soft);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.compare {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
.compare-card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 1.6rem;
|
||||
}
|
||||
.compare-card.sim { border-color: rgba(94, 234, 212, 0.45); }
|
||||
.compare-card h3 {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
font-size: 1.05rem;
|
||||
color: var(--text-strong);
|
||||
margin: 0 0 1.1rem;
|
||||
}
|
||||
.compare-card.sim h3 { color: var(--accent); }
|
||||
.compare-tag {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.72rem;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
font-weight: 400;
|
||||
}
|
||||
.compare-card ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
gap: 0.7rem;
|
||||
}
|
||||
.compare-card li {
|
||||
position: relative;
|
||||
padding-left: 1.4rem;
|
||||
color: var(--text-soft);
|
||||
line-height: 1.55;
|
||||
}
|
||||
.compare-card li::before {
|
||||
content: "–";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--muted);
|
||||
}
|
||||
.compare-card.sim li::before { content: "→"; color: var(--accent); }
|
||||
|
||||
.chips-label {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.78rem;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
margin: 1.9rem 0 0.6rem;
|
||||
}
|
||||
.chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ladder {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
|
||||
}
|
||||
.ladder-card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.1rem;
|
||||
}
|
||||
.ladder-top {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding-bottom: 0.9rem;
|
||||
}
|
||||
.tick-level {
|
||||
font-family: var(--mono);
|
||||
font-size: 1.35rem;
|
||||
font-weight: 600;
|
||||
color: var(--accent);
|
||||
}
|
||||
.tick-interval {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.85rem;
|
||||
color: var(--muted);
|
||||
}
|
||||
.ladder-card dl {
|
||||
margin: 0;
|
||||
display: grid;
|
||||
gap: 0.7rem;
|
||||
}
|
||||
.ladder-card dt {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.72rem;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
margin-bottom: 0.15rem;
|
||||
}
|
||||
.ladder-card dd {
|
||||
margin: 0;
|
||||
color: var(--text-soft);
|
||||
font-size: 0.96rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.invariants {
|
||||
margin-top: 1.5rem;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border-strong);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 1.4rem 1.6rem;
|
||||
}
|
||||
.invariants-title {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.78rem;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
margin: 0 0 0.9rem;
|
||||
}
|
||||
.invariants ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
.invariants li {
|
||||
position: relative;
|
||||
padding-left: 1.4rem;
|
||||
color: var(--text-soft);
|
||||
}
|
||||
.invariants li::before {
|
||||
content: "→";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--accent);
|
||||
font-family: var(--mono);
|
||||
}
|
||||
|
||||
.portal-grid,
|
||||
.trust-grid {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
}
|
||||
.feature-card,
|
||||
.trust-card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 1.5rem;
|
||||
}
|
||||
.feature-icon {
|
||||
display: block;
|
||||
color: var(--accent);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.feature-card h3,
|
||||
.trust-card h3 {
|
||||
font-size: 1.02rem;
|
||||
color: var(--text-strong);
|
||||
margin: 0 0 0.6rem;
|
||||
}
|
||||
.feature-card p,
|
||||
.trust-card p {
|
||||
margin: 0;
|
||||
color: var(--text-soft);
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.proof-note {
|
||||
margin: 2.2rem 0 0;
|
||||
padding: 1.1rem 1.3rem;
|
||||
border: 1px solid var(--border);
|
||||
border-left: 3px solid var(--accent);
|
||||
border-radius: 10px;
|
||||
background: var(--surface);
|
||||
color: var(--muted);
|
||||
font-size: 0.92rem;
|
||||
line-height: 1.7;
|
||||
max-width: 72ch;
|
||||
}
|
||||
.proof-note strong { color: var(--text-strong); font-weight: 600; }
|
||||
|
||||
.icode {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.9em;
|
||||
color: var(--text-strong);
|
||||
background: var(--surface-2);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 5px;
|
||||
padding: 0.05em 0.4em;
|
||||
}
|
||||
|
||||
.offer {
|
||||
display: grid;
|
||||
gap: 1.25rem;
|
||||
align-items: start;
|
||||
}
|
||||
.price-card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border-strong);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 1.9rem;
|
||||
}
|
||||
.price {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 0.45rem;
|
||||
margin: 0 0 0.75rem;
|
||||
}
|
||||
.amount {
|
||||
font-family: var(--mono);
|
||||
font-size: 2.9rem;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
color: var(--text-strong);
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
.per {
|
||||
font-size: 1.05rem;
|
||||
color: var(--muted);
|
||||
}
|
||||
.price-note {
|
||||
margin: 0 0 1.4rem;
|
||||
color: var(--text-soft);
|
||||
max-width: 34ch;
|
||||
}
|
||||
.seat-badge {
|
||||
display: inline-block;
|
||||
font-family: var(--mono);
|
||||
font-size: 0.85rem;
|
||||
color: var(--accent);
|
||||
border: 1px solid rgba(94, 234, 212, 0.5);
|
||||
border-radius: 999px;
|
||||
padding: 0.35rem 0.85rem;
|
||||
margin: 0 0 0.5rem;
|
||||
}
|
||||
.seat-note {
|
||||
margin: 0 0 1.6rem;
|
||||
color: var(--muted);
|
||||
font-size: 0.92rem;
|
||||
max-width: 34ch;
|
||||
}
|
||||
.cta-hint {
|
||||
margin: 0.9rem 0 0;
|
||||
color: var(--muted);
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
|
||||
.offer-lists {
|
||||
display: grid;
|
||||
gap: 1.25rem;
|
||||
}
|
||||
.offer-panel {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 1.5rem 1.6rem;
|
||||
}
|
||||
.offer-panel h3 {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.78rem;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
.offer-panel ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
gap: 0.65rem;
|
||||
}
|
||||
.offer-panel li {
|
||||
position: relative;
|
||||
padding-left: 1.5rem;
|
||||
color: var(--text-soft);
|
||||
line-height: 1.55;
|
||||
font-size: 0.96rem;
|
||||
}
|
||||
.list-include li::before {
|
||||
content: "+";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--accent);
|
||||
font-family: var(--mono);
|
||||
}
|
||||
.list-exclude li::before {
|
||||
content: "–";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--warn);
|
||||
font-family: var(--mono);
|
||||
}
|
||||
|
||||
.faq {
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
max-width: 780px;
|
||||
}
|
||||
.faq details {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
.faq summary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
padding: 1rem 1.25rem;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
color: var(--text-strong);
|
||||
list-style: none;
|
||||
}
|
||||
.faq summary::-webkit-details-marker { display: none; }
|
||||
.faq summary::after {
|
||||
content: "+";
|
||||
font-family: var(--mono);
|
||||
font-size: 1.25rem;
|
||||
color: var(--muted);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.faq details[open] summary::after { content: "–"; }
|
||||
.faq details[open] summary { border-bottom: 1px solid var(--border); }
|
||||
.faq details p {
|
||||
margin: 0;
|
||||
padding: 1rem 1.25rem 1.25rem;
|
||||
color: var(--text-soft);
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
.site-footer {
|
||||
border-top: 1px solid var(--border);
|
||||
padding-block: 3rem 2rem;
|
||||
}
|
||||
.footer-grid {
|
||||
display: grid;
|
||||
gap: 2rem;
|
||||
}
|
||||
.footer-tagline {
|
||||
margin: 0.9rem 0 0;
|
||||
color: var(--muted);
|
||||
font-size: 0.95rem;
|
||||
max-width: 30ch;
|
||||
}
|
||||
.footer-title {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
margin: 0 0 0.8rem;
|
||||
}
|
||||
.footer-grid .footer-title:nth-of-type(n+2) { margin-top: 1.6rem; }
|
||||
.footer-links {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.footer-links a {
|
||||
color: var(--text-soft);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
.footer-links a:hover { color: var(--text-strong); }
|
||||
.footer-install {
|
||||
display: block;
|
||||
font-family: var(--mono);
|
||||
font-size: 0.8rem;
|
||||
color: var(--text-soft);
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 0.6rem 0.8rem;
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.footer-small {
|
||||
margin: 2.5rem 0 0;
|
||||
padding-top: 1.5rem;
|
||||
border-top: 1px solid var(--border);
|
||||
color: var(--muted);
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.65;
|
||||
max-width: 78ch;
|
||||
}
|
||||
|
||||
@media (min-width: 760px) {
|
||||
.compare { grid-template-columns: 1fr 1fr; }
|
||||
.invariants ul { grid-template-columns: repeat(3, 1fr); gap: 0.6rem 1.5rem; }
|
||||
.footer-grid { grid-template-columns: 1.3fr 0.8fr 1.6fr; }
|
||||
}
|
||||
|
||||
@media (min-width: 920px) {
|
||||
.offer { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
html { scroll-behavior: auto; }
|
||||
*, *::before, *::after {
|
||||
transition-duration: 0.01ms !important;
|
||||
animation-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@
|
|||
|---|---|---|
|
||||
| `meta-skill-factory` | Метаскилл: превращение повторяющейся работы в скилл репо (шаги, чеклисты, критерии качества) | Заметил, что делаю одну и ту же работу 2+ раз — прежде чем делать руками в третий |
|
||||
| `presale-landing` | Производство пресейл/продуктовых лендингов: структура, честные мокапы, Stripe CTA, quality gates | Задача на лендинг/пресейл-страницу или её v2 |
|
||||
| `systemone-bench` | Decision-эндпоинт Ollama /v1/systemone (nimble) + GA-оптимизация: иерархический выбор top-2 группы → choice ≤9, GA последовательностей действий с фитнесом=AUM | Тик, где нужно выбрать одно действие из >9 однородных опций; оптимизация политик на симуляторе |
|
||||
|
||||
## Сторонние наборы
|
||||
|
||||
|
|
|
|||
95
skills/systemone-bench/SKILL.md
Normal file
95
skills/systemone-bench/SKILL.md
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
---
|
||||
name: systemone-bench
|
||||
description: Use when choosing between 3+ non-numeric action options via local Ollama decision endpoint (/v1/systemone, model nimble), or when optimizing action sequences with GA against a simulator. Turns "which of many options" into a measured, hierarchical choice instead of free-form LLM guessing.
|
||||
metadata:
|
||||
origin: t-261005074437-39a9b0 (внедрение systemone-bench в архитектуру харнеса)
|
||||
code: /root/systemone-bench (clone symbiot/systemone-bench @ git.cil.by; README.md там же)
|
||||
scope: любые тики харнеса, где нужен выбор действия из каталога >9 кандидатов; симуляция политик стартапа
|
||||
---
|
||||
|
||||
# systemone-bench: decision-эндпоинт + GA-оптимизация выбора
|
||||
|
||||
## Trigger rule
|
||||
|
||||
- [ ] Нужно выбрать ОДНО действие из >9 однородных опций (команды, конфиги, политики)
|
||||
- [ ] Опции разбиты (или разбиваются) на группы по 2–8 в группе
|
||||
- [ ] Есть критерий качества выбора (fitness / AUM / метрика), хотя бы приблизительный
|
||||
- [ ] Нужен выбор на симуляторе перед применением в бою (бесплатные прогоны)
|
||||
|
||||
Не использовать: для бинарных решений (это noul), для абсолютных оценок тяжести
|
||||
(score плох на абсолютных шкалах — подтверждено сюитом), если опций ≤ 9 (делай
|
||||
обычный choice без иерархии).
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Проверка окружения
|
||||
|
||||
```bash
|
||||
curl -s http://127.0.0.1:11434/api/tags | grep -o '"nimble"' # модель на месте
|
||||
cd /root/systemone-bench && python3 ga/ga.py --smoke # ~13с, должен быть зелёный
|
||||
cd /root/systemone-bench && python3 harness/hier.py --model nimble --quick # ~мин, e2e ~81% на quick-16
|
||||
```
|
||||
|
||||
Контракт эндпоинта: `choice` — criteria **объект**, 2–26 опций; `score` —
|
||||
criteria **массив** 2–26 уровней, `score = Σ i·pᵢ`; `noul` — вероятность «да».
|
||||
1–64 вопроса на запрос, тело ≤ 64 KiB, промпт ≤ 8194 токенов. `confidence` =
|
||||
концентрация распределения, НЕ вероятность правильности — не опирайся на него
|
||||
как на calibration.
|
||||
|
||||
### 2. Схема выбора: top-2 группы → choice по шортлисту ≤ 9
|
||||
|
||||
Главная рекомендация (README, рекомендация №1): flat-choice по всем 64 командам
|
||||
падает до 0% (эндпоинт режет >26 и точность падает), иерархия:
|
||||
|
||||
1. **stage1 (score)**: по группам (8 групп) — взять **top-2** группы, не top-1
|
||||
(top-2 hit 93.8–84.4% vs top-1 87.5–64.1% — у ~40% команд легитимный
|
||||
«второй дом» в таксономии).
|
||||
2. **stage2 (choice)**: объединённый шортлист кандидатов из двух групп —
|
||||
**строго ≤ 9** (точность 100% на шортлисте 6 → 50% на 10).
|
||||
3. **stage3 (score)**: если выбранная команда имеет параметры — ранжирование
|
||||
значений score-массивом (gold #1 в 96.9–100%).
|
||||
|
||||
Расчётный эффект: e2e 57.8% → ~78% (top-2 выборка × stage2 92%). Латентность
|
||||
полного конвейера ~520–580 мс — годится для тика, не для realtime.
|
||||
|
||||
### 3. GA: поиск последовательностей действий
|
||||
|
||||
```bash
|
||||
cd /root/systemone-bench && python3 ga/ga.py # полный прогон (pop 200 × 100 gen, ~17с)
|
||||
python3 ga/ga2.py --neardeath # при каком runway GA берёт опасные команды (layoff/delay_payroll)
|
||||
python3 ga/ga2.py --robust # максимин по crunch/base/growth/dying
|
||||
```
|
||||
|
||||
Фитнес = терминальный AUM основателя. GA бьёт greedy на +27–43% (8 нед) и
|
||||
random search до +59%. Что уже известно (не переоткрывать):
|
||||
|
||||
- GA эмерджентно строит цепочки пререкрешитов (pricing_power → raise_prices_10).
|
||||
- Опасные команды не выбирает при runway ≥ 2.5 мес — сигналят о реальном риске.
|
||||
- **Дыра (найдена smoke-прогоном)**: `sell_secondary` даёт +100 personal cash и
|
||||
спасает dying без опасных команд — симулятор вознаграждает личный кэш без
|
||||
штрафа за health компании. Обход: гейт по здоровью компании (напр., фитнес
|
||||
умножается на флаг health > порога, или штраф за dilution в personal cash).
|
||||
|
||||
### 4. Интеграция в тик харнеса
|
||||
|
||||
На тиках, где нужен выбор действия: собрать кандидатов → разбить на группы →
|
||||
stage1 score (top-2) → choice ≤9 → применить → записать выбор и исход событием
|
||||
в стор (для последующей GA-сшивки policy trace с fitness).
|
||||
|
||||
## Quality gates
|
||||
|
||||
- Шортлист stage2 **всегда ≤ 9**; если объединение top-2 групп даёт >9 — режь
|
||||
по stage1-score внутри групп.
|
||||
- stage1 всегда top-2, никогда top-1.
|
||||
- Для тяжести/абсолютов не использовать score — только ранжирование однородного.
|
||||
- Прогон ga/ga.py --smoke обязателен перед использованием GA-результатов.
|
||||
- Результат каждого GA-эксперимента фиксировать событием на доску (cli.py task/complete
|
||||
или event), не оставлять только в ga2-results.json.
|
||||
- Не доверять `confidence` как вероятности правильности.
|
||||
|
||||
## References
|
||||
|
||||
- README.md репо /root/systemone-bench — контракт эндпоинта, все метрики сессии 2026-10-04
|
||||
- harness/eval.py — 5 сюитов (шортлист, permutation, noul, score-тяжесть, батчинг)
|
||||
- ga/ga.py, ga/ga2.py — GA и сценарные эксперименты; ga-results.json, ga2-results.json
|
||||
- docs/systemone-bench-integration.md (репо харнеса) — интеграция и связь с AUM-петлёй
|
||||
|
|
@ -170,11 +170,21 @@ def _fake_http(url, headers):
|
|||
|
||||
_saved_http = aum._http_json
|
||||
aum._http_json = _fake_http
|
||||
# hermetic: OPENROUTER_KEY должен приходить из мока .env, а не из реального
|
||||
# .env хоста (на мигрированном инстансе ключа нет — тест падал негерметично)
|
||||
_saved_efv2 = aum._env_file_value
|
||||
# hermetic: OPENROUTER_KEY из реального .env хоста не существует (миграция
|
||||
# инстанса) — даём мок; STRIPE_LIVE_KEY читается из coop_env-файла теста
|
||||
# через настоящий _env_file_value, его не подменяем
|
||||
aum._env_file_value = (lambda path, prefix:
|
||||
"sk-or-test" if prefix == "OPENROUTER_KEY"
|
||||
else _saved_efv2(path, prefix))
|
||||
try:
|
||||
got2 = aum.refresh_auto_balances(path=bal_mock,
|
||||
coop_env=os.path.join(tmpdir, "stripe.env"))
|
||||
finally:
|
||||
aum._http_json = _saved_http
|
||||
aum._env_file_value = _saved_efv2
|
||||
d2 = json.load(open(bal_mock))
|
||||
check("refresh_auto: mock API → 60.0 и 123.45 записаны",
|
||||
len(got2) == 2 and abs(d2["accounts"][0]["amount_usd"] - 60.0) < 0.001
|
||||
|
|
|
|||
|
|
@ -93,30 +93,37 @@ check("db: генезис только", set(evs) <= {"PolicyChanged"}, evs)
|
|||
# Вендор возвращает top-k ближайших на ЛЮБОЙ запрос (пустой базы не бывает),
|
||||
# поэтому «наших знаний нет» проверяем по содержимому индекса: все чанки —
|
||||
# только из документов владельца. Положительный контроль: его тема — находится.
|
||||
# RAG-вендор (/root/rag) есть не на каждом хосте (миграция инстанса): без него
|
||||
# init_instance честно пишет «индексация пропущена» — тест проверяет только
|
||||
# то, что доступно (ES-изоляция, генезис, прод не задет), RAG — skip.
|
||||
_RAG_AVAILABLE = os.path.exists("/root/rag-venv/bin/python") and os.path.exists("/root/rag/rag.py")
|
||||
env2 = dict(env)
|
||||
r3 = subprocess.run([_PY, "-c", """
|
||||
if _RAG_AVAILABLE:
|
||||
r3 = subprocess.run([_PY, "-c", """
|
||||
import sys, json
|
||||
sys.path.insert(0, %r)
|
||||
from harness import rag_lookup
|
||||
his = rag_lookup.rag_query("теплица автополив датчики", k=3)
|
||||
print(json.dumps({"his": [c["source"] for c in his]}))
|
||||
""" % _REPO], capture_output=True, text=True, env=env2, timeout=120)
|
||||
try:
|
||||
try:
|
||||
rag = json.loads(r3.stdout.strip().splitlines()[-1])
|
||||
except Exception:
|
||||
except Exception:
|
||||
rag = {"his": []}
|
||||
rdb = sqlite3.connect(f"file:{os.path.join(home, 'rag', 'rag.db')}?mode=ro", uri=True)
|
||||
paths = [p for (p,) in rdb.execute("SELECT DISTINCT path FROM chunks")]
|
||||
rdb.close()
|
||||
check("rag: в индексе только документы владельца",
|
||||
rdb = sqlite3.connect(f"file:{os.path.join(home, 'rag', 'rag.db')}?mode=ro", uri=True)
|
||||
paths = [p for (p,) in rdb.execute("SELECT DISTINCT path FROM chunks")]
|
||||
rdb.close()
|
||||
check("rag: в индексе только документы владельца",
|
||||
paths and all(os.path.realpath(p).startswith(home + os.sep) for p in paths),
|
||||
paths)
|
||||
check("rag: его документ находится",
|
||||
check("rag: его документ находится",
|
||||
any("greenhouse" in s for s in rag["his"]), rag["his"])
|
||||
check("rag: база инстанса отдельная",
|
||||
check("rag: база инстанса отдельная",
|
||||
os.path.exists(os.path.join(home, "rag", "rag.db"))
|
||||
and os.path.realpath(os.path.join(home, "rag", "rag.db"))
|
||||
!= os.path.realpath("/root/rag/rag.db"))
|
||||
else:
|
||||
print("SKIP: RAG-вендор /root/rag отсутствует на этом хосте — контур RAG не проверялся")
|
||||
|
||||
# ── 3) прод не задет: кросс-записей нет ────────────────────────────────────
|
||||
# Прод живёт и пишет события каждую секунду; кроме того, трейсы ЭТОЙ сессии
|
||||
|
|
|
|||
160
tests/test_repo_sync.py
Normal file
160
tests/test_repo_sync.py
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""t-261007082307-2e2294: репо-гигиена — fetch origin в дневном тике (hermetic).
|
||||
|
||||
Запуск: ./venv/bin/python tests/test_repo_sync.py
|
||||
|
||||
Проверяет: _repo_sync_check пишет RepoSynced с behind/ahead (fetch в git-
|
||||
песочнице), один замер на день (повтор не плодит записи), при behind>0
|
||||
первый день эскалации НЕТ, второй день подряд — EscalationRaised (счётчик
|
||||
дней из истории стора, рестарт демона его не сбрасывает), ошибка fetch
|
||||
не валит шаг (behind=unknown честно фиксируется), env-порог
|
||||
SIMBIOT_REPO_SYNC_MAX_AGE_DAYS управляет чувствительностью.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from datetime import timedelta
|
||||
|
||||
# ── изоляция: своя БД в /tmp, git-песочница вместо реального репо ──
|
||||
_DST = "/tmp/simbiot-test-reposync.db"
|
||||
for p in (_DST, _DST + "-wal", _DST + "-shm"):
|
||||
if os.path.exists(p):
|
||||
os.unlink(p)
|
||||
os.environ["SIMBIOT_DB"] = _DST
|
||||
os.environ["SIMBIOT_REPO_SYNC_MAX_AGE_DAYS"] = "2"
|
||||
sys.path.insert(0, "/root/simbiot-harness")
|
||||
|
||||
from harness import es, paths # noqa: E402
|
||||
from harness.levels import LEVELS # noqa: E402
|
||||
from harness import scheduler # noqa: E402
|
||||
|
||||
fails = []
|
||||
|
||||
|
||||
def check(name, cond, extra=""):
|
||||
print(("PASS" if cond else "FAIL"), name, ("| " + extra) if not cond else "")
|
||||
if not cond:
|
||||
fails.append(name)
|
||||
|
||||
|
||||
def _git(cwd, *args):
|
||||
r = subprocess.run(["git"] + list(args), cwd=cwd,
|
||||
capture_output=True, timeout=30, check=True)
|
||||
return r.stdout.decode().strip()
|
||||
|
||||
|
||||
def synced_events(limit=200):
|
||||
out = []
|
||||
for e in es.get_events(limit=limit):
|
||||
if e["event_type"] == "RepoSynced":
|
||||
e["data"] = json.loads(e["data"])
|
||||
out.append(e)
|
||||
return out
|
||||
|
||||
|
||||
def escalations(limit=500):
|
||||
return [e for e in es.get_events(limit=limit)
|
||||
if e["event_type"] == "EscalationRaised"]
|
||||
|
||||
|
||||
def make_sandbox():
|
||||
"""bare origin + клон: init запушен, локально ещё 1 незапушенный коммит."""
|
||||
base = tempfile.mkdtemp(prefix="simbiot-reposync-")
|
||||
origin = os.path.join(base, "origin.git")
|
||||
_git(base, "init", "--bare", "-b", "master", origin)
|
||||
clone = os.path.join(base, "clone")
|
||||
_git(base, "clone", origin, clone)
|
||||
for d in (clone,):
|
||||
_git(d, "config", "user.email", "test@simbiot.local")
|
||||
_git(d, "config", "user.name", "simbiot-test")
|
||||
_git(clone, "commit", "--allow-empty", "-m", "init")
|
||||
_git(clone, "push", "origin", "master")
|
||||
_git(clone, "commit", "--allow-empty", "-m", "local-only") # ahead=1
|
||||
return clone, origin, base
|
||||
|
||||
|
||||
es.init_db()
|
||||
clone, origin, base = make_sandbox()
|
||||
_real_root = paths.REPO_ROOT
|
||||
paths.REPO_ROOT = clone # песочница вместо реального репо
|
||||
try:
|
||||
# ── 1. happy path: fetch ok, behind=0/ahead=1, RepoSynced записан ──
|
||||
res = scheduler._repo_sync_check(LEVELS["day"])
|
||||
check("fetch ok", res.get("fetch_ok") is True)
|
||||
check("behind=0", res.get("behind") == 0)
|
||||
check("ahead=1 (незапушенный коммит виден)", res.get("ahead") == 1)
|
||||
evs = synced_events(50)
|
||||
check("RepoSynced записан", len(evs) == 1, f"{len(evs)}")
|
||||
today = evs[0]["aggregate_id"].split(":", 1)[1]
|
||||
check("aggregate_id = repo-sync:<сегодня>",
|
||||
evs[0]["aggregate_id"] == "repo-sync:" +
|
||||
scheduler._now().strftime("%Y-%m-%d"))
|
||||
|
||||
# ── 2. повтор в тот же день — замер тот же, записей не больше ──
|
||||
again = scheduler._repo_sync_check(LEVELS["day"])
|
||||
check("повтор: те же числа", again.get("behind") == 0 and again.get("ahead") == 1)
|
||||
check("повтор в тот же день не плодит записи", len(synced_events(50)) == 1)
|
||||
|
||||
# ── 3. upstream ушёл вперёд: behind=1, эскалации ещё НЕТ (1-й день) ──
|
||||
work = os.path.join(base, "worktree")
|
||||
_git(base, "clone", origin, work)
|
||||
_git(work, "config", "user.email", "test@simbiot.local")
|
||||
_git(work, "config", "user.name", "simbiot-test")
|
||||
_git(work, "commit", "--allow-empty", "-m", "upstream change")
|
||||
_git(work, "push", "origin", "master")
|
||||
res3 = scheduler._repo_sync_check(LEVELS["day"])
|
||||
check("behind=1 увиден", res3.get("behind") == 1)
|
||||
check("эскалации НЕТ в первый день", len(escalations()) == 0)
|
||||
|
||||
# ── 4. вчера тоже был behind>0 → сегодня 2-й подряд → эскалация ──
|
||||
yday = (scheduler._now() - timedelta(days=1)).strftime("%Y-%m-%d")
|
||||
eid, _v = es.append_event("harness", f"repo-sync:{yday}", "RepoSynced", {
|
||||
"ts": yday + "T06:00:00+00:00", "fetch_ok": True,
|
||||
"behind": 1, "ahead": 1})
|
||||
db = es._conn()
|
||||
db.execute("UPDATE events SET timestamp=? WHERE id=?",
|
||||
(yday + "T06:00:00.000000+00:00", eid))
|
||||
db.commit()
|
||||
db.close()
|
||||
res4 = scheduler._repo_sync_check(LEVELS["day"])
|
||||
check("behind=1 стабилен", res4.get("behind") == 1)
|
||||
escs = escalations()
|
||||
check("эскалация ПОДНЯТА на 2-й день подряд", len(escs) == 1, f"{len(escs)}")
|
||||
if escs:
|
||||
reason = json.loads(escs[-1]["data"]).get("reason", "")
|
||||
check("причина упоминает behind и 2 дня",
|
||||
"behind=1" in reason and "2 дн" in reason, reason[:120])
|
||||
finally:
|
||||
paths.REPO_ROOT = _real_root
|
||||
shutil.rmtree(base, ignore_errors=True)
|
||||
|
||||
# ── 5. ошибка fetch не валит шаг (свежая БД: дедуп дня не мешает) ──
|
||||
_DST2 = "/tmp/simbiot-test-reposync2.db"
|
||||
for p in (_DST2, _DST2 + "-wal", _DST2 + "-shm"):
|
||||
if os.path.exists(p):
|
||||
os.unlink(p)
|
||||
es.DB_PATH = _DST2
|
||||
es._signature_schema_ready = False # латч на процесс: новая БД требует миграции
|
||||
es.init_db()
|
||||
paths.REPO_ROOT = "/nonexistent-repo-dir"
|
||||
try:
|
||||
res5 = scheduler._repo_sync_check(LEVELS["day"])
|
||||
check("fetch-fail: fetch_ok=False, не упал", res5.get("fetch_ok") is False)
|
||||
evs5 = synced_events(50)
|
||||
check("fetch-fail зафиксирован событием", len(evs5) == 1)
|
||||
if evs5:
|
||||
check("behind=unknown (None), не выдуманный 0",
|
||||
evs5[0]["data"].get("behind") is None)
|
||||
check("ошибка сохранена", bool(evs5[0]["data"].get("error")))
|
||||
finally:
|
||||
paths.REPO_ROOT = _real_root
|
||||
|
||||
# ── 6. env-порог читается при импорте ──
|
||||
check("порог = 2 из env", scheduler._REPO_SYNC_MAX_AGE_DAYS == 2)
|
||||
|
||||
print()
|
||||
print("ИТОГ:", "ВСЕ ОК" if not fails else f"ПРОВАЛЫ: {fails}")
|
||||
sys.exit(1 if fails else 0)
|
||||
|
|
@ -39,7 +39,9 @@ with open(os.path.join(tmpdir, ".env"), "w") as f:
|
|||
f.write("ROUTER_KEY=sk-envtest-key\n")
|
||||
f.write("# комментарий\n")
|
||||
|
||||
runtime._HARNESS_DIR = tmpdir
|
||||
# t-261004113351: runtime читает .env через paths.env_path() (SIMBIOT_HOME),
|
||||
# _HARNESS_DIR больше не источник — патчим актуальный механизм (с рестором)
|
||||
runtime._paths.env_path = lambda: os.path.join(tmpdir, ".env")
|
||||
|
||||
# 1. env отсутствует, .env есть → значение из .env (суть фикса)
|
||||
check("1: base_url из .env quando env пуст",
|
||||
|
|
@ -55,13 +57,14 @@ check("2: env приоритетнее .env",
|
|||
os.environ.pop("ROUTER_BASE_URL")
|
||||
|
||||
# 3. нет ни env, ни .env → дефолт с локальным роутером
|
||||
runtime._HARNESS_DIR = tempfile.mkdtemp(prefix="simbiot-envtest-empty-")
|
||||
runtime._paths.env_path = lambda: os.path.join(
|
||||
tempfile.mkdtemp(prefix="simbiot-envtest-empty-"), ".env")
|
||||
check("3: нет .env → дефолт 127.0.0.1:20128",
|
||||
runtime._router_base_url() == "http://127.0.0.1:20128/v1",
|
||||
str(runtime._router_base_url()))
|
||||
|
||||
# 4. setup_provider(): end-to-end маппинг .env → OPENAI_* env
|
||||
runtime._HARNESS_DIR = tmpdir
|
||||
runtime._paths.env_path = lambda: os.path.join(tmpdir, ".env")
|
||||
runtime.setup_provider()
|
||||
check("4: setup_provider кладёт OPENAI_BASE_URL из .env",
|
||||
os.environ["OPENAI_BASE_URL"] == "https://router.cil.by/v1",
|
||||
|
|
|
|||
91
tests/test_submit_title_guard.py
Normal file
91
tests/test_submit_title_guard.py
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""e-261003094628 (повтор e-261001082241): guard мусорных заголовков задач (hermetic).
|
||||
|
||||
Запуск: ./venv/bin/python tests/test_submit_title_guard.py
|
||||
|
||||
Первопричина серии 'list': LLM-тик нижнего уровня звал
|
||||
`cli.py task list` / `cli.py task <id>` (хотел доску/детали задачи), а
|
||||
`task` принимает позиционный title → argparse молча подсунул слово
|
||||
'list' / чужой id как заголовок и подал мусорную задачу. Min-уровень
|
||||
закрыл их как no-op и эскалировал — дважды.
|
||||
|
||||
Проверяет: submit_task режет субкомандо-подобные заголовки и чужие id
|
||||
задач БЕЗ деталей (ValueError), пропускает нормальные подачи, одно слово
|
||||
с деталями, обычные слова не из стоп-листа; CLI `task list` → exit 1 с
|
||||
ERROR, а не тихая подача.
|
||||
"""
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
# ── изоляция: своя БД в /tmp (prod не трогаем) ──
|
||||
_DST = "/tmp/simbiot-test-title-guard.db"
|
||||
for p in (_DST, _DST + "-wal", _DST + "-shm"):
|
||||
if os.path.exists(p):
|
||||
os.unlink(p)
|
||||
os.environ["SIMBIOT_DB"] = _DST
|
||||
sys.path.insert(0, "/root/simbiot-harness")
|
||||
|
||||
from harness import commands
|
||||
|
||||
commands.es.init_db()
|
||||
|
||||
fails = []
|
||||
|
||||
|
||||
def check(name, cond, extra=""):
|
||||
print(("PASS" if cond else "FAIL"), name, ("| " + str(extra)) if not cond else "")
|
||||
if not cond:
|
||||
fails.append(name)
|
||||
|
||||
|
||||
# ── 1. Субкомандо-подобные заголовки без деталей — режутся ──
|
||||
for bad in ("list", "tasks", "status", "events", "help", "LIST", " Complete "):
|
||||
try:
|
||||
commands.submit_task(bad, "", minutes=5)
|
||||
check(f"мусорный заголовок {bad!r} отвергнут", False, "подача прошла")
|
||||
except ValueError:
|
||||
check(f"мусорный заголовок {bad!r} отвергнут", True)
|
||||
|
||||
# ── 2. Чужой id задачи как заголовок без деталей — режется ──
|
||||
try:
|
||||
commands.submit_task("t-261003094008-ed226f", "", minutes=5)
|
||||
check("id задачи как заголовок отвергнут", False, "подача прошла")
|
||||
except ValueError:
|
||||
check("id задачи как заголовок отвергнут", True)
|
||||
|
||||
# ── 3. Нормальные подачи — проходят ──
|
||||
for title, details in (
|
||||
("Разбор голосового m-973940607 + HTML-страница", "транскрипт в docs/"),
|
||||
("list", "собрать список всех ключей в реестре"), # слово из стоп-листа, но с деталями
|
||||
("фикс деплоя", ""),
|
||||
):
|
||||
try:
|
||||
tid, size = commands.submit_task(title, details, minutes=5)
|
||||
ok = bool(tid) and size == "S"
|
||||
except ValueError as e:
|
||||
ok, tid = False, str(e)
|
||||
check(f"нормальная подача {title[:30]!r} прошла", ok, tid)
|
||||
|
||||
# ── 4. CLI: `cli.py task list` — показывает доску, а не плодит мусорную задачу ──
|
||||
# Контракт изменён в origin/master (t-261003125513/e-261004134613): 'task list'
|
||||
# и 'task <id>' показывают доску/задачу (exit 0). Интент guard'а прежний:
|
||||
# НЕТ тихой подачи мусорной задачи с заголовком 'list'.
|
||||
env = dict(os.environ, SIMBIOT_DB=_DST)
|
||||
r = subprocess.run(
|
||||
["./venv/bin/python", "cli.py", "task", "list"],
|
||||
capture_output=True, text=True, cwd="/root/simbiot-harness", env=env,
|
||||
)
|
||||
out = (r.stdout + r.stderr).strip()
|
||||
check("CLI task list → exit 0 (показ доски)", r.returncode == 0, f"rc={r.returncode} out={out!r}")
|
||||
check("CLI task list → не ERROR-подача", "ERROR" not in out, out)
|
||||
model = commands.es.run_projections()
|
||||
check("мусорной задачи 'list' в сторе нет",
|
||||
not any(t.get("title", "").strip().lower() == "list" and not t.get("details")
|
||||
for t in model["tasks"].values()))
|
||||
|
||||
print()
|
||||
if fails:
|
||||
print(f"FAILED: {len(fails)}")
|
||||
sys.exit(1)
|
||||
print("OK: все проверки прошли")
|
||||
|
|
@ -76,6 +76,13 @@ print("--- /помощь ---\n" + tg_commands.cmd_help())
|
|||
# ── 3. фильтр build_context: команда НЕ попадает мин-уровню ──
|
||||
model = es.run_projections()
|
||||
fake = json.loads(json.dumps(model))
|
||||
# доска: тестовая ready-задача (в живой базе ready может не быть — секция
|
||||
# «ДОСКА ЗАДАЧ» обязана появляться от самих задач, а не от состояния прода)
|
||||
fake.setdefault("tasks", {})
|
||||
fake["tasks"]["t-990099-000001"] = {
|
||||
"id": "t-990099-000001", "title": "разбор /лестница-запроса",
|
||||
"size": "S", "status": "ready", "ts": "2026-09-30T02:00:00+00:00",
|
||||
}
|
||||
fake.setdefault("inbox", {})
|
||||
# ключи — как у реальных сообщений: aggregate_id = f"m-{update_id}"
|
||||
fake["inbox"]["m-990001"] = {
|
||||
|
|
|
|||
7
~/.ssh/id_ed25519
Normal file
7
~/.ssh/id_ed25519
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACAidokdBiOPXDSuTfzeRoYSPjR4fKT2y3jyw4dxHVEj3AAAAJhDEfzrQxH8
|
||||
6wAAAAtzc2gtZWQyNTUxOQAAACAidokdBiOPXDSuTfzeRoYSPjR4fKT2y3jyw4dxHVEj3A
|
||||
AAAEC4Y/gtEVJCC2vPDgWHfTuuV2BkJKYJ1NaaxX4bxpuguSJ2iR0GI49cNK5N/N5GhhI+
|
||||
NHh8pPbLePLDh3EdUSPcAAAAD3NpbWJpb3QtaGFybmVzcwECAwQFBg==
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
1
~/.ssh/id_ed25519.pub
Normal file
1
~/.ssh/id_ed25519.pub
Normal file
|
|
@ -0,0 +1 @@
|
|||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICJ2iR0GI49cNK5N/N5GhhI+NHh8pPbLePLDh3EdUSPc simbiot-harness
|
||||
Loading…
Reference in a new issue