- harness/es.py: append-only event store + проекции (образец — кооп-портал) - harness/levels.py: уровни 1с/1м/1ч/1д, контекст ∝ масштабу - harness/scheduler.py: уровневые потоки, идемпотентные тики, absorbed-стабы - harness/isolated_runner.py + _llm_worker.py: LLM-тик в сабпроцессе (глобальный asyncio-рантайм Strands не переживает 2+ create_harness) - cli.py: init/task/tasks/status/events + requeue (написан самим минутным агентом) - 9router: отдельный ключ sk-simbiot, модель auto (glm-5.3 первым)
426 lines
No EOL
39 KiB
HTML
426 lines
No EOL
39 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Simbiot Harness — архитектура v0.1 (ревью)</title>
|
||
<style>
|
||
:root{
|
||
--paper:#faf8f3; --ink:#15150f; --carbon:#1c1b16; --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:1080px;margin:0 auto;padding:48px 28px 120px}
|
||
header{border-bottom:3px solid var(--ink);padding-bottom:28px;margin-bottom:40px;position:relative}
|
||
.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(34px,6vw,58px);line-height:1.02;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:64px 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:22px}
|
||
h3{font-size:15px;text-transform:uppercase;letter-spacing:.06em;margin:26px 0 10px;border-left:3px solid var(--signal);padding-left:10px}
|
||
p{margin:10px 0;max-width:820px}
|
||
.cardgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:14px;margin:18px 0}
|
||
.card{border:1px solid var(--ink);background:var(--card);padding:16px 16px 14px}
|
||
.card .cn{font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--signal);font-weight:700;letter-spacing:.15em}
|
||
.card h4{font-size:15px;margin:6px 0 6px;text-transform:uppercase;letter-spacing:.02em}
|
||
.card p{font-size:13.5px;color:var(--dim);margin:0;line-height:1.55}
|
||
table{width:100%;border-collapse:collapse;font-size:13.5px;margin:14px 0}
|
||
th{background:var(--ink);color:var(--paper);text-align:left;padding:9px 10px;font-family:ui-monospace,Menlo,monospace;font-size:11px;letter-spacing:.1em;text-transform:uppercase}
|
||
td{border-bottom:1px solid var(--line);padding:9px 10px;vertical-align:top}
|
||
tr.hl td{background:#fff3ea}
|
||
td .m{font-family:ui-monospace,Menlo,monospace;font-size:12px}
|
||
.pill{display:inline-block;border:1px solid var(--ink);padding:1px 7px;font-family:ui-monospace,Menlo,monospace;font-size:11px;white-space:nowrap}
|
||
.pill.s{border-color:var(--signal);color:var(--signal)}
|
||
figure{margin:26px 0;border:1px solid var(--ink);background:#fff}
|
||
figcaption{font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--dim);padding:8px 12px;border-top:1px solid var(--line);letter-spacing:.05em}
|
||
.steps{display:flex;flex-wrap:wrap;gap:0;margin:20px 0;counter-reset:st}
|
||
.step{flex:1 1 150px;min-width:150px;border:1px solid var(--ink);border-left:none;padding:14px 14px 12px;background:var(--card);position:relative;counter-increment:st}
|
||
.step:first-child{border-left:1px solid var(--ink)}
|
||
.step::before{content:counter(st,decimal-leading-zero);font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--signal);font-weight:700;letter-spacing:.15em}
|
||
.step b{display:block;font-size:13.5px;margin:5px 0 4px;text-transform:uppercase;letter-spacing:.02em}
|
||
.step span{font-size:12.5px;color:var(--dim);line-height:1.5;display:block}
|
||
.note{border:1px solid var(--ink);border-left:4px solid var(--signal);padding:14px 18px;margin:18px 0;background:var(--card);font-size:14.5px}
|
||
.note b{text-transform:uppercase;letter-spacing:.04em;font-size:13px}
|
||
.pyr{border:1px solid var(--ink);margin:18px 0;background:#fff}
|
||
.pyr .lvl{display:flex;align-items:center;gap:14px;padding:13px 16px;border-bottom:1px solid var(--line)}
|
||
.pyr .lvl:last-child{border-bottom:none}
|
||
.pyr .w{flex:0 0 130px;font-family:ui-monospace,Menlo,monospace;font-size:12px;font-weight:700;letter-spacing:.05em}
|
||
.pyr .bar{height:10px;background:var(--signal);opacity:.9}
|
||
.pyr .d{flex:1;font-size:13px;color:var(--dim)}
|
||
.two{display:grid;grid-template-columns:1fr 1fr;gap:18px}
|
||
@media(max-width:840px){.two{grid-template-columns:1fr}}
|
||
.box{border:1px solid var(--ink);padding:16px;background:var(--card)}
|
||
.box h5{font-family:ui-monospace,Menlo,monospace;font-size:11px;letter-spacing:.15em;text-transform:uppercase;color:var(--signal);margin-bottom:10px}
|
||
ul{margin:8px 0 8px 18px;font-size:14px}
|
||
li{margin:5px 0}
|
||
li::marker{color:var(--signal)}
|
||
.q{border:1px solid var(--ink);padding:14px 16px;margin:10px 0;display:flex;gap:12px;background:#fff}
|
||
.q .cb{flex:0 0 16px;height:16px;border:2px solid var(--ink);margin-top:2px}
|
||
.q b{font-size:14px}
|
||
.q p{font-size:13px;color:var(--dim);margin:3px 0 0;max-width:none}
|
||
footer{margin-top:80px;border-top:3px solid var(--ink);padding-top:18px;font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--dim);display:flex;justify-content:space-between;flex-wrap:wrap;gap:8px;letter-spacing:.05em}
|
||
svg text{font-family:ui-monospace,Menlo,monospace}
|
||
@media print{body{background:#fff}.wrap{padding:10px}section{page-break-inside:avoid}}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="wrap">
|
||
|
||
<header>
|
||
<div class="kicker">Архитектура · для ревью</div>
|
||
<h1>Simbiot Harness</h1>
|
||
<div class="sub">Принципиально новый агентный харнес на CQRS + Event Sourcing: агент живёт в тактах разного масштаба — секунда, минута, час, день, неделя, месяц, квартал, год — как человек. Стартовая точка — <b>strands-agents/harness-sdk</b> (Apache-2.0): его исполнительное ядро, поверх — наша временная ось и событийная спина.</div>
|
||
<div class="badges">
|
||
<span class="badge s">CQRS + Event Sourcing</span>
|
||
<span class="badge">Strands Harness SDK</span>
|
||
<span class="badge">9router — LLM-шлюз</span>
|
||
<span class="badge">Python 3.12 · SQLite WAL</span>
|
||
<span class="badge">v0.1 · ревью пройдено 29.09</span>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- ================= 01 ================= -->
|
||
<section id="s01">
|
||
<div class="snum">01 / СУТЬ</div>
|
||
<h2>Что мы строим</h2>
|
||
<p class="lead">Все существующие харнесы (Strands, Hermes, OpenClaw) — пассивны: они просыпаются, когда пришёл внешний запрос, и не имеют собственной временной оси. Simbiot Harness — экзистенциальный: он тикает сам.</p>
|
||
<p>Харнес — это не «функция, которую вызывают», а <b>живой процесс с ритмами</b>. Восемь вложенных циклов бьют одновременно: секундный — как сердцебиение, минутный — как реакция, часовой — как рабочий поток, дневной — как план дня, недельный — как ретро, месячный — как цели, квартальный — как стратегия, годовой — как идентичность. Чем крупнее цикл, тем больше контекста он может себе позволить и тем крупнее задачу он вправе взять.</p>
|
||
<p>Всё, что происходит — тики, решения, наблюдения, память, трата бюджета — это <b>события в append-only сторе</b>. Состояние харнеса не хранится, а <b>выводится replay'ем событий</b>. Выключили процесс — включили — он продолжил с того же места, потому что его «личность» — это события, а не RAM.</p>
|
||
<div class="note"><b>Одной фразой:</b> Strands даёт нам тело (агентный цикл, инструменты, хуки). Simbiot Harness даёт ему метаболизм — время, память и ритм — на событийной спине.</div>
|
||
</section>
|
||
|
||
<!-- ================= 02 ================= -->
|
||
<section id="s02">
|
||
<div class="snum">02 / ПРИНЦИПЫ</div>
|
||
<h2>Шесть принципов</h2>
|
||
<div class="cardgrid">
|
||
<div class="card"><div class="cn">P-1</div><h4>Всё есть событие</h4><p>Тик, взятая задача, дайджест, трасса каждого tool-call агента — события. Event store — единственный источник правды. Никакого «состояния в RAM».</p></div>
|
||
<div class="card"><div class="cn">P-2</div><h4>Тактовая лестница</h4><p>8 уровней: 1с → 1м → 1ч → 1д → 1н → 1мес → 1кв → 1г. У каждого — свой мандат, бюджет и модель. Мелкое решает мелкое, крупное — крупное.</p></div>
|
||
<div class="card"><div class="cn">P-3</div><h4>Контекст ∝ масштабу</h4><p>Секундный цикл не зовёт LLM вовсе, минутный получает 4K токенов, дневной — 128K, годовой — до 1M. Задача S-класса не занимает слот годового цикла.</p></div>
|
||
<div class="card"><div class="cn">P-4</div><h4>Непрерываемость</h4><p>Запущенная задача не прерывается тиками. Если исполнитель уровня занят — тик поглощается (skip), а не копится в очередь. Работа важнее метронома.</p></div>
|
||
<div class="card"><div class="cn">P-5</div><h4>Переваривание</h4><p>Верхние циклы едят выжимку нижних: дневной читает не сырые минуты, а часовые дайджесты. Пирамида консолидации памяти — как сон у человека.</p></div>
|
||
<div class="card"><div class="cn">P-6</div><h4>Воспроизводимость</h4><p>Стор можно переиграть с нуля — и харнес, и память, и решения соберутся заново. Отладка агента = чтение потока событий, а не логов.</p></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ================= 03 ================= -->
|
||
<section id="s03">
|
||
<div class="snum">03 / ЛЕСТНИЦА</div>
|
||
<h2>Тактовая лестница</h2>
|
||
<p class="lead">Каждый уровень — отдельная «личность»: свой мандат (что ему можно), свой бюджет контекста, своя модель и свой класс задач. Цикл может взять задачу своего класса и ниже; задача выше класса — ожидает уровень повыше.</p>
|
||
<table>
|
||
<tr><th>Цикл</th><th>Мандат</th><th>Мозг (9router)</th><th>Контекст</th><th>Задачи</th><th>Аналогия у человека</th></tr>
|
||
<tr><td class="m"><b>1 сек</b></td><td>Рефлекс: телеметрия, watchdog, heartbeat, алерты</td><td>без LLM — детерминированный код</td><td class="m">0</td><td><span class="pill">микро</span> замер, триггер</td><td>Сердцебиение</td></tr>
|
||
<tr><td class="m"><b>1 мин</b></td><td>Пульс: триаж входящих, быстрые ответы, мелкие фиксы</td><td><span class="m">glm-5.3-flash</span></td><td class="m">≤ 4K</td><td><span class="pill s">S</span> ≤ 5 мин работы</td><td>Реакция в разговоре</td></tr>
|
||
<tr><td class="m"><b>1 час</b></td><td>Дайджест: сборка часа, батч мелочей, проверка прогресса</td><td><span class="m">glm-5.3-flash</span></td><td class="m">≤ 32K</td><td><span class="pill s">M</span> ≤ 1 ч</td><td>Рабочий поток</td></tr>
|
||
<tr class="hl"><td class="m"><b>1 день</b></td><td>Манифест: план дня, выбор задач, дневной отчёт</td><td><span class="m">glm-5.3 (full)</span></td><td class="m">≤ 128K</td><td><span class="pill s">L</span> ≤ 1 день</td><td>План на день</td></tr>
|
||
<tr><td class="m"><b>1 неделя</b></td><td>Ретро: разбор недели, обучение, обновление скиллов</td><td><span class="m">glm-5.3 · effort: high</span></td><td class="m">≤ 200K</td><td><span class="pill s">XL</span> неделя</td><td>Недельное ретро</td></tr>
|
||
<tr><td class="m"><b>1 месяц</b></td><td>Цели: бюджет, OKR, ранги, накопления</td><td><span class="m">full</span></td><td class="m">≤ 256K</td><td><span class="pill s">XXL</span> месяц</td><td>Месячная цель</td></tr>
|
||
<tr><td class="m"><b>1 квартал</b></td><td>Стратегия: пересмотр направления, ставки</td><td><span class="m">pro-слот*</span></td><td class="m">≤ 512K</td><td><span class="pill s">XXL</span> квартал</td><td>Квартальная стратегия</td></tr>
|
||
<tr><td class="m"><b>1 год</b></td><td>Идентичность: миссия, «зачем существую», самоопределение</td><td><span class="m">pro-слот*</span></td><td class="m">≤ 1M</td><td><span class="pill s">XXL</span> год</td><td>Жизненный выбор</td></tr>
|
||
</table>
|
||
<p style="font-size:13px;color:var(--dim)">* pro-слот — зарезервировано под сильную модель (Claude Opus как личный брейн / Gemini 3 Pro через KIE): годовой цикл тикает 1 раз, дорогой запуск раз в год — приемлемо.</p>
|
||
</section>
|
||
|
||
<!-- ================= 04 ================= -->
|
||
<section id="s04">
|
||
<div class="snum">04 / ДИАГРАММА</div>
|
||
<h2>Архитектура</h2>
|
||
<figure>
|
||
<svg viewBox="0 0 1140 620" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;display:block">
|
||
<!-- defs -->
|
||
<defs>
|
||
<marker id="arr" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0L10 5L0 10z" fill="#15150f"/></marker>
|
||
<marker id="arrO" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0L10 5L0 10z" fill="#FF4F00"/></marker>
|
||
</defs>
|
||
<style>
|
||
.bx{fill:#f3f0e8;stroke:#15150f;stroke-width:1.5}
|
||
.bxW{fill:#fff;stroke:#15150f;stroke-width:1.5}
|
||
.bxS{fill:#15150f;stroke:#15150f;stroke-width:1.5}
|
||
.lab{fill:#15150f;font-size:13px;font-weight:700;letter-spacing:.08em}
|
||
.labS{fill:#faf8f3;font-size:13px;font-weight:700;letter-spacing:.08em}
|
||
.sub{fill:#6b675e;font-size:11px;letter-spacing:.02em}
|
||
.sig{fill:#FF4F00;font-size:10px;font-weight:700;letter-spacing:.2em}
|
||
.wire{stroke:#15150f;stroke-width:1.5;fill:none}
|
||
.wireO{stroke:#FF4F00;stroke-width:1.6;fill:none}
|
||
.wireD{stroke:#6b675e;stroke-width:1.2;fill:none;stroke-dasharray:5 4}
|
||
</style>
|
||
|
||
<!-- ====== ROW 1: CQRS plane ====== -->
|
||
<text x="20" y="34" class="sig">CQRS — ПЛОСКОСТЬ КОМАНД И ЗАПРОСОВ</text>
|
||
<!-- interfaces -->
|
||
<rect x="20" y="48" width="180" height="92" class="bx"/>
|
||
<text x="34" y="74" class="lab">ИНТЕРФЕЙСЫ</text>
|
||
<text x="34" y="94" class="sub">Telegram · Matrix · Web</text>
|
||
<text x="34" y="110" class="sub">cron · webhook · человек</text>
|
||
<!-- command side -->
|
||
<rect x="250" y="48" width="190" height="92" class="bx"/>
|
||
<text x="264" y="74" class="lab">COMMAND SIDE</text>
|
||
<text x="264" y="94" class="sub">валидация · идемпотентность</text>
|
||
<text x="264" y="110" class="sub">submit_task · set_policy</text>
|
||
<!-- event store -->
|
||
<rect x="490" y="48" width="220" height="230" class="bxS"/>
|
||
<text x="508" y="76" class="labS">EVENT STORE</text>
|
||
<text x="508" y="96" fill="#FF4F00" font-size="10" font-weight="700" letter-spacing=".15em">APPEND-ONLY · СПИНА</text>
|
||
<text x="508" y="122" fill="#9a958a" font-size="11px">events(aggregate, id,</text>
|
||
<text x="508" y="138" fill="#9a958a" font-size="11px"> type, data, version)</text>
|
||
<text x="508" y="170" fill="#9a958a" font-size="11px">CycleTicked · TaskClaimed</text>
|
||
<text x="508" y="186" fill="#9a958a" font-size="11px">DigestWritten · BudgetSpent</text>
|
||
<text x="508" y="218" fill="#9a958a" font-size="11px">replay → вся «личность»</text>
|
||
<text x="508" y="234" fill="#9a958a" font-size="11px">харнеса восстанавливается</text>
|
||
<text x="508" y="262" fill="#FF4F00" font-size="10" font-weight="700">SQLite · WAL</text>
|
||
<!-- projections -->
|
||
<rect x="760" y="48" width="200" height="110" class="bx"/>
|
||
<text x="774" y="74" class="lab">ПРОЕКЦИИ</text>
|
||
<text x="774" y="94" class="sub">task_board · digest_*</text>
|
||
<text x="774" y="110" class="sub">metrics · agent_state</text>
|
||
<text x="774" y="126" class="sub">(перестраиваются replay'ем)</text>
|
||
<!-- query side -->
|
||
<rect x="1000" y="48" width="120" height="110" class="bx"/>
|
||
<text x="1014" y="74" class="lab">QUERY</text>
|
||
<text x="1014" y="94" class="sub">API · дашборд</text>
|
||
<text x="1014" y="110" class="sub">человек смотрит</text>
|
||
<!-- arrows row1 -->
|
||
<line x1="200" y1="94" x2="245" y2="94" class="wire" marker-end="url(#arr)"/>
|
||
<line x1="440" y1="94" x2="485" y2="94" class="wireO" marker-end="url(#arrO)"/>
|
||
<text x="446" y="84" class="sig">APPEND</text>
|
||
<line x1="710" y1="94" x2="755" y2="94" class="wire" marker-end="url(#arr)"/>
|
||
<text x="716" y="84" class="sig">STREAM→</text>
|
||
<line x1="960" y1="103" x2="995" y2="103" class="wire" marker-end="url(#arr)"/>
|
||
|
||
<!-- ====== ROW 2: temporal plane ====== -->
|
||
<text x="20" y="322" class="sig">ВРЕМЕННАЯ ПЛОСКОСТЬ — ТАКТОВАЯ ЛЕСТНИЦА</text>
|
||
<!-- scheduler -->
|
||
<rect x="20" y="336" width="180" height="180" class="bx"/>
|
||
<text x="34" y="362" class="lab">SCHEDULER</text>
|
||
<text x="34" y="382" class="sub">8 уровней бьют по границам</text>
|
||
<text x="34" y="398" class="sub">тактов: 1с 1м 1ч 1д 1н</text>
|
||
<text x="34" y="414" class="sub">1мес 1кв 1г</text>
|
||
<text x="34" y="440" class="sub">тик занят → absorbed</text>
|
||
<text x="34" y="456" class="sub">(skip, не очередь)</text>
|
||
<text x="34" y="482" class="sub">asyncio · один процесс</text>
|
||
<!-- cycle runners -->
|
||
<rect x="250" y="336" width="190" height="180" class="bx"/>
|
||
<text x="264" y="362" class="lab">CYCLE RUNNERS</text>
|
||
<text x="264" y="382" class="sub">по одному исполнителю</text>
|
||
<text x="264" y="398" class="sub">на уровень → сериализация,</text>
|
||
<text x="264" y="414" class="sub">задача не прерывается</text>
|
||
<text x="264" y="444" class="sub">мандат + бюджет +</text>
|
||
<text x="264" y="460" class="sub">класс задач — из конфига</text>
|
||
<text x="264" y="486" class="sub">уровня (P-2, P-3)</text>
|
||
<!-- strands runtime -->
|
||
<rect x="490" y="336" width="220" height="180" class="bxW"/>
|
||
<text x="508" y="362" class="lab">STRANDS RUNTIME</text>
|
||
<text x="508" y="386" class="sub">create_harness() на каждый тик:</text>
|
||
<text x="508" y="406" class="sub">agent loop · tools · MCP</text>
|
||
<text x="508" y="422" class="sub">session (задача) · effort</text>
|
||
<text x="508" y="438" class="sub">context_manager · guardrails</text>
|
||
<text x="508" y="462" class="sub">HOOKS → каждая стадия цикла</text>
|
||
<text x="508" y="478" class="sub">агента пишет трассу событием</text>
|
||
<!-- llm gateway -->
|
||
<rect x="760" y="336" width="200" height="92" class="bx"/>
|
||
<text x="774" y="362" class="lab">LLM ШЛЮЗ</text>
|
||
<text x="774" y="382" class="sub">9router: flash / full / pro</text>
|
||
<text x="774" y="398" class="sub">модель задаётся уровнем</text>
|
||
<text x="774" y="414" class="sub">цикла — не задачей</text>
|
||
<!-- tools -->
|
||
<rect x="760" y="452" width="200" height="64" class="bx"/>
|
||
<text x="774" y="476" class="lab">ИНСТРУМЕНТЫ</text>
|
||
<text x="774" y="496" class="sub">shell · files · web · MCP · скиллы</text>
|
||
<!-- arrows row2 -->
|
||
<line x1="200" y1="415" x2="245" y2="415" class="wireO" marker-end="url(#arrO)"/>
|
||
<text x="204" y="404" class="sig">TICK</text>
|
||
<line x1="440" y1="415" x2="485" y2="415" class="wire" marker-end="url(#arr)"/>
|
||
<line x1="710" y1="382" x2="755" y2="382" class="wire" marker-end="url(#arr)"/>
|
||
<text x="712" y="372" class="sig">LLM</text>
|
||
<line x1="860" y1="452" x2="860" y2="427" class="wire" marker-end="url(#arr)"/>
|
||
|
||
<!-- vertical links between planes -->
|
||
<!-- scheduler → event store (CycleTicked) -->
|
||
<path d="M110 336 L110 200 Q110 185 125 185 L485 185" class="wireO" marker-end="url(#arrO)"/>
|
||
<text x="150" y="177" class="sig">CycleTicked → APPEND</text>
|
||
<!-- runners → projections (read context) -->
|
||
<path d="M345 336 L345 260 Q345 245 360 245 L755 245 L755 160" class="wireD" marker-end="url(#arr)"/>
|
||
<text x="420" y="238" class="sub">читает контекст: дайджесты нижних уровней + задачи класса</text>
|
||
<!-- strands hooks → event store -->
|
||
<path d="M600 336 L600 282" class="wireO" marker-end="url(#arrO)"/>
|
||
<text x="612" y="318" class="sig">HOOKS: трасса агента → события</text>
|
||
<!-- event store → scheduler catch-up? no. projections → runners already. -->
|
||
</svg>
|
||
<figcaption>РИС. 1 — Две плоскости. CQRS: интерфейсы → команды → event store → проекции → запросы. Временная: планировщик → исполнители циклов → Strands-агент → LLM. Замкнутый контур: события порождают циклы, циклы порождают события.</figcaption>
|
||
</figure>
|
||
</section>
|
||
|
||
<!-- ================= 05 ================= -->
|
||
<section id="s05">
|
||
<div class="snum">05 / ТИК</div>
|
||
<h2>Жизненный цикл тика</h2>
|
||
<p class="lead">Один тик уровня — от границы такта до записи результата. Ключевое правило — непрерываемость: занятый исполнитель поглощает тик.</p>
|
||
<div class="steps">
|
||
<div class="step"><b>Граница такта</b><span>Scheduler ловит момент 1с/1м/…/1г. Эмитит событие CycleTicked{level, seq}.</span></div>
|
||
<div class="step"><b>Исполнитель свободен?</b><span>Занят → TickAbsorbed: детерминированная дайджест-заглушка (без LLM), очередь не копим. Дорога (v0.2+): срочная реакция сможет прервать бегущую задачу или поставить её на паузу.</span></div>
|
||
<div class="step"><b>Сборка контекста</b><span>Мандат уровня + идентичность + память уровня + выжимка нижних (дайджесты) + задачи доступного класса. В пределах бюджета P-3.</span></div>
|
||
<div class="step"><b>Выбор задачи</b><span>Есть подходящая задача → TaskClaimed + сессия. Нет → цикл делает мандатную работу (дайджест/наблюдение) или проходит впустую — это честно.</span></div>
|
||
<div class="step"><b>Strands-цикл</b><span>create_harness(model, effort, tools уровня) → invoke. Каждый шаг — hook → событие. Задача не прерывается никакими тиками.</span></div>
|
||
<div class="step"><b>Завершение</b><span>TaskCompleted / TaskFailed (+ политика ретраев и промоушена выше). Результат → событие с ссылкой на артефакт.</span></div>
|
||
<div class="step"><b>Выжимка</b><span>Runner пишет DigestWritten{level, period} — сырое остаётся в трассе, в пирамиду идёт сжатое.</span></div>
|
||
</div>
|
||
<div class="note"><b>Non-preemption на практике:</b> у каждого уровня ровно один исполнитель (worker). Это даёт сериализацию бесплатно: день не может «отложить» час. Долгие L-задачи идут в фоне сессии, дневной цикл на следующих тиках лишь проверяет их статус — и пока они живы, новых L не берёт.</div>
|
||
</section>
|
||
|
||
<!-- ================= 06 ================= -->
|
||
<section id="s06">
|
||
<div class="snum">06 / ПАМЯТЬ</div>
|
||
<h2>Контекстная пирамида</h2>
|
||
<p class="lead">Контекст цикла — не «всё, что случилось», а многослойная сборка. Сырые данные живут в трассе событий; наверх поднимаются только выжимки. Это главный эконом токенов: наш фактический опыт — 3.1 млрд prompt-токенов за 14 дней в основном из-за перечитывания сырых контекстов.</p>
|
||
<div class="pyr">
|
||
<div class="lvl"><div class="w">ГОД · 1M</div><div class="bar" style="width:56%"></div><div class="d"><b>Идентичность:</b> миссия, ценности, «кто я». Пишется раз в год, кэшируется во все нижние уровни.</div></div>
|
||
<div class="lvl"><div class="w">КВАРТАЛ · 512K</div><div class="bar" style="width:48%"></div><div class="d"><b>Ставки:</b> 3–5 стратегических направлений + критерии их проверки.</div></div>
|
||
<div class="lvl"><div class="w">МЕСЯЦ · 256K</div><div class="bar" style="width:40%"></div><div class="d"><b>Цели:</b> OKR, бюджет, лимиты расходов на уровень.</div></div>
|
||
<div class="lvl"><div class="w">НЕДЕЛЯ · 200K</div><div class="bar" style="width:34%"></div><div class="d"><b>Уроки:</b> что сработало/нет, обновления скиллов и политики.</div></div>
|
||
<div class="lvl"><div class="w">ДЕНЬ · 128K</div><div class="bar" style="width:28%"></div><div class="d"><b>Манифест дня:</b> план + часовые дайджесты (не сырые минуты!) + доска задач L-класса.</div></div>
|
||
<div class="lvl"><div class="w">ЧАС · 32K</div><div class="bar" style="width:20%"></div><div class="d"><b>Дайджест часа:</b> сжатые минутные логи, статус фоновых задач, триаж входящих.</div></div>
|
||
<div class="lvl"><div class="w">МИНУТА · 4K</div><div class="bar" style="width:12%"></div><div class="d"><b>Пульс:</b> последние входящие, алерты секундного уровня, S-задачи.</div></div>
|
||
<div class="lvl"><div class="w">СЕК · 0</div><div class="bar" style="width:5%;background:var(--ink)"></div><div class="d"><b>Рефлекс:</b> без LLM. Телеметрия и правила. Дёшево тикать каждую секунду.</div></div>
|
||
</div>
|
||
<p style="font-size:13.5px;color:var(--dim)">Сборка контекста дневного цикла: идентичность (кэш) + квартальные ставки + месячные цели + недельные уроки + дайджесты 24 часов + доска задач. Сырые трассы не поднимаются выше своего уровня никогда — вверх идёт только сжатое.</p>
|
||
</section>
|
||
|
||
<!-- ================= 07 ================= -->
|
||
<section id="s07">
|
||
<div class="snum">07 / СОБЫТИЯ</div>
|
||
<h2>Событийная модель</h2>
|
||
<p class="lead">Стор — по образцу кооп-портала (aggregate, aggregate_id, event_type, data, timestamp, version). События иммутабельны; тестовые — единственное исключение при чистке.</p>
|
||
<div class="two">
|
||
<div class="box">
|
||
<h5>Aggregate: cycle</h5>
|
||
<ul>
|
||
<li><span class="m">CycleTicked</span> {level, seq} — граница такта</li>
|
||
<li><span class="m">TickAbsorbed</span> {reason: busy|skipped}</li>
|
||
<li><span class="m">DigestWritten</span> {level, period, summary_ref}</li>
|
||
</ul>
|
||
</div>
|
||
<div class="box">
|
||
<h5>Aggregate: task</h5>
|
||
<ul>
|
||
<li><span class="m">TaskSubmitted</span> {title, size S/M/L/XL, source}</li>
|
||
<li><span class="m">TaskClaimed</span> {level, session_id}</li>
|
||
<li><span class="m">TaskCompleted</span> {result_ref} / <span class="m">TaskFailed</span> {error}</li>
|
||
<li><span class="m">TaskPromoted</span> {from, to} — не по зубам уровню</li>
|
||
</ul>
|
||
</div>
|
||
<div class="box">
|
||
<h5>Aggregate: harness</h5>
|
||
<ul>
|
||
<li><span class="m">PolicyChanged</span> {budgets, mandates}</li>
|
||
<li><span class="m">BudgetSpent</span> {level, tokens, $}</li>
|
||
<li><span class="m">LevelPaused</span> / <span class="m">LevelResumed</span></li>
|
||
</ul>
|
||
</div>
|
||
<div class="box">
|
||
<h5>Aggregate: trace</h5>
|
||
<ul>
|
||
<li><span class="m">InvocationStarted</span> / <span class="m">ToolCalled</span> / <span class="m">ToolResult</span> — из hooks Strands: BeforeInvocationEvent, Before/AfterToolCallEvent</li>
|
||
<li>Полная трасса агентного цикла = поток событий. Дебаг = читать стор.</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
<h3>Проекции (query side)</h3>
|
||
<ul>
|
||
<li><b>task_board</b> — готовые/взятые/закрытые задачи по классам; who-can-claim-what</li>
|
||
<li><b>digest_{level}</b> — последние выжимки уровня (источник для контекстов уровней выше)</li>
|
||
<li><b>metrics</b> — тики/поглощения/расходы по уровням (видно стоимость ритма)</li>
|
||
<li><b>agent_state</b> — чем занят каждый уровень сейчас</li>
|
||
</ul>
|
||
</section>
|
||
|
||
<!-- ================= 08 ================= -->
|
||
<section id="s08">
|
||
<div class="snum">08 / STRANDS</div>
|
||
<h2>Маппинг на Strands Harness SDK</h2>
|
||
<p class="lead">Берём SDK как исполнительное ядро — не форкаем, а собираем поверх. Всё, чего нет в Strands (время, событийная спина, консолидация) — наш слой.</p>
|
||
<table>
|
||
<tr><th>Наш концепт</th><th>Механизм Strands</th><th>Комментарий</th></tr>
|
||
<tr><td>Cycle Runner уровня</td><td class="m">create_harness() на тик</td><td>Фабрика с конфигом уровня: model, effort, tools, instructions</td></tr>
|
||
<tr><td>Мандат уровня</td><td class="m">instructions / system prompt</td><td>Собственный слой поверх HARNESS_CONTRACT</td></tr>
|
||
<tr><td>Бюджет контекста</td><td class="m">context_manager + session</td><td>agentic-режим для день+, sliding window для минут</td></tr>
|
||
<tr><td>Непрерываемость</td><td class="m">background tasks + session id</td><td>Задача = сессия; тики не рвут её, цикл опрашивает</td></tr>
|
||
<tr><td>Трасса в стор</td><td class="m">hooks: add_hook(BeforeInvocationEvent, BeforeToolCallEvent…)</td><td>Наш плагин: каждый хук → append_event. Трасса = события</td></tr>
|
||
<tr><td>Инструменты</td><td class="m">builtin_tools + mcp_servers + skills dir</td><td>shell/read/write/edit/web — по уровню: минуте не даём shell, дню — даём</td></tr>
|
||
<tr><td>Модель по уровню</td><td class="m">model: provider/name, effort: auto…high</td><td>Через наш 9router-провайдер: flash/full/pro</td></tr>
|
||
<tr><td>Осторожность</td><td class="m">interventions (guardrails)</td><td>Необратимые действия — через подтверждение или только на высоких уровнях</td></tr>
|
||
<tr><td>Долгая память</td><td class="m">memory stores</td><td>Бэкенд — наши проекции digest_*, не файлы</td></tr>
|
||
</table>
|
||
<div class="two" style="margin-top:20px">
|
||
<div class="box"><h5>Берём из Strands как есть</h5>
|
||
<ul>
|
||
<li>Agent loop: лимиты ходов, токен-бюджеты, stop reasons</li>
|
||
<li>Hooks — типизированные события жизненного цикла</li>
|
||
<li>Сессии, structured output, MCP, мультитул-оркестрация</li>
|
||
<li>Guardrails/interventions, context offloader</li>
|
||
</ul>
|
||
</div>
|
||
<div class="box"><h5>Принципиально своё (новое)</h5>
|
||
<ul>
|
||
<li><b>Временная ось:</b> тактовая лестница — в Strands нет никакого «времени»</li>
|
||
<li><b>Событийная спина:</b> у Strands сессии = файлы; у нас — append-only стор + replay</li>
|
||
<li><b>Каскад консолидации</b> памяти (digestion) — у Strands нет иерархии памяти</li>
|
||
<li><b>Классы задач ↔ уровни цикла</b> — планировщик как у человека</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ================= 09 ================= -->
|
||
<section id="s09">
|
||
<div class="snum">09 / СТЕК</div>
|
||
<h2>Стек и размещение</h2>
|
||
<ul>
|
||
<li><b>Python 3.12</b>, один процесс-демон (systemd), asyncio-планировщик — как simbiotd, но на событийной спине</li>
|
||
<li><span class="m">pip install strands-harness</span> (Apache-2.0) + наш пакет <span class="m">simbiot-harness</span>: scheduler, runners, es-спина, проекции, hooks-плагин</li>
|
||
<li><b>Event store: SQLite WAL</b> — проверенный образец кооп-портала (events, projections, projection_state.json); идемпотентные команды; чистые тестовые события после E2E</li>
|
||
<li><b>LLM:</b> 9router (glm-5.3-flash / glm-5.3 / pro-слот) — своя модель на уровень, бюджет через BudgetSpent</li>
|
||
<li><b>Интерфейсы v0.1:</b> CLI + Telegram (таски от человека); Matrix/Web — после ревью</li>
|
||
<li><b>Деплой:</b> портал-бокс рядом с simbiotd; порт не нужен — только systemd-юнит и локальный CLI</li>
|
||
</ul>
|
||
<h3>Объём v0.1 (что строим первым)</h3>
|
||
<ul>
|
||
<li>Уровни <b>1с + 1м + 1ч + 1д</b> (рефлекс без LLM уже дёшев); неделя/месяц — v0.2, квартал/год — v0.3</li>
|
||
<li>Задачи S/M/L + промоушен классов, непрерываемость, дайджесты часа</li>
|
||
<li>E2E: человек кидает задачу в TG → минутный взял S → дневной собрал манифест → отчёт → метрики стоимости ритма в проекции</li>
|
||
</ul>
|
||
</section>
|
||
|
||
<!-- ================= 10 ================= -->
|
||
<section id="s10">
|
||
<div class="snum">10 / РЕШЕНИЯ</div>
|
||
<h2>Ревью пройдено — решения</h2>
|
||
<p class="lead">Прошли с владельцем 29.09.2026. Все шесть вопросов закрыты.</p>
|
||
<table>
|
||
<tr><th>Вопрос</th><th>Решение</th></tr>
|
||
<tr><td>Стоимость ритма (~$1–3/день за «жизнь» без задач)</td><td>✅ Принято — норма</td></tr>
|
||
<tr><td>Пропущенные такты</td><td>✅ Поглощённый тик пишет детерминированную дайджест-заглушку (без LLM). <b>Дальше (v0.2+):</b> срочная реакция сможет прервать бегущую задачу или поставить её на паузу</td></tr>
|
||
<tr><td>Один исполнитель на уровень</td><td>✅ Принято; N воркеров минутного уровня — v0.2 при необходимости</td></tr>
|
||
<tr><td>Идемпотентность тиков</td><td>✅ UNIQUE(level, seq); пропущенное время не навёрстывается</td></tr>
|
||
<tr><td>Pro-слот для квартала/года</td><td>✅ Принято</td></tr>
|
||
<tr><td>Имя и место</td><td>✅ Simbiot Harness — <b>отдельный репо-продукт</b> (отдельный продукт = отдельный репо)</td></tr>
|
||
</table>
|
||
<div class="note"><b>Следующий шаг:</b> сборка v0.1 — уровни 1с + 1м + 1ч + 1д, задачи S/M/L, ES-спина, дайджесты, hooks-плагин Strands, E2E: таска из Telegram → минутный цикл → дневной манифест → отчёт.</div>
|
||
</section>
|
||
|
||
<footer>
|
||
<span>SIMBIOT HARNESS · ARCHITECTURE v0.1</span>
|
||
<span>РЕВЬЮ ПРОЙДЕНО · 29.09.2026</span>
|
||
<span>STRANDS HARNESS SDK · APACHE-2.0</span>
|
||
</footer>
|
||
</div>
|
||
</body>
|
||
</html> |