Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@
"інвалідовуються",
"інвалідування",
"Інвалідувати",
"інвалідаціями",
"інвалідує",
"інвентаря",
"інжект",
Expand All @@ -237,6 +238,16 @@
"інжекту",
"інкрементує",
"інлайн",
"інстанс",
"Інстанс",
"інстанса",
"інстансам",
"інстансами",
"інстанси",
"Інстанси",
"інстансів",
"інстансом",
"інстансу",
"інтейк",
"ітеративно",
"Ітеративно",
Expand Down
5 changes: 5 additions & 0 deletions npm/.changes/260722-0643.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
bump: patch
section: Added
---
docs: глава архітектури `recurrence.md` — повторювані задачі через шаблон (`.mt/templates/`) + інстанси-кореневі вузли; політики overlap/catchup/retention, CLI `mt template list|run`; перехресні оновлення index/overview/operations/log
1 change: 1 addition & 0 deletions npm/docs/architecture/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* [Мандати й людино-центрична ескалація](mandates.md) - карта мандатів, профілі людей і моделей, decision-request, маршрутизація за важелем, прецедентний рушій
* [Багатомовність (i18n)](i18n.md) - base-канон і derived-переклади у `refs/mt/i18n`, worktree-матеріалізація, contract-aware перекладач, authored-захист
* [Мета-цикл (retro)](retro.md) - ретроспективний аналіз audit trail, приватні opt-in пропозиції виконавцю, застосування штатними правками
* [Повторювані задачі (recurrence)](recurrence.md) - мутабельний шаблон у `.mt/templates/`, матеріалізація інстансів планувальником wake, політики overlap/catchup/retention
* [Експлуатація](operations.md) - CLI-контракт, конфігурація, монорепо, security model, відмовостійкість, bootstrap, Definition of Done

## Довідково
Expand Down
7 changes: 6 additions & 1 deletion npm/docs/architecture/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,15 @@ mt sessions ← активні run-и акаунта, вк
(хто де, хто тримає claim, моя роль)
mt invite <root-node> <email> --role host|approver|viewer
mt members <root-node>

# повторювані задачі (НОВЕ)
mt template list ← шаблони + наступне спрацювання + останній інстанс
mt template run <name> ← позачергова матеріалізація (occurrence = now)
```

`mt watch`/`mt run --auto` зберігаються як однопострільні входи тієї самої логіки, що живе в agent-server (fallback-режим без сервера). Exit codes `mt scan`/`mt watch`: `0` — ок, `1` — є вузли, що потребують уваги.

`mt cleanup [--older-than N]` (дефолт 7 днів): orphan worktrees без active claim, мертві running-маркери, remote orphan run refs (старші `run_ref_ttl_days`), протухлі archive refs (старші `archive_ttl_days`).
`mt cleanup [--older-than N]` (дефолт 7 днів): orphan worktrees без active claim, мертві running-маркери, remote orphan run refs (старші `run_ref_ttl_days`), протухлі archive refs (старші `archive_ttl_days`), resolved-інстанси шаблонів понад `keep` ([recurrence.md](recurrence.md)).

## Конфігурація (`.mt.json`)

Expand Down Expand Up @@ -76,6 +80,7 @@ Baseline-ключі 0.2.0 з конкретними дефолт-значенн
| Паралелізм | `agent_concurrency` | [git.md](git.md) |
| Виконавці/моделі | ENV: `MT_AGENT_CLI`, `MT_CLOUD_AGENT_CLIS`, `MT_AGENT_CLI_MODEL_MAP` | [runtime.md](runtime.md), [stack.md](stack.md) |
| Поверхні/тули | `surface_profiles`, `mcp_servers` | [surfaces.md](surfaces.md) |
| Повторюваність | `templates_dir`; per-template `recurrence.md` (`schedule`/`every`, `tz`, `overlap`, `catchup`, `keep`) | [recurrence.md](recurrence.md) |
| Безпека | `skill_profiles` (sandbox), `secrets` (у `a.md`), `require_signed_approvals`, `device_key_path` | тут, [access.md](access.md) |
| Relay/хост | `relay_url`, `server_port_file` | тут, [runtime.md](runtime.md) |
| i18n | `i18n.{base_lang, eager, publish_langs, include, exclude, model_tier, ttl_days}` | [i18n.md](i18n.md) |
Expand Down
2 changes: 2 additions & 0 deletions npm/docs/architecture/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ timestamp: 2026-07-07
| **approver** | роль учасника, що підписує approvals з будь-якого пристрою **без git-доступу** | [access.md](access.md) |
| **base-мова** | канонічна мова контенту в `main`; переклади — derived у `refs/mt/i18n/*` | [i18n.md](i18n.md) |
| **authored-переклад** | версія мовою автора правки; захищена від перезапису зворотним перекладом | [i18n.md](i18n.md) |
| **шаблон** | мутабельний опис повторюваної задачі поза графом (`.mt/templates/<name>/`); не вузол | [recurrence.md](recurrence.md) |
| **інстанс** | звичайний кореневий вузол, матеріалізований із шаблона на спрацювання розкладу | [recurrence.md](recurrence.md) |
| **мандат** | зона, у межах якої власник вирішує сам без ескалації (`.mt/mandates.yaml`) | [mandates.md](mandates.md) |
| **decision-request** | упакована для власника розвилка: контекст, варіанти, рекомендація агента | [mandates.md](mandates.md) |
| **leverage-фасети** | декларативні поля розвилки (незворотність, blast radius, дивергенція, ціна) з детермінованим мапінгом на режим ескалації | [mandates.md](mandates.md) |
Expand Down
90 changes: 90 additions & 0 deletions npm/docs/architecture/recurrence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
---
type: architecture
description: 'Повторювані задачі: мутабельний шаблон поза графом, матеріалізація інстансів планувальником wake, політики overlap/catchup/retention'
tags: [recurrence, templates, scheduler]
timestamp: 2026-07-22
---

# Повторювані задачі (recurrence)

> Частина цільової архітектури **0.3.0-draft** — [зміст](index.md) · [огляд](overview.md)

## Концепція

Повторюваність **не живе всередині графа**: ОАГ ациклічний, `resolved` — термінальний стан, артефакти immutable — вузол не може «виконатись і чекати наступного разу». Механізм — **шаблон + інстанси**:

- **Шаблон** — мутабельний опис задачі поза `mt/` (у `.mt/templates/`); **не вузол**, scan його не бачить, станів не має. Правка шаблона діє з наступної матеріалізації й не торкається минулих інстансів.
- **Інстанс** — звичайний кореневий вузол `mt/<name>-<stamp>/`, матеріалізований планувальником на кожне спрацювання розкладу. Після матеріалізації він нічим не відрізняється від вузла, створеного `mt init`: ті самі стани, retry ladder, аудит, i18n, membership. Жодних нових derived-станів механізм не додає.

Кожне повторення отримує **власну ідентичність і повний version chain** (run-и, fact-и, аудити) — «звіт за цей тиждень» — окремий immutable артефакт, а не перезаписаний результат одного вузла.

## Файловий контракт шаблона

```
.mt/templates/<name>/
task.md ← схема task.md БЕЗ created_at/parent (заповнюються при матеріалізації)
a.md | h.md ← хто виконує інстанси (контракт graph.md, ніколи обидва)
recurrence.md ← розклад і політики (нижче)
deps/ ← опціонально: постійні deps інстансів (абсолютні dep-id від mt/)
```

`<name>` — за naming convention вузлів (`a-z`, `0-9`, `-`). Директорія в `.mt/` свідомо: усередині `mt/` шаблон світився б у scan як `orphan-node`.

### `recurrence.md`

```yaml
schema_version: 1
created_at: ISO8601
schedule: '*/15 9-18 * * 1-5' # повноцінний cron, 5 полів, хвилинна гранулярність
every: 90m # АБО інтервал: 30m | 4h | 3d — відлік від попереднього occurrence
tz: Europe/Kyiv # опціонально; default UTC; впливає лише на обчислення cron
enabled: true # false → пауза без видалення шаблона
overlap: skip # skip | chain | parallel (нижче)
catchup: collapse # collapse | all — надолуження після простою
keep: 20 # retention resolved-інстансів; 0 → без GC
```

Рівно **одне** з `schedule`/`every`; обидва або жодного → fail closed. Два типи розкладу свідомо:

- **`schedule`** — календарний: повний cron-вираз будь-якої частоти («щопонеділка о 3:00», «кожні 15 хв у робочі години»); обчислюється у `tz`.
- **`every`** — інтервальний, **відв'язаний від календаря**: наступне спрацювання = попередній `occurrence` + інтервал (sliding, без вирівнювання по добі). Cron принципово не виражає «кожні 90 хв» — для цього і є `every`.

**Хвилина — свідома нижня межа гранулярності**: матеріалізація — це fenced-коміт у remote, а точність спрацювання обмежена wake-механізмом (таймер живого agent-server точний; cron-fallback — з точністю частоти rescan). Суб-хвилинні розклади — не кейс графа задач.

## Матеріалізація інстанса

Планувальник — частина wake-логіки orchestrator-а ([runtime.md](runtime.md#wake-push-замість-polling)): agent-server ставить таймер на найближче спрацювання серед шаблонів; cron/periodic rescan — fallback, що гарантує спрацювання без always-on процесу (з точністю до частоти rescan).

На кожне спрацювання:

1. **Id інстанса — детермінований від моменту спрацювання**, уніфіковано для будь-якої частоти: `<name>-YYYYMMDD-hhmm` (occurrence у UTC, хвилинна точність — жодних спец-випадків «денного» формату). Той самий occurrence → той самий id → повторна спроба ідемпотентна.
2. Матеріалізуються `task.md` (+ `created_at` = момент спрацювання і два нові опційні поля фронтматеру — `template: <name>`, `occurrence: ISO8601`), прапор `a.md`/`h.md` і `deps/` з шаблона — **одним fenced atomic commit** (актор — wrapper), як `mt spawn --approve`.
3. Гонка хостів вирішується штатно: два agent-server прокинулись одночасно → обидва зібрали ідентичний вузол → перший fenced publish виграє, другий після rescan бачить наявний id і пропускає крок.

Інстанси — **кореневі вузли**, тож легітимні за інваріантом graph.md без змін (approved-план батька для них не потрібен). Групування — префіксом імені; підвішування інстансів під живий composite-вузол неможливе за побудовою: його `## Children` immutable після approve.

**Стан планувальника — derived, як усе інше**: жодних лічильників чи "last-run"-файлів. Останнє спрацювання відновлюється скануванням вузлів із `template: <name>` (max `occurrence`); годинник + `schedule`/`every` дають наступне.

## Політики

- **`overlap`** — що робити, коли настало спрацювання, а попередній інстанс ще не `resolved`:
- `skip` (default) — нічого не матеріалізувати; пропущені occurrence підпадають під `catchup`;
- `chain` — матеріалізувати з автоматичним dep на попередній інстанс: виконання серіалізується, а fact попереднього стає входом наступного (потік даних між повтореннями — задарма через штатний механізм `deps/`; ланцюжок лишається ОАГ);
- `parallel` — матеріалізувати незалежно.
- **`catchup`** — надолуження після простою (вимкнена машина, довгий `skip`): `collapse` (default) — один інстанс за найсвіжішим пропущеним occurrence, без бекфілу; `all` — по інстансу на кожне пропущене спрацювання.
- **Пауза (`enabled: false`)** — планувальник шаблон ігнорує; період паузи **не породжує «пропущених» спрацювань**: повернення `enabled: true` не запускає catchup за час паузи (catchup надолужує простій *системи*, пауза — свідоме рішення людини). Після ввімкнення відлік з нуля: для `schedule` — найближче майбутнє спрацювання cron, для `every` — момент ввімкнення + інтервал (момент ввімкнення — derived з git-історії `recurrence.md`: коміт, що виставив `enabled: true`; лічильників, як і скрізь, немає). Перемикання — звичайна правка мутабельного шаблона; поточний стан і наступне спрацювання видно у `mt template list`.
- **Retention** — розширення `mt cleanup` ([operations.md](operations.md)): resolved-інстанси понад `keep` найсвіжіших прибираються штатним kill-архівом (`<tasks-root>/.history/`). `failed`/`unresolvable` інстанси GC **не чіпає** — вони чекають людину, як будь-який вузол.

## CLI

```
mt template list ← шаблони + наступне спрацювання + останній інстанс
mt template run <name> ← позачергова матеріалізація (occurrence = now)
```

Конфіг: `.mt.json` → `templates_dir` (default `.mt/templates`). Решта політик — per-template у `recurrence.md`; глобальних ключів механізм не додає.

## Відхилені альтернативи

- **Freshness/TTL прийнятого fact** (`schedule:` у `task.md` вузла: fact, старший за період → знову `waiting`) — мінімальна дельта до derived-станів і готова каскадна інвалідація нащадків, але повторення втрачають окрему ідентичність: NNN-ланцюжок одного вузла росте необмежено, `failed_streak`/`budget_total_sec` вимагають per-occurrence скидання, а «звіт за минулий тиждень» існує лише в git-історії. Може повернутись окремим механізмом для refresh-класу задач («тримай дані свіжими»), якщо dogfood покаже потребу.
- **Періодичний `mt invalidate` зовнішнім планувальником** — працює без змін контракту, але семантично хибний: invalidate означає «результат неправильний», а не «настав наступний цикл», і засмічує `history/`-архів та audit trail хибними інвалідаціями.
1 change: 1 addition & 0 deletions npm/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ docgen:
* [Мандати й людино-центрична ескалація](architecture/mandates.md) - карта мандатів, профілі людей і моделей, decision-request, маршрутизація за важелем, прецедентний рушій
* [Багатомовність (i18n)](architecture/i18n.md) - base-канон і derived-переклади, worktree-матеріалізація, contract-aware перекладач
* [Мета-цикл (retro)](architecture/retro.md) - аналіз audit trail, приватні opt-in пропозиції виконавцю
* [Повторювані задачі (recurrence)](architecture/recurrence.md) - шаблон + інстанси: розклад поза графом, кожне спрацювання — звичайний кореневий вузол
* [Експлуатація](architecture/operations.md) - CLI, конфігурація, security model, відмовостійкість, наскрізні сценарії
* [Референсний стек](architecture/stack.md) - технологічні рішення реалізації (Rust device-шар, Bun relay/`@7n/mt`)

Expand Down
Loading
Loading