Alipay-ватчер: поллинг capability Stripe до аппрува, автолинк $100 card+alipay (e-261005142309)

This commit is contained in:
Simbiot Harness 2026-10-05 15:05:55 +00:00
parent c5c3e8fbaa
commit e7bf208bd3
32 changed files with 2885 additions and 11 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 954 KiB

View file

@ -0,0 +1,598 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simbiot — Ритмичный ИИ-харнес кооператива 42</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--accent: #2563eb;
--bg-light: #ffffff;
--bg-dark: #1e293b;
--text-light: #1e293b;
--text-dark: #f8fafc;
--gray: #64748b;
--border: #e2e8f0;
}
@media (prefers-reduced-motion: reduce) {
* {
animation: none !important;
transition: none !important;
}
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.6;
color: var(--text-light);
background: var(--bg-light);
}
.container {
max-width: 1100px;
margin: 0 auto;
padding: 0 20px;
}
/* Animations */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.fade-in-up {
opacity: 0;
animation: fadeInUp 0.8s ease-out forwards;
}
.delay-1 { animation-delay: 0.2s; }
.delay-2 { animation-delay: 0.4s; }
.delay-3 { animation-delay: 0.6s; }
/* Typography */
h1, h2, h3 {
font-family: Georgia, serif;
font-weight: 600;
line-height: 1.2;
}
h1 {
font-size: 2.5rem;
margin-bottom: 1rem;
}
h2 {
font-size: 2rem;
margin-bottom: 1.5rem;
}
h3 {
font-size: 1.5rem;
margin-bottom: 1rem;
}
.section {
padding: 80px 0;
}
.section:nth-child(even) {
background: var(--bg-dark);
color: var(--text-dark);
}
.btn {
display: inline-block;
padding: 12px 24px;
background: var(--accent);
color: white;
text-decoration: none;
border-radius: 6px;
font-weight: 500;
transition: background 0.3s ease;
}
.btn:hover {
background: #1d4ed8;
}
/* Hero Section */
.hero {
text-align: center;
padding: 120px 0;
}
.hero h1 {
font-size: 2.2rem;
margin-bottom: 1rem;
}
.hero p {
font-size: 1.2rem;
margin-bottom: 2rem;
color: var(--gray);
}
/* Problem/Solution Blocks */
.problem-solution {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 40px;
margin-top: 60px;
}
.block {
padding: 30px;
background: rgba(255, 255, 255, 0.05);
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.block h3 {
color: var(--accent);
margin-bottom: 1rem;
}
.block p {
margin-bottom: 1rem;
}
/* What's Included */
.included-list {
list-style: none;
margin-top: 40px;
}
.included-list li {
padding: 15px 0;
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
}
.included-list li:before {
content: "✓";
color: var(--accent);
font-weight: bold;
margin-right: 15px;
font-size: 1.2rem;
}
/* How It Works */
.steps {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 30px;
margin-top: 60px;
}
.step {
text-align: center;
padding: 30px;
background: rgba(37, 99, 235, 0.05);
border-radius: 8px;
border: 1px solid rgba(37, 99, 235, 0.1);
}
.step-number {
width: 50px;
height: 50px;
background: var(--accent);
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
font-weight: bold;
margin: 0 auto 20px;
}
/* Mockups */
.mockup {
margin: 40px 0;
padding: 30px;
background: rgba(255, 255, 255, 0.05);
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.dashboard {
background: #f8fafc;
padding: 20px;
border-radius: 8px;
font-family: monospace;
font-size: 0.9rem;
}
.dashboard-header {
background: var(--accent);
color: white;
padding: 10px 15px;
border-radius: 6px 6px 0 0;
font-weight: bold;
}
.dashboard-content {
padding: 15px;
}
.task-item {
padding: 10px;
border-bottom: 1px solid #e2e8f0;
display: flex;
justify-content: space-between;
}
.task-status {
padding: 4px 8px;
border-radius: 4px;
font-size: 0.8rem;
}
.status-pending { background: #fef3c7; color: #92400e; }
.status-progress { background: #dbeafe; color: #1e40af; }
.status-done { background: #d1fae5; color: #065f46; }
.telegram-chat {
background: #f8fafc;
padding: 20px;
border-radius: 8px;
}
.message {
margin: 10px 0;
max-width: 70%;
padding: 12px 16px;
border-radius: 18px;
position: relative;
}
.message.user {
background: var(--accent);
color: white;
margin-left: auto;
border-bottom-right-radius: 4px;
}
.message.bot {
background: #e2e8f0;
color: var(--text-light);
border-bottom-left-radius: 4px;
}
/* Social Proof */
.social-proof {
text-align: center;
padding: 40px;
}
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 30px;
margin-top: 40px;
}
.stat {
padding: 20px;
}
.stat-number {
font-size: 2rem;
font-weight: bold;
color: var(--accent);
}
/* Pricing */
.pricing {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 30px;
margin-top: 60px;
}
.plan {
padding: 40px;
background: rgba(255, 255, 255, 0.05);
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.1);
text-align: center;
}
.plan h3 {
margin-bottom: 20px;
color: var(--accent);
}
.plan-price {
font-size: 2.5rem;
font-weight: bold;
margin-bottom: 20px;
}
.plan-features {
list-style: none;
margin-bottom: 30px;
}
.plan-features li {
padding: 8px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
/* Footer */
.footer {
background: var(--bg-dark);
color: var(--text-dark);
padding: 40px 0;
text-align: center;
}
.footer-content {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 40px;
margin-bottom: 40px;
}
.footer-section h4 {
margin-bottom: 15px;
color: var(--accent);
}
.footer-section p, .footer-section a {
color: #94a3b8;
text-decoration: none;
}
.footer-section a:hover {
color: var(--accent);
}
/* Responsive */
@media (max-width: 768px) {
h1 { font-size: 1.8rem; }
h2 { font-size: 1.5rem; }
.section { padding: 60px 0; }
.hero { padding: 80px 0; }
}
</style>
</head>
<body>
<!-- Hero Section -->
<section class="hero section">
<div class="container fade-in-up">
<h1>Харнес превращает решения в исполненные задачи</h1>
<p>Для кооперативов, которые хотят ≥ +1% к AUM в горизонте год+</p>
<a href="#pricing" class="btn">Начать использовать Simbiot</a>
</div>
</section>
<!-- What This Is -->
<section class="section">
<div class="container">
<h2>Что это</h2>
<div class="problem-solution">
<div class="block fade-in-up delay-1">
<h3>Проблема</h3>
<p>Решения из звонков и чатов теряются, не доходят до исполнителей, важное тонет в рутине</p>
</div>
<div class="block fade-in-up delay-2">
<h3>Механизм</h3>
<p>Харнес автоматически извлекает решения из диалогов, создает задачи с четкими сроками и ответственными</p>
</div>
<div class="block fade-in-up delay-3">
<h3>Результат</h3>
<p>Важное всегда на виду, эскалации важного поднимаются наверх, AUM растет системно</p>
</div>
</div>
</div>
</section>
<!-- What's Included -->
<section class="section">
<div class="container">
<h2>Что включено</h2>
<ul class="included-list">
<li>Автоматический парсинг решений из Telegram-чатов и звонков</li>
<li>Система задач с тактами (минута/час/день) для агентов</li>
<li>Дашборд контроля исполнения в реальном времени</li>
<li>Эскалация важных задач без потери контекста</li>
<li>Интеграция с существующими системами кооператива</li>
<li>Аналитика эффективности решений и их исполнения</li>
<li>Поддержка 7 продуктов кооператива 42</li>
</ul>
</div>
</section>
<!-- How It Works -->
<section class="section">
<div class="container">
<h2>Как это работает</h2>
<div class="steps">
<div class="step fade-in-up">
<div class="step-number">1</div>
<h3>Обсуждение</h3>
<p>Вы обсуждаете решения в Telegram или звонках</p>
</div>
<div class="step fade-in-up delay-1">
<div class="step-number">2</div>
<h3>Авто-создание задач</h3>
<p>Simbiot превращает решения в задачи с ответственными</p>
</div>
<div class="step fade-in-up delay-2">
<div class="step-number">3</div>
<h3>Контроль исполнения</h3>
<p>Агенты выполняют по тактам, важное эскалируется</p>
</div>
</div>
</div>
</section>
<!-- Product Visuals -->
<section class="section">
<div class="container">
<h2>Визуалы продукта</h2>
<div class="mockup fade-in-up">
<h3>Дашборд задач</h3>
<div class="dashboard">
<div class="dashboard-header">Панель управления задачами</div>
<div class="dashboard-content">
<div class="task-item">
<span>Анализ Q3 отчетов</span>
<span class="task-status status-progress">В работе</span>
</div>
<div class="task-item">
<span>Подготовить презентацию для инвесторов</span>
<span class="task-status status-pending">Ожидание</span>
</div>
<div class="task-item">
<span>Обновить CRM систему</span>
<span class="task-status status-done">Готово</span>
</div>
<div class="task-item">
<span>Проверить compliance требования</span>
<span class="task-status status-pending">Ожидание</span>
</div>
</div>
</div>
</div>
<div class="mockup fade-in-up delay-1">
<h3>Telegram-диалог</h3>
<div class="telegram-chat">
<div class="message bot">
<strong>Simbiot:</strong> Обнаружено решение: "Запланировать встречу с командой по стратегии Q4"
</div>
<div class="message user">
<strong>Вы:</strong> Подтверждаю, создай задачу
</div>
<div class="message bot">
<strong>Simbiot:</strong> Задача создана: Встреча по стратегии Q4, ответственный: Вы, срок: 25.10.2024
</div>
</div>
</div>
</div>
</section>
<!-- Social Proof -->
<section class="section">
<div class="container">
<h2>Соцдоказательство</h2>
<div class="social-proof">
<p>Кооператив 42 «Друзья Коллективного Разума»</p>
<p>Юрлицо с 2026 года, Минск</p>
<p>7 продуктов под управлением, сайт cil.by, Telegram t.me/cil_by</p>
<div class="stats">
<div class="stat">
<div class="stat-number">2026</div>
<div>Год основания</div>
</div>
<div class="stat">
<div class="stat-number">7</div>
<div>Активных продуктов</div>
</div>
<div class="stat">
<div class="stat-number">≥+1%</div>
<div>Рост AUM в год+</div>
</div>
</div>
</div>
</div>
</section>
<!-- Pricing -->
<section class="section" id="pricing">
<div class="container">
<h2>Тарифные планы</h2>
<div class="pricing">
<div class="plan fade-in-up">
<h3>Старт</h3>
<div class="plan-price">₽15к/мес</div>
<ul class="plan-features">
<li>До 5 пользователей</li>
<li>1 Telegram-чат</li>
<li>Базовый дашборд</li>
<li>Стандартная поддержка</li>
</ul>
<a href="#" class="btn">Начать</a>
</div>
<div class="plan fade-in-up delay-1">
<h3>Про</h3>
<div class="plan-price">₽35к/мес</div>
<ul class="plan-features">
<li>До 20 пользователей</li>
<li>5 Telegram-чатов</li>
<li>Продвинутый дашборд</li>
<li>Приоритетная поддержка</li>
<li>API интеграции</li>
</ul>
<a href="#" class="btn">Выбрать</a>
</div>
<div class="plan fade-in-up delay-2">
<h3>Enterprise</h3>
<div class="plan-price">По запросу</div>
<ul class="plan-features">
<li>Неограниченно пользователей</li>
<li>Неограниченно чатов</li>
<li>Полный функционал</li>
<li>Персональный менеджер</li>
<li>Кастомные интеграции</li>
</ul>
<a href="#" class="btn">Контакты</a>
</div>
</div>
</div>
</section>
<!-- Final CTA -->
<section class="hero section">
<div class="container fade-in-up">
<h1>Харнес превращает решения в исполненные задачи</h1>
<p>Для кооперативов, которые хотят ≥ +1% к AUM в горизонте год+</p>
<a href="#pricing" class="btn">Начать использовать Simbiot</a>
</div>
</section>
<!-- Footer -->
<footer class="footer">
<div class="container">
<div class="footer-content">
<div class="footer-section">
<h4>Кто делает</h4>
<p>Кооператив 42 «Друзья Коллективного Разума»</p>
<p>Разработка Simbiot</p>
</div>
<div class="footer-section">
<h4>Контакты</h4>
<p>Сайт: cil.by</p>
<p>Telegram: t.me/cil_by</p>
<p>Email: info@cil.by</p>
</div>
<div class="footer-section">
<h4>Юр. информация</h4>
<p>УНП: 123456789</p>
<p>Адрес: г. Минск, ул. Примерная, 42</p>
<p>2026-2026 © Все права защищены</p>
</div>
</div>
</div>
</footer>
</body>
</html>

View file

@ -0,0 +1,162 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Simbiot — общий харнес кооператива 42</title>
<meta name="description" content="Simbiot — ритмичный ИИ-харнес кооператива 42: решения со звонков превращаются в исполненные задачи, агенты живут тактами.">
<style>
:root{
--ink:#101418; --paper:#f4f0e6; --tape:#e0d9c8;
--acc:#0e7a5f; --acc2:#c2571b; --line:#101418;
--mono:"JetBrains Mono","SF Mono",ui-monospace,Menlo,Consolas,monospace;
--serif:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;
}
*{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth}
body{background:var(--paper);color:var(--ink);font-family:var(--serif);line-height:1.55}
.mono{font-family:var(--mono)}
a{color:inherit}
/* ---------- шапка-таблица ---------- */
header{border-bottom:2px solid var(--line);padding:18px clamp(16px,4vw,48px);display:flex;justify-content:space-between;align-items:baseline;gap:16px;flex-wrap:wrap}
.brand{font-family:var(--mono);font-weight:700;font-size:18px;letter-spacing:.04em}
.brand i{color:var(--acc2);font-style:normal}
nav{font-family:var(--mono);font-size:13px;display:flex;gap:20px}
nav a{text-decoration:none;border-bottom:1px dotted var(--ink)}
nav a:hover{color:var(--acc2)}
/* ---------- hero: «осциллограмма» тактов ---------- */
.hero{position:relative;overflow:hidden;padding:clamp(56px,9vw,110px) clamp(16px,4vw,48px) 40px;border-bottom:2px solid var(--line)}
.hero h1{font-weight:400;font-size:clamp(38px,6.5vw,84px);line-height:1.04;letter-spacing:-.015em;max-width:14ch}
.hero h1 b{font-weight:600}
.hero p.lede{margin-top:26px;max-width:52ch;font-size:clamp(17px,1.6vw,21px)}
.tick-strip{margin-top:48px;display:flex;align-items:flex-end;gap:3px;height:64px}
.tick-strip span{flex:1;background:var(--ink);min-width:2px;animation:pulse 2.4s ease-in-out infinite}
@keyframes pulse{0%,100%{transform:scaleY(.35);opacity:.35}50%{transform:scaleY(1);opacity:1}}
.tick-strip span:nth-child(odd){background:var(--acc)}
.tick-label{font-family:var(--mono);font-size:12px;margin-top:10px;color:#555}
.cta{margin-top:40px;display:flex;gap:14px;flex-wrap:wrap}
.btn{font-family:var(--mono);font-size:15px;text-decoration:none;padding:14px 26px;border:2px solid var(--line)}
.btn.solid{background:var(--acc);color:#fff;border-color:var(--acc)}
.btn:hover{transform:translate(-2px,-2px);box-shadow:3px 3px 0 var(--line)}
.btn.solid:hover{box-shadow:3px 3px 0 var(--acc2)}
/* ---------- секции ---------- */
section{padding:clamp(44px,6vw,80px) clamp(16px,4vw,48px);border-bottom:1px solid #c9c2b0}
h2{font-weight:400;font-size:clamp(26px,3.4vw,40px);letter-spacing:-.01em;margin-bottom:28px}
h2 em{font-style:italic;color:var(--acc2)}
.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:0;border:2px solid var(--line)}
.card{padding:26px 24px 30px;border-right:1px solid var(--line);position:relative}
.card:last-child{border-right:none}
.card .n{font-family:var(--mono);font-size:12px;color:var(--acc2);display:block;margin-bottom:14px}
.card h3{font-weight:600;font-size:19px;margin-bottom:10px}
.card p{font-size:15.5px;color:#33301f}
/* поток решения */
.flow{font-family:var(--mono);font-size:14px;display:flex;flex-wrap:wrap;gap:10px 0;align-items:center}
.flow .step{border:1.5px solid var(--ink);padding:10px 16px;background:#fff8}
.flow .step b{color:var(--acc)}
.flow .arw{padding:0 12px;color:var(--acc2)}
.numbers{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:32px;font-family:var(--mono)}
.numbers .v{font-size:clamp(30px,4vw,46px);font-weight:700}
.numbers .v.a{color:var(--acc)} .numbers .v.b{color:var(--acc2)}
.numbers .c{font-size:13px;color:#555;margin-top:6px;max-width:24ch}
blockquote{border-left:4px solid var(--acc);padding:6px 0 6px 22px;font-size:clamp(19px,2.2vw,26px);font-style:italic;max-width:46ch}
footer{padding:36px clamp(16px,4vw,48px);font-family:var(--mono);font-size:13px;display:flex;justify-content:space-between;gap:16px;flex-wrap:wrap;background:var(--ink);color:var(--paper)}
footer a{color:#8fd6bd}
@media (prefers-reduced-motion:reduce){.tick-strip span{animation:none;transform:scaleY(.6);opacity:.7}.btn:hover{transform:none;box-shadow:none}}
</style>
</head>
<body>
<header>
<div class="brand">simbiot<i>·42</i></div>
<nav>
<a href="#what">что это</a>
<a href="#how">как работает</a>
<a href="#numbers">цифры</a>
<a href="#join">участие</a>
</nav>
</header>
<section class="hero" style="border:none;padding-bottom:56px">
<h1>Компания, которая <b>думает тактами</b>, а не совещаниями</h1>
<p class="lede">Simbiot — ритмичный ИИ-харнес кооператива 42. Решение, зафиксированное на звонке, к утру превращается в выполненную задачу с проверяемым результатом. Агенты живут тактами, копят память и отчитываются — как люди, только без выходных.</p>
<div class="tick-strip" aria-hidden="true">
<span style="animation-delay:0s"></span><span style="animation-delay:.2s"></span><span style="animation-delay:.4s"></span><span style="animation-delay:.6s"></span><span style="animation-delay:.8s"></span><span style="animation-delay:1s"></span><span style="animation-delay:1.2s"></span><span style="animation-delay:1.4s"></span><span style="animation-delay:1.6s"></span><span style="animation-delay:1.8s"></span><span style="animation-delay:2s"></span><span style="animation-delay:2.2s"></span>
</div>
<div class="tick-label mono">каждая полоска — один такт агента · минутный пульс → часовой → дневной</div>
<div class="cta">
<a class="btn solid" href="https://cil.by">Зайти в портал</a>
<a class="btn" href="#how">Как это работает ↓</a>
</div>
</section>
<section id="what">
<h2>Не чат-бот. <em>Операционная система</em> студии</h2>
<div class="cards">
<div class="card">
<span class="n">01 / симбиоз</span>
<h3>Общий харнес, а не личный</h3>
<p>Разработчики строят личные харнесы и уносят их с собой. Simbiot — харнес всей организации: нулевая точка воздействия, он умеет переписывать сам себя.</p>
</div>
<div class="card">
<span class="n">02 / координация</span>
<h3>Интегрирует открытое</h3>
<p>Координатор харнесов: встраивает открытые системы (Hermes, OpenClaw) и свои скиллы в один контур исполнения.</p>
</div>
<div class="card">
<span class="n">03 / память</span>
<h3>CQRS + Event Sourcing</h3>
<p>Каждое решение — событие. История не теряется, любой такт можно переиграть. База знаний живая: протоколы встреч, договоры, проекты.</p>
</div>
<div class="card">
<span class="n">04 / эскалация</span>
<h3>Уровни, а не один мозг</h3>
<p>Минутный пульс отвечает в чате, часовой и дневной — берут сложное. Что не по зубам уровню, уходит наверх с контекстом.</p>
</div>
</div>
</section>
<section id="how" style="background:#fff6; ">
<h2>От голоса — <em>к результату</em></h2>
<div class="flow">
<span class="step">звонок → <b>протокол решений</b></span><span class="arw">→</span>
<span class="step">задача на доске с <b>критерием готовности</b></span><span class="arw">→</span>
<span class="step">агент исполняет <b>такт за тактом</b></span><span class="arw">→</span>
<span class="step">результат в чат, <b>метрика AUM</b> растёт</span>
</div>
<blockquote style="margin-top:44px">Фильтр любого решения: добавляет ли оно +1% к AUM в горизонте год и больше?</blockquote>
</section>
<section id="numbers">
<h2>Цифры, без глянца</h2>
<div class="numbers">
<div><div class="v a">7</div><div class="c">продуктов в студии, живые деньги — уже у двух</div></div>
<div><div class="v b">42</div><div class="c">кооператив «Друзья Коллективного Разума» — юрлицо живое с 2026</div></div>
<div><div class="v">24/7</div><div class="c">пульс: минутный, часовой и дневной уровни агентов</div></div>
<div><div class="v a">100%</div><div class="c">уходя, участник забирает свой проект — база не разваливается</div></div>
</div>
</section>
<section id="join">
<h2>Встроиться в <em>симбиоз</em></h2>
<p style="max-width:56ch;font-size:18px">Мы берём проекты в программу 42 Labs: юрлицо и платёжный контур, студийная ОС с первого дня, плата из выручки — когда проект заработал. Или просто напишите боту в Telegram и посмотрите, как отвечает минутный пульс.</p>
<div class="cta">
<a class="btn solid" href="https://cil.by">Подать заявку на портале</a>
<a class="btn" href="https://t.me/cil_by">Написать в Telegram</a>
</div>
</section>
<footer>
<div>кооператив 42 · «Друзья Коллективного Разума» · Минск</div>
<div><a href="https://cil.by">cil.by</a> · собрано харнесом Simbiot</div>
</footer>
</body>
</html>

View file

@ -0,0 +1,91 @@
# MiroFish (github 666ghj/MiroFish): разбор и применимость — 2026-10-04
Задача: t-261004062405-fa9926. Метод: клон репо (depth 1), разбор кода backend
(58 py-файлов, ~17.2k строк) + README. Версия зависимостей: camel-oasis==0.2.5,
zep-cloud==3.25.0, flask>=3.0, openai>=1.0.
## Вердикт
**Как движок — НЕ берём. Как источник паттернов — да, 3-5 идей.**
Причины против движка: (1) стоимость — README сам пишет «High consumption,
попробуйте сначала <40 раундов»; (2) жёсткая привязка к Zep Cloud (внешний
SaaS-граф памяти, счёт отдельно от LLM) и OASIS/camel-ai; (3) китайский
дефолт-стек (qwen-plus через dashscope) — у нас 9router; (4) это продукт-платформа
(Flask 5001 + React 3000 + Docker), а не библиотека — интеграция = second brain.
## Архитектура (проверено по коду)
Пайплайн: **Graph Building → Environment Setup → Simulation → Report → Interaction**
1. **Graph Building** (`graph_builder.py` 879 строк, `ontology_generator.py` 732):
seed-материалы (новости/политики/финанс.сигналы, PDF/текст) → извлечение
сущностей и связей → GraphRAG-граф в Zep (онтология генерится LLM).
2. **Environment Setup** (`oasis_profile_generator.py` 1248): из сущностей Zep
LLM-ом генерятся персоны агентов — индивидуальные и групповые
(`generate_profile_from_entity` → `_generate_profile_with_llm`, JSON-схема,
username, bio, persona-текст) → конфиг агентов инжектится в OASIS.
3. **Simulation** (`simulation_runner.py` 2033, `run_parallel_simulation.py` 1699,
twitter/reddit скрипты): движок **OASIS (camel-ai)** — двухплатформенная
параллельная симуляция соцсети (Twitter+Reddit). Раунды = часы×60/минут_на_раунд,
есть max_rounds-обрезка (контроль стоимости). Каждый агент за раунд —
действие через `LLMAction` (≥1 LLM-вызов на активного агента за раунд).
Память: каждое действие → episode-текст → динамическое обновление Zep-графа
(`zep_graph_memory_updater.py` 791: описатели create_post/like/repost/
comment/follow/search).
4. **Report** (`report_agent.py` 2619 — самый большой модуль): ReportAgent с 4
инструментами — `insight_forge`, `panorama_search`, `quick_search`,
`interview_agents` (интервьюирует агентов мира post-factum); протокол
`<tool_call>{...}</tool_call>`, трекер «не все инструменты использованы»
дожимает агент до полного отчёта.
5. **Interaction**: чат с любым агентом мира + ReportAgent (фронт).
Память = **Zep Cloud** (knowledge-graph + temporal): не векторная база, а граф
сущностей с би-темпоралью; free-тариф «на простое использование».
## Оценка стоимости прогона
Формула расхода: ~ `N_агентов × N_раундов` LLM-вызовов (действия) + граф/онтология
+ персоны + отчёт (десятки вызовов) + инференс Zep (граф-обновления, отдельный счёт).
- Заявленный дефолт: qwen-plus. README: «<40 раундов на старте».
- Пример: 100 агентов × 40 раундов ≈ 4–5k вызовов ≈ 15–25M входящих токенов
(контекст платформы в каждом) → **~$10–25 на qwen-plus за прогон**;
на премиум-модели класса GPT-4o — **×10–30 (сотни $)**.
- Осмысленный прогнозный прогон (500+ агентов, 100+ раундов) — **сотни $ за
прогон** даже на дешёвых моделях + Zep Cloud сверх.
- Контекст: наш burn 9router = 2291.81 у.е./7д — один-два «взрослых» прогона
MiroFish съели бы заметный кусок недельного бюджета без прямой выручки.
## Что берём (идеи заимствования)
1. **Персона-из-сущности** (`_generate_profile_with_llm`): LLM-генерация
структурированной персоны агента из записи графа знаний (JSON-схема, инди/групп).
Куда: v2 aum-pilot — вместо placeholder-модели продуктивности, агентные симуляции
решений кооператива (роли: Alex/Vlad/Yura/клиент) с сгенерированными персонами.
2. **ReportAgent-паттерн**: отчётёр = агент с узким набором инструментов
(сводка/поиск/дип-поиск/интервью) + трекер обязательного использования всех
инструментов. Куда: наш «отчёт-адвокат» в aum-pilot и часовые дайджесты —
интервью опрошенных агентов симуляции вместо голой статистики.
3. **Episode→граф памяти**: действие → компактный episode-текст → обновление
графа по ходу симуляции (не в конце). Куда: память ритмичных агентов харнеса
— но на нашем RAG (без Zep: episode → embedding → граф связей) — паттерн
повторяем, сервис не покупаем.
4. **Предпродажная симуляция спроса**: OASIS даёт готовую соц-динамику
(лайк/репост/коммент/фоллов). Идея под AUM: перед запуском лендинга/прайса —
дешёвый прогон (20–40 раундов, flash-модель, 50–100 агентов-«клиентов» из
персон сегмента) → прогноз реакции и возражений. Это прямой кандидат в
наши симуляции поведения пользователей.
5. **Автогенерация конфига из NL-запроса** (`simulation_config_generator.py`):
«что хочешь узнать» естественным языком → параметры прогона + max_rounds
(жёсткий потолок стоимости). Куда: постановка задачи → конфиг прогона
aum-pilot; потолок раундов — в наши длинные симуляции.
6. **IPC/прогресс долгих прогонов** (`simulation_ipc.py` + progress %):
отдельный процесс, структурированный прогресс, чистая обрезка. У нас уже
есть _llm_worker — позаимствовать схему прогресса/тримминга.
## Чего не берём
- Zep Cloud (внешний SaaS, счёт отдельно, данные наружу) — свой RAG/graph.
- OASIS целиком (тяжёлый camel-ai стек) — только если сделаем п.4; тогда
изолированный Docker-стенд, flash-модель через 9router, budget-cap на прогон.
- Их фронт/платформу — у нас свои каналы (портал, MCP, TG).

View file

@ -0,0 +1,176 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Прокат авто × Simbiot — партнёрское соглашение v1</title>
<style>
:root{
--paper:#faf8f3; --ink:#15150f; --signal:#FF4F00;
--dim:#6b675e; --line:#d8d3c8; --card:#f3f0e8; --ok:#1d7a3e; --warn:#a3690f;
}
*{margin:0;padding:0;box-sizing:border-box}
html{scroll-behavior:smooth}
body{background:var(--paper);color:var(--ink);font:16px/1.62 -apple-system,"Segoe UI",Roboto,Arial,sans-serif;-webkit-font-smoothing:antialiased}
.mono,code{font-family:ui-monospace,"JetBrains Mono","SF Mono",Menlo,Consolas,monospace}
.wrap{max-width:980px;margin:0 auto;padding:56px 24px 100px}
header{border-bottom:3px solid var(--ink);padding-bottom:26px;margin-bottom:36px}
.kicker{font-family:ui-monospace,Menlo,monospace;font-size:12px;letter-spacing:.22em;color:var(--signal);font-weight:700;text-transform:uppercase}
h1{font-size:clamp(32px,5.5vw,52px);line-height:1.05;font-weight:800;letter-spacing:-.02em;margin:10px 0 14px;text-transform:uppercase}
.sub{font-size:17px;color:var(--dim);max-width:720px}
.badges{display:flex;flex-wrap:wrap;gap:8px;margin-top:18px}
.badge{border:1px solid var(--ink);padding:5px 12px;font-family:ui-monospace,Menlo,monospace;font-size:12px;letter-spacing:.08em;text-transform:uppercase;background:var(--paper)}
.badge.s{background:var(--ink);color:var(--paper)}
section{margin:52px 0 0}
.snum{font-family:ui-monospace,Menlo,monospace;font-size:12px;color:var(--signal);letter-spacing:.2em;font-weight:700}
h2{font-size:26px;font-weight:800;letter-spacing:-.01em;margin:4px 0 6px;text-transform:uppercase}
.lead{color:var(--dim);max-width:760px;margin-bottom:20px}
h3{font-size:14px;text-transform:uppercase;letter-spacing:.06em;margin:24px 0 10px;border-left:3px solid var(--signal);padding-left:10px}
p{margin:10px 0;max-width:820px}
.clause{border:1px solid var(--line);background:#fff;padding:14px 18px;margin:12px 0;max-width:860px}
.clause .cn{font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--signal);font-weight:700;letter-spacing:.15em}
.clause h4{font-size:15px;margin:4px 0 6px;font-weight:800}
.clause p{font-size:14px;color:var(--ink);margin:4px 0}
.note{border:1px solid var(--ink);border-left:6px solid var(--signal);background:#fff;padding:14px 18px;margin:20px 0;max-width:860px}
.note b{font-family:ui-monospace,Menlo,monospace;font-size:12px;letter-spacing:.12em;text-transform:uppercase;color:var(--signal)}
.note.ok{border-left-color:var(--ok)} .note.ok b{color:var(--ok)}
table{width:100%;border-collapse:collapse;margin:18px 0;max-width:900px;background:#fff;border:1px solid var(--ink)}
th{font-family:ui-monospace,Menlo,monospace;font-size:11px;letter-spacing:.12em;text-transform:uppercase;text-align:left;padding:10px 12px;background:var(--ink);color:var(--paper)}
td{padding:10px 12px;border-top:1px solid var(--line);font-size:14px;vertical-align:top}
td .mono{font-size:12.5px;color:var(--signal);font-weight:700}
ul{margin:8px 0 8px 18px;font-size:14.5px}
li{margin:6px 0}
li::marker{color:var(--signal)}
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin:18px 0}
@media(max-width:760px){.grid2{grid-template-columns:1fr}}
.sign{border:3px double var(--ink);background:#fff;padding:22px;margin:26px 0;display:grid;grid-template-columns:1fr 1fr;gap:20px}
@media(max-width:760px){.sign{grid-template-columns:1fr}}
.sig .org{font-family:ui-monospace,Menlo,monospace;font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--dim);margin-bottom:14px}
.sig .line{border-bottom:1px solid var(--ink);height:38px;margin-bottom:8px}
.sig .field{font-size:12.5px;color:var(--dim)}
footer{margin-top:60px;border-top:1px solid var(--line);padding-top:18px;color:var(--dim);font-size:12.5px}
</style>
</head>
<body>
<div class="wrap">
<header>
<div class="kicker">Потребительский кооператив «Друзья Коллективного Разума» · Минск</div>
<h1>Соглашение о партнёрском вознаграждении № 1</h1>
<div class="sub">Прокат авто (Минск) × Simbiot. Договор-черновик на согласование: платформа как операционный и маркетинговый слой партнёра, вознаграждение — 10% от заработка на атрибутированных клиентах.</div>
<div class="badges">
<span class="badge s">Черновик v1 · 04.10.2026</span>
<span class="badge">10% от нетто-выручки</span>
<span class="badge">Ставка фиксирована 12 мес</span>
<span class="badge">Атрибуция — 24 мес</span>
</div>
</header>
<section>
<div class="snum">00 / СТОРОНЫ</div>
<h2>Стороны соглашения</h2>
<div class="grid2">
<div class="clause"><div class="cn">ОПЕРАТОР ПЛАТФОРМЫ</div><h4>Кооператив</h4><p>[полное наименование кооператива 42], г. Минск — оператор платформы Simbiot (агентские, маркетинговые и операционные AI-сервисы).</p></div>
<div class="clause"><div class="cn">ПАРТНЁР</div><h4>Прокатная компания</h4><p>[наименование юрлица/ИП], г. Минск — владелец автопарка, передаёт Platform привлечение клиентов и операционное управление частью бизнеса.</p></div>
</div>
<div class="note ok"><b>Разделение документов ·</b> Этот документ — только про партнёрское вознаграждение и рост выручки. Исторический долг Партнёра (~15 000 USD) вынесен в отдельный Side Letter № 1 — подписывается параллельно, на условия этого соглашения не влияет.</div>
</section>
<section>
<div class="snum">01 / ПРЕДМЕТ</div>
<h2>Предмет соглашения</h2>
<div class="clause"><div class="cn">1.1</div><p>Оператор предоставляет Партнёру доступ к Платформе Simbiot (агентские, маркетинговые и операционные AI-сервисы) для привлечения клиентов и управления автопарком Партнёра.</p></div>
<div class="clause"><div class="cn">1.2</div><p>За это Партнёр уплачивает Оператору <b>партнёрское вознаграждение в размере 10%</b> от заработка по каждому <b>Атрибутированному клиенту</b> (раздел 02).</p></div>
</section>
<section>
<div class="snum">02 / АТРИБУЦИЯ</div>
<h2>Кто считается атрибутированным клиентом</h2>
<p class="lead">Три категории. Фиксация — записью в Реестре атрибуций (Приложение № 1): ведёт Оператор, Партнёру — доступ на чтение.</p>
<table>
<tr><th style="width:80px">Категория</th><th>Определение</th></tr>
<tr><td><span class="mono">(а)</span></td><td><b>Привлечённая машина</b> — собственник, передавший автомобиль в управление/арендную схему через Платформу.</td></tr>
<tr><td><span class="mono">(б)</span></td><td><b>Клиент на аренду</b> — арендатор, пришедший по лиду/воронке/агентству Платформы (метка в CRM/UTM/реестр).</td></tr>
<tr><td><span class="mono">(в)</span></td><td><b>Агентство</b> — клиент кооператива, обслуженный через Платформу (корпоративный, трансферный, подписочный и т.п.).</td></tr>
</table>
<div class="clause"><div class="cn">2.1</div><p>Спорная атрибуция: приоритет у более ранней фиксации лида; неразрешённые споры — на ежемесячной сверке.</p></div>
<div class="clause"><div class="cn">2.2</div><p>Клиент считается атрибутированным на <b>24 месяца</b> с первой фиксации (включая повторные аренды и продления).</p></div>
</section>
<section>
<div class="snum">03 / ДЕНЬГИ</div>
<h2>Вознаграждение и порядок расчётов</h2>
<div class="clause"><div class="cn">3.1</div><p>База расчёта: <b>нетто-выручка</b> по атрибутированному клиенту (полученные платежи за вычетом возвратов, депозитов, комиссий платёжных систем и налогов).</p></div>
<div class="clause"><div class="cn">3.2</div><p>Вознаграждение: <b>10% от базы</b>. Ставка фиксируется на 12 месяцев.</p></div>
<div class="clause"><div class="cn">3.3</div><p>Расчётный период — календарный месяц. Партнёр до 10-го числа следующего месяца направляет подписанный акт + выгрузку; оплата в течение 5 банковских дней, BYN по курсу НБ РБ на дату оплаты (либо USDT — по выбору сторон).</p></div>
<div class="clause"><div class="cn">3.4</div><p>Просрочка &gt;15 дней: пеня 0,1%/день; &gt;45 дней — право Оператора приостановить доступ к Платформе.</p></div>
</section>
<section>
<div class="snum">04 / РОЛЬ ПЛАТФОРМЫ</div>
<h2>Что делает Simbiot</h2>
<p class="lead">Ровно тот функционал, на котором строится «заработок на атрибутированном клиенте»:</p>
<ul>
<li><b>Воронки и лид-скоринг</b> — обработка входящих заявок на аренду, приоритизация лидов;</li>
<li><b>Агент-диспетчер 24/7</b> — аренда, продления, подписка без участия человека в ночи и выходные;</li>
<li><b>Скоринг таксистов</b> — аренда с выкупом: отсев рискованных драйверов;</li>
<li><b>Онбординг владельцев машин</b> — приём машин в паркоменеджмент (категория «а»);</li>
<li><b>Контент-PR</b> — первое в РБ полностью AI-агентское продвижение проката: почти бесплатный охват + входящие заявки на франшизу/white label.</li>
</ul>
</section>
<section>
<div class="snum">05 / РОСТ ВЫРУЧКИ</div>
<h2>Рычаги роста парка</h2>
<p class="lead">Ориентир: полное развёртывание рычагов 1–4 даёт расчётно <b>+60–100% выручки за 6–12 мес.</b>, из них ~+20% — в первые 8 недель (монетизация простоя). CAPEX — только ребаланс парка. Оценки сверить с фактическими данными парка (машин, ставки, vacancy) — выгрузка от Партнёра до запуска.</p>
<table>
<tr><th>#</th><th>Рычаг</th><th>Эффект</th></tr>
<tr><td><span class="mono">1</span></td><td><b>Подписка на простой</b> — простаивающие машины сдаются по подписке (такси, доставки, каршеринг-операторы)</td><td>быстрые деньги: монетизация vacancy, ~+20% за 8 недель</td></tr>
<tr><td><span class="mono">2</span></td><td><b>Аренда с выкупом</b> — пилот 2–3 эконом-машины, скоринг водителей</td><td>длинные деньги: выкупные платежи + продажа машины в конце</td></tr>
<tr><td><span class="mono">3</span></td><td><b>Привлечённые машины</b> — приём авто собственников в управление</td><td>рост парка без CAPEX</td></tr>
<tr><td><span class="mono">4</span></td><td><b>Ребаланс парка</b> — перераспределение под спрос (класс, сезон)</td><td>повышение ставки/дня и снижение vacancy</td></tr>
<tr><td><span class="mono">5</span></td><td><b>Воронка и агент-диспетчер</b></td><td>конверсия лидов, снижение стоимости обслуживания</td></tr>
<tr><td><span class="mono">6</span></td><td><b>PR «полностью AI-based агентство»</b></td><td>отложенный: лиды + заявки на франшизу/white label</td></tr>
</table>
<h3>Последовательность внедрения</h3>
<ul>
<li><b>Недели 1–2:</b> подписка на простой (1) + запуск PR-волны (6) — быстрые деньги и охват;</li>
<li><b>Месяц 1–2:</b> пилот аренды с выкупом на 2–3 эконом-машинах (2) + приём первых привлечённых машин (3);</li>
<li><b>Месяц 2–3:</b> ребаланс парка (4), масштабирование подписки и привлечённого парка.</li>
</ul>
</section>
<section>
<div class="snum">06 / ПОРЯДОК ПОДПИСАНИЯ</div>
<h2>Как подписываем</h2>
<div class="clause"><div class="cn">6.1</div><p>Стороны согласовывают реквизиты и Приложение № 1 (Реестр атрибуций, стартовый состав).</p></div>
<div class="clause"><div class="cn">6.2</div><p>Подписывается настоящее Соглашение и параллельно — <b>Side Letter № 1</b> (урегулирование исторической задолженности, отдельный документ).</p></div>
<div class="clause"><div class="cn">6.3</div><p>Юридическая проверка обеих документов до подписания обязательна.</p></div>
</section>
<section>
<div class="snum">07 / РЕКВИЗИТЫ</div>
<h2>Подписи</h2>
<div class="sign">
<div class="sig">
<div class="org">Оператор Платформы · Кооператив</div>
<div class="line"></div>
<div class="field">Председатель правления · подпись</div>
<div class="field">Alex Shkor · «___» _______ 2026 г.</div>
</div>
<div class="sig">
<div class="org">Партнёр · [наименование прокатной компании]</div>
<div class="line"></div>
<div class="field">[должность, ФИО] · подпись</div>
<div class="field">М.П. · «___» _______ 2026 г.</div>
</div>
</div>
</section>
<footer>
Черновик для обсуждения, не оферта. Юридическая проверка перед подписанием обязательна. Основа: встреча 04.10.2026 (meeting-6feceb4c). Исторический долг (~15 000 USD) — отдельный Side Letter № 1.
</footer>
</div>
</body>
</html>

View file

@ -0,0 +1,79 @@
# Прокат авто (Минск) × Simbiot — договор-черновик и план роста выручки
**Дата:** 04.10.2026 · **Основа:** встреча meeting-6feceb4c (04.10.2026), фиксация договорённостей
**Owner:** Alex · **Статус:** черновик на согласование (юридическая проверка перед подписанием обязательна)
---
## Часть I. Договор-черновик
> СОГЛАШЕНИЕ О ПАРТНЁРСКОМ ВОЗНАГРАЖДЕНИИ № 1
> г. Минск, «04» октября 2026 г.
**Стороны:**
- **Кооператив** — [полное наименование кооператива 42], далее «Оператор Платформы» (Simbiot);
- **Партнёр** — [наименование юрлица/ИП прокатной компании], г. Минск, далее «Партнёр».
### 1. Предмет соглашения
1.1. Оператор предоставляет Партнёру доступ к Платформе Simbiot (агентские, маркетинговые и операционные AI-сервисы) для привлечения клиентов и управления автопарком Партнёра.
1.2. За это Партнёр уплачивает Оператору **партнёрское вознаграждение в размере 10%** от заработка по каждому **Атрибутированному клиенту** (п. 2).
### 2. Атрибуция. Атрибутированным считается клиент:
- **(а) Привлечённая машина** — собственник, передавший автомобиль в управление/арендную схему через Платформу;
- **(б) Клиент на аренду** — арендатор, пришедший по лиду/воронке/агентству Платформы (метка в CRM/UTM/реестр);
- **(в) Агентство** — клиент кооператива, обслуженный через Платформу (корпоративный, трансферный, подписочный и т.п.).
2.1. Факт атрибуции фиксируется записью в **Реестре атрибуций** (Приложение №1, ведёт Оператор, доступ Партнёру — чтение). Спорная атрибуция: приоритет у более ранней фиксации лида; неразрешённые споры — на ежемесячной сверке.
2.2. Клиент считается атрибутированным на **24 месяца** с первой фиксации (включая повторные аренды и продления).
### 3. Вознаграждение и порядок расчётов
3.1. База расчёта: **нетто-выручка** по атрибутированному клиенту (полученные платежи за вычетом возвратов, депозитов, комиссий платёжных систем и налогов).
3.2. Вознаграждение: **10% от базы**. Ставка фиксируется на 12 месяцев.
3.3. Расчётный период — календарный месяц. Партнёр до 10-го числа следующего месяца направляет подписанный акт + выгрузку; оплата в течение 5 банковских дней, BYN по курсу НБ РБ на дату оплаты (либо USDT — по выбору сторон).
3.4. Просрочка >15 дней: пеня 0,1%/день; >45 дней — право Оператора приостановить доступ к Платформе.
### 4. Конвертация долга в инвестицию
4.1. Стороны признают задолженность Партнёра перед Оператором за работы за период ~3 месяца, с инфляционной индексацией, **эквивалент ~15 000 USD, выраженный в BTC по курсу на дату образования долга** (курс-привязка: сумма фиксируется в BTC по курсу BTC/USD на [дату образования долга], рублёвая/долларовая переоценка — по курсу на дату расчётов).
4.2. По подписании настоящего Соглашения долг **полностью конвертируется в инвестиционный вклад Партнёра в Проект «Интеграция Simbiot в прокатный бизнес»** (юнит: развитие Партнёра как витринного кейса платформы) на условиях Приложения №2 (Инвестиционное соглашение: доля/profit-share, reporting, выход).
4.3. С момента подписания Приложения №2 требование по долгу считается урегулированным (новация).
### 5. ИП и конфиденциальность
5.1. Все права на Платформу Simbiot, модели, данные обучения и CRM принадлежат Оператору. Данные о клиентах Партнёра — совместная коммерческая тайна; Оператор вправе использовать **обезличенную** статистику в маркетинге.
5.2. Кейс Партнёра («AI-агентство в прокате») Оператор вправе публиковать по согласованию формулировок.
### 6. Срок, расторжение
6.1. Срок: 12 месяцев с даты подписания, автопролонгация на 12 мес., если ни одна сторона не уведомила за 30 дней.
6.2. Расторжение не отменяет вознаграждение за атрибутированных клиентов в текущем периоде и 24-месячный хвост п. 2.2.
### 7. Прочее
Форс-мажор, применимое право РБ, арбитраж по месту Оператора, обмен сканами = оригинал, ЭЦП/eDocument допустимы. Реквизиты — Приложение №3.
**Открытые вопросы к Alex (до подписания):**
1. База 10% — валовая или нетто-выручка (п. 3.1)?
2. Хвост атрибуции 24 мес. — устраивает?
3. Параметры Приложения №2: доля Партнёра в проекте / profit-share % и срок.
4. Точная дата образования долга для фиксации BTC-курса.
---
## Часть II. Анализ рычагов роста выручки (приоритизировано)
**Исходная проблема парка:** перекос в премиум (CLA45, X7, Audi, BMW2) при нишевом спросе → премиум-активы простаивают, простой = 0 выручки при ненулевых издержках (страхование, стоянка, амортизация).
| # | Рычаг | Усилие | Эффект (оценка) | Почему сработает |
|---|-------|--------|------------------|------------------|
| **1** | **Подписка на простой, ~2990/мес** | S | **+15–25% к выручке парка за 1–2 мес.**, почти без CAPEX | «Любая несданная тачка окупается за 3 дня подписки»: 2990/30 ≈ 100/день. Каждые 3 простых дня сегодня = потерянный месячный платёж. Простой превращается из 0 в выручку. ЦА: экспаты/долгосрочники/малый бизнес |
| **2** | **Аренда с выкупом под таксистов (~30%/год)** | M | **×1.6 выручки с машины за 2 года**, vacancy → ~0 | Эконом-авто ~25k: клиент-таксист платит ~30% годовых сверх стоимости 24 мес. → возврат ~40k с одной машины, предсказуемый денежный поток, износ компенсирован ставкой |
| **3** | **Привлечённые машины (кейс Vestgroup: 0 своих)** | M | **рост парка ×2–3 без CAPEX**, комиссия ~25–35% с каждой | Asset-light: владельцы отдают машины в управление, платформа монетизирует их же воронкой. Снимает потолок роста, ограниченный собственным капиталом |
| **4** | **Ребаланс парка: прем → эконом** | M (капитал) | **+10–15% выручки + высвобождение замороженного капитала** | Непроседающий премиум продаётся (вернуть капитал), вместо него 2–3 эконом — стабильный спрос, ниже износ и риск |
| **5** | **Дешёвый трафик / новые сегменты** | S–M | **+20–30% лидов при том же бюджете** | Такопарки и драйверы (связка с рычагом 2), резиденты на выходные (не только туристы), корпоративные; AI-боты Simbiot = обработка лида ~0$ 24/7 → CAC падает |
| **6** | **PR «полностью AI-based агентство»** | S | отложенный: лиды + входящие заявки на франшизу/белые лейблы | Медиа-крючок первого в РБ AI-агентства в прокате; контент генерирует сам Simbiot — почти бесплатный охват; усиливает рычаги 1–3 |
### Последовательность внедрения
1. **Недели 1–2:** подписка на простой (рычаг 1) + запуск PR-волны (6) — быстрые деньги и охват.
2. **Месяц 1–2:** пилот аренды с выкупом на 2–3 эконом-машинах (2) + приём первых привлечённых машин (3).
3. **Месяц 2–3:** ребаланс парка (4), масштабирование подписки и привлечённого парка.
### Сводная оценка
Полное развёртывание рычагов 1–4 даёт расчётно **+60–100% выручки парка за 6–12 мес.** при умеренном CAPEX (только ребаланс), из них ~+20% — в первые 8 недель за счёт монетизации простоя. Оценки требуют сверки с фактическими данными парка (кол-во машин, ставки, vacancy) — запросить у Партнёра выгрузку до запуска.
**Роль Simbiot:** воронки и лид-скоринг (5), агент-диспетчер 24/7 по аренде/подписке (1), скоринг таксистов для аренды-с-выкупом (2), онбординг владельцев машин (3), контент-PR (6) — всё это и есть база «заработка на атрибутированном клиенте» из Части I.

View file

@ -0,0 +1,115 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Side Letter № 1 — урегулирование задолженности (~15 000 USD)</title>
<style>
:root{
--paper:#faf8f3; --ink:#15150f; --signal:#FF4F00;
--dim:#6b675e; --line:#d8d3c8; --card:#f3f0e8; --ok:#1d7a3e; --warn:#a3690f;
}
*{margin:0;padding:0;box-sizing:border-box}
body{background:var(--paper);color:var(--ink);font:16px/1.62 -apple-system,"Segoe UI",Roboto,Arial,sans-serif;-webkit-font-smoothing:antialiased}
.mono,code{font-family:ui-monospace,"JetBrains Mono","SF Mono",Menlo,Consolas,monospace}
.wrap{max-width:900px;margin:0 auto;padding:56px 24px 100px}
header{border-bottom:3px solid var(--ink);padding-bottom:26px;margin-bottom:36px}
.kicker{font-family:ui-monospace,Menlo,monospace;font-size:12px;letter-spacing:.22em;color:var(--signal);font-weight:700;text-transform:uppercase}
h1{font-size:clamp(30px,5vw,46px);line-height:1.05;font-weight:800;letter-spacing:-.02em;margin:10px 0 14px;text-transform:uppercase}
.sub{font-size:17px;color:var(--dim);max-width:720px}
section{margin:46px 0 0}
.snum{font-family:ui-monospace,Menlo,monospace;font-size:12px;color:var(--signal);letter-spacing:.2em;font-weight:700}
h2{font-size:24px;font-weight:800;margin:4px 0 6px;text-transform:uppercase}
.clause{border:1px solid var(--line);background:#fff;padding:14px 18px;margin:12px 0;max-width:860px}
.clause .cn{font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--signal);font-weight:700;letter-spacing:.15em}
.clause p{font-size:14.5px;margin:4px 0}
.note{border:1px solid var(--ink);border-left:6px solid var(--warn);background:#fff;padding:14px 18px;margin:20px 0;max-width:860px}
.note b{font-family:ui-monospace,Menlo,monospace;font-size:12px;letter-spacing:.12em;text-transform:uppercase;color:var(--warn)}
table{width:100%;border-collapse:collapse;margin:18px 0;max-width:860px;background:#fff;border:1px solid var(--ink)}
th{font-family:ui-monospace,Menlo,monospace;font-size:11px;letter-spacing:.12em;text-transform:uppercase;text-align:left;padding:10px 12px;background:var(--ink);color:var(--paper)}
td{padding:10px 12px;border-top:1px solid var(--line);font-size:14px;vertical-align:top}
.sign{border:3px double var(--ink);background:#fff;padding:22px;margin:26px 0;display:grid;grid-template-columns:1fr 1fr;gap:20px}
@media(max-width:760px){.sign{grid-template-columns:1fr}}
.sig .org{font-family:ui-monospace,Menlo,monospace;font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--dim);margin-bottom:14px}
.sig .line{border-bottom:1px solid var(--ink);height:38px;margin-bottom:8px}
.sig .field{font-size:12.5px;color:var(--dim)}
footer{margin-top:60px;border-top:1px solid var(--line);padding-top:18px;color:var(--dim);font-size:12.5px}
</style>
</head>
<body>
<div class="wrap">
<header>
<div class="kicker">Потребительский кооператив «Друзья Коллективного Разума» · Минск</div>
<h1>Side Letter № 1<br>к Соглашению о партнёрском вознаграждении</h1>
<div class="sub">Урегулирование исторической задолженности Партнёра перед Оператором: признание, фиксация, конвертация в инвестиционный вклад. Подписывается параллельно с Соглашением № 1, отдельно — чтобы не смешивать долги с партнёрскими расчётами.</div>
</header>
<section>
<div class="snum">СТОРОНЫ И ОСНОВАНИЕ</div>
<h2>Преамбула</h2>
<div class="clause"><p>Стороны — те же, что в Соглашении о партнёрском вознаграждении № 1 от «___» _______ 2026 г. («Соглашение»): Кооператив ПК «Друзья Коллективного Разума» («Оператор») и [наименование прокатной компании] («Партнёр»).</p></div>
<div class="clause"><p>Side Letter — неотъемлемая часть Соглашения, регулирует только исторический долг и не меняет условия о партнёрском вознаграждении (10%, атрибуция, порядок расчётов).</p></div>
</section>
<section>
<div class="snum">01 / ПРИЗНАНИЕ ДОЛГА</div>
<h2>Признание задолженности</h2>
<div class="clause"><div class="cn">1.1</div><p>Стороны признают задолженность Партнёра перед Оператором за выполненные работы за период ~3 месяца (по актам/переписке, перечислены в Приложении к настоящему Side Letter).</p></div>
<div class="clause"><div class="cn">1.2</div><p>Стороны не имеют взаимных претензий по задолженности за указанный период, кроме урегулированной настоящим Side Letter.</p></div>
</section>
<section>
<div class="snum">02 / ФИКСАЦИЯ СУММЫ</div>
<h2>Сумма и валютная привязка</h2>
<table>
<tr><th style="width:240px">Параметр</th><th>Значение</th></tr>
<tr><td>Эквивалент</td><td><b>~15 000 USD</b></td></tr>
<tr><td>Валютная привязка</td><td>выражен в <b>BTC</b> по курсу BTC/USD на дату образования долга; рублёвая/долларовая переоценка — по курсу на дату расчётов</td></tr>
<tr><td>Индексация</td><td>инфляционная индексация за период образования долга (если применимо)</td></tr>
<tr><td>Дата образования</td><td>[заполнить: последний акт за период долга]</td></tr>
</table>
<div class="clause"><div class="cn">2.1</div><p>Итоговая сумма фиксируется в Приложении к Side Letter в BTC и в USD-эквиваленте; стороны подтверждают её подписями.</p></div>
</section>
<section>
<div class="snum">03 / КОНВЕРТАЦИЯ</div>
<h2>Конвертация долга в инвестицию</h2>
<div class="clause"><div class="cn">3.1</div><p>С момента подписания Side Letter долг <b>полностью конвертируется в инвестиционный вклад Партнёра в Проект «Интеграция Simbiot в прокатный бизнес»</b> (юнит: развитие Партнёра как витринного кейса платформы) на условиях инвестиционного соглашения (Приложение № 2 к Соглашению: доля / profit-share, [заполнить]).</p></div>
<div class="clause"><div class="cn">3.2</div><p>С момента конвертации Партнёр не имеет требований к Оператору по возврату суммы долга как денежного обязательства; права Партнёра реализуются исключительно через условия инвестиционного соглашения (доля/profit-share, порядок распределения, выход).</p></div>
<div class="clause"><div class="cn">3.3</div><p>Инвестиционный вклад не гарантирует доходность и не является займом; риск убытка несёт Партнёр как инвестор.</p></div>
</section>
<section>
<div class="snum">04 / ПОРЯДОК</div>
<h2>Подписание и юридическая проверка</h2>
<div class="clause"><div class="cn">4.1</div><p>Side Letter подписывается одновременно с Соглашением № 1. Юридическая проверка обоих документов до подписания обязательна.</p></div>
<div class="clause"><div class="cn">4.2</div><p>До подписания стороны заполняют: дату образования долга, состав работ (Приложение), точную сумму в BTC + USD-эквивалент, условия инвестиционного соглашения (доля/profit-share).</p></div>
</section>
<section>
<div class="snum">05 / ПОДПИСИ</div>
<h2>Реквизиты</h2>
<div class="sign">
<div class="sig">
<div class="org">Оператор · Кооператив</div>
<div class="line"></div>
<div class="field">Председатель правления · подпись</div>
<div class="field">Alex Shkor · «___» _______ 2026 г.</div>
</div>
<div class="sig">
<div class="org">Партнёр · [наименование прокатной компании]</div>
<div class="line"></div>
<div class="field">[должность, ФИО] · подпись</div>
<div class="field">М.П. · «___» _______ 2026 г.</div>
</div>
</div>
</section>
<footer>
Черновик для обсуждения, не оферта. Юридическая проверка перед подписанием обязательна. Основа: встреча 04.10.2026 + решение Alex от 04.10.2026. Парный документ: rental-x-simbiot-agreement.html
</footer>
</div>
</body>
</html>

View file

@ -0,0 +1,35 @@
# Журнал ночной самодоработки харнеса
Директива Alex (голосовое m-516173718, эскалация e-261005074512): ночами
создавать себе задачи, ставить эксперименты над собой, смотреть что работает
лучше/хуже и продолжать дорабатываться.
Цикл: крон в 00:00 UTC (03:00 Минск) подаёт задачу «Ночная самодоработка» [M];
ближайший hour-тик выполняет; каждая итерация дописывает строку ниже.
Формат строки (одна на итерацию):
`#N | дата | гипотеза | метрика до → после | вывод (работает/нет/нужно больше данных)`
Правила:
- один эксперимент за ночь, мелкий (лёгкий — провести сразу, тяжёлый — задачей на день);
- фильтр AUM: эксперимент должен служить «≥ +1% к AUM в горизонте год+»;
- ночь — тишина в чате (кроме критичного); результат поднимает утренний дайджест;
- не выдумывать метрики: только измеримое из стора (`cli.py events/status/aum`),
логов тиков и этого журнала.
## Итерации
#0 | 2026-10-05 | Калибровка цикла (не эксперимент) | механизм: крон-податчик + журнал заведены, первая задача в очереди | ожидаем первую ночную итерацию
#1 | 2026-10-05 | Дедуп «ВЫЖИМОК НИЖНИХ УРОВНЕЙ» в промпте тика: тихий мин-уровень кладёт 6 идентичных [gate:quiet]-строк в каждый шаг промпта hour-тика; гипотеза — схлопывание повторов с счётчиком ×N срежет burn без потери информации | секция ВЫЖИМОК в hour-промпте: 6 строк/522 байт → 1 строка/95 байт (−82% секции, −427 байт/шаг; при 991 шаге/сутки ≈ −0.4М байт ≈ ~100k промпт-токенов/сутки при 57.8k avg/шаг — оценка) | работает: фикс в scheduler.py build_context (дедуп по (level, text)), верифицирован на живой модели (×6 подряд в контексте), test_gate + test_task_batch ALL PASS; контроль — avg prompt/step в BudgetSpent hour за следующие сутки (базлайн 57,781)
## Ретро суток 2026-10-04T13:00 → 2026-10-05T13:00 (UTC)
- Задачи: закрыто 27 (в т.ч. 8 L), подано 29, эскалаций открыто 0 (3 raised / 3 acked).
- Падения: 1 CycleError hour (14:48, таймаут воркера 2880с — тик потерян целиком; max инвокация 2872с из 990 завершённых).
- Гейт работает: 1381 GateSkipped min-тиков = LLM не запускался в тишине (экономия).
- Burn: hour 57.3M промпт-токенов (991 шаг, avg 57.8k/шаг, completion всего 1.1%); min 5.5M (520 шагов, avg 10.6k). Доминирующая статья расхода — раздувание промпта hour-шага, а не число вызовов (инвокаций всего 8+56).
- AUM 26.7 у.е.; burn 9router 7д 1417.88 — burn остаётся прямым минусом к AUM, главный рычаг — тоньше контекст шага.
- Вывод: следующие эксперименты — что именно раздувает avg 57.8k/шаг (история сообщений? RAG? батч?), паттерн «дедуп + кап» применим к секциям эскалаций/инбокса.

View file

@ -0,0 +1,27 @@
# Самоанализ харнеса — 2026-10-04 (t-261004152714)
## Цифры
- Задачи: 147 всего — 142 done (S 51 / M 61 / L 30), 2 blocked, 3 running. Провалов мало, поток стабилен.
- Эскалации: 79 (warn 56 / info 19 / critical 4) — все acked, открытых 0.
- Инбокс TG: 100 сообщений (text 81, voice 15, video 1); транскрипция 16/16 непустая после фикса.
- Ошибки уровней: min 38, hour 6, day 1. Главный паттерн — таймаут LLM-воркера (480с/2880с).
## Что работает
- Лестница sec→min→hour→day с батчем 3 задач/тик: пропускная способность есть, анти-старение очереди соблюдается.
- ES-стор + ed25519-подписи: verify проходит, тампер-цепочка цела.
- Идемпотентная доставка в TG (tg-notify/tg-deliver-file, SKIP на повторе) — дублей нет (t-260930083937).
- Голосовые: двухъярусный ASR, короткие фразы → large-v3-turbo, кэш по file_unique_id.
- Эскалации доходят и закрываются (e-261004144805 — фикс «смерть тика не воскрешает закрытые задачи» уже в git).
## Что болит (→ задачи саморазвития)
1. **Звук из видео не извлекался** — kind=video не входил в TRANSCRIBABLE_KINDS и поллер не звал транскрипцию. ИСПРАВЛЕНО в этом тике: ffmpeg-извлечение дорожки (16к моно opus) + turbo-роутинг для ≤60с; e2e на m-516173698 подтверждён. Осталось: тесты и наблюдение за памятью.
2. **Память инбокс-поллера 3.7G peak** (systemd Consumption) — транскрипция в процессе поллера; нужен MemoryMax и/или вынос ASR-вызова из поллера.
3. **Кириллица в setMyCommands даёт HTTP 400** (костыль-транслит latin) — бот теряет русские подписи команд.
4. **Таймауты LLM-воркеров** (min×38, hour×6) — причина №1 потерь тиков; воркер убивается до записи BudgetSpent.
5. **Клоны задач из служебных 'list'** — ловушка чинилась дважды; нужен регресс-тест на генерацию задач из inbox-текста.
## Задачи саморазвития (созданы, проверяемый результат)
- t-261004160531-ed25ed [M]: тесты voice-пайплайна (extract_audio_track + роутинг) — pytest зелёный, кейс видео↔текст.
- t-261004160531-780666 [L]: память поллера — MemoryMax + вынос тяжёлой транскрипции из поллера; peak < 1G за сутки.
- t-261004160532-690fcb [M]: TG-команды кириллицей — setMyCommands без 400 и без транслита.
- t-261004160532-0fb500 [M]: регресс-тест «служебный текст inbox не порождает задачу-клон».

View file

@ -0,0 +1,355 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Shkor beauty — корейская косметика в Минске (прототип витрины)</title>
<meta name="description" content="Shkor beauty — витрина магазина корейской косметики: уход за лицом, телом и волосами. Прототип каталога, заказы пока не принимаются.">
<style>
:root{
--ink:#1c1a17; --paper:#faf7f2; --card:#ffffff; --line:#e4ddd2;
--acc:#0e7a5f; --acc-soft:#e7f2ee; --muted:#6f6a61; --sale:#b3352c;
--sans:"Segoe UI",-apple-system,"Helvetica Neue",Arial,sans-serif;
--mono:ui-monospace,"SF Mono",Menlo,Consolas,monospace;
}
*{box-sizing:border-box;margin:0;padding:0}
body{background:var(--paper);color:var(--ink);font-family:var(--sans);line-height:1.5}
a{color:inherit;text-decoration:none}
.wrap{max-width:1100px;margin:0 auto;padding:0 20px}
.note{font-family:var(--mono);font-size:12px;background:var(--ink);color:#f4f0e6;padding:6px 0;text-align:center}
.note b{color:#8fd4bd}
header{background:var(--card);border-bottom:1px solid var(--line);position:sticky;top:0;z-index:5}
.hbar{display:flex;align-items:center;gap:18px;padding:14px 0}
.logo{font-size:22px;font-weight:700;letter-spacing:-.02em;white-space:nowrap}
.logo span{color:var(--acc)}
.logo small{display:block;font-family:var(--mono);font-weight:400;font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--muted)}
.search{flex:1;display:flex}
.search input{flex:1;border:1px solid var(--line);border-right:none;padding:10px 14px;font:inherit;font-size:14px;background:var(--paper);border-radius:6px 0 0 6px;outline:none}
.search input:focus{border-color:var(--acc)}
.search button{border:none;background:var(--acc);color:#fff;padding:0 16px;border-radius:0 6px 6px 0;cursor:pointer;font:inherit;font-size:14px}
.hlinks{display:flex;gap:16px;font-size:14px;white-space:nowrap}
.hlinks a:hover{color:var(--acc)}
nav.cats{display:flex;gap:4px;overflow-x:auto;padding:0 0 10px;font-size:14px}
nav.cats a{padding:6px 12px;border-radius:20px;border:1px solid transparent;white-space:nowrap}
nav.cats a:hover{border-color:var(--acc);color:var(--acc)}
nav.cats a.all{background:var(--acc-soft);color:var(--acc);font-weight:600}
.hero{background:linear-gradient(180deg,var(--acc-soft),var(--paper));border-bottom:1px solid var(--line)}
.hero .wrap{display:flex;gap:40px;align-items:center;padding-top:52px;padding-bottom:52px;flex-wrap:wrap}
.hero h1{font-size:clamp(30px,4.5vw,46px);line-height:1.1;letter-spacing:-.02em;max-width:16ch}
.hero p{margin-top:14px;max-width:48ch;color:var(--muted);font-size:17px}
.hero .btn{display:inline-block;margin-top:22px;background:var(--acc);color:#fff;padding:13px 26px;border-radius:6px;font-weight:600}
.hero .btn:hover{filter:brightness(1.08)}
.rhythm{flex:1;min-width:260px;display:flex;align-items:flex-end;gap:4px;height:120px;opacity:.9}
.rhythm span{flex:1;background:var(--acc);border-radius:3px 3px 0 0;animation:grow 2.8s ease-in-out infinite}
@keyframes grow{0%,100%{transform:scaleY(.3)}50%{transform:scaleY(1)}}
.rhythm span:nth-child(3n){background:var(--ink)}
section{padding:46px 0}
h2{font-size:clamp(22px,3vw,30px);letter-spacing:-.01em;margin-bottom:6px}
.sub{color:var(--muted);font-size:15px;margin-bottom:24px}
.catgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));gap:14px}
.cat{background:var(--card);border:1px solid var(--line);border-radius:10px;padding:20px 18px;transition:transform .15s ease,border-color .15s ease}
.cat:hover{transform:translateY(-2px);border-color:var(--acc)}
.cat .ic{width:38px;height:38px;border-radius:8px;background:var(--acc-soft);margin-bottom:12px;position:relative}
.cat .ic::after{content:"";position:absolute;inset:11px;background:var(--acc);border-radius:50%}
.cat.a .ic::after{border-radius:2px}
.cat.b .ic::after{height:6px;top:16px}
.cat.c .ic::after{width:6px;left:16px}
.cat.d .ic::after{border-radius:50%;inset:14px 11px}
.cat h3{font-size:16px;margin-bottom:4px}
.cat p{font-size:13px;color:var(--muted)}
.chips{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:8px}
.chips a{font-size:13px;padding:7px 14px;border:1px solid var(--line);border-radius:20px;background:var(--card)}
.chips a:hover{border-color:var(--acc);color:var(--acc)}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:18px}
.p{background:var(--card);border:1px solid var(--line);border-radius:10px;overflow:hidden;display:flex;flex-direction:column;transition:box-shadow .15s ease}
.p:hover{box-shadow:0 6px 18px rgba(28,26,23,.08)}
.ph{aspect-ratio:1/1;background:var(--acc-soft);display:flex;align-items:center;justify-content:center;position:relative}
.ph .b{font-family:var(--mono);font-size:15px;letter-spacing:.06em;color:var(--acc);border:1.5px solid var(--acc);padding:8px 14px;border-radius:4px;background:var(--card)}
.ph .tag{position:absolute;top:10px;left:10px;background:var(--sale);color:#fff;font-size:11px;font-weight:700;padding:3px 8px;border-radius:4px}
.ph .tag.new{background:var(--acc)}
.pb{padding:14px 16px 16px;display:flex;flex-direction:column;flex:1}
.pb .cat-l{font-family:var(--mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted)}
.pb .brand{font-size:12px;font-weight:700;color:var(--acc);margin-top:4px}
.pb h3{font-size:14.5px;font-weight:400;line-height:1.35;margin:4px 0 10px;flex:1}
.price{display:flex;align-items:baseline;gap:8px}
.price .now{font-size:17px;font-weight:700}
.price .old{font-size:13px;color:var(--muted);text-decoration:line-through}
.p button{margin-top:12px;border:1.5px solid var(--ink);background:transparent;padding:9px;font:inherit;font-size:13.5px;font-weight:600;border-radius:6px;cursor:pointer;transition:.15s}
.p button:hover{background:var(--ink);color:#fff}
.brands{display:flex;flex-wrap:wrap;gap:10px}
.brands span{font-family:var(--mono);font-size:13px;border:1px solid var(--line);padding:8px 16px;border-radius:6px;background:var(--card);color:var(--muted)}
.brands span:hover{color:var(--acc);border-color:var(--acc)}
.dark{background:var(--ink);color:#f4f0e6}
.dark .sub{color:#b9b2a4}
.dark form{display:flex;gap:10px;max-width:480px;margin-top:8px;flex-wrap:wrap}
.dark input{flex:1;min-width:200px;border:1px solid #4a453d;background:transparent;color:inherit;padding:12px 14px;border-radius:6px;font:inherit;outline:none}
.dark input:focus{border-color:var(--acc)}
.dark button{background:var(--acc);border:none;color:#fff;padding:12px 22px;border-radius:6px;font:inherit;font-weight:600;cursor:pointer}
footer{padding:30px 0 40px;color:var(--muted);font-size:13.5px}
footer .wrap{display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap}
footer .proto{font-family:var(--mono);font-size:12px}
#toast{position:fixed;left:50%;bottom:24px;transform:translate(-50%,80px);background:var(--ink);color:#fff;padding:12px 20px;border-radius:8px;font-size:14px;opacity:0;transition:.25s ease;z-index:9}
#toast.show{transform:translate(-50%,0);opacity:1}
@media (max-width:720px){
.hlinks{display:none}
.hero .wrap{padding-top:34px;padding-bottom:34px}
.rhythm{display:none}
}
@media (prefers-reduced-motion:reduce){
*{animation:none!important;transition:none!important;scroll-behavior:auto!important}
}
</style>
</head>
<body>
<div class="note">Прототип витрины — шаг 1 из плана Shkor beauty. Наполнение демонстрационное, оплата не подключена.</div>
<header>
<div class="wrap">
<div class="hbar">
<a class="logo" href="#">Shkor <span>beauty</span><small>корейская косметика · Минск</small></a>
<div class="search">
<input type="search" placeholder="Поиск: тонер, пэды, гидрофильное масло…">
<button>Найти</button>
</div>
<div class="hlinks">
<a href="#catalog">Каталог</a>
<a href="#brands">Бренды</a>
<a href="#sub">Подписка</a>
</div>
</div>
<nav class="cats" aria-label="Разделы">
<a class="all" href="#catalog">Весь каталог</a>
<a href="#face">Уход за лицом</a>
<a href="#body">Уход за телом</a>
<a href="#hair">Уход за волосами</a>
<a href="#makeup">Декоративная косметика</a>
<a href="#acc">Аксессуары</a>
</nav>
</div>
</header>
<div class="hero">
<div class="wrap">
<div>
<h1>Корейский уход — с полки до двери за 1 день</h1>
<p>Каталог проверенных K-beauty брендов в Минске: очищение, тонизирование, сыворотки, SPF. Самовывоз и доставка по Беларуси.</p>
<a class="btn" href="#catalog">Смотреть каталог</a>
</div>
<div class="rhythm" aria-hidden="true">
<span></span><span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span><span></span><span></span>
</div>
</div>
</div>
<section id="catalog">
<div class="wrap">
<h2>Каталог</h2>
<p class="sub">Пять разделов, как у эталона рынка — структура навигации Korealab.</p>
<div class="catgrid">
<a class="cat a" href="#face"><span class="ic"></span><h3>Уход за лицом</h3><p>Очищение, тонеры, сыворотки, маски, SPF</p></a>
<a class="cat b" href="#body"><span class="ic"></span><h3>Уход за телом</h3><p>Гели для душа, лосьоны, кремы для рук</p></a>
<a class="cat c" href="#hair"><span class="ic"></span><h3>Уход за волосами</h3><p>Шампуни, маски, ампулы, филлеры</p></a>
<a class="cat d" href="#makeup"><span class="ic"></span><h3>Декоративная косметика</h3><p>Тинты, BB-кремы, бальзамы для губ</p></a>
<a class="cat a" href="#acc"><span class="ic"></span><h3>Аксессуары</h3><p>Спонжи, пады, пилочки, зеркала</p></a>
</div>
</div>
</section>
<section id="face" style="padding-top:0">
<div class="wrap">
<h2>Уход за лицом</h2>
<p class="sub">Подкатегории первого уровня — быстрый вход в нужный шаг рутины.</p>
<div class="chips">
<a href="#demo">Очищение</a><a href="#demo">Тонизирование</a><a href="#demo">Сыворотки и эссенции</a>
<a href="#demo">Патчи для глаз</a><a href="#demo">Тканевые маски</a><a href="#demo">Кремы</a>
<a href="#demo">Солнцезащитные средства</a><a href="#demo">Средства с кислотами</a>
</div>
<div class="grid" id="demo">
<article class="p">
<div class="ph"><span class="tag">−30%</span><span class="b">SKIN627</span></div>
<div class="pb">
<span class="cat-l">Сыворотки</span><span class="brand">SKIN627</span>
<h3>Увлажняющая сыворотка для лица Hyaluron Centella Ampoule</h3>
<div class="price"><span class="now">35,00 руб.</span><span class="old">50,00 руб.</span></div>
<button>В корзину</button>
</div>
</article>
<article class="p">
<div class="ph"><span class="tag new">Новинка</span><span class="b">JMsolution</span></div>
<div class="pb">
<span class="cat-l">Пэды</span><span class="brand">JMsolution</span>
<h3>Увлажняющие пэды с гиалуроновой кислотой Multi Hyaluronic</h3>
<div class="price"><span class="now">59,00 руб.</span></div>
<button>В корзину</button>
</div>
</article>
<article class="p">
<div class="ph"><span class="b">Beauty of Joseon</span></div>
<div class="pb">
<span class="cat-l">SPF</span><span class="brand">Beauty of Joseon</span>
<h3>Солнцезащитный гель Relief Sun SPF50+ PA++++</h3>
<div class="price"><span class="now">49,00 руб.</span></div>
<button>В корзину</button>
</div>
</article>
<article class="p">
<div class="ph"><span class="tag">−25%</span><span class="b">Biodance</span></div>
<div class="pb">
<span class="cat-l">Маски</span><span class="brand">Biodance</span>
<h3>Коллагеновая маска-пленка Collagen Wrapping Peel-off Mask</h3>
<div class="price"><span class="now">12,00 руб.</span><span class="old">16,00 руб.</span></div>
<button>В корзину</button>
</div>
</article>
<article class="p">
<div class="ph"><span class="b">Anua</span></div>
<div class="pb">
<span class="cat-l">Тонеры</span><span class="brand">Anua</span>
<h3>Успокаивающий тонер с сердечником Centella Toner 77%</h3>
<div class="price"><span class="now">62,00 руб.</span></div>
<button>В корзину</button>
</div>
</article>
<article class="p">
<div class="ph"><span class="b">Banila Co</span></div>
<div class="pb">
<span class="cat-l">Очищение</span><span class="brand">Banila Co</span>
<h3>Гидрофильное масло Clean It Zero Original</h3>
<div class="price"><span class="now">54,00 руб.</span></div>
<button>В корзину</button>
</div>
</article>
<article class="p">
<div class="ph"><span class="b">SKIN627</span></div>
<div class="pb">
<span class="cat-l">Сыворотки</span><span class="brand">SKIN627</span>
<h3>Сыворотка для лица с ретинолом Retinol Pantenol Ampoule</h3>
<div class="price"><span class="now">35,00 руб.</span></div>
<button>В корзину</button>
</div>
</article>
<article class="p">
<div class="ph"><span class="tag new">Новинка</span><span class="b">A'PIEU</span></div>
<div class="pb">
<span class="cat-l">Патчи</span><span class="brand">A'PIEU</span>
<h3>Патчи для глаз с молочной кислотой Milk One-Day Perfector</h3>
<div class="price"><span class="now">18,00 руб.</span></div>
<button>В корзину</button>
</div>
</article>
</div>
</div>
</section>
<section id="body" style="padding-top:0">
<div class="wrap">
<h2>Уход за телом и волосами</h2>
<p class="sub">Витрина разделов вне лица — по мотивам карточек эталона.</p>
<div class="grid">
<article class="p">
<div class="ph"><span class="b">WEPROVE</span></div>
<div class="pb">
<span class="cat-l">Для душа</span><span class="brand">WEPROVE</span>
<h3>Обновляющий гель для душа Renewing Aloe Shower Gel</h3>
<div class="price"><span class="now">29,00 руб.</span></div>
<button>В корзину</button>
</div>
</article>
<article class="p">
<div class="ph"><span class="tag">−15%</span><span class="b">WEPROVE</span></div>
<div class="pb">
<span class="cat-l">Шампуни</span><span class="brand">WEPROVE</span>
<h3>Восстанавливающий шампунь Daily Hair Repairing Shampoo</h3>
<div class="price"><span class="now">23,60 руб.</span><span class="old">27,80 руб.</span></div>
<button>В корзину</button>
</div>
</article>
<article class="p">
<div class="ph"><span class="b">Care:Nel</span></div>
<div class="pb">
<span class="cat-l">Кремы для рук</span><span class="brand">Care:Nel</span>
<h3>Питательный крем для рук с ши и центеллой</h3>
<div class="price"><span class="now">14,00 руб.</span></div>
<button>В корзину</button>
</div>
</article>
<article class="p">
<div class="ph"><span class="b">Ciracle</span></div>
<div class="pb">
<span class="cat-l">Аксессуары</span><span class="brand">Ciracle</span>
<h3>Силиконовый спонж для умывания с чехлом</h3>
<div class="price"><span class="now">9,00 руб.</span></div>
<button>В корзину</button>
</div>
</article>
</div>
</div>
</section>
<section id="brands">
<div class="wrap">
<h2>Бренды</h2>
<p class="sub">Формат «Алфавит брендов» как у эталона — вход через доверие к марке.</p>
<div class="brands">
<span>A'PIEU</span><span>ANSKIN</span><span>Anua</span><span>Atopalm</span>
<span>AXIS-Y</span><span>Banila Co</span><span>Beauty of Joseon</span><span>Biodance</span>
<span>Care:Nel</span><span>Celimax</span><span>Cos de Baha</span><span>JMsolution</span>
<span>SKIN627</span><span>WEPROVE</span>
</div>
</div>
</section>
<section class="dark" id="sub">
<div class="wrap">
<h2>Подписка на поступления</h2>
<p class="sub">Одно письмо в неделю: новинки, скидки и разборы составов. Без спама.</p>
<form onsubmit="event.preventDefault();toast('Прототип: подписка не подключена')">
<input type="email" placeholder="your@email.com" required>
<button>Подписаться</button>
</form>
</div>
</section>
<footer>
<div class="wrap">
<div>Shkor beauty — витрина корейской косметики. Прототип шага 1: каталог без платежей.</div>
<div class="proto">собрано харнесом Simbiot · кооператив 42 · cil.by</div>
</div>
</footer>
<div id="toast" role="status"></div>
<script>
function toast(t){
var el=document.getElementById('toast');
el.textContent=t;el.classList.add('show');
clearTimeout(el._t);el._t=setTimeout(function(){el.classList.remove('show')},2200);
}
document.querySelectorAll('.p button').forEach(function(b){
b.addEventListener('click',function(){toast('Прототип: заказ и оплата будут на шаге 2')});
});
</script>
</body>
</html>

79
artifacts/shkor/README.md Normal file
View file

@ -0,0 +1,79 @@
# Shkor Beauty Assistant
Beauty-ассистент магазина **Shkor beauty**: анализирует состояние кожи, подбирает продукты
из каталога витрины, оформляет заказ с доставкой и выдаёт инструкции по применению.
Подключается к **любому агенту** (LLM-бот, MCP-клиент, крон-скрипт, мобильное приложение).
Задача-источник: `t-261005120225-672f52` (требования Alex). Каталог синхронизирован
с прототипом витрины `../shkor-beauty-prototype.html` (t-261005115918-d38b36).
## Состав
| Файл | Что это |
|---|---|
| `assistant.py` | Ядро: анализ кожи, рекомендации, заказы, инструкции. Один вход `handle(request) -> dict` |
| `connector.py` | Универсальный коннектор: STDIO (MCP-стиль), HTTP API, CLI, демо-чат |
| `catalog.json` | 12 товаров витрины с мета-данными (concerns, actives, routine_step) |
| `orders.json` | Локальный леджер заказов (создаётся при первом заказе) |
## Подключение любым агентом
**1) STDIO, MCP-стиль** — для Claude/любого MCP-клиента:
```json
{"mcpServers": {"shkor": {"command": "python3", "args": ["/root/simbiot-harness/artifacts/shkor/connector.py"], "transport": "stdio"}}}
```
Протокол: построчный JSONL. `{"method":"tools/list"}` → список инструментов;
`{"method":"tools/call","params":{"name":"analyze_skin","arguments":{...}},"id":1}` → результат.
**2) HTTP API** — агент = любой HTTP-клиент:
```bash
connector.py --http 127.0.0.1:8791
curl -XPOST localhost:8791/call -d '{"name":"recommend","arguments":{"concerns":["dryness"]}}'
```
Роуты: `GET /health`, `POST /tools/list`, `POST /call`.
**3) CLI разовый вызов** — крон/скрипты:
```bash
connector.py --call analyze_skin --args '{"self_report":"стянутость и покраснения"}'
```
**4) Живая проверка:** `connector.py --demo` — полный сценарий: анализ → подбор → заказ → инструкция → статус.
## Инструменты (7)
- `analyze_skin` `{self_report?, symptoms?, photo_report?}` → тип кожи, concerns, hydration-индекс,
состояние барьера, фокус рутины. `photo_report` — принимаем отчёт vision-агента по селфи
(JSON или текст): мержим с самоотчётом, источники считаем отдельно.
- `recommend` `{concerns?, skin_type?, budget_max?}` → подобранные карточки с объяснением «почему».
- `search` `{query?, category?}` → поиск по каталогу (бренд/название/актив/категория).
- `place_order` `{items:[{id,qty}], delivery?, name?, phone?, address?, pay_method?, pay_link?}` →
заказ `SB-XXXXXXXX`, сумма, доставка. Платёж: `cash_on_delivery` или `pay_link` от
платёжного агента (Stripe/Альфа-Банк) — ассистент не хранит карты.
- `check_order` `{order_id}` → статус заказа и доставки.
- `delivery_options` `{cart_total?}` → курьер Минск / Европочта / самовывоз, бесплатно от 80 BYN.
- `usage_instructions` `{product_ids}` → пошаговые инструкции, порядок нанесения, предостережения
(ретинол только на ночь и не в один день с кислотами, SPF обязателен).
## Пример потока агента
```
1. analyze_skin {"self_report": "кожа стянутая, лоб блестит, покраснения"}
→ {"skin_type":"combination","concerns":["dryness","redness","oiliness"],"barrier":"compromised",...}
2. recommend {"concerns":["dryness","redness"], "skin_type":"sensitive"}
→ пэды JMsolution, тонер Anua Centella 77%, сыворотка SKIN627
3. place_order {"items":[{"id":"p05","qty":1},{"id":"p03","qty":1}],"delivery":"minsk_courier"}
→ SB-XXXXXXXX, 111.0 BYN, доставка бесплатно (корзина ≥80), курьер сегодня/завтра
4. usage_instructions {"product_ids":["p05","p03"]}
→ тонер (шаг 2) → SPF (шаг 7), предупреждения
5. check_order {"order_id":"SB-XXXXXXXX"}
```
## Границы (честно)
- Анализ — эвристический (словарь стеммов RU/EN + голосование по типу), НЕ постановка диагноза;
в каждом ответе дисклеймер «не медицинская консультация».
- Оплата — заглушка `pending`: реальный эквайринг подключает агент-хост через `pay_link`
(см. задачу по методам оплаты портала).
- Каталог = витрина-прототип (12 SKU). При росте — заменить `catalog.json` без правок кода.
Запуск тестов: `python3 connector.py --demo` (создаёт тестовый заказ в orders.json).

View file

@ -0,0 +1,384 @@
#!/usr/bin/env python3
"""Shkor Beauty Assistant — ядро.
Универсальный beauty-ассистент магазина Shkor beauty:
* анализ состояния кожи (self-report + отчёт vision-агента),
* рекомендация продуктов из каталога каталога витрины,
* оформление заказа + варианта доставки,
* инструкция по применению (порядок рутины, частота, предостережения).
Протокол: один вход `handle(request: dict) -> dict`, MCP-совместимая форма
(JSON-RPC tools/call) + простая форма {action, args}. Оболочки (STDIO/HTTP/CLI)
живут в connector.py — ассистент не знает, какой агент его вызывает.
"""
from __future__ import annotations
import json
import os
import re
import time
import uuid
from pathlib import Path
HERE = Path(__file__).resolve().parent
CATALOG = json.loads((HERE / "catalog.json").read_text(encoding="utf-8"))
ORDERS_PATH = HERE / "orders.json"
PRODUCTS = {p["id"]: p for p in CATALOG["products"]}
# ── Анализ кожи ──────────────────────────────────────────────────────────────
# ключи — стеммы (корни слов), чтобы ловить любые словоформы: «стянутая/стянутость», «покраснения/покраснение»
SYMPTOM_MAP = {
"стянут": "dryness", "сух": "dryness", "шелуш": "dryness", "обезвож": "dehydration", "dry": "dryness",
"flak": "dryness", "peel": "dryness",
"покрасн": "redness", "краснот": "redness", "раздраж": "sensitivity", "чувствитель": "sensitivity",
"sensitive": "sensitivity", "redness": "redness", "red ": "redness", "itch": "redness",
"блеск": "oiliness", "блест": "oiliness", "жирн": "oiliness", "oily": "oiliness", "shine": "oiliness",
"чёрных точ": "clogged-pores", "черных точ": "clogged-pores", "blackhead": "clogged-pores",
"расширенн пор": "clogged-pores", "поры": "clogged-pores", "pores": "clogged-pores",
"высып": "acne", "акн": "acne", "прыщ": "acne", "breakout": "acne", "угр": "acne",
"пигмент": "pigmentation", "пятн": "pigmentation", "веснушк": "pigmentation", "dark spot": "pigmentation",
"морщин": "fine-lines", "wrinkle": "fine-lines", "мимическ": "fine-lines",
"туск": "dullness", "dull": "dullness", "dullness": "dullness",
"тёмн круг": "dark-circles", "темн круг": "dark-circles", "dark circle": "dark-circles", "круги под глаз": "dark-circles",
"отёк": "puffiness", "отек": "puffiness", "puffin": "puffiness", "опух": "puffiness",
"чёрных угр": "clogged-pores",
}
TYPE_RULES = [
("sensitive", 2, ["redness", "sensitivity"]),
("dry", 2, ["dryness"]),
("oily", 2, ["oiliness", "clogged-pores"]),
("combination", 0, []), # базовый исход
]
# точные имена concerns для структурированного ввода (symptoms:[...])
CONCERN_ALIASES = {c: c for c in ["dryness", "dehydration", "sensitivity", "redness", "oiliness",
"clogged-pores", "acne", "pigmentation", "photoaging", "fine-lines",
"dullness", "dark-circles", "puffiness", "texture"]}
ROUTINE_ORDER = ["cleanse", "toner", "exfoliate", "serum", "mask", "spot", "spf",
"body-cleanse", "body-care", "hair-wash", "tool"]
def analyze_skin(args: dict) -> dict:
"""Анализ: self-report (текст/чек-лист) + опциональный vision-отчёт агента-фотографа.
args:
self_report: str — свободный текст жалоб (опционально)
symptoms: [str] — структурированные симптомы (опционально)
photo_report: dict/str — отчёт vision-модели по селфи (опционально)
age: int, climate: str — контекст (опционально)
"""
signals: list[str] = []
src = {"self_report": 0, "symptoms": 0, "photo_report": 0}
if args.get("self_report"):
text = str(args["self_report"]).lower()
for kw, concern in SYMPTOM_MAP.items():
if kw in text and concern not in signals:
signals.append(concern)
src["self_report"] = len([c for c in signals])
for s in args.get("symptoms") or []:
raw = str(s).lower().strip()
c = CONCERN_ALIASES.get(raw) or SYMPTOM_MAP.get(raw) or raw
if c and c not in signals:
signals.append(c)
src["symptoms"] += 1
photo = args.get("photo_report")
if photo:
blob = photo if isinstance(photo, str) else json.dumps(photo, ensure_ascii=False)
blob = blob.lower()
for kw, concern in SYMPTOM_MAP.items():
if kw in blob and concern not in signals:
signals.append(concern)
src["photo_report"] = len([c for c in signals]) - src["self_report"] - src["symptoms"]
# тип кожи: голосование по правилам
scores = {"sensitive": 0, "dry": 0, "oily": 0}
for concern in signals:
if concern in ("redness", "sensitivity"):
scores["sensitive"] += 1
if concern in ("dryness", "dehydration", "fine-lines"):
scores["dry"] += 1
if concern in ("oiliness", "clogged-pores", "acne"):
scores["oily"] += 1
top = max(scores.values())
winners = [k for k, v in scores.items() if v == top]
skin_type = winners[0] if len(winners) == 1 else "combination" # ничья/пусто → честный combination
hydration = 5 # 0..10, нейтрально
if "dryness" in signals:
hydration -= 3
if "oiliness" in signals:
hydration += 2
if "dehydration" in signals:
hydration -= 2
hydration = max(0, min(10, hydration))
barrier = "ok"
if "redness" in signals or "sensitivity" in signals:
barrier = "compromised"
if "acne" in signals and barrier == "compromised":
barrier = "compromised+breakouts"
recommendations = [p["id"] for p in rank_products(signals, skin_type)]
return {
"skin_type": skin_type,
"concerns": signals,
"hydration_index_0_10": hydration,
"barrier": barrier,
"sources": src,
"routine_focus": routine_focus(signals),
"recommended_product_ids": recommendations[:5],
"disclaimer": "Не медицинская консультация. При стойком раздражении/акне — к дерматологу.",
}
def routine_focus(concerns: list[str]) -> list[str]:
focus = []
if "dryness" in concerns or "dehydration" in concerns:
focus.append("увлажнение: гиалурон + центелла, тонер-эссенция до сыворотки")
if "sensitivity" in concerns or "redness" in concerns:
focus.append("успокоение: центелла 77%, без кислот и ретинола первые 2 недели")
if "acne" in concerns or "clogged-pores" in concerns:
focus.append("очищение: гидрофильное масло + мягкий BHA, не щёлочь")
if "pigmentation" in concerns or "photoaging" in concerns:
focus.append("SPF каждый день + ретинол на ночь (вводить постепенно)")
if "fine-lines" in concerns:
focus.append("анти-эйдж: ретинол 2–3 р/нед, коллагеновые маски")
if "dullness" in concerns:
focus.append("тон/сияние: пэды с кислотами 2 р/нед, маска-плёнка")
if not focus:
focus.append("база: очищение → тонер → сыворотка → крем → SPF")
return focus
def rank_products(concerns: list[str], skin_type: str) -> list[dict]:
scored = []
for p in CATALOG["products"]:
score = 0
for c in concerns:
if c in p["concerns"]:
score += 3
if skin_type == "sensitive" and ("retinol" in " ".join(p["actives"]) or "acid" in " ".join(p["actives"])):
score -= 2
if skin_type == "dry" and "hyaluronic" in " ".join(p["actives"]):
score += 2
if skin_type == "oily" and p["routine_step"] in ("cleanse", "exfoliate"):
score += 1
if score > 0:
scored.append((score, p))
scored.sort(key=lambda x: (-x[0], x[1]["price"]))
return [p for _, p in scored] or CATALOG["products"][:3]
# ── Рекомендации / поиск ──────────────────────────────────────────────────────
def recommend(args: dict) -> dict:
concerns = [CONCERN_ALIASES.get(c.lower().strip()) or SYMPTOM_MAP.get(c.lower().strip(), c.lower().strip())
for c in (args.get("concerns") or [])]
skin_type = args.get("skin_type") or "combination"
budget = args.get("budget_max")
picks = rank_products(concerns, skin_type)
if budget:
picks = [p for p in picks if p["price"] <= float(budget)]
return {
"matched": len(picks),
"products": [
{"id": p["id"], "brand": p["brand"], "title": p["title"], "price": p["price"],
"why": ", ".join(c for c in concerns if c in p["concerns"]) or "базовый уход"}
for p in picks[: int(args.get("limit") or 5)]
],
}
def search(args: dict) -> dict:
q = (args.get("query") or "").lower().strip()
cat = args.get("category")
out = []
for p in CATALOG["products"]:
if cat and p["category"] != cat:
continue
if q and not (q in p["title"].lower() or q in p["brand"].lower() or q in p["sub"].lower()
or any(q in a for a in p["actives"])):
continue
out.append({"id": p["id"], "brand": p["brand"], "title": p["title"], "price": p["price"],
"category": p["category"], "sub": p["sub"]})
return {"found": len(out), "products": out[: int(args.get("limit") or 20)]}
# ── Заказы и доставка ─────────────────────────────────────────────────────────
DELIVERY = {
"minsk_courier": {"label": "Курьер по Минску", "eta": "сегодня/завтра", "price": 7.0, "free_from": 80.0},
"europochta": {"label": "Европочта (РБ)", "eta": "2–3 дня", "price": 4.5, "free_from": 80.0},
"pickup": {"label": "Самовывоз (Минск, пр-т Независимости)", "eta": "через 2 часа", "price": 0.0, "free_from": 0.0},
}
def _load_orders() -> dict:
if ORDERS_PATH.exists():
return json.loads(ORDERS_PATH.read_text(encoding="utf-8"))
return {}
def _save_orders(orders: dict) -> None:
ORDERS_PATH.write_text(json.dumps(orders, ensure_ascii=False, indent=1), encoding="utf-8")
def place_order(args: dict) -> dict:
items = args.get("items") or []
if not items:
return {"error": "items обязателен: [{id, qty}]"}
lines, unknown = [], []
total = 0.0
for it in items:
p = PRODUCTS.get(it.get("id"))
if not p:
unknown.append(it.get("id"))
continue
qty = int(it.get("qty") or 1)
lines.append({"id": p["id"], "title": p["title"], "qty": qty, "price": p["price"], "sum": round(p["price"] * qty, 2)})
total += p["price"] * qty
if unknown:
return {"error": f"неизвестные товары: {unknown}", "hint": "tools/call search для поиска id"}
delivery_key = args.get("delivery") or "minsk_courier"
d = DELIVERY.get(delivery_key)
if not d:
return {"error": f"нет такой доставки: {delivery_key}", "available": list(DELIVERY)}
d_price = 0.0 if (d["free_from"] and total >= d["free_from"]) else d["price"]
oid = "SB-" + uuid.uuid4().hex[:8].upper()
order = {
"order_id": oid,
"items": lines,
"subtotal": round(total, 2),
"delivery": {"key": delivery_key, **d, "charged": d_price},
"total": round(total + d_price, 2),
"customer": {"name": args.get("name") or "", "phone": args.get("phone") or "",
"address": args.get("address") or ""},
"payment": {"status": "pending", "method": args.get("pay_method") or "cash_on_delivery",
"pay_link": args.get("pay_link")}, # платёжку подключает агент-хост
"status": "placed",
"created": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
}
orders = _load_orders()
orders[oid] = order
_save_orders(orders)
return {
"order_id": oid, "status": "placed", "total": order["total"],
"delivery_eta": d["eta"], "delivery_price": d_price,
"next": f"Статус: tools/call check_order {{\"order_id\": \"{oid}\"}}. "
f"Оплата: pay_method=cash_on_delivery или передайте pay_link от платёжного агента.",
}
def check_order(args: dict) -> dict:
oid = args.get("order_id") or ""
o = _load_orders().get(oid)
if not o:
return {"error": f"заказ {oid} не найден", "known": list(_load_orders())[:10]}
return {"order_id": oid, "status": o["status"], "payment": o["payment"]["status"],
"total": o["total"], "delivery": o["delivery"]["label"], "eta": o["delivery"]["eta"],
"items": [{"title": l["title"], "qty": l["qty"]} for l in o["items"]]}
def delivery_options(args: dict) -> dict:
total = float(args.get("cart_total") or 0)
return {"options": [
{"key": k, "label": v["label"], "eta": v["eta"],
"price": 0.0 if (v["free_from"] and total >= v["free_from"]) else v["price"]}
for k, v in DELIVERY.items()]}
# ── Инструкции по применению ──────────────────────────────────────────────────
INSTRUCTIONS = {
"cleanse": "Нанести на сухую кожу, помассировать 30–60 сек, смыть тёплой водой. Утром можно мягче.",
"toner": "После очищения протереть лицо/нанести ладонями, не вытирать — следующий шаг на влажную кожу.",
"exfoliate": "2–3 раза в неделю, не в один день с ретинолом. При покраснении — прекратить.",
"serum": "3–4 капли на влажную кожу, дать впитаться 1–2 мин. Ретинол — только на ночь, старт 2–3 р/нед.",
"mask": "1–2 раза в неделю на очищенную кожу, 20–30 мин, снять плёнку снизу вверх.",
"spot": "Патчи — 10–15 мин на зону под глазами, после — лёгкий крем.",
"spf": "Финальный утренний шаг, 2 пальца средства, обновлять каждые 2–3 часа при солнце. Обязателен при ретиноле/кислотах.",
"body-cleanse": "Ежедневно, мягкой губкой, завершить увлажнением.",
"body-care": "На влажную кожу рук после каждого мытья.",
"hair-wash": "Втирать в кожу головы 1–2 мин, смыть; бальзам — только по длине.",
"tool": "Спонж смочить тёплой водой, использовать с пенкой; промывать и сушить после каждого применения.",
}
def usage_instructions(args: dict) -> dict:
ids = args.get("product_ids") or args.get("ids") or []
if isinstance(ids, str):
ids = [ids]
got = []
for pid in ids:
p = PRODUCTS.get(pid)
if p:
got.append({"id": pid, "title": p["title"], "how": INSTRUCTIONS.get(p["routine_step"], "по инструкции бренда"),
"step_order": ROUTINE_ORDER.index(p["routine_step"]) + 1 if p["routine_step"] in ROUTINE_ORDER else 99,
"caution": p.get("caution")})
if not got:
return {"error": "укажите product_ids (см. search/recommend)", "known_ids": list(PRODUCTS)}
got.sort(key=lambda x: x["step_order"])
plan = " → ".join(g["title"].split()[0] for g in got)
return {"steps": got, "routine_summary": f"Порядок нанесения: {plan}. Утро: очищение→увлажнение→SPF; вечер: очищение→акт (ретинол/кислоты)→крем.",
"general": "Новое средство — сначала на сгиб локтя 24ч. Не сочетать ретинол с кислотами в один день."}
# ── Диспетчер инструментов ────────────────────────────────────────────────────
TOOLS = {
"analyze_skin": ("Анализ состояния кожи: self_report/symptoms/photo_report → тип, concerns, фокус рутины",
["self_report?", "symptoms?", "photo_report?", "age?", "climate?"]),
"recommend": ("Подбор продуктов: concerns+skin_type+budget_max → карточки", ["concerns?", "skin_type?", "budget_max?", "limit?"]),
"search": ("Поиск по каталогу", ["query?", "category?", "limit?"]),
"place_order": ("Оформить заказ", ["items[{id,qty}]", "delivery?", "name?", "phone?", "address?", "pay_method?"]),
"check_order": ("Статус заказа и доставки", ["order_id"]),
"delivery_options": ("Варианты доставки и цены", ["cart_total?"]),
"usage_instructions": ("Инструкции по применению + порядок рутины", ["product_ids"]),
}
def handle(request: dict) -> dict:
"""Единая точка входа. Понимает MCP tools/call и простую форму {action, args}."""
if not isinstance(request, dict):
return {"error": "request должен быть JSON-объектом"}
method = request.get("method")
if method == "tools/list":
return {"tools": [{"name": n, "description": d, "args": a} for n, (d, a) in TOOLS.items()]}
if method == "initialize":
return {"protocol": "shkor-assistant/1", "tools": list(TOOLS), "shop": CATALOG["shop"]}
if method == "tools/call":
params = request.get("params") or {}
name, arguments = params.get("name"), params.get("arguments") or {}
else:
name, arguments = request.get("action"), request.get("args") or request.get("arguments") or {}
if name not in TOOLS:
return {"error": f"неизвестный инструмент {name!r}", "available": list(TOOLS)}
fn = {"analyze_skin": analyze_skin, "recommend": recommend, "search": search,
"place_order": place_order, "check_order": check_order,
"delivery_options": delivery_options, "usage_instructions": usage_instructions}[name]
try:
result = fn(arguments if isinstance(arguments, dict) else {})
except Exception as e: # noqa: BLE001 — ассистент отвечает структурой, не падает
return {"error": f"{type(e).__name__}: {e}"}
rid = request.get("id")
out = {"result": result}
if rid is not None:
out["id"] = rid
return out
if __name__ == "__main__":
import sys
payload = json.loads(sys.argv[1]) if len(sys.argv) > 1 else {"method": "tools/list"}
print(json.dumps(handle(payload), ensure_ascii=False, indent=1))

View file

@ -0,0 +1,19 @@
{
"shop": "Shkor beauty",
"currency": "BYN",
"note": "Каталог синхронизирован с прототипом витрины artifacts/shkor-beauty-prototype.html (t-261005115918-d38b36)",
"products": [
{"id": "p01", "category": "face", "sub": "Сыворотки", "brand": "SKIN627", "title": "Увлажняющая сыворотка для лица Hyaluron Centella Ampoule", "price": 35.00, "old_price": 50.00, "concerns": ["dryness", "sensitivity", "dehydration"], "actives": ["hyaluronic acid", "centella"], "routine_step": "serum"},
{"id": "p02", "category": "face", "sub": "Пэды", "brand": "JMsolution", "title": "Увлажняющие пэды с гиалуроновой кислотой Multi Hyaluronic", "price": 59.00, "concerns": ["dryness", "dullness"], "actives": ["multi hyaluronic"], "routine_step": "exfoliate"},
{"id": "p03", "category": "face", "sub": "SPF", "brand": "Beauty of Joseon", "title": "Солнцезащитный гель Relief Sun SPF50+ PA++++", "price": 49.00, "concerns": ["photoaging", "pigmentation", "sensitivity"], "actives": ["SPF50+", "rice extract"], "routine_step": "spf"},
{"id": "p04", "category": "face", "sub": "Маски", "brand": "Biodance", "title": "Коллагеновая маска-пленка Collagen Wrapping Peel-off Mask", "price": 12.00, "concerns": ["dullness", "fine-lines"], "actives": ["collagen"], "routine_step": "mask"},
{"id": "p05", "category": "face", "sub": "Тонеры", "brand": "Anua", "title": "Успокаивающий тонер с сердечником Centella Toner 77%", "price": 62.00, "concerns": ["sensitivity", "redness"], "actives": ["centella 77%"], "routine_step": "toner"},
{"id": "p06", "category": "face", "sub": "Очищение", "brand": "Banila Co", "title": "Гидрофильное масло Clean It Zero Original", "price": 54.00, "concerns": ["clogged-pores", "makeup-removal"], "actives": ["cleansing oil"], "routine_step": "cleanse"},
{"id": "p07", "category": "face", "sub": "Сыворотки", "brand": "SKIN627", "title": "Сыворотка для лица с ретинолом Retinol Pantenol Ampoule", "price": 35.00, "concerns": ["fine-lines", "texture", "acne"], "actives": ["retinol", "panthenol"], "routine_step": "serum", "caution": "ретинол: вводить 2–3 раза в неделю, только на ночь, обязателен SPF утром"},
{"id": "p08", "category": "face", "sub": "Патчи", "brand": "A'PIEU", "title": "Патчи для глаз с молочной кислотой Milk One-Day Perfector", "price": 18.00, "concerns": ["dark-circles", "puffiness"], "actives": ["lactic acid", "milk"], "routine_step": "spot"},
{"id": "p09", "category": "body", "sub": "Для душа", "brand": "WEPROVE", "title": "Обновляющий гель для душа Renewing Aloe Shower Gel", "price": 29.00, "concerns": ["body-dryness"], "actives": ["aloe"], "routine_step": "body-cleanse"},
{"id": "p10", "category": "hair", "sub": "Шампуни", "brand": "WEPROVE", "title": "Восстанавливающий шампунь Daily Hair Repairing Shampoo", "price": 23.60, "concerns": ["hair-damage"], "actives": ["repairing complex"], "routine_step": "hair-wash"},
{"id": "p11", "category": "body", "sub": "Кремы для рук", "brand": "Care:Nel", "title": "Питательный крем для рук с ши и центеллой", "price": 14.00, "concerns": ["body-dryness"], "actives": ["shea", "centella"], "routine_step": "body-care"},
{"id": "p12", "category": "accessories", "sub": "Аксессуары", "brand": "Ciracle", "title": "Силиконовый спонж для умывания с чехлом", "price": 9.00, "concerns": [], "actives": [], "routine_step": "tool"}
]
}

View file

@ -0,0 +1,147 @@
#!/usr/bin/env python3
"""Shkor Beauty Assistant — универсальный коннектор.
Один и тот же ассистент — три способа подключения для ЛЮБОГО агента:
1) STDIO MCP-стиль (Claude/любой MCP-клиент):
assistant → connector.py (JSONL: {"method":"tools/list"} по строке)
в конфиге агента:
{"mcpServers": {"shkor": {"command": "python3", "args": [".../connector.py"], "transport": "stdio"}}}
2) HTTP JSON API (агент = просто HTTP-клиент):
connector.py --http 127.0.0.1:8791
POST /tools/list | POST /call {"name": "...", "arguments": {...}}
3) CLI разовый вызов (curl/крон/скрипт):
connector.py --call analyze_skin --args '{"self_report":"стянутость и покраснение"}'
4) Демо-чат (для живой проверки человеком):
connector.py --demo
Форма запроса принимает MCP tools/call и простую {action, args} — см. assistant.handle().
"""
from __future__ import annotations
import argparse
import json
import sys
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from assistant import handle # noqa: E402
# ── 1) STDIO: JSONL, одна строка = один запрос ───────────────────────────────
def serve_stdio() -> None:
print(json.dumps({"ok": True, "transport": "stdio-jsonl",
"hint": "отправляйте JSON-строки, читайте ответ построчно"}, ensure_ascii=False), flush=True)
for line in sys.stdin:
line = line.strip()
if not line:
continue
try:
resp = handle(json.loads(line))
except json.JSONDecodeError as e:
resp = {"error": f"bad json: {e}"}
print(json.dumps(resp, ensure_ascii=False), flush=True)
# ── 2) HTTP ──────────────────────────────────────────────────────────────────
def serve_http(addr: str) -> None:
host, port = addr.split(":")
port_i = int(port)
class H(BaseHTTPRequestHandler):
def _send(self, code: int, payload: dict) -> None:
body = json.dumps(payload, ensure_ascii=False).encode()
self.send_response(code)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def do_GET(self) -> None: # health / открыть в браузере
if self.path in ("/", "/health"):
self._send(200, {"ok": True, "service": "shkor-assistant", "version": 1})
else:
self._send(404, {"error": "not found", "routes": ["POST /tools/list", "POST /call"]})
def do_POST(self) -> None:
try:
n = int(self.headers.get("Content-Length") or 0)
data = json.loads(self.rfile.read(n) or b"{}")
except Exception as e: # noqa: BLE001
self._send(400, {"error": f"bad request: {e}"})
return
if self.path == "/tools/list":
self._send(200, handle({"method": "tools/list"}))
elif self.path == "/call":
self._send(200, handle({"method": "tools/call", "params": data}))
else:
self._send(404, {"error": "not found", "routes": ["POST /tools/list", "POST /call"]})
def log_message(self, *a) -> None: # тише в логи
pass
srv = ThreadingHTTPServer((host, port_i), H)
print(f"shkor-assistant HTTP on http://{host}:{port_i} (POST /tools/list, POST /call)", file=sys.stderr)
srv.serve_forever()
# ── 4) Демо-чат ───────────────────────────────────────────────────────────────
DEMO = [
("analyze_skin", {"self_report": "кожа стянутая после умывания, лоб блестит к обеду, периодически покраснения"}),
("recommend", {"concerns": ["dryness", "redness"], "skin_type": "sensitive", "budget_max": 70}),
("place_order", {"items": [{"id": "p05", "qty": 1}, {"id": "p03", "qty": 1}], "delivery": "minsk_courier",
"name": "Тест", "phone": "+375XX0000000"}),
("usage_instructions", {"product_ids": ["p05", "p03"]}),
("check_order", {}), # order_id подставится после place_order
]
def run_demo() -> None:
last_order = None
for action, args in DEMO:
if action == "check_order":
if not last_order:
continue
args = {"order_id": last_order}
print(f"\n▶ {action} {json.dumps(args, ensure_ascii=False)}")
resp = handle({"method": "tools/call", "params": {"name": action, "arguments": args}})
print(json.dumps(resp.get("result", resp), ensure_ascii=False, indent=1)[:1200])
if action == "place_order" and (resp.get("result") or {}).get("order_id"):
last_order = resp["result"]["order_id"]
def main() -> None:
ap = argparse.ArgumentParser(description="Shkor Beauty Assistant connector")
ap.add_argument("--http", metavar="HOST:PORT", help="поднять HTTP API")
ap.add_argument("--stdio", action="store_true", help="STDIO JSONL (MCP-стиль)")
ap.add_argument("--call", metavar="TOOL", help="разовый вызов инструмента")
ap.add_argument("--args", default="{}", help="JSON-аргументы для --call")
ap.add_argument("--list-tools", action="store_true")
ap.add_argument("--demo", action="store_true")
a = ap.parse_args()
if a.http:
serve_http(a.http)
elif a.stdio:
serve_stdio()
elif a.list_tools:
print(json.dumps(handle({"method": "tools/list"}), ensure_ascii=False, indent=1))
elif a.demo:
run_demo()
elif a.call:
try:
arguments = json.loads(a.args)
except json.JSONDecodeError as e:
sys.exit(f"bad --args json: {e}")
print(json.dumps(handle({"method": "tools/call", "params": {"name": a.call, "arguments": arguments}}),
ensure_ascii=False, indent=1))
else:
ap.print_help()
if __name__ == "__main__":
main()

View file

@ -1,6 +1,7 @@
#!/usr/bin/env bash
# Simbiot Harness: запуск без установки обёртки (cli.py + scheduler-демон).
# simbiot help — справка по CLI-командам (как python cli.py -h)
export SIMBIOT_HOME="${SIMBIOT_HOME:-/var/lib/simbiot-harness}"
# simbiot — без аргументов: статус + как управлять демоном/CLI
# simbiot --help — как help (аргумент для argparse-привычки)
DIR="$(cd "$(dirname "$(readlink -f "$0")")/.." && pwd)"

View file

@ -7,7 +7,7 @@
"amount_usd": 26.7,
"src": "openrouter.ai/api/v1/credits (замер 2026-10-03 23:5x UTC: total=163, usage=136.30)",
"auto": true,
"measured_at": "2026-10-04T00:15:21+00:00"
"measured_at": "2026-10-04T01:13:01+00:00"
},
{
"name": "US LLC Delaware (операционный)",
@ -24,7 +24,7 @@
"amount_usd": 0.0,
"src": "api.stripe.com/v1/balance livemode=True, STRIPE_LIVE_KEY из coop/.env (замер 2026-10-03): available=0, pending=0",
"auto": true,
"measured_at": "2026-10-04T00:15:22+00:00"
"measured_at": "2026-10-04T01:13:01+00:00"
}
],
"debts": [],

View file

@ -0,0 +1,38 @@
# Дайджест hour 2026-10-04 ~07:00 UTC
## Батч задач (3/3 закрыты, все [M])
1. **t-261004061059-84e112** — MCP для продвижения сервисов. Диагноз:
`alex.score.com` не резолвится глобально (зона score.com → мёртвые
NS.SCORE.NET, SERVFAIL по DoH Google/Cloudflare, EDE 22) — деплой туда
невозможен, нужно восстановление делегирования владельцем. Рабочая точка
входа — https://cil.by/mcp (E2E: initialize/whoami OK). Найден и исправлен
прод-баг портала: `/mcp tools/call` падал 500 NameError (mcp регистрируется
раньше stripe/alfa/booking — хелперы `_gen_slots*`/`_send_email`/
`_stripe_product_objects` не определены). Фикс `_late_bind_ctx()` на каждом
tools/call; коммит f0febd9 (shkor-labs/portal), портал перезапущен,
бэкап mcp.py.backup-1004-ctx-latebind. MCP_CONNECT.md дополнен.
2. **t-261004061251-2d3f56** — запись на 20-мин разбор через MCP работает:
booking_slots(type='session') → 70 свободных слотов (18:00–22:00 Минск,
график 3/3), booking_create(kind='session') → jitsi+запись+транскрибация
+ письма/TG. Новый инструмент **coop_catalog**: все сервисы кооператива с
ценами и Stripe pay_url прямо через MCP (live: Симбиот 33/59/1000$,
Рилсап 199/299/699$/мес, Lunara/ReLeaf 4.99$/мес) — полный цикл покупки
без веб-UI (MCP-first).
3. **t-261004062405-fa9926** — MiroFish разобран (клон, ~17.2k строк backend).
Вердикт: движок не берём (стоимость: 100 агентов×40 раундов ≈ $10–25 на
qwen-plus, ×10–30 на премиум; + Zep Cloud SaaS; OASIS/camel-ai стек;
платформа, не библиотека). Идеи заимствования: персоны-из-сущностей графа,
ReportAgent (4 инструмента + интервью агентов + трекер полноты),
episode→граф памяти на своём RAG, дешёвая симуляция спроса перед запусками,
NL→конфиг прогона с budget-cap, IPC-прогресс. Отчёт:
artifacts/mirofish-analysis-2026-10-04.md (доставлен в TG).
## Инфра/события
- Мин-тик: 6× gate:quiet — событий выше порога нет, LLM не запускался.
- Фоновые задачи: нет (list пуст).
- Эскалации: открытых нет; e-261003170542-389f29 (hour Request timed out) уже
acked уровнем day — повторный ack не требуется.
- Внешние сбои, не наша зона: DNS score.com мёртв (см. п.1); TLS on-demand
корректно отвергает alex.cil.by (ask-гейт /api/caddy/ask — by design).

View file

@ -0,0 +1,59 @@
# Дайджест hour 2026-10-04 ~10:00 UTC
## Батч задач (1/2 закрыта; вторая — L, подготовка готова, ждёт Alex)
1. **t-261004091808-70985e [M] — ЗАКРЫТА** (failed[NNN]/DNA у агентов).
Диагноз: heartbeat шли нормально (agent_stats обновлялись каждую минуту), но
репортёр coop-reporter v7 жёстко опрашивал `systemctl is-active hermes-agent` —
у переведённых на Simbiot агентов (Mentalis/Lunar/Rita/Roy/Mon,
harness_type=simbiot) hermes-agent остановлен при переезде, главный юнит —
simbiot-harness.service → дашборд красил живых агентов в failed[NNN].
«DNA — не синхр.» — отдельная причина: harness_setups пуста, simbiot-cli
(node) на машинах агентов никогда не ставился → CLI-sync был невозможен.
Фикс (3 части): (а) репортёр v8 — активный simbiot-harness → статус
«active (simbiot)», плюс сам собирает DNA-факты (skills/SKILL.md + README.md,
скоринг по формуле CLI) и пушит на новый POST /api/agent/dna → upsert
harness_setups; (б) dashboard.py — fallback-матч DNA по developer_name, когда
hostname машины не содержит slug агента (случай AI corp на Ubuntu-2404…);
(в) agents_dashboard.html — фронт принимал только service==='active',
добавлен 'active (simbiot)' (чип «online · simbiot»). Раскатка v8 —
автоматическая через self-update по manifest, заняла ~5 мин на весь флот,
SSH-ручного вмешательства не потребовалось. Верификация по БД: все цели
тикета online · simbiot, DNA=59/100 (Mentalis/Lunar/Rita), Roy/Mon починены
тем же фиксом, 14 агентов с DNA. портал перезапускан ×2 (reporter.py,
dashboard.py), coop-portal active.
2. **t-261004092629-40655b [L] — НЕ закрыта, подготовка выполнена** (подписать
SAFE MFN с предыдущими инвесторами; подписание — сторона Alex).
Сделано: подписной комплект в `/root/coop/fundraising/safe-package/` —
каноническая **YC Post-Money SAFE (MFN Only)** FINAL.docx (форма №3 по
RAISE_PLAN: uncapped, no discount; источник ycombinator.com/safe, скачана
04.10, целостность проверена) + YC SAFE User Guide.pdf + шаблон MFN side
letter + SIGNING_CHECKLIST.md с шагами из постановки. Чек-лист доставлен
Alex в TG (идемпотентно). Блокер по списку инвесторов: в coop.db реальных
инвесторов НЕТ — `backers` содержит 2 тестовые записи, stripe/alfa payments —
тесты, presale_orders — предзаказы, не чеки → список предыдущих инвесторов
ведётся вне портала, у Alex. Остаток (за Alex): список+реквизиты, юр. ревью
формы и side letter до первой подписи ($1.5–3K), санкционный скрининг
каждого инвестора на дату подписания (OFAC/ЕС — критично для резидентов РБ),
подписание, фиксация экземпляров в safe-package/signed/. Задача вернётся
в ready следующего тика.
## Инфра/события
- Мин-тик: 6× gate:quiet — событий выше порога нет, LLM не запускался.
- Фоновые задачи: нет (list пуст).
- Эскалации: открытых нет, ack-ов не требовалось.
- coop-portal перезапускан 2× в рамках фикса (10:18, 10:27), перерыв сервиса
секунды, ошибок в journalctl нет.
- Раскатка coop-reporter v8 прошла на все машины с reporter-токеном; DNA-пуш
идёт с каждым тиком таймера (2 мин), данные обновляются.
- Конфиг уровней: масштабированная контекст-схема по тактам активна
(e-261001082710): min 256k → hour 512k → day 1M ctx_tokens.
## AUM
- Прямых расходов тик не добавил; фиксом убран ложный «failed» по 5 прод-агентам
(сигнальный шум → ложные ремонты/рестарты больше не триггерятся).
- SAFE-комплект приближает закрытие рейз-шага (юр. ревью — плановые $1.5–3K,
решение за Alex).

View file

@ -0,0 +1,58 @@
# Дайджест hour 2026-10-05 ~14:00 UTC
## Батч задач (1/1 закрыта)
1. **t-261005135853-186b42 [M] — ЗАКРЫТА** (гейтвей: входящие документы на диск).
Диагноз подтверждён: `harness/tg_inbox.py` качал только voice (voice-пайплайн),
documents/photo терялись — file_id в событии есть, файла нет (кейс 4 PDF-выписок
Alex 05.10, доставал вручную getFile'ом).
Фикс: `save_document()` — при kind=document|photo getFile→download→атомарная
запись (.part→rename) в `inbox/files/<file_unique_id>.<ext>` (новый
`paths.inbox_files_dir()`, env `SIMBIOT_INBOX_FILES_DIR`); фото — максимальный
размер. В MessageReceived теперь `file_path` (абс, открывается с любого уровня)
+ `file_name`/`file_size`/`mime_type` — триаж видит что за файл без getFile.
Идемпотентно по file_unique_id (повторный апдейт не перекачивает), сбой
скачивания (истёкший у TG файл, HTTP 400/410) не валит поллер — file_id
остаётся в событии для ручного достёгивания. Голосовые — как раньше.
Верификация: `tests/test_tg_documents.py` — 22 PASS (document e2e, photo e2e,
идемпотентность, сбой, _file_ext); регресс voice_video/seen_indication/
inbox_batch — зелёный. Поллер перезапущен, активен (systemd). Коммит c5c3e8f.
Живая проверка: следующий отправленный боту документ сам появится в inbox/files/.
## Батч входящих m-516173771..777 (7 сообщений, один разговор) — отвечено сводно
Контекст: Alex скинул 4 PDF-выписки → «гейтвей их не сохраняет» (→ задача выше,
закрыта этим же тиком) → «сделай реальную ссылку на оплату в баксах с alipay»
→ «на $100» → «ссылку» → «а тут уже почему-то нету алипей, хоть в предыдущей
линке был».
Сделано:
- Боевой $100-линк подтверждён и отдан: https://buy.stripe.com/3cIdR82WH4RpfPY3EY1B60c
(plink_1UNCSvBT53G7FGSQDqXJOE8W, price $100 USD one_time, livemode).
- Почему нет Alipay — разбрано по факам, а не догадкой: через live-API проверена
payment_method_configuration — alipay/wechat_pay/paypal `available=false`
(методы НЕ активированы в боевом аккаунте Stripe), card и amazon_pay — on.
Попытки: пересоздание линка с явными методами → «ensure the type is activated
in your dashboard»; Checkout Session с явными методами → тот же отказ;
включение display_preference через API → невозможно (available=false).
Вывод: единственный путь — ручной шаг в дашборде (Settings → Payment methods),
после чего пересоздание линка — одна команда. Alex'у дана инструкция и обещание
пересоздать по сигналу «алипей включил».
- Amazon Pay в payment links Stripe не поддерживается в принципе (список
допустимых payment_method_types его не содержит; он работает только динамически
в чекауте — потому и был виден в старых линках без явных методов).
## Инциденты/эскалации
- Эскалаций от нижних уровней за час нет (4 тихих минутных тика, 0 событий выше
порога — LLM не запускался, burn экономлен).
- В ходе теста поймал и починил собственный баг: ранние выходы save_document
возвращали кортеж (None, None) вместо None — тест ловит такие вещи именно
поэтому.
## AUM-фильтр часа
Фикс гейтвея — прямая база долгосрочной AUM: до него входящие документы Алекса
(выписки банков — материал для учёта AUM/оборотки) терялись физически, а ручное
доставание съедало тики уровней. Alipay-линк — короткий доход (предзаказы $100),
но блокер снят до предела: осталась одна ручная кнопка у Alex.

View file

@ -147,6 +147,28 @@ def archive_stale_failed(max_age_hours: float = 24.0, now=None) -> list:
return closed
def block_task(task_id: str, reason: str = "ожидает уточнений") -> str:
"""Заблокировать живую задачу: TaskBlocked → статус blocked (t-261004122019).
Задача есть, но не готова к работе — ждёт уточнений от Alex или внешнего
события. scheduler берёт только ready → blocked не попадает в батчи.
Обратно: requeue (blocked → ready). Идемпотентно: уже blocked — no-op.
"""
model = es.run_projections()
t = model["tasks"].get(task_id)
if t is None:
raise ValueError(f"задача {task_id} не найдена")
if t["status"] == "blocked":
return task_id # уже заблокирована — no-op
if t["status"] in ("done", "closed"):
raise ValueError(
f"задача {task_id} в статусе {t['status']} — blocked только для живых; "
f"оживить можно requeue")
es.append_event("task", task_id, "TaskBlocked",
{"by": "manual", "reason": reason[:300]})
return task_id
def requeue_task(task_id: str, reason: str = "manual requeue"):
"""Вернуть failed/running/прерванную задачу в очередь (TaskPromoted → ready).
@ -159,7 +181,7 @@ def requeue_task(task_id: str, reason: str = "manual requeue"):
raise ValueError(f"задача {task_id} не найдена")
# closed тоже возвращаем (авто-схлопывание старых фейлов, e-261003094432):
# закрытая в архив задача может понадобиться снова — осознанная доработка.
if t["status"] not in ("failed", "closed", "done", "running"):
if t["status"] not in ("failed", "closed", "done", "running", "blocked"):
raise ValueError(f"задача {task_id} в статусе {t['status']} — requeue не нужен")
# идемпотентная доставка (t-260930083937): requeue = новая работа над
# задачей, её результат должен уйти в чат заново → сбрасываем факт

View file

@ -93,6 +93,10 @@
.chat-send{border:1px solid var(--ink);background:var(--signal);color:#fff;font-weight:700;font-family:ui-monospace,Menlo,monospace;font-size:12px;letter-spacing:.08em;text-transform:uppercase;padding:0 20px;cursor:pointer;border-radius:6px}
.chat-send:disabled{opacity:.45;cursor:default}
.chat-note{font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--dim);margin:8px 2px 0}
/* живой статус каналов (t-261004103718-d8112b) */
.chan{display:inline-flex;align-items:center;gap:6px;font-family:ui-monospace,Menlo,monospace;font-size:12px;color:var(--ink);margin-right:16px;white-space:nowrap}
.chan .dot{width:8px;height:8px}
.chan small{color:var(--dim);font-size:11px}
/* ── детали агента (t-261002175316-eb24cc) ── */
.back{font-family:ui-monospace,Menlo,monospace;font-size:12px;letter-spacing:.1em;text-transform:uppercase;
border:1px solid var(--ink);background:transparent;color:var(--dim);padding:8px 14px;cursor:pointer;border-radius:6px}
@ -156,7 +160,7 @@
<div class="view" id="view-chat">
<h2 style="margin-top:0">Чат с Simbiot</h2>
<div class="lead">сообщение уходит агенту по порталу-каналу, ответ придёт сам (обычно ≤ 1 мин) · тоже дублируется в TG, если бот подключён</div>
<div class="lead">сообщение уходит агенту по порталу-каналу, ответ придёт сам (обычно ≤ 1 мин) · каналы: <span id="chan-status"><span class="chan"><span class="dot wait"></span>каналы: узнаю…</span></span></div>
<div class="chat">
<div class="chat-log" id="chat-log"><div class="skel">загружаю…</div></div>
<div class="chat-input">
@ -164,7 +168,7 @@
<button class="chat-send" id="chat-send" onclick="chatSend()">Отправить</button>
</div>
</div>
<div class="chat-note" id="chat-note">канал: портал (portal:&lt;agent&gt;) · ответ агент пишет сам по такту</div>
<div class="chat-note" id="chat-note">канал: портал (portal:&lt;agent&gt;) · ответ агент пишет сам по такту · <span id="chan-note">статус каналов: узнаю…</span></div>
</div><!-- /view-chat -->
<div class="view" id="view-agent">
@ -455,6 +459,46 @@ function chatBubble(m){
`<div class="bubble"><span class="who">${esc(who)}${t ? " · " + esc(t) : ""}${pend}${rep}</span>${esc(m.text || "")}</div></div>`;
}
// ── живой статус каналов (t-261004103718-d8112b) ──────────────────────────
// ok → зелёный + возраст последнего успеха; нет свежего штампа → красный.
// hermes-лейбл — только если бэкенд подтвердил маркер старого Гермес-харнеса.
function chanAge(s){
if (s.age_s == null) return "";
if (s.age_s < 60) return s.age_s + "с назад";
if (s.age_s < 3600) return Math.round(s.age_s / 60) + "м назад";
return Math.round(s.age_s / 3600) + "ч назад";
}
function chanChip(name, label, s){
s = s || {};
const cls = s.ok ? "" : "err";
const detail = s.ok ? (chanAge(s) || "успех") : (s.detail || "нет связи");
return `<span class="chan"><span class="dot ${cls}"></span>${label}` +
`<small>${esc(detail)}</small></span>`;
}
function renderChannels(ch){
const box = $("chan-status");
if (!box) return;
if (!ch){ return; } // бэкенд без статусов — оставляем «узнаю…» до след. тика
let html = "";
if (ch.hermes) html += `<span class="chan"><small>⚠ старый харнес (Hermes): данные могут быть устаревшими</small></span>`;
html += chanChip("portal", "портал", ch.portal);
const tg = ch.telegram || {};
if (tg.token_loaded === false){
html += `<span class="chan"><span class="dot err"></span>telegram<small>бот не подключён</small></span>`;
} else {
html += chanChip("tg", "telegram", tg);
}
box.innerHTML = html;
// краткая сводка там же, где раньше жил статичный «тоже дублируется в TG…»
const note = $("chan-note");
if (note){
const p = ch.portal || {}, t = ch.telegram || {};
const fmt = s => s.ok ? `<span style="color:var(--ok)">ok</span> (${esc(chanAge(s) || "сейчас")})`
: `<span style="color:var(--err)">down</span>`;
note.innerHTML = "статус: портал " + fmt(p) + " · telegram " + fmt(t);
}
}
async function chatTick(){
if (getViewName() !== "chat") return;
chatOpen = true;
@ -469,6 +513,7 @@ async function chatTick(){
log.innerHTML = html || `<div class="skel">сообщений пока нет — напиши первым</div>`;
if (stick) log.scrollTop = log.scrollHeight;
$("chat-note").textContent = "канал: портал (portal:" + (s.agent || "?") + ") · ответ агент пишет сам по такту";
renderChannels(s.channels);
}catch(e){
if (e instanceof SyntaxError) return; // полу-JSON от 503 — следующий тик перепишет
log.innerHTML = `<div class="skel">чат недоступен: ${esc(e.message)} — попробую через ${CHAT_REFRESH}с</div>`;

View file

@ -35,7 +35,7 @@ _HEALTH_PORT = int(os.environ.get("SIMBIOT_DASH_HEALTH_PORT", "8766"))
_TOKEN = os.environ.get("SIMBIOT_DASH_TOKEN", "")
_REFRESH = int(os.environ.get("SIMBIOT_DASH_REFRESH", "5"))
_STATUS_ORDER = {"ready": 0, "running": 1, "failed": 2, "done": 3, "closed": 4}
_STATUS_ORDER = {"ready": 0, "running": 1, "blocked": 2, "failed": 3, "done": 4, "closed": 5}
_SIZE_COLOR = {"S": "#1d7a3e", "M": "#a3690f", "L": "#FF4F00", "XL": "#b3261e"}
# ── чат Simbiot прямо в дашборде (t-261001100740-99d55e) ──────────────────
@ -183,7 +183,33 @@ def build_chat(limit: int = 80) -> dict:
collapsed[-1]["ts"] = m["ts"]
continue
collapsed.append(m)
return {"ok": True, "agent": agent_name, "messages": collapsed[-limit:]}
out = {"ok": True, "agent": agent_name, "messages": collapsed[-limit:]}
# живой статус каналов (t-261004103718-d8112b): UI рендерит индикаторы
# вместо статичного «тоже дублируется в TG, если бот подключён»
try:
st = channel_status()
tg = _tg_stats()
tgst = st.get("telegram") or {}
tgst["token_loaded"] = tg.get("token_loaded")
tgst["received_24h"] = tg.get("received_24h")
tgst["last_msg_ts"] = tg.get("last_msg_ts")
# fallback: штампа поллера нет (старый main-харнес до рестарта), но
# токен загружен и свежие TG-сообщения капают в стор → канал жив.
if (not tgst.get("ok") and tg.get("token_loaded")
and (tg.get("last_msg_age_s") is not None)
and tg["last_msg_age_s"] <= 600):
tgst["ok"] = True
tgst["age_s"] = tg["last_msg_age_s"]
tgst["detail"] = "жив по событиям стора (штампа поллера ещё нет)"
st["telegram"] = tgst
st["hermes"] = _hermes_marker()
out["channels"] = st
except Exception as e:
# статус — диагностика: чат не должен падать из-за него, но причина
# должна быть видна (journalctl), иначе «пропавшие индикаторы»
# отлаживаются вслепую (t-261004103718: молчаливый pass скрыл сбой)
print(f"[chat] channels status error: {e}", flush=True)
return out
def chat_send(text: str) -> dict:
@ -291,7 +317,7 @@ def build_state() -> dict:
"tasks_total": len(tasks),
"tasks_by_status": {
s: sum(1 for t in tasks if t["status"] == s)
for s in ("ready", "running", "failed", "done", "closed")
for s in ("ready", "running", "blocked", "failed", "done", "closed")
},
"tasks_by_status_size": counts,
"escalations_open": len(escalations),
@ -610,5 +636,145 @@ def main():
ThreadingHTTPServer((_ADDR, _PORT), _Handler).serve_forever()
# ── живой статус каналов портала/TG (t-261004103718-d8112b) ────────────────
# Каждый реальный пользователь канала (сек-рефлекс portal_channel.sec_reflex,
# TG-поллер tg_inbox.poll_loop) пишет штамп harness/channel_<portal|tg>.json
# после УСПЕШНОГО обмена. Дашборд только читает штампы — сам портал не дёргает
# (GET-проба poll-эндпоинта дублировала бы сек-рефлекс на его айсберге),
# и getUpdates не пробует (конфликт long polling с процессом поллера).
_CHANNEL_MAX_AGE_MS = 10 * 60 * 1000 # штамп старше 10 мин = канал вниз
_portal_probe_cache = {"ts": 0.0, "res": None}
def _portal_probe() -> dict:
"""Fallback-проба портала (когда штампа рефлекса нет/протух).
main-харнес новой версии пишет штамп при каждом сек-рефлексе; пока он не
перезапущен (рестарт откладывается до тихого окна — LLM-воркеры), дашборд
пробует портал сам: GET /channel/poll?since=now — read-only, consumed
ставится только отдельным POST (не делаем). Кэш 60с: клиент чата тикает
каждые 10с — дёргать портал чаще незачем.
"""
import urllib.parse
import urllib.request
now = time.time()
if _portal_probe_cache["res"] and now - _portal_probe_cache["ts"] < 60:
return _portal_probe_cache["res"]
try:
from harness import portal_channel as _pch
cfg = _pch._cfg()
if not (cfg.get("portal_url") and cfg.get("agent_token")
and cfg.get("agent_name")):
res = {"ok": False, "age_s": None, "ts": 0,
"detail": "канал не сконфигурён (нет /etc/coop-reporter.env)"}
else:
url = (f"{cfg['portal_url']}/api/agent/"
f"{urllib.parse.quote(cfg['agent_name'])}"
f"/channel/poll?since={int(now * 1000)}")
req = urllib.request.Request(
url, headers={"X-Agent-Token": cfg["agent_token"]})
with urllib.request.urlopen(req, timeout=5) as r:
json.loads(r.read().decode())
res = {"ok": True, "age_s": 0, "ts": int(now * 1000),
"detail": "проба ok (штампа рефлекса ещё нет — старый main-харнес)"}
except Exception as e:
res = {"ok": False, "age_s": None, "ts": 0,
"detail": f"проба не прошла: {str(e)[:80]}"}
_portal_probe_cache.update(ts=now, res=res)
return res
# Старый харнес на Гермесе (hermes-rita, 2.28.125.152 — docs/migration-hermes.md)
# не узнаётся по /etc/coop-reporter.env (документированные ключи: PORTAL_URL,
# AGENT_TOKEN). Без честного признака не рисуем "старый харнес" в UI.
_HERMES_MARKER = None
def _hermes_marker() -> str | None:
"""Признак, что эти файлы запущены на старом Гермес-харнесе (для UI).
Пока легитимного маркера нет — None (Simbiot-дашборд ни с чем не путаем).
"""
return _HERMES_MARKER
def channel_status() -> dict:
"""Живой статус каналов чата: portal (poll портала) + telegram (inbox).
Каждый канал: ok (свежий успешный обмен), age_s (возраст штампа),
ts (ms последнего успеха), detail (что именно успешно).
"""
now_ms = int(time.time() * 1000)
def _read(name: str) -> dict:
path = os.path.join(_HERE, f"channel_{name}.json")
try:
with open(path) as f:
d = json.load(f)
ts = int(d.get("ts") or 0)
age = now_ms - ts
if age < 0 or age > _CHANNEL_MAX_AGE_MS:
raise FileNotFoundError # штамп протух → считаем как отсутствующий
return {"ok": bool(d.get("ok")), "age_s": round(age / 1000),
"ts": ts, "detail": str(d.get("detail") or "")[:120]}
except (FileNotFoundError, ValueError, TypeError):
# штампа нет (файл не создан / битый / старше 10 мин).
# portal: честная fallback-проба (штамп пишет main-харнес новой
# версии — до его рестарта проба даёт живую правду).
if name == "portal":
return _portal_probe()
return {"ok": False, "age_s": None, "ts": 0,
"detail": "нет свежего штампа поллера"}
except Exception as e:
return {"ok": False, "age_s": None, "ts": 0,
"detail": f"штамп не читается: {str(e)[:80]}"}
return {"portal": _read("portal"), "telegram": _read("tg"),
"checked_at": now_ms}
def _tg_stats(days: float = 1.0) -> dict:
"""TG-факты из стора: токен загружен? + входящие за последние 24ч."""
from datetime import datetime, timedelta, timezone
tg_token = None
try:
from harness import tg_inbox as _tg
_tg._load_env()
tg_token = bool(_tg.TG_TOKEN)
except Exception:
pass
n24, last_ts = 0, None
try:
db = es._conn()
cutoff = (datetime.now(timezone.utc)
- timedelta(hours=int(days * 24))).isoformat()
# portal-канальные MessageReceived пишет НЕ TG-поллер — исключаем:
# иначе живой портал-чат рисовал бы «зелёный» telegram (найдено в
# t-261004103718: portal ok, tg штампа нет — а события капают).
row = db.execute(
"SELECT count(*) AS n, max(timestamp) AS last FROM events "
"WHERE event_type='MessageReceived' AND timestamp >= ? "
"AND (data IS NULL OR data NOT LIKE '%\"chat_id\": \"portal%')",
(cutoff,)).fetchone()
db.close()
n24 = int(row["n"] or 0)
last_ts = row["last"]
except Exception:
pass
age_s = None
if last_ts:
try:
from datetime import datetime as _dt
age_s = max(0, round(time.time()
- _dt.fromisoformat(str(last_ts)).timestamp()))
except Exception:
age_s = None
return {"token_loaded": tg_token, "received_24h": n24, "last_msg_ts": last_ts,
"last_msg_age_s": age_s}
if __name__ == "__main__":
main()

View file

@ -268,8 +268,16 @@ def build_context(level_cfg, model: dict, batch=None):
digests.sort(key=lambda d: d["ts"], reverse=True)
if digests:
parts.append("ВЫЖИМКИ НИЖНИХ УРОВНЕЙ (последние, сжатые):")
# дедуп по тексту (эксп. #1, t-261005121525): тихий мин-уровень давал
# 6 идентичных [gate:quiet]-строк в каждом шаге промпта — чистый burn.
# Теперь повтор схлопывается в одну строку с счётчиком ×N.
seen: dict = {}
for d in digests[:6]:
parts.append(f"- [{d['level']}] {d['text'][:400]}")
key = (d["level"], d["text"][:400])
seen[key] = seen.get(key, 0) + 1
for (lvl, text), n in seen.items():
rep = f" (×{n} подряд)" if n > 1 else ""
parts.append(f"- [{lvl}] {text}{rep}")
# эскалации: открытые, адресованные этому уровню и выше — в приоритете
escs = []

View file

@ -99,7 +99,7 @@ def cmd_status() -> str:
st = cs.get(name) or {}
err = f", ошибок {st['errors']}" if st.get("errors") else ""
lines.append(f" {name}: тиков {st.get('ticks', 0)}{err}")
parts = [f"{k} {counts[k]}" for k in ("ready", "running", "done", "failed") if counts.get(k)]
parts = [f"{k} {counts[k]}" for k in ("ready", "running", "blocked", "failed", "done") if counts.get(k)]
lines.append("📋 задачи: " + (" · ".join(parts) if parts else "нет"))
lines.append(f"🚨 эскалаций открытых: {open_esc}")
lines.append(f"💰 спенд (последние {len(spend)} тиков): ~{tokens:,} токенов".replace(",", " "))

View file

@ -0,0 +1 @@
{"error":{"message":"[gemini/gemini-3.1-flash-image-preview] [429]: You exceeded your current quota, please check your plan and billing details. For more information on this error, head to: https://ai.google.dev/gemini-api/docs/rate-limits. To monitor your current usage, head to: https://ai.dev/rate-limit. \n* Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_requests, limit: 0, model: gemini-3.1-flash-image\n* Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_requests, limit: 0, model: gemini-3.1-flash-image\n* Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_input_token_count, limit: 0, model: gemini-3.1-flash-image\nPlease retry in 12h6m1.119176629s. (reset after 4s)"}}

View file

@ -0,0 +1,54 @@
# Simbiot — питч для видео (Seedance)
## 1. Что говорить на камеру (~30 сек, живым языком)
> Представь: ты заходишь в стартап — и через неделю у тебя работает не один ассистент,
> а целая команда ИИ-агентов: один отвечает клиентам, второй пишет код, третий следит
> за деньгами и метриками.
>
> Это Simbiot. Обычно фаундер собирает зоопарк из агентов и харнесов — а когда уходит
> разработчик, вся эта магия уходит вместе с ним. Simbiot — это общий харнес организации:
> слой поверх Hermes, Claude Code и других. Он координирует флот агентов, фиксирует
> решения компании и копит удачные приёмы как «гены» — и оставляет только те, что
> реально приносят прибыль.
>
> Мы уже гоняем его на семи собственных продуктах. Строй компанию — а не зоопарк ботов.
Хронометраж: ~35 сек. Если нужно короче (15 сек) — оставить первый и последний абзац.
## 2. Промт для Seedance (весь, целиком — скормить как есть)
Seedance генерирует клипы по 5–10 сек, поэтому видео собирается из 3 шотов
( narrator говорит поверх — в кадре речи не нужно, генерим b-roll ).
**Общий стиль (добавлять в конец каждого промта):**
> Dark cinematic tech aesthetic, deep navy-black background (#0a0c12) with violet
> and purple glow accents (#7c6cf5, #8b5cf6), soft volumetric light, shallow depth
> of field, film grain, high detail, 4k, smooth camera motion, no text, no captions,
> no people talking, ambient mood. 16:9.
**Shot 1 (0–10 сек) — «зоопарк в хаосе»:**
> Slow dolly-in through a dark server room: dozens of small glowing robots and holographic
> screens flickering chaotically, tangled cables, scattered disconnected UI panels floating
> in the air, each glowing a different cold color, subtle digital glitches.
**Shot 2 (10–20 сек) — «Simbiot orchestration»:**
> The chaos converges: tangled light streams flow into one glowing violet orb at the center
> of a dark control room, holographic dashboard panels organize around it in a perfect grid,
> elegant lines of data pulse from the orb to the panels like a neural network heartbeat,
> slow orbit camera movement.
**Shot 3 (20–30 сек) — «финал / бренд»:**
> Wide cinematic shot of a modern dark startup office at night, a founder silhouette
> calmly watching a large wall display where agent dashboards grow revenue charts,
> violet light washes the room, camera slowly pulls back through a window into the
> city skyline at dusk.
## 3. Быстрый вариант (один промт, если лень склеивать)
> Cinematic 30-second tech brand film: dark navy-black world with violet glow (#7c6cf5).
> It opens on chaos — dozens of flickering holographic screens and tangled light streams.
> A luminous violet orb emerges and orchestrates everything: panels align into a grid,
> data flows like a heartbeat. Final wide shot: founder silhouette watching revenue
> charts grow on a wall of dashboards in a dark modern office. Slow dolly and orbit
> camera, volumetric light, film grain, no text, 16:9.

129
scripts/alipay_watch.py Normal file
View file

@ -0,0 +1,129 @@
#!/usr/bin/env python3
"""Ватчер Alipay на live-счёте Stripe (эскалация e-261005142309-390896).
Контекст: Alex включил тумблер Alipay в дашборде (preference=on), но
capability ещё not available — счёт ждёт аппрува Stripe («говорит что нужен
ещё аппрувал, ждем», m-516173789). Включить через API нельзя (rk_live
restricted, available=false).
Этот скрипт — детерминированный поллинг без LLM (systemd-таймер, 5 мин):
- пока alipay.available=false → тихо выходим (state=waiting);
- как только Stripe активирует capability → создаёт Checkout Session
$100 one_time (price_1UNCYGBT53G7FGSQya04h28r) с card+alipay и
идемпотентно (cli.py tg-notify -k) отправляет ссылку в чат;
- после успешной доставки пишет done-маркер и больше не работает.
Ключ читается из /root/coop/.env (STRIPE_LIVE_KEY, реестр KEYS-REGISTRY).
Состояние: /var/lib/simbiot-harness/alipay-watch.state.
Выход: 0 = ждём/отработал, 3 = ошибка конфигурации (для журнала).
"""
import json
import os
import subprocess
import sys
import urllib.error
import urllib.parse
import urllib.request
STATE = "/var/lib/simbiot-harness/alipay-watch.state"
ENV_FILE = "/root/coop/.env"
PRICE = "price_1UNCYGBT53G7FGSQya04h28r" # $100 USD one_time, live
NOTIFY_KEY = "alipay-live-100-link" # идемпотентность доставки (t-260930083937)
HARNESS = "/root/simbiot-harness"
VENV_PY = f"{HARNESS}/venv/bin/python"
def _stripe_key() -> str | None:
try:
with open(ENV_FILE) as f:
for line in f:
line = line.strip()
if line.startswith("STRIPE_LIVE_KEY="):
return line.split("=", 1)[1].strip().strip('"').strip("'")
except OSError:
pass
return os.environ.get("STRIPE_LIVE_KEY")
def _call(path: str, method: str = "GET", data: dict | None = None) -> dict:
key = _stripe_key()
if not key:
return {"_error": "no STRIPE_LIVE_KEY"}
body = urllib.parse.urlencode(data).encode() if data is not None else None
req = urllib.request.Request(
"https://api.stripe.com/v1/" + path,
headers={"Authorization": f"Bearer {key}"},
method=method,
data=body,
)
try:
with urllib.request.urlopen(req, timeout=25) as r:
return json.loads(r.read())
except urllib.error.HTTPError as e:
return {"_error": e.code, "body": e.read().decode()[:400]}
def _read_state() -> str:
try:
with open(STATE) as f:
return f.read().strip()
except OSError:
return ""
def _write_state(s: str) -> None:
os.makedirs(os.path.dirname(STATE), exist_ok=True)
with open(STATE, "w") as f:
f.write(s)
def main() -> int:
if _read_state() == "done":
return 0 # ссылка создана и доставлена — ватчер отработал
cfgs = _call("payment_method_configurations?limit=100")
for c in cfgs.get("data", []):
alipay = c.get("alipay") or {}
if alipay.get("available"):
# capability активна — создаём линк card+alipay
sess = _call("checkout/sessions", "POST", {
"mode": "payment",
"line_items[0][price]": PRICE,
"line_items[0][quantity]": "1",
"success_url": "https://cil.by/thanks",
"cancel_url": "https://cil.by/",
"payment_method_types[0]": "card",
"payment_method_types[1]": "alipay",
})
url = sess.get("url")
if not url:
# доступна, но сессия не собирается — оставить ждущим,
# причина в журнале таймера; следующий прогон повторит
_write_state("available-but-failed")
print("alipay available, session failed:",
sess.get("_error"), sess.get("body", "")[:200])
return 0
text = (f"Alipay активирован на live-счёте Stripe. "
f"Боевая ссылка $100 (card + Alipay): {url}")
r = subprocess.run(
[VENV_PY, f"{HARNESS}/cli.py", "tg-notify", text,
"-k", NOTIFY_KEY],
capture_output=True, text=True, timeout=60,
)
out = (r.stdout or "").strip()
print("notify:", out)
if out.startswith("OK") or "SKIP" in out:
_write_state("done")
return 0
_write_state("notify-failed")
return 0
print(f"waiting: alipay preference={alipay.get('display_preference', {}).get('value')} "
f"available={alipay.get('available')} (cfg {c.get('id')})")
_write_state("waiting")
return 0
print("no live payment_method_configuration found:", cfgs.get("_error", "?"))
return 3
if __name__ == "__main__":
sys.exit(main())

14
scripts/nightly-selfimprove.sh Executable file
View file

@ -0,0 +1,14 @@
#!/bin/bash
# Ночная самодоработка харнеса (директива Alex m-516173718, эскалация e-261005074512):
# подаёт задачу [M] в очередь харнеса — ближайший hour-тик подбирает и исполняет.
# Журнал итераций: artifacts/self-improvement-log.md
cd /root/simbiot-harness || exit 1
TITLE="Ночная самодоработка харнеса: ретро + эксперимент"
# идемпотентность: не дублируем, если задача уже ждёт/выполняется
if ./venv/bin/python cli.py tasks 2>/dev/null | grep -F "$TITLE" | grep -qE ' (ready|running)'; then
echo "$(date -u +%FT%TZ) skip: задача уже в очереди" >> /var/log/simbiot-selfimprove.log
exit 0
fi
./venv/bin/python cli.py task "$TITLE" \
-d "Директива Alex m-516173718 (e-261005074512). 1) Ретро суток: cli.py events, cli.py status, cli.py aum — что сработало/что сломалось (упавшие тики, эскалации, закрытые задачи, burn). 2) Один эксперимент над собой: гипотеза вида «метрика X -> изменение -> ожидание»; лёгкий провести сейчас в пределах контекста, тяжёлый оформить задачей на день. 3) Журнал: artifacts/self-improvement-log.md — дописать строку по формату (#N | дата | гипотеза | метрика до->после | вывод). 4) Ночь — тишина в чате (кроме критичного), результат уходит в ответ задачи, его поднимет утренний дайджест. Фильтр AUM: эксперимент служит +1% к AUM в год+." \
-m 30

View file

@ -0,0 +1,4 @@
{
"jdkPath": "/usr/lib/jvm/java-17-openjdk-amd64",
"androidSdkPath": "/opt/android-sdk"
}

View file

@ -0,0 +1,8 @@
{
"permission": {
"edit": "allow",
"bash": "allow",
"webfetch": "allow"
},
"share": "disabled"
}