simbiot-harness/architecture.html
simbiot-min ed502650f4 feat: эскалации (min→hour→day) + фикс хвоста событий в CLI
Часть 1 (часовой уровень, задача t-260929155421-9f7bff):
- EscalationRaised/Acknowledged в ES-спину, проекция escalations
- CLI: escalate / escalations / escalation-ack
- уровни видят открытые эскалации в контексте; scheduler перезапускает
  критическую эскалацию сразу (не дожидаясь границы такта)

Часть 2 (минутный, этот тик):
- fix(cli): 'events -n N' показывал первые N событий вместо хвоста стора
  (README обещает хвост) — теперь хвост; --after N — от id N вперёд
- + es.count_events()
2026-09-29 16:13:23 +00:00

428 lines
No EOL
39 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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&nbsp;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">EscalationRaised</span> {from_level, to_level, reason, context, severity} — важное с нижнего уровня наверх</li>
<li><span class="m">EscalationAcknowledged</span> {level, note} — верхний уровень принял/отработал</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>