opencode-orchestrator-uni 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,230 @@
1
+ # Session discipline: how to run development
2
+
3
+ This document defines the session protocol: where a session starts and ends, its
4
+ context budget, when to delegate, how to treat tool output, and what survives the
5
+ session. It complements `context-efficiency.md` (the instruction-file budget) and
6
+ `root-runbook.md` (the audit loop): those cover artifacts and verdicts, this one
7
+ covers keeping context small and decisions pinned.
8
+
9
+ ## Why it matters
10
+
11
+ - Context is a consumable resource, not the project's memory. As input grows,
12
+ quality degrades even on simple tasks (context rot); "it fits the window" does
13
+ not mean "the model uses it".
14
+ - Compaction is a lossy summary. After it the agent remembers a paraphrase of
15
+ decisions, not the decisions; a second compaction paraphrases a paraphrase.
16
+ - A measured long session: 13 253 turns, 19 compactions, the median prompt grew
17
+ from 314k to 780k tokens. Almost all of that is re-sending history, not work.
18
+ This is an internal measurement of a consumer project: the artifact is not
19
+ attached to the repository, and the figures are not reproducible from it.
20
+
21
+ The protocol keeps the working state small and the decisions in files that
22
+ outlive the session.
23
+
24
+ ## 1. A session is one slice
25
+
26
+ A session is one slice with a measurable DoD, not the whole project.
27
+
28
+ | Rule | Value |
29
+ | --- | --- |
30
+ | Estimated prompt budget | ≤ 150k tokens |
31
+ | Compactions per slice | ≤ 1 |
32
+ | Stop signal | the first compaction |
33
+ | Required output | handoff + commit |
34
+
35
+ - At the first sign of approaching the budget, close the slice instead of pushing
36
+ on: handoff, commit, new session.
37
+ - After the first compaction the slice always closes: the summary has already lost
38
+ details and further work runs on a paraphrase.
39
+ - Cut work by waves, gates or fix batches; a phase change (plan → implementation →
40
+ acceptance) always starts a new session.
41
+
42
+ ## 2. Role separation
43
+
44
+ - Investigation and verification ("find", "understand", "reproduce") go to
45
+ subagents: they get a clean context and return 1–2k tokens of findings instead
46
+ of a dump.
47
+ - Implementation goes to one writer per workspace: related decisions must be made
48
+ in one context (otherwise parallel agents diverge in style and contracts).
49
+ - Decisions, consolidation and acceptance belong to the root orchestrator.
50
+ - The audit loop is unchanged: re-audit resumes the same sessions; short audit
51
+ sessions do not threaten the budget.
52
+
53
+ Rule of thumb: delegate **investigation** broadly, **implementation** narrowly,
54
+ keep **integration** centralized.
55
+
56
+ ## 3. Tool batching
57
+
58
+ Every turn re-sends the whole prompt. One tool per turn multiplies the budget by
59
+ the number of small steps.
60
+
61
+ - ≥ 3 independent commands per turn, or one script instead of five to ten calls.
62
+ - Move long WSL commands into a wrapper script: a 250-character line in every turn
63
+ costs more than a file read once.
64
+ - For file reads use `read` with `limit`/`offset`, not a full `cat`.
65
+ - Target metric: ≥ 2 tools per turn.
66
+
67
+ ## 4. Tool output
68
+
69
+ - Anything over 20k chars goes to a file; context gets the head (4k) and the path.
70
+ The guard does this automatically for `shell`, `bash`, `webfetch`.
71
+ - Screenshots and binary attachments are files plus a path, never base64 in
72
+ context.
73
+ - `execute` (Code Mode) is not capped by the guard: limit the result in the script
74
+ itself — return a summary and a path, not hundreds of kilobytes of raw JSON.
75
+ - `read`, `write`, `edit` and the kit's own tools are not capped: they have their
76
+ own contract, and losing content breaks the work.
77
+
78
+ ## 5. State in files
79
+
80
+ - `handoff` — the required output of a slice (template below); default path
81
+ `docs/planning/handoff-<YYYY-MM-DD>.md`, the project root when the directory
82
+ does not exist.
83
+ - `decisions` — decisions and reasons; one decision per entry; default path
84
+ `docs/planning/decisions-<YYYY-MM-DD>.md`.
85
+ - `state` — branch, gates, open risks, the next three steps; refreshed before a
86
+ new session; default path `docs/planning/state.md`.
87
+ - A new session starts from `state` and the target SPEC section, not from a
88
+ retelling of the previous session.
89
+
90
+ ## 6. Models
91
+
92
+ - Default to the cheap model; the expensive one only for architecture decisions,
93
+ reviews and dispute resolution.
94
+ - Document edits, reading and mechanical checks do not need an expensive model.
95
+ - Measurement: 223 turns of the expensive model cost $63, 104 turns of the cheap
96
+ one cost $0.09. One model switch saves more than any prompt tweak. This is an
97
+ internal measurement of a consumer project: the artifact is not attached to the
98
+ repository, and the figures are not reproducible from it.
99
+
100
+ ## 7. Metrics
101
+
102
+ | Metric | Target | Where |
103
+ | --- | --- | --- |
104
+ | Estimated prompt (median) | ≤ 150k | `orch_uni_session_stats` |
105
+ | Compactions per slice | ≤ 1 | `orch_uni_session_stats` |
106
+ | Tools per turn (`toolCalls/requests`, reported as `toolsPerTurn`) | ≥ 2 | `orch_uni_session_stats` |
107
+ | Share of capped outputs among the tool's calls (`toolOutputsCappedByTool.shell / toolCallsByTool.shell`; likewise `bash`, `webfetch`) | < 5% | `orch_uni_session_stats` |
108
+ | Handoff at slice end | present | working tree |
109
+
110
+ `orch_uni_session_stats` reports the counters of the current plugin instance and
111
+ the configured limits; historical runs are counted from the session database on
112
+ the project side.
113
+
114
+ ## 8. Guard
115
+
116
+ The guard is a set of plugin hooks plus a compaction-event subscription, not a
117
+ set of bans:
118
+
119
+ - `session.context` — estimates the prompt and adds one short reminder when it
120
+ crosses a budget level. The reminder is injected once at a level transition,
121
+ not on every request; within a level the text is stable, so the prefix cache is
122
+ not broken.
123
+ - compaction events — the `session.compaction` hook is registered on every host:
124
+ it counts attempts (`compactionAttempts`) and sends a tentative note to the
125
+ nearest request when the terminal event is delayed; the note names no
126
+ compaction number and claims no limit. Only `session.compaction.ended`
127
+ confirms a compaction (deduplicated by `event.id` or the durable position);
128
+ only it moves `compactions` and the limit. `session.compaction.failed`
129
+ increments `compactionFailures`, drops the tentative note and keeps a
130
+ confirmed note that is still waiting for delivery. When the event stream ends
131
+ or throws, the guard reports `eventLive: false`, `compactionSource: "hook"` and
132
+ `streamFailures: 1` (the per-session `errors` stays `0`); the hook is already
133
+ registered, so attempts keep counting, while no confirmed compaction or stop
134
+ signal is invented — no false "limit reached" on that path. `guard` exposes
135
+ the source, liveness and stream failures.
136
+ - `tool.execute.after` — caps the output of the configured tools and stores the
137
+ full text in a private per-process root
138
+ `os.tmpdir()/opencode-tool-output-<random>/` (POSIX 0700 for the root and the
139
+ session directory, 0600 for the file). Windows ignores mode (measured: 0666 for
140
+ the root, the session directory and the file): privacy there rests on the ACL of
141
+ `os.tmpdir()` and is not checked by code. The root is created
142
+ with `mkdtemp` at setup, so a pre-created directory or junction named
143
+ `opencode-tool-output` is never used; on POSIX the root must belong to the
144
+ current uid with no group/other access, otherwise the guard disables itself with
145
+ a warning (fail-open: the result is untouched). Session directories (`s-<uuid>`)
146
+ and files (`c-<uuid>.txt`, written with `wx`) are random, and each `realpath` is
147
+ proven inside the root; the root is removed when the plugin unloads (best
148
+ effort). A string with a lone surrogate is normalized once (U+FFFD), so the
149
+ file, the head and the counters describe the same string. Residual risk
150
+ accepted: a same-user attacker can still swap a session
151
+ directory between `realpath` and `writeFile` (Node has no `openat`). If `TMPDIR`
152
+ points inside the workspace, the files land there.
153
+
154
+ Configuration (defaults):
155
+
156
+ ```jsonc
157
+ {
158
+ "sessionGuard": {
159
+ "enabled": true,
160
+ "promptTokensSoft": 150000,
161
+ "maxCompactions": 1
162
+ },
163
+ "toolOutputGuard": {
164
+ "enabled": true,
165
+ "maxChars": 20000,
166
+ "headChars": 4000,
167
+ "tools": ["shell", "bash", "webfetch"]
168
+ }
169
+ }
170
+ ```
171
+
172
+ `maxCompactions: 0` disables the compaction limit. `guard.toolOutputEnabled` is
173
+ `true` only when the private root exists **and** the effective tool list is not
174
+ empty; `guard.toolOutputTools` is that list (normalized, protected and kit names
175
+ removed) and `guard.unkeyedCalls` counts hook calls without a non-empty
176
+ `sessionID` (such input is dropped, not bucketed under `""`). An empty `tools`
177
+ list — explicit or after every entry was rejected — disables capping with a
178
+ warning. Accepted limits: a session directory `s-<uuid>` lives until the plugin
179
+ unloads — it is removed neither on `session.deleted` nor after a refused cap
180
+ (a readonly result, for example) and is released with the root; lone-surrogate
181
+ normalization relies on ES2024 `String.prototype.isWellFormed`/`toWellFormed`
182
+ (the minimum runtime version is not pinned in `engines`, so on an old runtime the
183
+ cap degrades fail-open: `errors` +1, the result is untouched).
184
+ In the counters, `toolCalls`
185
+ and the per-tool counters include nested Code Mode calls, and `outputCharsSaved`
186
+ is an upper estimate in characters (the injected stub is subtracted).
187
+ `toolCallsByTool` and `toolOutputsCappedByTool` split calls and capped outputs by
188
+ the normalized tool name (namespace stripped, lower-case). Duplicate compaction
189
+ events are counted once only when the host supplies `event.id` or a durable
190
+ position; without either identity a re-delivered event may be counted again
191
+ (fail-open in the direction of counting).
192
+
193
+ The guard is fail-open: a hook error neither breaks the request nor corrupts the
194
+ tool result. It writes nothing to the working tree (except when `TMPDIR` points
195
+ inside the workspace — see above) and blocks nothing — it only reminds and caps
196
+ output. Either part can be disabled with its `enabled` flag.
197
+
198
+ ## Handoff template
199
+
200
+ ```md
201
+ # Handoff: <slice> — <YYYY-MM-DD>
202
+
203
+ <!-- File: docs/planning/handoff-<YYYY-MM-DD>.md; use the project root when the directory is absent. -->
204
+
205
+ ## Goal and DoD
206
+ One sentence of the goal and a verifiable definition of done.
207
+
208
+ ## Done
209
+ - What changed; what proves it (command, test, commit).
210
+
211
+ ## Not done
212
+ - What remains and why (blocker, owner decision, next wave).
213
+
214
+ ## Decisions
215
+ - Decision — reason; link to the document or commit.
216
+
217
+ ## Risks and limits
218
+ - Accepted risk — by whom and until when.
219
+
220
+ ## Next step
221
+ One concrete action for the next session.
222
+ ```
223
+
224
+ ## Slice checklist
225
+
226
+ - [ ] The slice is closed by DoD, not by "ran out of time".
227
+ - [ ] The handoff is written and references evidence.
228
+ - [ ] Decisions and risks are in files, not only in chat.
229
+ - [ ] The working tree is clean or explicitly described in the handoff.
230
+ - [ ] The next session starts from `state` and SPEC, not from a retelling.
@@ -0,0 +1,196 @@
1
+ # Token-efficient AGENTS.md и SPEC.md
2
+
3
+ Файл инструкций — это бюджет внимания, а не документация. Оплачивается каждая строка: она участвует в контексте каждой сессии и конкурирует с задачей. Цель — минимум проверяемых правил, которые нельзя вывести из кода и которые нужны в большинстве сессий.
4
+
5
+ ## Бюджет
6
+
7
+ | Файл | Цель | Потолок |
8
+ |---|---:|---:|
9
+ | Глобальный `AGENTS.md` | 25–40 строк / <500 токенов | 60 строк |
10
+ | Корневой `AGENTS.md` | 100–200 строк / 1–2k токенов | 200 строк |
11
+ | Вложенный `AGENTS.md` | 20–60 строк / <700 токенов | 100 строк |
12
+ | `SPEC.md` | 80–150 строк / ≤1,5k слов | 2k слов |
13
+ | `ADR` | 30–60 строк / <700 токенов | 100 строк |
14
+
15
+ `orch_uni_context_lint` применяет один бюджет корня (`max_lines`, по умолчанию 200 строк) и бюджет слов SPEC (`max_words`, по умолчанию 1500 — цель из таблицы; потолок 2k включается через `max_words: 2000`). Цифры по уровням выше — ориентир для автора, а не отдельные лимиты инструмента. Инструмент сообщает о том, что не проверял (ссылки вне корня, каталоги глубже глубины 3, незакрытый фенс), INFO-находками, поэтому `pass` никогда не означает «непроверено».
16
+
17
+ Правило отбора: если строка не проверяема, выводима из кода/линтера или нужна реже половины сессий — удалить либо вынести в ссылку.
18
+
19
+ ## AGENTS.md: 12 правил
20
+
21
+ 1. Команды — в начало: install, build, test, one test, lint, run; с флагами и рабочей директорией.
22
+ 2. Только невыводимое: флаги, ловушки, порядок слоёв, запреты, версии.
23
+ 3. Одна инструкция — одна строка, императив, проверяемо.
24
+ 4. Стиль — линтеру; не пересказывать README и стандарт языка.
25
+ 5. Явные «Never» и «Ask first»: секреты, `dist/`, prod, миграции, зависимости.
26
+ 6. Приоритеты — в тексте: что переопределяет что; при конфликте — остановись и спроси.
27
+ 7. Один источник правды: `AGENTS.md` + симлинк/импорт для `CLAUDE.md`.
28
+ 8. Ссылки вместо инлайна; инлайнить только то, что нужно всегда.
29
+ 9. Правило добавлять после повторной ошибки агента, а не «на будущее».
30
+ 10. Мёртвое правило удалять: это налог на каждый запрос.
31
+ 11. Стабильное — вверх, даты/ветки/задачи — вниз (prefix cache).
32
+ 12. Ревьюить файл как код; менять при смене тулинга/CI.
33
+
34
+ ## Шаблон AGENTS.md
35
+
36
+ ```md
37
+ # AGENTS.md
38
+
39
+ ## Приоритеты
40
+ 1. Явное указание пользователя в задаче.
41
+ 2. SPEC.md активной фичи.
42
+ 3. Ближайший AGENTS.md.
43
+ 4. Этот файл.
44
+ 5. Глобальный AGENTS.md.
45
+ При конфликте — остановись и спроси.
46
+
47
+ ## Команды
48
+ | Задача | Команда | Примечание |
49
+ |---|---|---|
50
+ | Установка | `<...>` | `<версия>` |
51
+ | Сборка | `<...>` | `<флаг>` |
52
+ | Тесты | `<...>` | `<лимит>` |
53
+ | Один тест | `<...>` | `<имя>` |
54
+ | Линт/типы | `<...>` | |
55
+ | Запуск | `<...>` | `<порт>` |
56
+
57
+ ## Границы
58
+ - `<path>/` — сгенерировано, не редактировать.
59
+ - `<path>/` — вендорное, не редактировать.
60
+ - Новые файлы — в `<path>`.
61
+
62
+ ## Конвенции
63
+ - <Одно проверяемое правило на строку.>
64
+
65
+ ## Проверка
66
+ `<analyze>` → `<lint>` → `<test>`.
67
+ Готово = все команды зелёные + `<DoD>`.
68
+
69
+ ## Никогда / сначала спроси
70
+ - Никогда: секреты, `dist/`, prod, применённые миграции.
71
+ - Сначала спроси: схема БД, публичный API, новые зависимости, CI.
72
+
73
+ ## Указатели
74
+ - `SPEC.md` — контракт фичи.
75
+ - `docs/adr/` — решения.
76
+ - `docs/pitfalls.md` — ловушки.
77
+ ```
78
+
79
+ ## SPEC.md: 12 правил
80
+
81
+ 1. Одна фича — один файл, 1–3 страницы; больше — split через roadmap.
82
+ 2. Стабильные ID (`R1`, `AC1.1`), не перенумеровывать.
83
+ 3. Одно требование — одно наблюдаемое поведение.
84
+ 4. EARS или Given/When/Then — единообразно; MUST/SHOULD/MAY — редко.
85
+ 5. Никакого HOW: стек, файлы, классы — в plan/design/ADR.
86
+ 6. Контракты — ссылками, не копиями.
87
+ 7. Non-goals — 3–7 пунктов.
88
+ 8. Инварианты — отдельно от acceptance criteria.
89
+ 9. Таблицы — для AC и трассировки; проза — для контекста.
90
+ 10. Frontmatter: `status`, `owner`, `version`, `supersedes`; без changelog в теле.
91
+ 11. Не более трёх открытых вопросов, у каждого owner и дата; `TBD` в других местах запрещён.
92
+ 12. DoD и метрики — измеримые; «быстрее» без числа не считается.
93
+
94
+ ## Шаблон SPEC.md
95
+
96
+ ```md
97
+ ---
98
+ id: SPEC-000
99
+ title: <название>
100
+ status: draft
101
+ owner: "@team"
102
+ version: 1.0.0
103
+ supersedes: —
104
+ ---
105
+
106
+ # SPEC-000: <название>
107
+
108
+ ## 1. Контекст и проблема
109
+ <3–5 строк: кто страдает, в чём боль, почему сейчас.>
110
+
111
+ ## 2. Цель и non-goals
112
+ **Цель:** <одно предложение>.
113
+ **Non-goals:** <3–7 пунктов «сознательно не делаем»>.
114
+ <!-- один пункт на строку с `-` и отступом 0–4; перенесённый абзац — один пункт;
115
+ фенс, `<pre>` и bullet с отступом 5+ — код, не пункт. -->
116
+
117
+ ## 3. Сценарии
118
+ | ID | Роль | Цель | Ценность | Независимый тест |
119
+ |---|---|---|---|---|
120
+ | S1 | <...> | <...> | <...> | <...> |
121
+
122
+ ## 4. Требования (EARS)
123
+ - **R1** — WHEN <триггер>, THE SYSTEM SHALL <результат>.
124
+ - **R2** — IF <условие>, THEN THE SYSTEM SHALL <...>.
125
+
126
+ ## 5. Acceptance criteria
127
+ | AC | R | Given | When | Then | Test |
128
+ |---|---|---|---|---|---|
129
+ | AC1.1 | R1 | <...> | <...> | <...> | `test_...` |
130
+
131
+ ## 6. Инварианты и краевые случаи
132
+ - Инвариант: <всегда истинно>.
133
+ - Пусто/лимит/конкурентность/ошибка: <...>.
134
+
135
+ ## 7. Контракты
136
+ Ссылки: `<openapi.yaml>`, `<schema.sql>`, `<types.ts>`.
137
+
138
+ ## 8. Нефункциональные требования
139
+ | Метрика | Цель | Как проверяем |
140
+ |---|---|---|
141
+ | Latency p95 | <...> | <...> |
142
+
143
+ ## 9. Метрики успеха
144
+ - **SC-1** — <число и способ проверки>.
145
+
146
+ ## 10. Открытые вопросы
147
+ - **Q1** — <...>; owner: <...>; до <дата>.
148
+
149
+ ## 11. Трассировка
150
+ | R | AC | Task | Test |
151
+ |---|---|---|---|
152
+ | R1 | AC1.1 | T1 | `test_...` |
153
+
154
+ ## 12. Definition of Done
155
+ - [ ] AC закрыты.
156
+ - [ ] Тесты зелёные.
157
+ - [ ] Контракты и docs/ADR обновлены.
158
+ ```
159
+
160
+ ## Команда /agents-md
161
+
162
+ 1. Определи режим: аудит существующего файла или черновик с нуля.
163
+ 2. Для аудита: `orch_uni_context_lint` → `context-lint`; покажи SGR и `closing_list`.
164
+ 3. Для черновика: собери факты и задай не более 8 вопросов; не выдумывай команды и пути.
165
+ 4. Заполни шаблон; уложись в 150–200 строк.
166
+ 5. Прогони линтер, покажи diff и открытые вопросы.
167
+ 6. Канонический файл меняй только после подтверждения; правки — через `fix-runner`.
168
+
169
+ ## Команда /spec
170
+
171
+ 1. Зафиксируй бриф и задай не более 5 уточняющих вопросов.
172
+ 2. Non-goals, приоритеты и метрики утверждает владелец; не придумывай их.
173
+ 3. Заполни шаблон: EARS, AC с тестами, инварианты, трассировка, DoD.
174
+ 4. Прогони `orch_uni_context_lint` и `context-lint`; покажи diff и вопросы.
175
+ 5. Канонический файл меняй только после подтверждения.
176
+
177
+ ## Ревью
178
+
179
+ - [ ] `AGENTS.md` ≤200 строк, `SPEC.md` ≤1,5k слов.
180
+ - [ ] Каждая строка проверяема.
181
+ - [ ] Нет дублей с README/линтером/CI.
182
+ - [ ] Приоритеты и границы прописаны.
183
+ - [ ] Все относительные ссылки существуют.
184
+ - [ ] У каждого R есть AC, у каждого AC есть тест.
185
+ - [ ] Non-goals и DoD на месте.
186
+ - [ ] Есть baseline на 10–20 задачах.
187
+
188
+ ## Анти-паттерны
189
+
190
+ | Анти-паттерн | Почему плохо | Что делать |
191
+ |---|---|---|
192
+ | Автогенерация «на всякий случай» | −0,5…−2% успеха, +20–23% косты | Писать вручную, только высокосигнальное |
193
+ | Пересказ README/архитектуры | Дублирование, нулевой эффект | Ссылка на docs |
194
+ | Дублирование линтера | Токены на то, что и так проверяется | Линтер/CI |
195
+ | Общие фразы («пиши чисто») | Непроверяемо | Императив + пример |
196
+ | Противоречия между уровнями | Модель выбирает произвольно | Явные приоритеты |
@@ -5,6 +5,8 @@
5
5
  - **Root (главный агент)** — не аудирует: формирует бриф, запускает субагентов, сводит отчёты; по «недоказуемым» — решение владельца: root оформляет вопрос, фиксирует риск и эскалирует. Ведёт реестр принятых рисков, вносит правки отдельными коммитами, решает «ship».
6
6
  - **Субагенты** (read-only): `factcheck`, `empiric`, `invariant`, `arbiter`, (на воротах) `redteam`, `meta`.
7
7
 
8
+ Сессионная гигиена среза — границы, бюджет контекста (≤150k токенов, ≤1 компакция) и handoff — в `session-discipline.md`.
9
+
8
10
  ## Шаблон брифа `case-<id>.md` (общий для всех шагов)
9
11
 
10
12
  ```
@@ -0,0 +1,225 @@
1
+ # Гигиена сессии: как вести разработку
2
+
3
+ Документ задаёт протокол работы сессии: где её границы, какой бюджет контекста,
4
+ когда делегировать, как обращаться с выводом инструментов и что остаётся после
5
+ сессии. Он дополняет `context-efficiency.md` (бюджет файлов инструкций) и
6
+ `root-runbook.md` (аудиторский цикл): там — про артефакты и вердикты, здесь —
7
+ про то, как не сжечь контекст и не потерять решения по дороге.
8
+
9
+ ## Зачем это нужно
10
+
11
+ - Контекст — расходуемый ресурс, а не память проекта. С ростом входа качество
12
+ падает даже на простых задачах (context rot); «влезло в окно» не значит
13
+ «модель это использует».
14
+ - Компакция — lossy-пересказ. После неё агент помнит не решения, а их краткое
15
+ изложение; повторная компакция пересказывает пересказ.
16
+ - Замер длинной сессии: 13 253 хода, 19 компакций, медиана промпта выросла с
17
+ 314k до 780k токенов. Почти всё это — повторная пересылка истории, а не работа.
18
+ Это внутренний замер проекта-потребителя: артефакт в репозиторий не приложен,
19
+ из этого репозитория цифры не воспроизводятся.
20
+
21
+ Цель протокола — держать рабочее состояние маленьким, а решения — в файлах,
22
+ которые переживают сессию.
23
+
24
+ ## 1. Сессия = срез
25
+
26
+ Сессия — это один срез с измеримым DoD, а не весь проект.
27
+
28
+ | Правило | Значение |
29
+ | --- | --- |
30
+ | Бюджет оценки промпта | ≤ 150k токенов |
31
+ | Компакций на срез | ≤ 1 |
32
+ | Стоп-сигнал | первая компакция |
33
+ | Обязательный выход | handoff + коммит |
34
+
35
+ - При первом признаке приближения к бюджету — не «дожимать», а закрывать срез:
36
+ handoff, коммит, новая сессия.
37
+ - После первой компакции срез закрывается в любом случае: суммаризация уже
38
+ потеряла детали, и дальнейшая работа идёт по пересказу.
39
+ - Резать работу по волнам, гейтам или пакетам правок; смена фазы (план →
40
+ реализация → приёмка) — всегда новая сессия.
41
+
42
+ ## 2. Разделение ролей
43
+
44
+ - Разведка и проверка («найти», «понять», «воспроизвести») — субагентам: у них
45
+ чистый контекст, а в ответ возвращается 1–2k токенов выводов, а не дамп.
46
+ - Реализация — одному писателю на рабочее пространство: связанные решения должны
47
+ приниматься в одном контексте (иначе параллельные агенты расходятся в стиле и
48
+ контрактах).
49
+ - Решения, сведение и приёмка — root-оркестратору.
50
+ - Аудиторский цикл не меняется: re-audit — resume тех же сессий; короткие
51
+ аудиторские сессии бюджету не угрожают.
52
+
53
+ Практическое правило: делегируй **исследование** широко, **реализацию** — узко,
54
+ **интеграцию** держи централизованной.
55
+
56
+ ## 3. Батчинг инструментов
57
+
58
+ Каждый ход — это полная пересылка промпта. Один инструмент за ход умножает
59
+ бюджет на число мелочей.
60
+
61
+ - ≥ 3 независимые команды за ход или один скрипт вместо пяти-десяти вызовов.
62
+ - Длинные команды WSL выноси в скрипт-обёртку: строка на 250 символов в каждом
63
+ ходе дороже, чем файл, который читается один раз.
64
+ - Для чтения файлов — `read` с `limit`/`offset`, а не `cat` целиком.
65
+ - Цель по метрике: ≥ 2 инструментов на ход.
66
+
67
+ ## 4. Вывод инструментов
68
+
69
+ - Всё, что больше 20k символов, — в файл; в контекст идёт голова (4k) и путь.
70
+ Это делает guard автоматически для `shell`, `bash`, `webfetch`.
71
+ - Скриншоты и бинарные вложения — файлом и путём, не base64 в контексте.
72
+ - `execute` (Code Mode) guard не режет: ограничивай результат в самом скрипте —
73
+ возвращай сводку и путь, а не сырой JSON на сотни килобайт.
74
+ - `read`, `write`, `edit` и инструменты комплекта не урезаются: у них свой
75
+ контракт, и потеря содержимого ломает работу.
76
+
77
+ ## 5. Состояние в файлах
78
+
79
+ - `handoff` — обязательный выход среза (шаблон ниже); путь по умолчанию —
80
+ `docs/planning/handoff-<YYYY-MM-DD>.md`, если каталога нет — корень проекта.
81
+ - `decisions` — решения и причины; одно решение — одна запись; путь по умолчанию —
82
+ `docs/planning/decisions-<YYYY-MM-DD>.md`.
83
+ - `state` — ветка, гейты, открытые риски, следующие три шага; обновляется перед
84
+ новой сессией; путь по умолчанию — `docs/planning/state.md`.
85
+ - Новая сессия начинается с `state` и целевого раздела SPEC, а не с пересказа
86
+ прошлой сессии.
87
+
88
+ ## 6. Модели
89
+
90
+ - По умолчанию — дешёвая модель; дорогая — только на архитектурные решения,
91
+ ревью и разбор спорного.
92
+ - Правки документов, чтение и механические проверки не требуют дорогой модели.
93
+ - Замер: 223 хода дорогой модели — $63, 104 хода дешёвой — $0.09. Один
94
+ переключатель модели экономит больше, чем любая оптимизация промпта. Это
95
+ внутренний замер проекта-потребителя: артефакт в репозиторий не приложен, из
96
+ этого репозитория цифры не воспроизводятся.
97
+
98
+ ## 7. Метрики
99
+
100
+ | Метрика | Цель | Где смотреть |
101
+ | --- | --- | --- |
102
+ | Оценка промпта (медиана) | ≤ 150k | `orch_uni_session_stats` |
103
+ | Компакций на срез | ≤ 1 | `orch_uni_session_stats` |
104
+ | Инструментов на ход (`toolCalls/requests`, он же `toolsPerTurn`) | ≥ 2 | `orch_uni_session_stats` |
105
+ | Доля урезанных выводов среди вызовов инструмента (`toolOutputsCappedByTool.shell / toolCallsByTool.shell`; так же `bash`, `webfetch`) | < 5% | `orch_uni_session_stats` |
106
+ | Handoff в конце среза | есть | рабочее дерево |
107
+
108
+ `orch_uni_session_stats` отдаёт счётчики текущего инстанса плагина и настроенные
109
+ лимиты; исторические прогоны считаются по базе сессий на стороне проекта.
110
+
111
+ ## 8. Guard
112
+
113
+ Guard — это хуки плагина и подписка на события компакции, а не запреты:
114
+
115
+ - `session.context` — оценивает промпт и добавляет одно короткое напоминание при
116
+ переходе на новый уровень бюджета. Напоминание инжектится один раз при
117
+ переходе уровня, а не в каждом запросе; внутри уровня текст стабилен, поэтому
118
+ префикс-кэш не рвётся.
119
+ - события компакции — хук `session.compaction` регистрируется всегда: он считает
120
+ попытки (`compactionAttempts`) и отдаёт tentative-напоминание ближайшему
121
+ запросу при задержке терминального события; напоминание не называет номер
122
+ компакции и не заявляет лимит. Подтверждает компакцию только
123
+ `session.compaction.ended` (дедуп по `event.id` или durable-позиции); он же
124
+ двигает `compactions` и лимит. `session.compaction.failed` увеличивает
125
+ `compactionFailures`, снимает tentative-note и сохраняет confirmed-note, ещё
126
+ ждущий доставки. Если поток событий завершился или упал, guard выставляет
127
+ `eventLive: false`, `compactionSource: "hook"` и `streamFailures: 1`
128
+ (per-session `errors` остаётся `0`), хук уже зарегистрирован и попытки
129
+ продолжают считаться; подтверждённых компакций и стопа на этом пути нет —
130
+ ложного «лимит исчерпан» не бывает. Источник, liveness и сбои потока видны в
131
+ `guard`.
132
+ - `tool.execute.after` — урезает вывод настроенных инструментов и складывает
133
+ полный текст в приватный корень на инстанс плагина
134
+ `os.tmpdir()/opencode-tool-output-<random>/` (POSIX: 0700 на корень и каталог
135
+ сессии, 0600 на файл). На Windows mode не действует (замер: 0666 у корня,
136
+ каталога сессии и файла): приватность там держится только на ACL каталога
137
+ `os.tmpdir()` и кодом не проверяется. Корень создаётся `mkdtemp` при
138
+ setup, поэтому заранее созданный каталог или junction `opencode-tool-output` не
139
+ используется; на POSIX корень обязан принадлежать текущему uid и не давать
140
+ доступа group/other, иначе guard отключается с warning (fail-open: результат не
141
+ трогается). Каталог сессии (`s-<uuid>`) и файл (`c-<uuid>.txt`, запись `wx`) —
142
+ случайные, их `realpath` проверяется внутри корня; при выгрузке плагина корень
143
+ удаляется best effort. Строка с lone surrogate нормализуется один раз (U+FFFD),
144
+ и файл, голова и счётчики описывают уже одну и ту же строку. Остаточный риск
145
+ принят: same-user атакующий всё ещё может
146
+ подменить каталог сессии между `realpath` и `writeFile` (в Node нет `openat`).
147
+ Если `TMPDIR` указывает внутрь воркспейса, файлы попадут туда.
148
+
149
+ Конфигурация (значения по умолчанию):
150
+
151
+ ```jsonc
152
+ {
153
+ "sessionGuard": {
154
+ "enabled": true,
155
+ "promptTokensSoft": 150000,
156
+ "maxCompactions": 1
157
+ },
158
+ "toolOutputGuard": {
159
+ "enabled": true,
160
+ "maxChars": 20000,
161
+ "headChars": 4000,
162
+ "tools": ["shell", "bash", "webfetch"]
163
+ }
164
+ }
165
+ ```
166
+
167
+ `maxCompactions: 0` отключает лимит компакций. `guard.toolOutputEnabled` равен
168
+ `true` только когда приватный корень создан **и** эффективный список инструментов
169
+ непуст; `guard.toolOutputTools` — этот список (после нормализации и отбрасывания
170
+ защищённых и kit-имён), `guard.unkeyedCalls` — число вызовов хуков без непустого
171
+ `sessionID` (такой ввод отбрасывается, а не сливается в бакет `""`). Пустой
172
+ `tools` — явный или после отбрасывания всех имён — выключает кап с warning.
173
+ Принятые ограничения: каталог сессии `s-<uuid>` живёт до выгрузки плагина — он не
174
+ удаляется ни при `session.deleted`, ни после отброшенного капа (например, при
175
+ неизменяемом результате), а освобождается вместе с корнем; нормализация lone
176
+ surrogate опирается на ES2024 `String.prototype.isWellFormed`/`toWellFormed`
177
+ (минимальная версия рантайма в `engines` не зафиксирована — на старом рантайме кап
178
+ деградирует fail-open: `errors` +1, результат не тронут).
179
+ В счётчиках `toolCalls` и
180
+ per-tool счётчики включают вложенные вызовы Code Mode, а `outputCharsSaved` —
181
+ верхняя оценка в символах (внедрённая заглушка вычитается). `toolCallsByTool` и
182
+ `toolOutputsCappedByTool` разносят вызовы и урезанные выводы по нормализованному
183
+ имени инструмента (namespace снят, регистр нижний). Дубликаты событий компакции
184
+ считаются один раз только при наличии `event.id` или durable-позиции; без
185
+ identity повторная доставка может пересчитать событие (fail-open в сторону
186
+ счёта).
187
+
188
+ Guard fail-open: ошибка хука не ломает запрос и не портит результат инструмента.
189
+ Он не пишет в рабочее дерево (кроме случая `TMPDIR` внутри воркспейса — см.
190
+ выше) и не блокирует действия — только напоминает и урезает вывод. Отключить
191
+ можно любым из флагов `enabled`.
192
+
193
+ ## Шаблон handoff
194
+
195
+ ```md
196
+ # Handoff: <срез> — <YYYY-MM-DD>
197
+
198
+ <!-- Файл: docs/planning/handoff-<YYYY-MM-DD>.md; нет каталога — корень проекта. -->
199
+
200
+ ## Цель и DoD
201
+ Одно предложение цели и проверяемый критерий готовности.
202
+
203
+ ## Сделано
204
+ - Что изменено; чем подтверждено (команда, тест, коммит).
205
+
206
+ ## Не сделано
207
+ - Что осталось и почему (блокер, решение владельца, следующая волна).
208
+
209
+ ## Решения
210
+ - Решение — причина; ссылка на документ или коммит.
211
+
212
+ ## Риски и ограничения
213
+ - Принятый риск — кем и до какого момента.
214
+
215
+ ## Следующий шаг
216
+ Одно конкретное действие для следующей сессии.
217
+ ```
218
+
219
+ ## Чек-лист среза
220
+
221
+ - [ ] Срез закрыт по DoD, а не по «кончилось время».
222
+ - [ ] Handoff записан и ссылается на доказательства.
223
+ - [ ] Решения и риски зафиксированы в файлах, а не только в переписке.
224
+ - [ ] Рабочее дерево чистое или явно описано в handoff.
225
+ - [ ] Следующая сессия начинается с `state` и SPEC, а не с пересказа.