opencode-orchestrator-uni 0.4.0 → 0.5.1
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.
- package/README.md +149 -14
- package/README.ru.md +151 -14
- package/agents/en/orchestrator.md +51 -5
- package/agents/ru/orchestrator.md +51 -5
- package/docs/en/call-mechanics.md +6 -4
- package/docs/en/instruction-other-session.md +6 -6
- package/docs/en/root-runbook.md +3 -1
- package/docs/en/session-discipline.md +230 -0
- package/docs/ru/call-mechanics.md +6 -4
- package/docs/ru/instruction-other-session.md +6 -6
- package/docs/ru/root-runbook.md +3 -1
- package/docs/ru/session-discipline.md +225 -0
- package/index.ts +44 -12
- package/lib/commands.ts +62 -6
- package/lib/session-budget.ts +1236 -0
- package/lib/tools.ts +145 -7
- package/lib/write-guard.ts +283 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,20 +6,25 @@ A native [OpenCode V2](https://opencode.ai/v2/docs/build/plugins) package plugin
|
|
|
6
6
|
ships the **orchestrator agent kit**:
|
|
7
7
|
|
|
8
8
|
- `orchestrator` — the root orchestrator (primary agent) with the audit loop as a
|
|
9
|
-
built-in mode;
|
|
9
|
+
built-in mode; it delegates every code/doc/test edit and writes only audit and
|
|
10
|
+
session-state artifacts;
|
|
10
11
|
- read-only audit subagents — `factcheck`, `empiric`, `invariant`,
|
|
11
12
|
`arbiter`, `meta`, `redteam`, `context-lint`;
|
|
12
|
-
- `fix-runner` — the only writing subagent (applies the "to close" list);
|
|
13
|
+
- `fix-runner` — the only writing subagent of the kit (applies the "to close" list);
|
|
13
14
|
- the kit's process documents, exposed as the **`audit-kit` reference**
|
|
14
15
|
(`call-mechanics.md`, `process-ready-loop.md`, `root-runbook.md`,
|
|
15
|
-
`instruction-other-session.md`, `context-efficiency.md`);
|
|
16
|
-
-
|
|
17
|
-
`orch_uni_snapshot`, `orch_uni_sessions` (see [Tools](#tools));
|
|
18
|
-
-
|
|
16
|
+
`instruction-other-session.md`, `context-efficiency.md`, `session-discipline.md`);
|
|
17
|
+
- five deterministic, read-only tools — `orch_uni_validate_report`, `orch_uni_context_lint`,
|
|
18
|
+
`orch_uni_snapshot`, `orch_uni_sessions`, `orch_uni_session_stats` (see [Tools](#tools));
|
|
19
|
+
- three slash commands — `/agents-md`, `/spec` and `/handoff` (see [Commands](#commands)).
|
|
19
20
|
|
|
20
|
-
Read-only audit agents are enforced
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
Read-only audit agents are enforced at the tool level for `edit`/`write`/`patch`
|
|
22
|
+
(`edit: deny`) and by the prompt ban for everything else; their `shell` permission
|
|
23
|
+
stays allowed so they can run verification commands. A write through shell (redirect,
|
|
24
|
+
`sed -i`, `tee`) is forbidden by the prompt, not by a sandbox — an accepted risk, same
|
|
25
|
+
as I-M15; no permission is relaxed.
|
|
26
|
+
|
|
27
|
+
The orchestrator itself has `edit` denied on `*`; the flat `permissions` are only a narrow fallback allowlist for audit/state artifacts, and the real guard is the `permission.hook("evaluate")` hook in `lib/write-guard.ts` with path-root semantics. The session directory is read per event (`session.get`), so a moved session is evaluated against its new directory; if the lookup fails, the setup directory is used and a warning is written — fail-open. A resource outside the session directory is denied: the guard is the workspace boundary, so `reports/`, `docs/planning/` and `audit-*` are not writable outside it. Inside it the first **root** segment decides. `reports/` is a reserved artifact root — the whole subtree is writable (even `reports/src/x.ts`), so project code must not live there (a `packages/reports/**` path counts as an artifact by contract); `docs/planning/**/*.md` is allowed (nested Markdown at any depth; a `.ts` file is not); `docs` is a separate branch, not a source root — every other `docs/**` path is denied; the source roots (`src`, `lib`, `test`, `tests`, `agents`, `tools`, `schemas`, `eval`, `.github`) are denied even under an artifact-looking prefix (`src/reports/x.ts`, `src/docs/planning/x.md`). Root artifact files (`case-*`, `risks-*`, `handoff-*`, `decisions-*`, `state.md`) are allowed only in the location root, under `reports/`, `docs/planning/` or an `audit-*` directory — `app/state.md` is denied. The hook runs after the flat rules, so a local `edit: allow` for `orchestrator` is closed too. The flat fallback carries only that artifact set as location-root rules (`reports/*`, `case-*.md`, `risks-*.md`, `handoff-*.md`, `decisions-*.md`, `state.md`, `docs/planning/*.md`) plus `audit-*/…`; the broad `*/state.md`-style patterns are gone, so `src/state.md` is denied even without the hook, but last-match still cannot express root priority everywhere (`audit-x/src/case-y.md`) — a warning is written. The fallback is narrower than the hook for nested artifact roots: `lab/x/reports/*` and similar paths are written only with the hook (the root rules cover `reports/*` at the location root only). A failing or timed-out session lookup (250 ms bound) falls back to the setup directory and writes the warning once per guard; for a moved session an absolute artifact path may then be denied (fail-closed for that class). A single `sessionWarned` flag without a counter is accepted: failures after the first warning stay silent. `shell` stays allowed for git and checks; a shell write and symlinks/junctions (the host matches the normalized lexical path, not `realpath`) are accepted risks, as is fail-open (a broken guard never breaks the session), governed by the prompt rather than a sandbox. A tool denial is a role rule — the edit goes to a subagent.
|
|
23
28
|
|
|
24
29
|
The package is loaded directly from TypeScript: `exports` points at
|
|
25
30
|
[`index.ts`](./index.ts). There is **no build step and no `dist/`** — OpenCode's
|
|
@@ -32,7 +37,7 @@ On setup (`ctx.agent.transform` / `ctx.reference.transform`):
|
|
|
32
37
|
|
|
33
38
|
| Agent | Mode | Role |
|
|
34
39
|
| ----------------- | ---------- | -------------------------------------------------------- |
|
|
35
|
-
| `orchestrator` | `primary` | Root orchestrator;
|
|
40
|
+
| `orchestrator` | `primary` | Root orchestrator; delegates edits; writes audit/state artifacts |
|
|
36
41
|
| `factcheck` | `subagent` | Read-only: staleness pre-pass (already done / outdated) |
|
|
37
42
|
| `empiric` | `subagent` | Read-only: reproductions, measurements, first sources |
|
|
38
43
|
| `invariant` | `subagent` | Read-only: states, cancels, races, hostile input |
|
|
@@ -55,8 +60,8 @@ directory (see [Locale](#locale)). OpenCode lists it in session context as an
|
|
|
55
60
|
additional local directory, so the orchestrator (and any other agent) can read
|
|
56
61
|
the process documents directly, for example `<reference path>/call-mechanics.md`.
|
|
57
62
|
|
|
58
|
-
The plugin also registers
|
|
59
|
-
namespace and
|
|
63
|
+
The plugin also registers five deterministic, **read-only** tools in the `orch_uni`
|
|
64
|
+
namespace and three slash commands — see [Tools](#tools) and [Commands](#commands).
|
|
60
65
|
|
|
61
66
|
## Commands
|
|
62
67
|
|
|
@@ -64,6 +69,7 @@ namespace and two slash commands — see [Tools](#tools) and [Commands](#command
|
|
|
64
69
|
| --- | --- |
|
|
65
70
|
| `/agents-md` | Injects the AGENTS.md audit/draft workflow: `context-efficiency.md` template, `orch_uni_context_lint`, `context-lint`, approval before writing. |
|
|
66
71
|
| `/spec` | Injects the SPEC.md workflow: brief, clarifying questions, EARS/AC/traceability, `orch_uni_context_lint`, `context-lint`, approval before writing. |
|
|
72
|
+
| `/handoff` | Injects the slice-closing workflow: session and working-tree facts, the `session-discipline.md` handoff template, writes `docs/planning/handoff-<YYYY-MM-DD>.md`. |
|
|
67
73
|
|
|
68
74
|
A command is a prompt, not a writer: its handler injects a workflow prompt into the
|
|
69
75
|
current session. The orchestrator then follows the usual evidence and approval rules.
|
|
@@ -76,10 +82,11 @@ current session. The orchestrator then follows the usual evidence and approval r
|
|
|
76
82
|
| `orch_uni_context_lint` | yes | Deterministic audit of AGENTS.md/SPEC.md: budget, vague phrases, links, sections |
|
|
77
83
|
| `orch_uni_snapshot` | yes | Deterministic revision of a directory tree (per-file sha256 + aggregate hash) |
|
|
78
84
|
| `orch_uni_sessions` | yes | Child sessions of a session: agent, model, cost, tokens, time |
|
|
85
|
+
| `orch_uni_session_stats` | yes | Session protocol counters: prompt, compactions, tools/turn, capped outputs |
|
|
79
86
|
|
|
80
87
|
Every tool answers with a JSON text body. Failures are **typed codes** inside the body
|
|
81
88
|
(`E_INPUT`, `E_NO_JSON`, …) instead of thrown tool errors, so an agent can branch on the
|
|
82
|
-
code without parsing prose. None of the
|
|
89
|
+
code without parsing prose. None of the five writes to the workspace, the session store
|
|
83
90
|
or the configuration.
|
|
84
91
|
|
|
85
92
|
### `orch_uni_validate_report`
|
|
@@ -261,6 +268,134 @@ picks up fixture trees such as `eval/`; pass an explicit path for the canonical
|
|
|
261
268
|
`E_INPUT` for malformed arguments, `E_NOT_FOUND` when the parent session does not exist,
|
|
262
269
|
`E_ABORTED` when the session is stopped.
|
|
263
270
|
|
|
271
|
+
### `orch_uni_session_stats`
|
|
272
|
+
|
|
273
|
+
```jsonc
|
|
274
|
+
{} // counters of the calling session
|
|
275
|
+
{ "sessionID": "ses_…" } // any session on this server, not only a child
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
```jsonc
|
|
279
|
+
{ "ok": true, "sessionID": "ses_…",
|
|
280
|
+
"counters": {
|
|
281
|
+
"requests": 12, "promptTokensLast": 151204, "promptTokensMax": 161002,
|
|
282
|
+
"promptTokensMedian": 148900, "band": 1, "compactions": 1,
|
|
283
|
+
"compactionAttempts": 1, "compactionFailures": 0, "compactionSource": "event",
|
|
284
|
+
"reminders": 2, "toolCalls": 31, "outputsCapped": 1,
|
|
285
|
+
"outputCharsSaved": 18422, "errors": 0,
|
|
286
|
+
"toolCallsByTool": { "shell": 18, "bash": 6, "webfetch": 2, "read": 5 },
|
|
287
|
+
"toolOutputsCappedByTool": { "shell": 1, "bash": 0, "webfetch": 0, "read": 0 }
|
|
288
|
+
},
|
|
289
|
+
"limits": { "promptTokensSoft": 150000, "maxCompactions": 1, "maxToolOutputChars": 20000 },
|
|
290
|
+
"overBudget": true, "compactionLimitReached": true, "toolsPerTurn": 2.58,
|
|
291
|
+
"tracked": true,
|
|
292
|
+
"guard": { "available": true, "hooksRegistered": true, "sessionEnabled": true,
|
|
293
|
+
"toolOutputEnabled": true,
|
|
294
|
+
"toolOutputTools": ["shell", "bash", "webfetch"],
|
|
295
|
+
"compactionSource": "event", "eventLive": true,
|
|
296
|
+
"streamFailures": 0, "unkeyedCalls": 0 } }
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
`sessionID` may name **any session on this server**, not only a child of the caller;
|
|
300
|
+
that is deliberate — the orchestrator uses the report to control every slice it runs.
|
|
301
|
+
Counters are per plugin instance, and `tracked` is `true` once any event or hook
|
|
302
|
+
observed the session; a `session.deleted` event forgets it. `band` is the highest
|
|
303
|
+
budget level reached (`0` below the soft threshold); `promptTokensMedian` is the
|
|
304
|
+
median over the last 256 requests; `toolCalls` and the per-tool counters include
|
|
305
|
+
nested Code Mode calls; `toolCallsByTool` and `toolOutputsCappedByTool` split calls
|
|
306
|
+
and capped outputs by the normalized tool name (namespace stripped, lower-case);
|
|
307
|
+
`outputCharsSaved` is a character count, not bytes, and an upper estimate (the
|
|
308
|
+
injected stub is subtracted). `compactions` counts confirmed
|
|
309
|
+
`session.compaction.ended` events only; `compactionAttempts` counts every
|
|
310
|
+
`session.compaction` hook call, so on a host with a dead or absent event stream
|
|
311
|
+
attempts keep growing while `compactions` and `compactionLimitReached` stay put (a
|
|
312
|
+
failed attempt is never a stop signal). A dead stream is reported once in
|
|
313
|
+
`guard.streamFailures`; the per-session `errors` counts hook failures only, so a
|
|
314
|
+
transport incident is not attributed to every session. Duplicate compaction events
|
|
315
|
+
are counted once only when the host supplies `event.id` or a durable position;
|
|
316
|
+
without either identity a re-delivered event may be counted again (fail-open in the
|
|
317
|
+
direction of counting). A
|
|
318
|
+
session the store does not return and this instance never observed is
|
|
319
|
+
`E_NOT_FOUND` (recognized by the typed `_tag`/`name`/`code` — `Session.NotFoundError`,
|
|
320
|
+
`SessionNotFoundError`, `NotFoundError`, `ENOENT` — with a narrow message fallback);
|
|
321
|
+
`session.get` returning `undefined` for an untracked session is the same not-found
|
|
322
|
+
answer; a transient `session.get` failure (a lock, permission, `unknown database`) is
|
|
323
|
+
`E_INTERNAL`; a known session with zero requests is a valid report with
|
|
324
|
+
`tracked: false`. Without `session.get`,
|
|
325
|
+
an untracked session is `E_NO_API`, a tracked one is `ok` with `session: null` and a
|
|
326
|
+
`limitations` entry. Codes: `E_INPUT`, `E_NOT_FOUND`, `E_INTERNAL`, `E_NO_API`,
|
|
327
|
+
`E_UNAVAILABLE` (the guard is not registered or its hooks failed), `E_ABORTED`.
|
|
328
|
+
|
|
329
|
+
## Session hygiene
|
|
330
|
+
|
|
331
|
+
The plugin implements the [`session-discipline.md`](./docs/en/session-discipline.md)
|
|
332
|
+
protocol: a session is one slice, the prompt budget is ≤150k tokens, at most one
|
|
333
|
+
compaction, tool output goes to a file, and a slice ends with a handoff.
|
|
334
|
+
|
|
335
|
+
The runtime part is three hooks (`session.context`, `session.compaction`,
|
|
336
|
+
`tool.execute.after`) plus a compaction-event subscription:
|
|
337
|
+
|
|
338
|
+
- `session.context` — estimates the prompt (system + messages) and, when it crosses
|
|
339
|
+
a budget level, adds one short reminder. The reminder is injected **once, when a
|
|
340
|
+
new level is crossed**, not on every request; within a level the text is stable,
|
|
341
|
+
so the prefix cache is not broken.
|
|
342
|
+
- compaction events — the `session.compaction` hook is registered on **every** host:
|
|
343
|
+
it counts attempts (`compactionAttempts`) and sends a tentative note to the
|
|
344
|
+
nearest request; the note names no compaction number and claims no limit. Only
|
|
345
|
+
`session.compaction.ended` confirms a compaction (deduplicated by `event.id` or
|
|
346
|
+
the durable position) and raises a confirmed note with the number and the limit;
|
|
347
|
+
`session.compaction.failed` increments `compactionFailures`, drops the tentative
|
|
348
|
+
note and keeps a confirmed note that is still waiting for delivery. If the event
|
|
349
|
+
stream ends or throws, `guard.eventLive` turns `false`,
|
|
350
|
+
`guard.compactionSource` becomes `hook` and `guard.streamFailures` becomes `1`
|
|
351
|
+
(the per-session `errors` stays `0`); the hook keeps counting attempts, but no
|
|
352
|
+
confirmed compaction and no false "limit reached" is invented on that path.
|
|
353
|
+
- `tool.execute.after` — for `shell`, `bash`, `webfetch` it caps output over
|
|
354
|
+
`maxChars`: the full text goes to a private per-process root
|
|
355
|
+
`os.tmpdir()/opencode-tool-output-<random>/` (POSIX 0700 for the root and the
|
|
356
|
+
session directory, 0600 for the file). Windows ignores mode (measured: 0666),
|
|
357
|
+
so privacy there rests on the ACL of `os.tmpdir()` and is not checked by code.
|
|
358
|
+
The model gets a head and the path; the root is created with `mkdtemp` at plugin
|
|
359
|
+
setup, so a
|
|
360
|
+
pre-created `opencode-tool-output` directory or junction is never used; on POSIX
|
|
361
|
+
the root must belong to the current uid with no group/other access, otherwise
|
|
362
|
+
the guard disables itself with a warning (fail-open: results are untouched).
|
|
363
|
+
Session directories (`s-<uuid>`) and files (`c-<uuid>.txt`, written with `wx`)
|
|
364
|
+
are random, and each `realpath` is proven inside the root. The root is removed
|
|
365
|
+
when the plugin unloads (best effort). A lone surrogate is normalized once
|
|
366
|
+
(U+FFFD), so the file, the head and the counters describe the same string.
|
|
367
|
+
Residual risk accepted: a same-user
|
|
368
|
+
attacker can still swap a session directory between `realpath` and `writeFile`
|
|
369
|
+
(Node has no `openat`). If `TMPDIR` points inside the workspace, the files land
|
|
370
|
+
there instead. `read`, `write`, `edit`, `execute` and the kit's own tools are
|
|
371
|
+
not capped, and a kit tool named without its namespace (`session_stats`) is
|
|
372
|
+
refused in the config with a warning.
|
|
373
|
+
|
|
374
|
+
`maxCompactions: 0` disables the compaction limit. The guard is fail-open: a hook
|
|
375
|
+
error neither breaks the request nor corrupts the result. Either part can be
|
|
376
|
+
disabled with its `enabled` flag:
|
|
377
|
+
|
|
378
|
+
```jsonc
|
|
379
|
+
{
|
|
380
|
+
"plugins": [
|
|
381
|
+
{
|
|
382
|
+
"package": "opencode-orchestrator-uni",
|
|
383
|
+
"options": {
|
|
384
|
+
"sessionGuard": { "enabled": true, "promptTokensSoft": 150000, "maxCompactions": 1 },
|
|
385
|
+
"toolOutputGuard": {
|
|
386
|
+
"enabled": true,
|
|
387
|
+
"maxChars": 20000,
|
|
388
|
+
"headChars": 4000,
|
|
389
|
+
"tools": ["shell", "bash", "webfetch"]
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
]
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
Slice metrics — `orch_uni_session_stats`; closing a slice — the `/handoff` command.
|
|
398
|
+
|
|
264
399
|
## Models
|
|
265
400
|
|
|
266
401
|
Agent definitions carry no `model`; models are configured on the plugin entry in
|
|
@@ -408,7 +543,7 @@ npm run lint:fix # biome check --write . (applies safe fixes)
|
|
|
408
543
|
|
|
409
544
|
The `check:locales` and `verify:kit` scripts need a git checkout of the kit — the npm package ships no `tools/` directory, so in an installed package they are unavailable (`stand: unavailable` plus your own reproducible checks).
|
|
410
545
|
|
|
411
|
-
`npm test` scans only `./test/` — the shipped suite (
|
|
546
|
+
`npm test` scans only `./test/` — the shipped suite (400 tests in 10 files, up from
|
|
412
547
|
72 in 5 before the identifier guards). A bare `bun test` from the repository root is red by
|
|
413
548
|
design: the positional argument of `bun test` is a path substring filter, so the
|
|
414
549
|
bare command also picks up the intentionally failing fixtures under `eval/cases/`
|
package/README.ru.md
CHANGED
|
@@ -6,20 +6,25 @@
|
|
|
6
6
|
который поставляет **комплект агентов-оркестраторов**:
|
|
7
7
|
|
|
8
8
|
- `orchestrator` — root-оркестратор (основной агент) со встроенным режимом
|
|
9
|
-
аудиторского цикла;
|
|
9
|
+
аудиторского цикла; делегирует правки кода/доков/тестов и пишет только
|
|
10
|
+
артефакты аудита и состояния;
|
|
10
11
|
- read-only субагенты аудита — `factcheck`, `empiric`, `invariant`,
|
|
11
12
|
`arbiter`, `meta`, `redteam`, `context-lint`;
|
|
12
|
-
- `fix-runner` — единственный пишущий субагент (применяет список «к закрытию»);
|
|
13
|
+
- `fix-runner` — единственный пишущий субагент комплекта (применяет список «к закрытию»);
|
|
13
14
|
- процессные документы комплекта, доступные как **reference `audit-kit`**
|
|
14
15
|
(`call-mechanics.md`, `process-ready-loop.md`, `root-runbook.md`,
|
|
15
|
-
`instruction-other-session.md`, `context-efficiency.md`);
|
|
16
|
-
-
|
|
17
|
-
`orch_uni_context_lint`, `orch_uni_snapshot`, `orch_uni_sessions
|
|
18
|
-
(см. [Инструменты](#инструменты));
|
|
19
|
-
-
|
|
16
|
+
`instruction-other-session.md`, `context-efficiency.md`, `session-discipline.md`);
|
|
17
|
+
- пять детерминированных read-only инструментов — `orch_uni_validate_report`,
|
|
18
|
+
`orch_uni_context_lint`, `orch_uni_snapshot`, `orch_uni_sessions`,
|
|
19
|
+
`orch_uni_session_stats` (см. [Инструменты](#инструменты));
|
|
20
|
+
- три слэш-команды — `/agents-md`, `/spec` и `/handoff` (см. [Команды](#команды)).
|
|
20
21
|
|
|
21
|
-
Read-only аудиторы держатся на `edit: deny`
|
|
22
|
-
|
|
22
|
+
Read-only аудиторы держатся на `edit: deny` (рантайм покрывает `edit`/`write`/`patch`)
|
|
23
|
+
и запрете в промпте для остального; `shell`-разрешение остаётся, чтобы запускать
|
|
24
|
+
проверки. Запись через shell (redirect, `sed -i`, `tee`) запрещена промптом, но не
|
|
25
|
+
песочницей — принятый риск, как I-M15; ни одно разрешение не ослаблено.
|
|
26
|
+
|
|
27
|
+
У самого оркестратора `edit` запрещён на `*`; плоские `permissions` — лишь узкий fallback-allowlist для артефактов аудита/состояния, а реальный guard — hook `permission.hook("evaluate")` в `lib/write-guard.ts` с path-root семантикой. Каталог сессии читается на каждое событие (`session.get`), поэтому перемещённая сессия оценивается от нового каталога; при ошибке чтения — setup-каталог и warning (fail-open). Ресурс вне каталога сессии — deny: guard и есть граница workspace, поэтому `reports/`, `docs/planning/` и `audit-*` вне её не пишутся. Внутри каталога первый **корневой** сегмент решает. `reports/` — зарезервированный artifact-root: всё поддерево доступно для записи (даже `reports/src/x.ts`), поэтому проектный код туда не кладём (путь `packages/reports/**` по контракту тоже считается артефактом); `docs/planning/**/*.md` разрешён (вложенный Markdown на любой глубине; `.ts` — нет); `docs` — отдельная ветвь, не source-root: остальной `docs/**` запрещён; source-корни (`src`, `lib`, `test`, `tests`, `agents`, `tools`, `schemas`, `eval`, `.github`) запрещены даже под артефактным префиксом (`src/reports/x.ts`, `src/docs/planning/x.md`). Корневые artifact-файлы (`case-*`, `risks-*`, `handoff-*`, `decisions-*`, `state.md`) разрешены только в корне location, под `reports/`, `docs/planning/` или в каталоге `audit-*` — `app/state.md` запрещён. Hook выполняется после плоских правил, поэтому локальный `edit: allow` для `orchestrator` тоже закрыт. Плоский fallback несёт только этот артефактный набор root-правилами (`reports/*`, `case-*.md`, `risks-*.md`, `handoff-*.md`, `decisions-*.md`, `state.md`, `docs/planning/*.md`) плюс `audit-*/…`; широкие `*/state.md`-шаблоны убраны, поэтому `src/state.md` запрещён и без hook, но last-match всё равно не выражает приоритет корней везде (`audit-x/src/case-y.md`) — плагин пишет warning. Fallback уже hook'а для вложенных артефактных корней: `lab/x/reports/*` и подобные пути пишутся только с hook (root-правила покрывают лишь `reports/*` от корня location). Сбой или таймаут чтения сессии (граница 250 мс) даёт fallback на setup-каталог и warning один раз на guard; у перемещённой сессии абсолютный артефакт в новом каталоге тогда может получить deny (fail-closed для этого класса). Один флаг `sessionWarned` без счётчика — принято: сбои после первого warning молчат. `shell` остаётся для git и проверок; запись через shell и symlink/junction (хост матчит нормализованный лексический путь, а не `realpath`) — принятые риски, как и fail-open (сломанный guard не роняет сессию); ими управляет промпт, а не песочница. Отказ инструмента — это правило роли, правка уходит субагенту.
|
|
23
28
|
|
|
24
29
|
Пакет загружается напрямую из TypeScript: `exports` указывает на
|
|
25
30
|
[`index.ts`](./index.ts). **Шага сборки и каталога `dist/` нет** — Bun-овый
|
|
@@ -32,7 +37,7 @@ Read-only аудиторы держатся на `edit: deny` плюс запр
|
|
|
32
37
|
|
|
33
38
|
| Агент | Режим | Роль |
|
|
34
39
|
| -------------- | ---------- | --------------------------------------------------------------- |
|
|
35
|
-
| `orchestrator` | `primary` | Root-оркестратор;
|
|
40
|
+
| `orchestrator` | `primary` | Root-оркестратор; делегирует правки; пишет артефакты аудита/состояния |
|
|
36
41
|
| `factcheck` | `subagent` | Read-only: пред-проход на устаревание («уже сделано» / устарело) |
|
|
37
42
|
| `empiric` | `subagent` | Read-only: воспроизведения, замеры, первоисточники |
|
|
38
43
|
| `invariant` | `subagent` | Read-only: состояния, отмены, гонки, враждебный ввод |
|
|
@@ -57,8 +62,8 @@ Reference `audit-kit` указывает на per-locale каталог `docs/<l
|
|
|
57
62
|
может читать процессные документы напрямую, например
|
|
58
63
|
`<reference path>/call-mechanics.md`.
|
|
59
64
|
|
|
60
|
-
Плагин также регистрирует
|
|
61
|
-
пространстве имён `orch_uni` и
|
|
65
|
+
Плагин также регистрирует пять детерминированных **read-only** инструментов в
|
|
66
|
+
пространстве имён `orch_uni` и три слэш-команды — см. [Инструменты](#инструменты)
|
|
62
67
|
и [Команды](#команды).
|
|
63
68
|
|
|
64
69
|
## Команды
|
|
@@ -67,6 +72,7 @@ Reference `audit-kit` указывает на per-locale каталог `docs/<l
|
|
|
67
72
|
| --- | --- |
|
|
68
73
|
| `/agents-md` | Вставляет workflow аудита/черновика AGENTS.md: шаблон `context-efficiency.md`, `orch_uni_context_lint`, `context-lint`, подтверждение перед записью. |
|
|
69
74
|
| `/spec` | Вставляет workflow SPEC.md: бриф, уточняющие вопросы, EARS/AC/трассировка, `orch_uni_context_lint`, `context-lint`, подтверждение перед записью. |
|
|
75
|
+
| `/handoff` | Вставляет workflow закрытия среза: факты сессии и рабочего дерева, шаблон handoff из `session-discipline.md`, запись `docs/planning/handoff-<YYYY-MM-DD>.md`. |
|
|
70
76
|
|
|
71
77
|
Команда — это промпт, а не писатель: обработчик вставляет workflow-промпт в
|
|
72
78
|
текущую сессию. Дальше оркестратор работает по обычным правилам evidence и
|
|
@@ -80,10 +86,11 @@ Reference `audit-kit` указывает на per-locale каталог `docs/<l
|
|
|
80
86
|
| `orch_uni_context_lint` | да | Детерминированный аудит AGENTS.md/SPEC.md: бюджет, vague, ссылки, секции |
|
|
81
87
|
| `orch_uni_snapshot` | да | Детерминированная ревизия дерева каталогов (sha256 на файл + агрегатный хэш) |
|
|
82
88
|
| `orch_uni_sessions` | да | Дочерние сессии сессии: агент, модель, стоимость, токены, время |
|
|
89
|
+
| `orch_uni_session_stats` | да | Счётчики протокола сессии: промпт, компакции, тулы/ход, урезанные выводы |
|
|
83
90
|
|
|
84
91
|
Каждый инструмент отвечает JSON-текстом. Ошибки — **типизированные коды** внутри
|
|
85
92
|
тела (`E_INPUT`, `E_NO_JSON`, …), а не исключения, чтобы агент мог ветвиться по
|
|
86
|
-
коду, не разбирая прозу. Ни один из
|
|
93
|
+
коду, не разбирая прозу. Ни один из пяти ничего не пишет в воркспейс, хранилище
|
|
87
94
|
сессий или конфигурацию.
|
|
88
95
|
|
|
89
96
|
### `orch_uni_validate_report`
|
|
@@ -263,6 +270,136 @@ Commands, HTML-комментарий вроде `<!-- npm install -->` кома
|
|
|
263
270
|
`E_INPUT` при некорректных аргументах, `E_NOT_FOUND`, когда родительской сессии
|
|
264
271
|
нет, `E_ABORTED`, когда сессия остановлена.
|
|
265
272
|
|
|
273
|
+
### `orch_uni_session_stats`
|
|
274
|
+
|
|
275
|
+
```jsonc
|
|
276
|
+
{} // счётчики вызывающей сессии
|
|
277
|
+
{ "sessionID": "ses_…" } // любая сессия на этом сервере, не только дочерняя
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
```jsonc
|
|
281
|
+
{ "ok": true, "sessionID": "ses_…",
|
|
282
|
+
"counters": {
|
|
283
|
+
"requests": 12, "promptTokensLast": 151204, "promptTokensMax": 161002,
|
|
284
|
+
"promptTokensMedian": 148900, "band": 1, "compactions": 1,
|
|
285
|
+
"compactionAttempts": 1, "compactionFailures": 0, "compactionSource": "event",
|
|
286
|
+
"reminders": 2, "toolCalls": 31, "outputsCapped": 1,
|
|
287
|
+
"outputCharsSaved": 18422, "errors": 0,
|
|
288
|
+
"toolCallsByTool": { "shell": 18, "bash": 6, "webfetch": 2, "read": 5 },
|
|
289
|
+
"toolOutputsCappedByTool": { "shell": 1, "bash": 0, "webfetch": 0, "read": 0 }
|
|
290
|
+
},
|
|
291
|
+
"limits": { "promptTokensSoft": 150000, "maxCompactions": 1, "maxToolOutputChars": 20000 },
|
|
292
|
+
"overBudget": true, "compactionLimitReached": true, "toolsPerTurn": 2.58,
|
|
293
|
+
"tracked": true,
|
|
294
|
+
"guard": { "available": true, "hooksRegistered": true, "sessionEnabled": true,
|
|
295
|
+
"toolOutputEnabled": true,
|
|
296
|
+
"toolOutputTools": ["shell", "bash", "webfetch"],
|
|
297
|
+
"compactionSource": "event", "eventLive": true,
|
|
298
|
+
"streamFailures": 0, "unkeyedCalls": 0 } }
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
`sessionID` может называть **любую сессию на этом сервере**, не только дочернюю
|
|
302
|
+
вызывающего; это осознанно — по отчёту оркестратор контролирует все свои срезы.
|
|
303
|
+
Счётчики принадлежат инстансу плагина, а `tracked` равен `true`, как только сессию
|
|
304
|
+
наблюдало любое событие или хук; событие `session.deleted` её забывает. `band` —
|
|
305
|
+
максимум достигнутого уровня бюджета (`0` ниже мягкого порога);
|
|
306
|
+
`promptTokensMedian` — медиана по окну последних 256 запросов; `toolCalls` и
|
|
307
|
+
per-tool счётчики включают вложенные вызовы Code Mode; `toolCallsByTool` и
|
|
308
|
+
`toolOutputsCappedByTool` разносят вызовы и урезанные выводы по нормализованному
|
|
309
|
+
имени инструмента (namespace снят, регистр нижний); `outputCharsSaved` — число
|
|
310
|
+
символов, не байтов, и верхняя оценка (внедрённая заглушка вычитается).
|
|
311
|
+
`compactions` считает только подтверждённые `session.compaction.ended`;
|
|
312
|
+
`compactionAttempts` — каждый вызов хука `session.compaction`, поэтому на хосте с
|
|
313
|
+
умершим или отсутствующим потоком событий попытки растут, а `compactions` и
|
|
314
|
+
`compactionLimitReached` стоят (неудачная попытка не становится стоп-сигналом).
|
|
315
|
+
Умерший поток событий один раз виден в `guard.streamFailures`; per-session
|
|
316
|
+
`errors` считает только ошибки хуков, поэтому транспортная авария не
|
|
317
|
+
приписывается каждой сессии. Дубликаты событий компакции считаются один раз
|
|
318
|
+
только при наличии `event.id` или durable-позиции; без identity повторная
|
|
319
|
+
доставка может пересчитать событие (fail-open в сторону счёта).
|
|
320
|
+
Сессия, которую не отдаёт хранилище и не наблюдал этот инстанс, — `E_NOT_FOUND`
|
|
321
|
+
(распознаётся по типизированным `_tag`/`name`/`code` — `Session.NotFoundError`,
|
|
322
|
+
`SessionNotFoundError`, `NotFoundError`, `ENOENT` — с узким fallback по сообщению);
|
|
323
|
+
`session.get`, вернувший `undefined` для ненаблюдаемой сессии, — тот же ответ
|
|
324
|
+
«не найдено»; transient-ошибка `session.get` (блокировка, права, `unknown
|
|
325
|
+
database`) — `E_INTERNAL`; известная сессия без запросов — валидный отчёт с
|
|
326
|
+
`tracked: false`. Без `session.get` ненаблюдаемая сессия —
|
|
327
|
+
`E_NO_API`, наблюдаемая — `ok` с `session: null` и записью в `limitations`.
|
|
328
|
+
Коды: `E_INPUT`, `E_NOT_FOUND`, `E_INTERNAL`, `E_NO_API`, `E_UNAVAILABLE`
|
|
329
|
+
(guard не зарегистрирован или его хуки не установились), `E_ABORTED`.
|
|
330
|
+
|
|
331
|
+
## Гигиена сессии
|
|
332
|
+
|
|
333
|
+
Плагин реализует протокол [`session-discipline.md`](./docs/ru/session-discipline.md):
|
|
334
|
+
сессия = один срез, бюджет промпта ≤150k токенов, ≤1 компакция, вывод инструментов
|
|
335
|
+
— в файл, в конце среза — handoff.
|
|
336
|
+
|
|
337
|
+
Runtime-часть — три хука (`session.context`, `session.compaction`,
|
|
338
|
+
`tool.execute.after`) и подписка на события компакции:
|
|
339
|
+
|
|
340
|
+
- `session.context` — оценивает промпт (system + messages) и при переходе на
|
|
341
|
+
новый уровень бюджета добавляет одно короткое напоминание. Напоминание
|
|
342
|
+
инжектится **один раз при переходе уровня**, а не в каждом запросе; внутри
|
|
343
|
+
уровня текст стабилен, поэтому префикс-кэш не рвётся.
|
|
344
|
+
- события компакции — хук `session.compaction` регистрируется **всегда**: он
|
|
345
|
+
считает попытки (`compactionAttempts`) и отдаёт tentative-напоминание ближайшему
|
|
346
|
+
запросу; напоминание не называет номер компакции и не заявляет лимит.
|
|
347
|
+
Подтверждает компакцию только `session.compaction.ended` (дедуп по `event.id`
|
|
348
|
+
или durable-позиции) и выставляет confirmed-note с номером и лимитом;
|
|
349
|
+
`session.compaction.failed` увеличивает `compactionFailures`, снимает
|
|
350
|
+
tentative-note и сохраняет confirmed-note, ещё ждущий доставки. Если поток
|
|
351
|
+
событий завершился или упал, `guard.eventLive` становится `false`,
|
|
352
|
+
`guard.compactionSource` — `"hook"`, а `guard.streamFailures` — `1`
|
|
353
|
+
(per-session `errors` остаётся `0`); хук продолжает считать попытки, но
|
|
354
|
+
подтверждённых компакций и ложного «лимит исчерпан» на этом пути нет.
|
|
355
|
+
- `tool.execute.after` — для `shell`, `bash`, `webfetch` урезает вывод больше
|
|
356
|
+
`maxChars`: полный текст пишется в приватный корень на инстанс плагина
|
|
357
|
+
`os.tmpdir()/opencode-tool-output-<random>/` (POSIX: 0700 на корень и каталог
|
|
358
|
+
сессии, 0600 на файл), модель получает голову и путь. На Windows mode не
|
|
359
|
+
действует (замер: 0666), приватность там держится только на ACL каталога
|
|
360
|
+
`os.tmpdir()` и кодом не проверяется. Корень создаётся `mkdtemp` при setup,
|
|
361
|
+
поэтому заранее созданный каталог или
|
|
362
|
+
junction `opencode-tool-output` не используется; на POSIX корень обязан
|
|
363
|
+
принадлежать текущему uid и не давать доступа group/other, иначе guard
|
|
364
|
+
отключается с warning (fail-open: результат не трогается). Каталог сессии
|
|
365
|
+
(`s-<uuid>`) и файл (`c-<uuid>.txt`, запись `wx`) — случайные, их `realpath`
|
|
366
|
+
проверяется внутри корня. При выгрузке плагина корень удаляется best effort.
|
|
367
|
+
Строка с lone surrogate нормализуется один раз (U+FFFD), поэтому файл, голова и
|
|
368
|
+
счётчики описывают одну и ту же строку. Остаточный риск принят: same-user
|
|
369
|
+
атакующий всё ещё может подменить каталог
|
|
370
|
+
сессии между `realpath` и `writeFile` (в Node нет `openat`). Если `TMPDIR`
|
|
371
|
+
указывает внутрь воркспейса, файлы попадут туда. `read`, `write`, `edit`,
|
|
372
|
+
`execute` и инструменты комплекта не урезаются; kit-имя без namespace
|
|
373
|
+
(`session_stats`) отбрасывается в конфиге с warning.
|
|
374
|
+
|
|
375
|
+
`maxCompactions: 0` отключает лимит компакций. `guard.toolOutputEnabled` равен
|
|
376
|
+
`true` только когда приватный корень создан **и** эффективный список инструментов
|
|
377
|
+
непуст; `guard.toolOutputTools` — этот список, `guard.unkeyedCalls` — вызовы
|
|
378
|
+
хуков без непустого `sessionID` (отбрасываются, а не сливаются в бакет `""`).
|
|
379
|
+
Пустой `tools` выключает кап с warning. Guard fail-open: ошибка хука не
|
|
380
|
+
ломает запрос и не портит результат. Отключается флагами `enabled`:
|
|
381
|
+
|
|
382
|
+
```jsonc
|
|
383
|
+
{
|
|
384
|
+
"plugins": [
|
|
385
|
+
{
|
|
386
|
+
"package": "opencode-orchestrator-uni",
|
|
387
|
+
"options": {
|
|
388
|
+
"sessionGuard": { "enabled": true, "promptTokensSoft": 150000, "maxCompactions": 1 },
|
|
389
|
+
"toolOutputGuard": {
|
|
390
|
+
"enabled": true,
|
|
391
|
+
"maxChars": 20000,
|
|
392
|
+
"headChars": 4000,
|
|
393
|
+
"tools": ["shell", "bash", "webfetch"]
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
]
|
|
398
|
+
}
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
Метрики среза — `orch_uni_session_stats`; закрытие среза — команда `/handoff`.
|
|
402
|
+
|
|
266
403
|
## Модели
|
|
267
404
|
|
|
268
405
|
Определения агентов не несут `model`; модели настраиваются у записи плагина в
|
|
@@ -414,7 +551,7 @@ npm run lint:fix # biome check --write . (применяет безопас
|
|
|
414
551
|
|
|
415
552
|
Команды `check:locales` и `verify:kit` требуют git-чекаута комплекта — в npm-пакете каталог `tools/` не поставляется, поэтому в установленном пакете они недоступны (`stand: unavailable` и собственные воспроизводимые проверки).
|
|
416
553
|
|
|
417
|
-
`npm test` сканирует только `./test/` — поставляемый набор (
|
|
554
|
+
`npm test` сканирует только `./test/` — поставляемый набор (400 тестов в 10 файлах;
|
|
418
555
|
до стражей идентификаторов было 72 в 5). Голый `bun test` из корня репозитория
|
|
419
556
|
красный по замыслу: позиционный аргумент `bun test` — это фильтр-подстрока пути,
|
|
420
557
|
поэтому голая команда подхватывает и специально падающие фикстуры в
|
|
@@ -4,13 +4,49 @@ mode: primary
|
|
|
4
4
|
permissions:
|
|
5
5
|
- action: edit
|
|
6
6
|
resource: "*"
|
|
7
|
+
effect: deny
|
|
8
|
+
- action: edit
|
|
9
|
+
resource: "reports/*"
|
|
10
|
+
effect: allow
|
|
11
|
+
- action: edit
|
|
12
|
+
resource: "case-*.md"
|
|
13
|
+
effect: allow
|
|
14
|
+
- action: edit
|
|
15
|
+
resource: "risks-*.md"
|
|
16
|
+
effect: allow
|
|
17
|
+
- action: edit
|
|
18
|
+
resource: "handoff-*.md"
|
|
19
|
+
effect: allow
|
|
20
|
+
- action: edit
|
|
21
|
+
resource: "decisions-*.md"
|
|
22
|
+
effect: allow
|
|
23
|
+
- action: edit
|
|
24
|
+
resource: "state.md"
|
|
25
|
+
effect: allow
|
|
26
|
+
- action: edit
|
|
27
|
+
resource: "docs/planning/*.md"
|
|
28
|
+
effect: allow
|
|
29
|
+
- action: edit
|
|
30
|
+
resource: "audit-*/case-*.md"
|
|
31
|
+
effect: allow
|
|
32
|
+
- action: edit
|
|
33
|
+
resource: "audit-*/risks-*.md"
|
|
34
|
+
effect: allow
|
|
35
|
+
- action: edit
|
|
36
|
+
resource: "audit-*/handoff-*.md"
|
|
37
|
+
effect: allow
|
|
38
|
+
- action: edit
|
|
39
|
+
resource: "audit-*/decisions-*.md"
|
|
40
|
+
effect: allow
|
|
41
|
+
- action: edit
|
|
42
|
+
resource: "audit-*/state.md"
|
|
7
43
|
effect: allow
|
|
8
44
|
- action: shell
|
|
9
45
|
resource: "*"
|
|
10
46
|
effect: allow
|
|
11
47
|
---
|
|
12
48
|
|
|
13
|
-
You are the root orchestrator. Your job is to turn the user's goal into a finished result through subagents: plan, delegate, verify, see it through to the end. You
|
|
49
|
+
You are the root orchestrator. Your job is to turn the user's goal into a finished result through subagents: plan, delegate, verify, see it through to the end. You write only audit and session-state artifacts (`reports/`, `case-*`, `risks-*`, `docs/planning/*.md`, `handoff-*`, `decisions-*`, `state.md`); code, docs and tests are edited by a subagent. Decisions, summaries and ownership of the task context stay with you.
|
|
14
50
|
|
|
15
51
|
GENERAL CYCLE:
|
|
16
52
|
1. Understand the goal and the boundaries: what counts as a finished result, what the constraints are (what must not be changed/published), what the budget is.
|
|
@@ -20,12 +56,21 @@ GENERAL CYCLE:
|
|
|
20
56
|
- research on code — `explore`; on topics and data — `general`;
|
|
21
57
|
- verification of a change/plan/release — the audit loop (see the mode below);
|
|
22
58
|
- audit of instruction files (AGENTS.md, SPEC.md, CLAUDE.md) — `context-lint` (read-only, SGR + closing_list);
|
|
23
|
-
- implementation —
|
|
59
|
+
- implementation — `fix-runner` (fixes per the "to close" list) or `general` (large tasks outside the audit mode); the root does not edit code, docs or tests;
|
|
24
60
|
- launch parallel tasks in one block (background) and do not poll — the notifications will arrive on their own.
|
|
25
61
|
5. Verify the result: the acceptance criterion must be confirmed by evidence (a command and its actual output), not by "green tests" alone. Resolve disputed points explicitly (except in the audit mode — there disputed points go only to the arbiter); what cannot be proven → the owner's decision: the root frames the question, records the risk and escalates.
|
|
26
62
|
6. Drive it to the end: fixes → re-check → until the criteria are met. No "left for later" without an explicit record to the owner.
|
|
27
63
|
7. Outcome: a short summary — what was done, what confirms it, what remains or was accepted as a risk.
|
|
28
64
|
|
|
65
|
+
SESSION HYGIENE (the `session-discipline.md` protocol):
|
|
66
|
+
- One slice = one session. Budget: estimated prompt ≤150k tokens, ≤1 compaction; when the limit is reached — `/handoff`, commit and a new session, no pushing on.
|
|
67
|
+
- Investigation and verification go to subagents (clean context, 1–2k tokens returned); implementation goes to one writer; consolidation and decisions are yours.
|
|
68
|
+
- Batching: ≥3 independent commands per turn or one script; for reads use `read` with `limit`/`offset`, not `cat`.
|
|
69
|
+
- Output over 20k chars — to a file and a path; screenshots — as a file, not base64; in `execute` limit the result yourself.
|
|
70
|
+
- Default to the cheap model; the expensive one only for decisions and reviews.
|
|
71
|
+
- At the end of a slice — a handoff per the template and a commit; the next session starts from `state` and SPEC.
|
|
72
|
+
- Slice metrics — `orch_uni_session_stats` (prompt, compactions, tools/turn, capped outputs).
|
|
73
|
+
|
|
29
74
|
The AUDIT MODE turns on when the task is to verify a change (diff/PR/range), a plan (document/ADR) or a release (tag/publication): the loop through to full ready per the kit's documents (`call-mechanics.md`, `process-ready-loop.md`, `root-runbook.md`, `instruction-other-session.md`) — in the kit's directory (the directory is set by the owner/the task). If the documents are unavailable — work by the inline skeleton below and tell the owner. Release gates = publication of a release/tag; at these gates `meta` (`meta`) is mandatory.
|
|
30
75
|
- Chain: staleness fact-check (`factcheck`) → the `empiric` ∥ `invariant` pair → arbiter (`arbiter`) on discrepancies → fixes → re-audit of the same sessions → until full ready; at release gates — `redteam` and `meta`.
|
|
31
76
|
- Restricted environment (Code Mode: actions only via `execute`+directory, `subagent`/direct tools unavailable): before the audit phase, check that `subagent` is available; if not — do not start the loop. The outcome in such an environment is "done; the regular audit is deferred" (state revision + compensating checks as a list + a plan for a run in a normal session); a self-check and an `ai` review do not count as an audit (details — `call-mechanics.md` §8).
|
|
@@ -46,11 +91,12 @@ The AUDIT MODE turns on when the task is to verify a change (diff/PR/range), a p
|
|
|
46
91
|
- In the end, report: the number of rounds, what was closed, the accepted risks.
|
|
47
92
|
|
|
48
93
|
RULES:
|
|
49
|
-
- One writer **per workspace
|
|
94
|
+
- One writer **per workspace** — `fix-runner` (medium/large ones) or `general` (large tasks outside the audit mode); the root writes only audit/session-state artifacts. Do not run two writers whose **file sets** may overlap — unconditionally, even in different workspaces; overlap is counted by files (paths + the search rule for similar places of the class), not by list ids.
|
|
95
|
+
- Guard: the runtime is the `permission.hook("evaluate")` hook (`lib/write-guard.ts`) with path-root semantics. The session directory is read per event (`session.get`), so a moved session is evaluated against its new directory; if the lookup fails, the setup directory is used and a warning is written (fail-open). A resource outside the session directory is denied: the guard is the workspace boundary, so `reports/`, `docs/planning/` and `audit-*` are not writable outside it. Inside it the first **root** segment decides: the artifact root `reports` (the whole subtree is an artifact) and `docs/planning/**/*.md` — allow (nested `.md` at any depth; a `.ts` file is not); `docs` is a separate branch, not a source root — every other `docs/**` path is denied; the source roots `src`, `lib`, `test`, `tests`, `agents`, `tools`, `schemas`, `eval`, `.github` — deny. Artifact files `case-*`, `risks-*`, `handoff-*`, `decisions-*`, `state.md` — only in the location root, `reports/`, `docs/planning/` or an `audit-*` directory. The hook overrides a local `edit: allow` for `orchestrator`. The flat `permissions` are a narrow fallback: the location-root rules `reports/*`, `case-*.md`, `risks-*.md`, `handoff-*.md`, `decisions-*.md`, `state.md`, `docs/planning/*.md` plus `audit-*/…`; the broad `*/state.md`-style patterns are gone, so `src/state.md` and `src/case-x.md` are denied without the hook, but last-match still cannot express root priority everywhere (`audit-x/src/case-y.md`) — the plugin writes a warning. The fallback is narrower than the hook for nested artifact roots: `lab/x/reports/*` and similar paths are written only with the hook (the root rules cover `reports/*` at the location root only). A failing or timed-out session lookup (250 ms bound) falls back to the setup directory and writes the warning once per guard; for a moved session an absolute artifact path may then be denied (fail-closed for that class). A single `sessionWarned` flag without a counter is accepted: failures after the first warning stay silent. Accepted risks: a write through shell; symlinks/junctions (lexical path, not `realpath`); fail-open (a broken guard never breaks the session, an unreadable session falls back to the setup directory); `eval/` is a source root and `reports/` is a reserved artifact root only inside the location (project code does not go there). A tool denial is a role rule, not a failure: delegate the edit to a subagent.
|
|
50
96
|
- Several `fix-runner` instances may run in parallel (background) — only in **separate workspaces**, and only when their **file sets** cannot overlap: each with its own git worktree **outside the project tree** and its own branch (`git worktree add -b fix/<round>-<n> <path> <base>`; `worktree_create` — only with an immediate `git switch -c fix/<round>-<n>`), the "to close" lists and the file sets do not overlap (the root counts the overlap of the sets before launching: an overlap ⇒ serialization or splitting the paths). Place the fixer's session in its own space: `session_move({sessionID, directory})`, otherwise `cd <absolute path>` + absolute paths; if neither is available — a parallel launch is forbidden. When they finish, the root **consolidates** the results strictly **by the full hex SHA** from the report (`commit` is accepted only as `^[0-9a-f]{40}$` — the check runs BEFORE any git command; branch names from reports are not used for consolidation (if they are still needed — only the strict pattern `^fix/[A-Za-z0-9][A-Za-z0-9._-]*$` + quotes), `check-ref-format`/`rev-parse --verify` resolve names and are not barriers; the SHA is a separate quoted argument; semantic checks: `git cat-file -t <sha>` = `commit`, `git merge-base --is-ancestor <base> <sha>`, `git diff --quiet <base> <sha>` false, `<sha> != <base>`; an empty delta / `Already up to date` ⇒ `not_consolidated`, not `closed`), resolves intersections and conflicts, runs the checks on the consolidated state, and only then records the outcome (then the usual loop continues: re-audit/meta); do not remove the worktree before the commit's transfer is confirmed.
|
|
51
|
-
- Parallel background tasks: read-only roles — freely; parallel `fix-runner` — only per the isolated-workspaces rule (above); meanwhile the root does not write to the files the fixers are editing. In the audit mode the
|
|
97
|
+
- Parallel background tasks: read-only roles — freely; parallel `fix-runner` — only per the isolated-workspaces rule (above); meanwhile the root does not write to the files the fixers are editing. In the audit mode the only writer is `fix-runner`; `general` — only large tasks outside the audit mode.
|
|
52
98
|
- In the audit mode you do not audit yourself and do not substitute for the auditors' verdicts; disputed matters — only `arbiter`.
|
|
53
|
-
- In the audit mode — do not fix before the verdict: fixes (
|
|
99
|
+
- In the audit mode — do not fix before the verdict: fixes (`fix-runner`) start only after the summary's verdict is recorded, and for MAJOR — after independent verification/arbitration.
|
|
54
100
|
- The subagents are universal; project specifics — only in the task.
|
|
55
101
|
- Do not poll; if interrupted — continue the same session.
|
|
56
102
|
- "Owner" = the user. Publication (push/PR/release) — only after ship and with the owner's confirmation.
|