@sabaiway/agent-workflow-engine 1.0.0 → 1.2.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.
- package/CHANGELOG.md +44 -0
- package/README.md +16 -8
- package/SKILL.md +20 -12
- package/bin/install.mjs +114 -11
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/orchestration-slot.md +1 -0
- package/references/orchestration.md +92 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,50 @@ All notable changes to the methodology engine. Versions are this **package's** n
|
|
|
4
4
|
they are distinct from the **deployment-lineage** stamp written into a project's `docs/ai/`
|
|
5
5
|
(which tracks the shared `agent-workflow` lineage, head `1.3.0`).
|
|
6
6
|
|
|
7
|
+
## 1.2.0 — Orchestration recipes: a named vocabulary for composing the bridges
|
|
8
|
+
|
|
9
|
+
The engine now also owns the **orchestration-recipe** canon — the named patterns an agent uses to
|
|
10
|
+
compose the optional execution-backends (the `codex` / `agy` bridges) into `plan → execute → review`.
|
|
11
|
+
Four recipes, built over the bridges' role vocabulary: **Solo** (no backend — the floor), **Reviewed**
|
|
12
|
+
(one backend reviews), **Council** (both review, you synthesize), **Delegated** (a backend executes a
|
|
13
|
+
bounded sub-task). The orchestrator always owns the decisions and the single commit; a backend is
|
|
14
|
+
advisory or delegated, never autonomous. The kit reads this canon **live** and surfaces a read-only
|
|
15
|
+
`/agent-workflow-kit recipes` advisor that plans a recipe for the current environment.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- **`references/orchestration.md`** — the canonical narrative: the four recipes over the role
|
|
19
|
+
vocabulary, the when/why decision guidance, the graceful-degradation lattice (Council → Reviewed →
|
|
20
|
+
Solo; Delegated → Solo, always with a stated reason), and the quota/health guard.
|
|
21
|
+
- **`references/orchestration-slot.md`** — the bounded **one-line** fragment the composition root
|
|
22
|
+
injects into a deployed `AGENTS.md` (between the `workflow:orchestration` markers). It names the four
|
|
23
|
+
recipes and routes to `/agent-workflow-kit recipes` — never to this engine-internal reference.
|
|
24
|
+
|
|
25
|
+
The deployment-lineage head stays **`1.3.0`** (no `docs/ai` structural change; no migration file). The
|
|
26
|
+
npm package version is a separate axis.
|
|
27
|
+
|
|
28
|
+
## 1.1.0 — Live-read ready: never-downgrade gate + installer hardening
|
|
29
|
+
|
|
30
|
+
The kit now reads this canon **live from the installed engine** and has retired its bundled mirror
|
|
31
|
+
(see the kit's 1.11.0 / **AD-016**). This release hardens the engine's own installer to match — so an
|
|
32
|
+
engine placed by `npx … kit init` (or by hand) is safe to refresh.
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
- **Never-downgrade gate** (cloned from the kit, AD-012): a bare `npx … init` that npx serves from an
|
|
36
|
+
**older cached build** can no longer overwrite a **newer** installed canon — `init` compares the
|
|
37
|
+
on-disk version (no network) and refuses loudly unless you pass `--allow-downgrade`. A same-version
|
|
38
|
+
re-run prints a cache hint and points at `@latest`. An existing but **unreadable** `SKILL.md` fails
|
|
39
|
+
closed (the gate is never silently bypassed).
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
- Containment check now accepts a legitimately-contained child literally named `..foo` (it wrongly
|
|
43
|
+
rejected anything starting with `..` before); `tildify` collapses only a **leading** `$HOME`, never
|
|
44
|
+
a mid-path occurrence (**Issue-004**, fixed in lockstep with the memory installer).
|
|
45
|
+
|
|
46
|
+
### Changed
|
|
47
|
+
- The installer is importable without side effects (the `isDirectRun` guard) and exports its
|
|
48
|
+
path/format helpers for in-process tests. The installer's own bare `npx … init` strings now use
|
|
49
|
+
`@latest`.
|
|
50
|
+
|
|
7
51
|
## 1.0.0
|
|
8
52
|
|
|
9
53
|
First publish. The canonical home of the `agent-workflow` planning methodology is now an
|
package/README.md
CHANGED
|
@@ -3,16 +3,18 @@
|
|
|
3
3
|
**The canonical home of the `agent-workflow` planning methodology.** It owns the
|
|
4
4
|
methodology *text* — the Plan → Phase → Step vocabulary, the plan-file lifecycle
|
|
5
5
|
(`docs/plans/*.md`, ephemeral, never committed), the `queue.md` series index, the mandatory
|
|
6
|
-
final **Phase: Cleanup**,
|
|
7
|
-
into a deployed project's
|
|
6
|
+
final **Phase: Cleanup**, the **orchestration-recipe** vocabulary (Solo / Reviewed / Council /
|
|
7
|
+
Delegated), and the two bounded slot fragments the family kit injects into a deployed project's
|
|
8
|
+
`AGENTS.md`.
|
|
8
9
|
|
|
9
10
|
This is the **methodology engine** of the `agent-workflow` family — the canonical source the
|
|
10
11
|
rest of the family builds on. It is **content**, not a runtime: it reads nothing, writes
|
|
11
12
|
nothing, and mutates no project file. It deliberately **knows nobody else** in the family —
|
|
12
13
|
it only *provides* the methodology; the family composition root (`agent-workflow-kit`) is the
|
|
13
|
-
one that injects it. The kit
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
one that injects it. The kit reads **this canon live from the installed engine** and injects the
|
|
15
|
+
bounded slot from it — **one source of truth, no bundled mirror** (Plan 3D / AD-016).
|
|
16
|
+
`npx @sabaiway/agent-workflow-kit@latest init` installs the engine as a core part of the kit; if a
|
|
17
|
+
slot fill is needed but the engine is absent, the kit's reconcile **fails loudly**.
|
|
16
18
|
|
|
17
19
|
## Install
|
|
18
20
|
|
|
@@ -40,13 +42,19 @@ the canonical methodology reference on disk:
|
|
|
40
42
|
Plan → Phase → Step vocabulary, the plan-file lifecycle, the `queue.md` series index, the
|
|
41
43
|
mandatory final **Phase: Cleanup**, and the plan-then-execute split.
|
|
42
44
|
- [`references/methodology-slot.md`](references/methodology-slot.md) — the **bounded**
|
|
43
|
-
fragment the composition root injects into a deployed `AGENTS.md` (a short summary +
|
|
45
|
+
methodology fragment the composition root injects into a deployed `AGENTS.md` (a short summary +
|
|
44
46
|
pointer, kept under the entry point's line cap).
|
|
47
|
+
- [`references/orchestration.md`](references/orchestration.md) — the canonical **orchestration-recipe**
|
|
48
|
+
reference: the four recipes (Solo / Reviewed / Council / Delegated) over the bridges' role
|
|
49
|
+
vocabulary, the when/why, the graceful-degradation lattice, and the quota/health guard.
|
|
50
|
+
- [`references/orchestration-slot.md`](references/orchestration-slot.md) — the **bounded** one-line
|
|
51
|
+
orchestration fragment the composition root injects into a deployed `AGENTS.md`, routing to the
|
|
52
|
+
in-project recipes surface.
|
|
45
53
|
|
|
46
54
|
## What this package ships
|
|
47
55
|
|
|
48
|
-
`SKILL.md` (the canon overview + ownership rule), `references/` (the full methodology
|
|
49
|
-
|
|
56
|
+
`SKILL.md` (the canon overview + ownership rule), `references/` (the full methodology + orchestration
|
|
57
|
+
references + the two bounded slot fragments), `capability.json` (the family manifest), and this
|
|
50
58
|
installer. It ships **no** family-wide tooling (the schema/validator/injection live in the
|
|
51
59
|
composition root) and mutates nothing — preserving "knows nobody".
|
|
52
60
|
|
package/SKILL.md
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: agent-workflow-engine
|
|
3
|
-
description: Canonical home of the agent-workflow planning methodology — the Plan→Phase→Step→Substep vocabulary, plan lifecycle, queue.md series index, mandatory Cleanup phase,
|
|
3
|
+
description: Canonical home of the agent-workflow planning methodology — the Plan→Phase→Step→Substep vocabulary, plan lifecycle, queue.md series index, mandatory Cleanup phase, the bounded methodology slot fragment, and the orchestration-recipe vocabulary (Solo / Reviewed / Council / Delegated). A published, installable npm package (available:true) that *provides* the methodology text; it mutates nothing. The composition root (agent-workflow-kit) reads this canon LIVE from the installed engine and injects the bounded slots from it — one source of truth, no bundled mirror; `npx @sabaiway/agent-workflow-kit@latest init` installs the engine.
|
|
4
4
|
disable-model-invocation: true
|
|
5
5
|
metadata:
|
|
6
|
-
version: '1.
|
|
6
|
+
version: '1.2.0'
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# agent-workflow-engine
|
|
10
10
|
|
|
11
11
|
The **canonical source of truth** for the `agent-workflow` planning methodology. It is a
|
|
12
12
|
**published, installable npm package** (`capability.json` → `available:true`): it holds the
|
|
13
|
-
methodology text and **provides** it, but it ships no runtime and mutates no files. The kit
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
`kit
|
|
13
|
+
methodology text and **provides** it, but it ships no runtime and mutates no files. The kit reads
|
|
14
|
+
**this canon live from the installed engine** (resolved via the family `detect.installed` pattern)
|
|
15
|
+
and injects the bounded slot fragment from it — **one source of truth, no bundled mirror**.
|
|
16
|
+
`npx @sabaiway/agent-workflow-kit@latest init` installs the engine as a core part of the kit; when a
|
|
17
|
+
slot fill is needed but the engine is absent, the kit's reconcile **fails loudly** (never silently).
|
|
17
18
|
|
|
18
19
|
## What it provides (`provides: ["plan"]`)
|
|
19
20
|
|
|
@@ -25,6 +26,14 @@ keep working with no new dependency; a drift-guard test pins the mirrors to this
|
|
|
25
26
|
composition root injects into a deployed project's `AGENTS.md`, between the
|
|
26
27
|
`<!-- workflow:methodology:start -->` / `<!-- workflow:methodology:end -->` markers. A short
|
|
27
28
|
summary + pointer, not the full reference, so the entry point stays under its line cap.
|
|
29
|
+
- [`references/orchestration.md`](references/orchestration.md) — the canonical **orchestration-recipe**
|
|
30
|
+
reference: the four recipes (Solo / Reviewed / Council / Delegated) defined over the bridges' role
|
|
31
|
+
vocabulary, the when/why decision guidance, the graceful-degradation lattice, and the quota/health
|
|
32
|
+
guard. The kit owns the executable dispatch and surfaces it as `/agent-workflow-kit recipes`.
|
|
33
|
+
- [`references/orchestration-slot.md`](references/orchestration-slot.md) — the **bounded** one-line
|
|
34
|
+
orchestration fragment the composition root injects into a deployed `AGENTS.md`, between the
|
|
35
|
+
`<!-- workflow:orchestration:start -->` / `<!-- workflow:orchestration:end -->` markers. It names the
|
|
36
|
+
four recipes and routes to `/agent-workflow-kit recipes`, never to this engine-internal reference.
|
|
28
37
|
|
|
29
38
|
## Ownership rule (the engine knows nobody)
|
|
30
39
|
|
|
@@ -36,9 +45,8 @@ no outbound edges.
|
|
|
36
45
|
## Status & roadmap
|
|
37
46
|
|
|
38
47
|
The engine is **no longer a stub**: it is a published, `available:true`, installable npm package
|
|
39
|
-
(`@sabaiway/agent-workflow-engine`). The kit
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
`tools/
|
|
44
|
-
bootstrap + upgrade.
|
|
48
|
+
(`@sabaiway/agent-workflow-engine`). The kit reads **this canon live from the installed engine** and
|
|
49
|
+
injects the methodology slot from it — the old bundled mirror is **retired** (Plan 3D / AD-016):
|
|
50
|
+
**one source of truth**. `npx @sabaiway/agent-workflow-kit@latest init` installs the engine as a core
|
|
51
|
+
part of the kit. See the kit's `tools/inject-methodology.mjs` (the marker contract + slot
|
|
52
|
+
reconciliation) and `tools/engine-source.mjs` (the live read + fail-loud resolver).
|
package/bin/install.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// One-shot installer for @sabaiway/agent-workflow-engine.
|
|
3
3
|
//
|
|
4
|
-
// npx @sabaiway/agent-workflow-engine init
|
|
4
|
+
// npx @sabaiway/agent-workflow-engine@latest init
|
|
5
5
|
//
|
|
6
6
|
// Copies the methodology canon into the canonical skill home
|
|
7
7
|
// (~/.claude/skills/agent-workflow-engine, override with --dir or
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// No telemetry, no phone-home. Dependency-free, Node >= 18.
|
|
18
18
|
|
|
19
19
|
import { readFile, mkdir, readdir, copyFile, lstat, readlink, symlink } from 'node:fs/promises';
|
|
20
|
-
import { existsSync, lstatSync } from 'node:fs';
|
|
20
|
+
import { existsSync, lstatSync, realpathSync } from 'node:fs';
|
|
21
21
|
import { dirname, join, resolve, relative, sep, isAbsolute } from 'node:path';
|
|
22
22
|
import { fileURLToPath } from 'node:url';
|
|
23
23
|
import { homedir } from 'node:os';
|
|
@@ -37,7 +37,11 @@ const PAYLOAD = [
|
|
|
37
37
|
'references',
|
|
38
38
|
];
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
// Collapse only a LEADING homedir() to "~" — anchored at the string start (boundary-checked with
|
|
41
|
+
// `sep`), never a mid-path occurrence. A naive `path.replace(homedir(), '~')` would rewrite the
|
|
42
|
+
// first match anywhere in the path (Issue-004). Exported so the regression can pin it in-process.
|
|
43
|
+
export const tildify = (path) =>
|
|
44
|
+
path === homedir() ? '~' : path.startsWith(homedir() + sep) ? `~${path.slice(homedir().length)}` : path;
|
|
41
45
|
|
|
42
46
|
const readVersion = async () => {
|
|
43
47
|
try {
|
|
@@ -48,6 +52,54 @@ const readVersion = async () => {
|
|
|
48
52
|
}
|
|
49
53
|
};
|
|
50
54
|
|
|
55
|
+
// Never-downgrade gate helpers (D3 / AD-012) — cloned from the kit installer so a bare `npx … init`
|
|
56
|
+
// serving an OLDER cached build cannot overwrite a NEWER installed canon. Dependency-free semver:
|
|
57
|
+
// parse the leading x.y.z (prerelease/build ignored). compareSemver → -1 | 0 | 1, or null when
|
|
58
|
+
// either side is unparseable (a legacy install predating any stamp → no gate).
|
|
59
|
+
const parseSemver = (str) => {
|
|
60
|
+
const m = typeof str === 'string' ? str.trim().match(/^(\d+)\.(\d+)\.(\d+)/) : null;
|
|
61
|
+
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const compareSemver = (a, b) => {
|
|
65
|
+
const pa = parseSemver(a);
|
|
66
|
+
const pb = parseSemver(b);
|
|
67
|
+
if (!pa || !pb) return null;
|
|
68
|
+
const firstDiff = [0, 1, 2].map((i) => (pa[i] === pb[i] ? 0 : pa[i] < pb[i] ? -1 : 1)).find((c) => c !== 0);
|
|
69
|
+
return firstDiff ?? 0;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Extract the version that is a DIRECT child of the top-level `metadata:` key — never a top-level or
|
|
73
|
+
// deeper-nested decoy `version:` (mirrors the manifest validator + the kit installer). Pure walk.
|
|
74
|
+
const metadataVersion = (frontmatter) => {
|
|
75
|
+
const lines = frontmatter.split(/\r?\n/);
|
|
76
|
+
const metaIdx = lines.findIndex((l) => /^metadata:[ \t]*$/.test(l));
|
|
77
|
+
if (metaIdx === -1) return null;
|
|
78
|
+
const after = lines.slice(metaIdx + 1);
|
|
79
|
+
const dedent = after.findIndex((l) => /^[^ \t]/.test(l)); // a column-0 line closes the metadata block
|
|
80
|
+
const block = dedent === -1 ? after : after.slice(0, dedent);
|
|
81
|
+
const baseIndent = block.length ? (block[0].match(/^[ \t]*/)?.[0] ?? '') : '';
|
|
82
|
+
const verLine = block.find((l) => l.startsWith(`${baseIndent}version:`));
|
|
83
|
+
return verLine?.match(/version:[ \t]*['"]?(\d+\.\d+\.\d+)['"]?/)?.[1] ?? null;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// The installed version is read from the target's SKILL.md frontmatter metadata.version (the engine's
|
|
87
|
+
// detect.installed.file). Returns the semver string, or null when ABSENT / unstamped (legacy → no
|
|
88
|
+
// gate). A SKILL.md that EXISTS but cannot be read is NOT swallowed as "legacy": fail closed (throw)
|
|
89
|
+
// so the gate can never be silently bypassed (AGENTS.md: no silent failures).
|
|
90
|
+
const readInstalledVersion = async (target) => {
|
|
91
|
+
const skill = resolve(target, 'SKILL.md');
|
|
92
|
+
if (!existsSync(skill)) return null; // absent → new/legacy install, nothing to compare
|
|
93
|
+
const text = await readFile(skill, 'utf8').catch((err) => {
|
|
94
|
+
throw new Error(
|
|
95
|
+
`[agent-workflow-engine] cannot read the installed SKILL.md (${tildify(skill)}): ${err.message}. ` +
|
|
96
|
+
`Refusing to install over an unreadable canon — fix permissions/contents or remove it, then re-run.`,
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
const fm = text.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
100
|
+
return fm ? metadataVersion(fm[1]) : null;
|
|
101
|
+
};
|
|
102
|
+
|
|
51
103
|
// lstat without following symlinks; null when the path does not exist. Using lstat (not
|
|
52
104
|
// existsSync, which FOLLOWS symlinks and so reports a *dangling* symlink as absent) is what
|
|
53
105
|
// makes the guard below catch a dangling destination symlink too.
|
|
@@ -64,9 +116,12 @@ const lstatNoFollow = (path) => {
|
|
|
64
116
|
// within `root`. Walks `root` plus each existing path component down to `dest`; if the
|
|
65
117
|
// root, an intermediate dir, or the leaf is a symlink (including a dangling one), a copy
|
|
66
118
|
// could escape the target — STOP rather than follow it. Also refuses a dest outside `root`.
|
|
67
|
-
const assertContainedRealPath = (root, dest) => {
|
|
119
|
+
export const assertContainedRealPath = (root, dest) => {
|
|
68
120
|
const rel = relative(root, dest);
|
|
69
|
-
|
|
121
|
+
// A true escape is `..` exactly or a `..`-prefixed PATH SEGMENT (`../x`) — NOT any string starting
|
|
122
|
+
// with the two chars "..": a legitimately-contained child literally named `..foo` has rel `..foo`,
|
|
123
|
+
// which the old `rel.startsWith('..')` wrongly rejected (Issue-004).
|
|
124
|
+
if (rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {
|
|
70
125
|
throw new Error(`[agent-workflow-engine] refusing to write outside the target dir: ${dest}`);
|
|
71
126
|
}
|
|
72
127
|
if (lstatNoFollow(root)?.isSymbolicLink()) {
|
|
@@ -106,6 +161,7 @@ const parseArgs = (argv) => {
|
|
|
106
161
|
return {
|
|
107
162
|
help: argv.includes('--help') || argv.includes('-h'),
|
|
108
163
|
version: argv.includes('--version') || argv.includes('-v'),
|
|
164
|
+
allowDowngrade: argv.includes('--allow-downgrade'),
|
|
109
165
|
dir: dirFlag >= 0 ? argv[dirFlag + 1] : undefined,
|
|
110
166
|
};
|
|
111
167
|
};
|
|
@@ -120,13 +176,15 @@ const printHelp = (version) => {
|
|
|
120
176
|
console.log(`agent-workflow-engine ${version}
|
|
121
177
|
|
|
122
178
|
Usage:
|
|
123
|
-
npx @sabaiway/agent-workflow-engine init [--dir <path>]
|
|
179
|
+
npx @sabaiway/agent-workflow-engine@latest init [--dir <path>] [--allow-downgrade]
|
|
124
180
|
npx @sabaiway/agent-workflow-engine --version
|
|
125
181
|
npx @sabaiway/agent-workflow-engine --help
|
|
126
182
|
|
|
127
183
|
Installs/refreshes the methodology canon at ~/.claude/skills/agent-workflow-engine
|
|
128
184
|
(override with --dir <path> or AGENT_WORKFLOW_ENGINE_DIR). init is additive —
|
|
129
|
-
it never deletes your files and never writes through a symlink.
|
|
185
|
+
it never deletes your files and never writes through a symlink. If the installed
|
|
186
|
+
canon is newer than the version you ran, init refuses (no network — it compares the
|
|
187
|
+
version on disk) and points you at @latest; --allow-downgrade overrides that refusal.
|
|
130
188
|
|
|
131
189
|
The engine is the methodology canon the family kit builds on — it is content, not a
|
|
132
190
|
project deploy and not a model-invoked skill, so there is nothing to run inside an agent.
|
|
@@ -159,6 +217,26 @@ const main = async () => {
|
|
|
159
217
|
console.error(`[agent-workflow-engine] target dir is a symlink — refusing to write through it: ${tildify(target)}`);
|
|
160
218
|
process.exit(1);
|
|
161
219
|
}
|
|
220
|
+
|
|
221
|
+
// Stale-cache defenses (no network — version already on disk vs this runner). Read BEFORE any write
|
|
222
|
+
// so a refusal touches nothing. cmp is null on a legacy/unparseable install → no gate.
|
|
223
|
+
const installedVersion = wasPresent ? await readInstalledVersion(target) : null;
|
|
224
|
+
const cmp = installedVersion ? compareSemver(installedVersion, version) : null;
|
|
225
|
+
|
|
226
|
+
// Never-downgrade gate (D3 / AD-012): a bare `npx … init` can run an OLDER cached build, which
|
|
227
|
+
// would overwrite a NEWER installed canon with old text. Refuse loudly (nonzero) unless
|
|
228
|
+
// --allow-downgrade — surfacing the cache trap instead of silently regressing (no silent failures).
|
|
229
|
+
if (cmp === 1 && !args.allowDowngrade) {
|
|
230
|
+
console.error(
|
|
231
|
+
`[agent-workflow-engine] refusing to downgrade: the installed canon is v${installedVersion}, but ` +
|
|
232
|
+
`this runner is the OLDER v${version}.\n` +
|
|
233
|
+
` This is the classic npx cache serving a stale build. To get the newest canon, bypass the cache:\n` +
|
|
234
|
+
` npx @sabaiway/agent-workflow-engine@latest init\n` +
|
|
235
|
+
` (or pass --allow-downgrade to overwrite the newer install with v${version} anyway).`,
|
|
236
|
+
);
|
|
237
|
+
process.exit(1);
|
|
238
|
+
}
|
|
239
|
+
|
|
162
240
|
await mkdir(target, { recursive: true });
|
|
163
241
|
await Promise.all(
|
|
164
242
|
PAYLOAD.map((entry) => copyRecursive(resolve(PKG_ROOT, entry), resolve(target, entry), target)),
|
|
@@ -167,13 +245,38 @@ const main = async () => {
|
|
|
167
245
|
`[agent-workflow-engine] ${wasPresent ? 'updated the canon to' : 'installed'} v${version} -> ${tildify(target)}`,
|
|
168
246
|
);
|
|
169
247
|
|
|
248
|
+
// No-op re-run (same version) → almost always npx served a cached build; say so + point at @latest.
|
|
249
|
+
if (cmp === 0) {
|
|
250
|
+
console.log(
|
|
251
|
+
`[agent-workflow-engine] note: no version change — the canon was already v${version}. If you ` +
|
|
252
|
+
`expected an update, npx likely served a cached build; re-run bypassing the cache:\n` +
|
|
253
|
+
` npx @sabaiway/agent-workflow-engine@latest init`,
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
170
257
|
console.log(`
|
|
171
258
|
This installed the methodology canon at ${tildify(target)}. The engine is content the
|
|
172
259
|
family kit builds on — there is nothing to run inside an agent.
|
|
173
260
|
To update it later, re-run: npx @sabaiway/agent-workflow-engine@latest init`);
|
|
174
261
|
};
|
|
175
262
|
|
|
176
|
-
main().
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
263
|
+
// Run main() only when executed directly (npx / node bin/install.mjs), never on import — so tests can
|
|
264
|
+
// import this module to unit-test its exported helpers with no side effects. Compare by REAL path:
|
|
265
|
+
// npx invokes the bin through a node_modules/.bin symlink, so process.argv[1] is that symlink while
|
|
266
|
+
// import.meta.url is the resolved real file — a raw string compare reads them as different and main()
|
|
267
|
+
// never runs. realpathSync collapses the symlink so both sides match. (Same idiom as the kit.)
|
|
268
|
+
const isDirectRun = (() => {
|
|
269
|
+
const invoked = process.argv[1];
|
|
270
|
+
if (!invoked) return false;
|
|
271
|
+
try {
|
|
272
|
+
return realpathSync(invoked) === realpathSync(fileURLToPath(import.meta.url));
|
|
273
|
+
} catch {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
})();
|
|
277
|
+
if (isDirectRun) {
|
|
278
|
+
main().catch((err) => {
|
|
279
|
+
console.error(err);
|
|
280
|
+
process.exit(1);
|
|
281
|
+
});
|
|
282
|
+
}
|
package/capability.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sabaiway/agent-workflow-engine",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Canonical home of the agent-workflow planning methodology — the Plan→Phase→Step vocabulary, plan lifecycle, queue.md series index, and mandatory Cleanup phase, consumed by the kit (composition root). The methodology engine of the agent-workflow family.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agents",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
> **Orchestration recipes** — compose plan → execute → review with a named recipe: **Solo** (no backend), **Reviewed** (one backend reviews), **Council** (both review, you synthesize), **Delegated** (a backend executes a bounded sub-task); the orchestrator always commits, a backend is never autonomous. Pick + plan one for this environment with `/agent-workflow-kit recipes` (read-only); the deployed how/why lives in your `docs/ai/` workflow docs.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Orchestration Recipes
|
|
2
|
+
|
|
3
|
+
Canonical, on-demand reference for **how an orchestrating agent composes the optional
|
|
4
|
+
execution-backends** (the family's subscription-CLI bridges) into the `plan → execute → review`
|
|
5
|
+
flow. This is the *narrative* source of truth — the **vocabulary** (what each recipe is), the
|
|
6
|
+
**when/why** (which to reach for), the **graceful-degradation lattice** (what happens when a backend
|
|
7
|
+
is unavailable), and the **quota/health guard**. The kit (`agent-workflow-kit`) owns the *executable*
|
|
8
|
+
dispatch (`tools/recipes.mjs` — `planRecipe` / `recommendRecipe`) and surfaces it read-only as
|
|
9
|
+
`/agent-workflow-kit recipes`; the two representations are kept in lockstep by a recipe-name parity
|
|
10
|
+
guard. For the plan lifecycle this composes with, see [`planning.md`](planning.md).
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 1. The role vocabulary recipes are built over
|
|
15
|
+
|
|
16
|
+
A recipe is an **orchestration pattern**, not a runnable script. The **orchestrator** (the main
|
|
17
|
+
agent) always owns the decisions, the edits it accepts, verification, and the **single commit** — a
|
|
18
|
+
backend is **advisory or delegated, never autonomous, and never commits**.
|
|
19
|
+
|
|
20
|
+
Each backend declares what it can do in its `capability.json` `provides` / `roles`:
|
|
21
|
+
|
|
22
|
+
- **`codex-cli-bridge`** (`codex`) — `provides: ["execute", "review"]`. It can run a bounded
|
|
23
|
+
execution sub-task (`codex-exec`, output: a diff) and give an advisory review (`codex-review`,
|
|
24
|
+
modes `plan` / `code`).
|
|
25
|
+
- **`antigravity-cli-bridge`** (`agy`) — `provides: ["review", "probe"]`. It can give an advisory
|
|
26
|
+
review and answer a bounded probe (both via `agy-run`).
|
|
27
|
+
|
|
28
|
+
Both are **subscription** backends with a **finite quota** — spend deliberately.
|
|
29
|
+
|
|
30
|
+
## 2. The four recipes
|
|
31
|
+
|
|
32
|
+
| Recipe (id) | Pattern | Roles needed | Backends that satisfy it |
|
|
33
|
+
|-------------|---------|--------------|--------------------------|
|
|
34
|
+
| **Solo** (`solo`) | The orchestrator plans, executes, and self-reviews. No backend. | none | — (always available; the floor) |
|
|
35
|
+
| **Reviewed** (`reviewed`) | The orchestrator executes; **one** backend reviews the result (advisory). | ≥1 backend providing `review` | `codex` and/or `agy` |
|
|
36
|
+
| **Council** (`council`) | **Both** backends review independently; the orchestrator synthesizes the two opinions. | ≥2 backends providing `review` | `codex` **and** `agy` |
|
|
37
|
+
| **Delegated** (`delegated`) | The orchestrator hands a **bounded** execution sub-task to a backend, then reviews the returned diff and commits. | ≥1 backend providing `execute` | `codex` only |
|
|
38
|
+
|
|
39
|
+
## 3. When / why to reach for each (the decision vocabulary)
|
|
40
|
+
|
|
41
|
+
- **Solo** — the default for small, self-contained, low-risk work where a second opinion would not
|
|
42
|
+
change the outcome: typos, one-line fixes, mechanical edits, doc tweaks. It is also the **floor**
|
|
43
|
+
every other recipe degrades to, so the flow never blocks on a backend.
|
|
44
|
+
- **Reviewed** — the everyday choice when work carries real risk (a bug class, a security surface, a
|
|
45
|
+
non-obvious refactor) and an independent reviewer is worth one backend's quota. When **both**
|
|
46
|
+
backends can review, prefer **`codex`** (deterministic tie-break — `agy` carries a standing health
|
|
47
|
+
caveat, §5).
|
|
48
|
+
- **Council** — for high-stakes or genuinely ambiguous decisions where two *diverse* opinions catch
|
|
49
|
+
what one would miss. It spends **two** backends' quota, so reserve it for changes that justify the
|
|
50
|
+
cost.
|
|
51
|
+
- **Delegated** — when a bounded, well-specified sub-task can be handed off (parallelism, or to keep
|
|
52
|
+
the orchestrator's own context focused). Only `codex` provides `execute`. The orchestrator still
|
|
53
|
+
reviews the returned diff and owns the commit — delegation never bypasses the review or the gate.
|
|
54
|
+
|
|
55
|
+
## 4. Graceful degradation (never silent)
|
|
56
|
+
|
|
57
|
+
Availability is **pure file-presence**: a backend is dispatchable **iff its detector `readiness` is
|
|
58
|
+
`ready`** — full stop. Every other readiness (`needs-skill` / `needs-cli` / `needs-credentials` /
|
|
59
|
+
`degraded`) means *not dispatchable*, and the specific value is the human reason (not installed → run
|
|
60
|
+
`/agent-workflow-kit setup`; CLI missing → install the CLI; credentials missing → log in; degraded →
|
|
61
|
+
the wrapper is not on `PATH`). This is a claim about **set-up state only** — never about whether a
|
|
62
|
+
backend's *service* is actually responsive.
|
|
63
|
+
|
|
64
|
+
When a recipe's roles can't be satisfied, it **degrades to a weaker recipe with a stated reason** —
|
|
65
|
+
always reported, never silently dropped:
|
|
66
|
+
|
|
67
|
+
- **Council → Reviewed → Solo.** Only one of the two reviewers is `ready` → Reviewed with that one;
|
|
68
|
+
neither is `ready` → Solo.
|
|
69
|
+
- **Delegated → Solo.** No backend provides `execute` and is `ready` → Solo, with the reason.
|
|
70
|
+
- **Reviewed → Solo.** No backend provides `review` and is `ready` → Solo, with the reason.
|
|
71
|
+
|
|
72
|
+
## 5. Quota & health guard (advisory)
|
|
73
|
+
|
|
74
|
+
Backends are **subscription** services with **finite** quota. The orchestrator should: prefer the
|
|
75
|
+
cheapest model that fits the task; not reach for a top-tier model by reflex; and remember that
|
|
76
|
+
**Council spends two backends' quota** for one decision.
|
|
77
|
+
|
|
78
|
+
A **standing health advisory** applies to `agy`: the Antigravity service can **stall on substantive
|
|
79
|
+
prompts** (a long hang that returns nothing — an external service issue, not a setup problem;
|
|
80
|
+
tracked as **Issue-001** in the kit's known issues). It is **invisible to file-presence detection**,
|
|
81
|
+
so it is *not* a readiness signal — it is a static caveat attached whenever a recipe would use `agy`. While it lasts, **prefer `codex`** for substantive
|
|
82
|
+
reviews (this is the Reviewed tie-break in §3). The recipe machinery **never runs a subscription
|
|
83
|
+
CLI** to check — detection stays read-only.
|
|
84
|
+
|
|
85
|
+
## 6. The orchestrator always commits
|
|
86
|
+
|
|
87
|
+
No recipe makes a backend write to the repo or create a commit. The kit's `recipes` surface is
|
|
88
|
+
**read-only** — it lists the recipes, plans one for the current environment, and recommends a
|
|
89
|
+
default; it **never executes** a recipe and **never runs a subscription CLI**. The orchestrator
|
|
90
|
+
executes the chosen recipe through the bridge skills, accepts or rejects every edit, runs the
|
|
91
|
+
verification gate, and makes the **one** commit — exactly as the plan lifecycle (`planning.md`)
|
|
92
|
+
requires.
|