@deftai/directive-content 0.86.0 → 0.88.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.
Files changed (45) hide show
  1. package/.agents/skills/deft-directive-swarm/SKILL.md +1 -996
  2. package/QUICK-START.md +6 -0
  3. package/Taskfile.yml +3 -1
  4. package/UPGRADING.md +24 -2
  5. package/coding/coding.md +5 -1
  6. package/commands.md +1 -1
  7. package/contracts/runtime-authority.md +34 -8
  8. package/conventions/references.md +1 -1
  9. package/docs/getting-started.md +6 -0
  10. package/docs/no-deft-directive.md +87 -0
  11. package/docs/openclaw-agent-host.md +170 -0
  12. package/docs/product-signal.md +2 -0
  13. package/docs/writing-ste100.md +53 -0
  14. package/glossary.md +37 -39
  15. package/package.json +2 -1
  16. package/packs/rules/rules-pack-0.1.json +25 -1
  17. package/packs/skills/skills-pack-0.1.json +5 -5
  18. package/packs/strategies/strategies-pack-0.1.json +4 -4
  19. package/skills/deft-directive-review-cycle/SKILL.md +116 -14
  20. package/skills/deft-directive-setup/SKILL.md +35 -8
  21. package/skills/deft-directive-swarm/SKILL.md +81 -956
  22. package/skills/deft-directive-swarm/references/core-ops.md +144 -0
  23. package/skills/deft-directive-swarm/references/core-phase-0.md +200 -0
  24. package/skills/deft-directive-swarm/references/core-phase-1-2.md +73 -0
  25. package/skills/deft-directive-swarm/references/core-phase-3.md +145 -0
  26. package/skills/deft-directive-swarm/references/core-phase-4.md +71 -0
  27. package/skills/deft-directive-swarm/references/core-phase-5-6.md +317 -0
  28. package/skills/deft-directive-swarm/references/host-cursor.md +25 -0
  29. package/skills/deft-directive-swarm/references/host-generic.md +27 -0
  30. package/skills/deft-directive-swarm/references/host-grok-build.md +37 -0
  31. package/skills/deft-directive-swarm/references/host-openclaw.md +70 -0
  32. package/skills/deft-directive-swarm/references/host-warp.md +37 -0
  33. package/skills/deft-directive-triage/SKILL.md +40 -55
  34. package/skills/deft-directive-write-skill/SKILL.md +17 -0
  35. package/strategies/artifact-guards.md +24 -14
  36. package/strategies/discuss.md +40 -1
  37. package/strategies/interview.md +103 -30
  38. package/strategies/probe.md +27 -1
  39. package/tasks/engine-invoke.cjs +69 -13
  40. package/tasks/engine-invoke.test.cjs +188 -0
  41. package/tasks/triage-queue.yml +1 -1
  42. package/tasks/verify.yml +19 -0
  43. package/templates/agent-prompt-preamble.md +16 -5
  44. package/templates/swarm-greptile-poller-prompt.md +21 -12
  45. package/vbrief/vbrief.md +4 -2
@@ -104,7 +104,7 @@ feature/component name if probing a sub-scope. Use the same value consistently
104
104
  throughout the session. Examples: `my-app-probe`, `auth-probe`.
105
105
 
106
106
  - ! Produce a `vbrief/proposed/{scope}-probe.vbrief.json` scope vBRIEF with three mandatory narratives:
107
- - `LockedDecisions` — what was resolved and why
107
+ - `LockedDecisions` — what was resolved and why (when the lock is an intentional under-build, include dual-path graduation fields: `now`, `later`, `graduationRef`, `trigger`, `status` — see [Graduation (Now+Later)](#graduation-nowlater-when-locking-an-under-build-2899))
108
108
  - `SurfacedRisks` — concerns raised, even if not fully resolved
109
109
  - `DeferredDecisions` — explicitly acknowledged items with justification
110
110
  - ! Each entry in a narrative includes: **question asked**, **answer given**, **status** (locked / deferred / risk-accepted)
@@ -114,6 +114,30 @@ throughout the session. Examples: `my-app-probe`, `auth-probe`.
114
114
 
115
115
  ! After emitting the probe scope vBRIEF to `vbrief/proposed/`, surface the GitHub-issue tracking hint from [emit-hints.md](./emit-hints.md) — name all three patterns (none / `--umbrella` / `--per-vbrief`).
116
116
 
117
+ ## Graduation (Now+Later) when locking an under-build (#2899)
118
+
119
+ Probe already separates **locked**, **deferred**, and **risk-accepted** branches. **Graduation** is a fourth case that must not collapse into `DeferredDecisions`:
120
+
121
+ | Concept | Probe role |
122
+ |---|---|
123
+ | `LockedDecisions` (permanent) | Approach is decided and is the end-product path |
124
+ | `DeferredDecisions` | *Undecided* open question with justification |
125
+ | **Graduation** on a lock | *Decided* weaker **Now** + *decided* end-product **Later** |
126
+ | rapid **graduate** | Strategy-level spike → fresh spec; not dual-path tracking here |
127
+
128
+ Glossary naming for Graduation is owned by sibling work (#2907); this section is the Wave A probe contract.
129
+
130
+ - ! When probe **locks** a temporary / weaker approach and the end-product approach is also decided, record the dual-path shape on that `LockedDecisions` entry (not as a `DeferredDecisions` open question):
131
+ - `now` — what ships in the near path
132
+ - `later` — end-product approach
133
+ - `graduationRef` — GitHub issue and/or scope xBRIEF path for Later work
134
+ - `trigger` — condition that makes Later required (free text)
135
+ - `status` — `open` | `shipped` | `cancelled` (cancel with justification)
136
+ - ! Permanent locks and true open questions do **not** gain graduation fields — do not ticket-spam every `DeferredDecisions` item into a graduation
137
+ - ! `task scope:complete` on a Now story MUST NOT close linked graduation work; Later stays open until shipped or explicitly cancelled
138
+ - ⊗ Accepting "we'll harden it later" as a locked under-build without dual-path fields, or filing it only as `DeferredDecisions`
139
+ - ⊗ Closing graduation work solely because the MVP / Now story completed
140
+
117
141
  ---
118
142
 
119
143
  ## Then: Chaining Gate
@@ -143,6 +167,8 @@ written, return to the [chaining gate](./interview.md#chaining-gate).
143
167
  ## Anti-Patterns
144
168
 
145
169
  - ⊗ Accepting "we'll figure it out later" without marking it as explicitly deferred
170
+ - ⊗ Recording a *decided* weaker-Now + stronger-Later path only as `DeferredDecisions` (or chat) instead of dual-path graduation fields on the lock (#2899)
171
+ - ⊗ Treating Now-story `scope:complete` as closure of linked graduation work (#2899)
146
172
  - ⊗ Asking generic checklist questions instead of following the decision tree
147
173
  - ⊗ Letting vague answers pass without pushing for concrete specifics
148
174
  - ⊗ Using codebase exploration as a substitute for asking the user about deliberate design choices
@@ -12,6 +12,29 @@
12
12
 
13
13
  const { spawnSync } = require("node:child_process");
14
14
 
15
+ /**
16
+ * cmd.exe command separators / metacharacters. Free-text DEFT_ENGINE_CMD_JSON
17
+ * tokens (release --summary text, CLI_ARGS, #2547) may legitimately contain
18
+ * these; double-quoting renders them literal to cmd.exe's parser so a token can
19
+ * never break out of its argv slot (subprocess-scm-01 / #2911).
20
+ */
21
+ const WIN32_CMD_METACHAR_RE = /[\s"&|<>^()%!]/;
22
+
23
+ /**
24
+ * Quote a single argument for `cmd.exe /d /s /c` so that shell metacharacters
25
+ * stay inside one argv token. Mirrors tasks/engine-pm-run.cjs quoteWin32Arg but
26
+ * also quotes cmd.exe separators (& | < > ^ ( ) % !) because engine-invoke
27
+ * forwards operator free-text, not an allowlisted command.
28
+ * @param {string} arg
29
+ */
30
+ function quoteWin32Arg(arg) {
31
+ const s = String(arg);
32
+ if (s.length > 0 && !WIN32_CMD_METACHAR_RE.test(s)) {
33
+ return s;
34
+ }
35
+ return `"${s.replace(/"/g, '""')}"`;
36
+ }
37
+
15
38
  /** Minimal POSIX-ish shell word splitter (double/single quotes, escapes). */
16
39
  function shellSplit(input) {
17
40
  const out = [];
@@ -75,15 +98,8 @@ function main() {
75
98
  process.exit(2);
76
99
  }
77
100
 
78
- let execPath;
79
- let execArgv;
80
- if (mode === "vendored") {
81
- execPath = process.execPath;
82
- execArgv = [target, ...argv];
83
- } else if (mode === "global") {
84
- execPath = target;
85
- execArgv = argv;
86
- } else {
101
+ const plan = buildSpawnPlan(mode, target, argv);
102
+ if (!plan) {
87
103
  console.error(`deft: engine-invoke unknown mode ${JSON.stringify(mode)}`);
88
104
  process.exit(2);
89
105
  }
@@ -97,11 +113,12 @@ function main() {
97
113
  // stdio inherit (not pipe): piped stdout/stderr deadlocks when the child emits
98
114
  // more than the OS pipe buffer before exit — observed as greenfield smoke
99
115
  // hanging then CI SIGTERM exit 143 with no output (#2554 / #2547).
100
- const result = spawnSync(execPath, execArgv, {
116
+ const result = spawnSync(plan.command, plan.args, {
101
117
  stdio: "inherit",
102
118
  env: childEnv,
103
- // Global deft/directive on Windows are .cmd shims; shell:false cannot spawn them (#2415).
104
- shell: mode === "global" && process.platform === "win32",
119
+ // Never shell:true even on win32 global (subprocess-scm-01 / #2911). The
120
+ // win32 .cmd shim is reached through a tightly quoted cmd.exe wrapper below.
121
+ shell: plan.shell,
105
122
  // CREATE_NO_WINDOW: hide console windows from Cursor Task / nested shells (#2563).
106
123
  windowsHide: true,
107
124
  });
@@ -109,8 +126,47 @@ function main() {
109
126
  process.exit(code === null ? 1 : code);
110
127
  }
111
128
 
129
+ /**
130
+ * Resolve the concrete spawn command/args for a mode+target without ever using
131
+ * shell:true. On the win32 global path the target is a `.cmd` shim that Node
132
+ * refuses to spawn with shell:false (CVE-2024-27980 / #2415); shell:true would
133
+ * let cmd.exe re-parse free-text DEFT_ENGINE_CMD_JSON tokens (subprocess-scm-01
134
+ * / #2911). Instead route through `cmd.exe /d /s /c` with every token tightly
135
+ * quoted so metacharacters stay inside a single argv token — aligned with
136
+ * tasks/engine-pm-run.cjs executeAllowlisted().
137
+ *
138
+ * @param {string} mode
139
+ * @param {string} target
140
+ * @param {string[]} argv
141
+ * @param {{ platform?: string, nodePath?: string }} [opts]
142
+ * @returns {{ command: string, args: string[], shell: false } | null}
143
+ */
144
+ function buildSpawnPlan(mode, target, argv, opts = {}) {
145
+ const platform = opts.platform || process.platform;
146
+ const nodePath = opts.nodePath || process.execPath;
147
+
148
+ let execPath;
149
+ let execArgv;
150
+ if (mode === "vendored") {
151
+ execPath = nodePath;
152
+ execArgv = [target, ...argv];
153
+ } else if (mode === "global") {
154
+ execPath = target;
155
+ execArgv = argv;
156
+ } else {
157
+ return null;
158
+ }
159
+
160
+ if (mode === "global" && platform === "win32") {
161
+ const commandLine = [execPath, ...execArgv].map(quoteWin32Arg).join(" ");
162
+ return { command: "cmd.exe", args: ["/d", "/s", "/c", commandLine], shell: false };
163
+ }
164
+
165
+ return { command: execPath, args: execArgv, shell: false };
166
+ }
167
+
112
168
  if (require.main === module) {
113
169
  main();
114
170
  }
115
171
 
116
- module.exports = { shellSplit };
172
+ module.exports = { shellSplit, quoteWin32Arg, buildSpawnPlan, WIN32_CMD_METACHAR_RE };
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const assert = require("node:assert/strict");
5
+ const { describe, it } = require("node:test");
6
+ const {
7
+ shellSplit,
8
+ quoteWin32Arg,
9
+ buildSpawnPlan,
10
+ WIN32_CMD_METACHAR_RE,
11
+ } = require("./engine-invoke.cjs");
12
+
13
+ const WIN32 = { platform: "win32", nodePath: "/node" };
14
+ const POSIX = { platform: "linux", nodePath: "/node" };
15
+
16
+ /**
17
+ * Split a `cmd.exe`-quoted command line into top-level tokens, honouring
18
+ * double-quote grouping and the `""` escaped-quote convention. Used to assert
19
+ * that a metacharacter-bearing arg survives as exactly one argv token and is
20
+ * never seen by cmd.exe as a command separator.
21
+ * @param {string} line
22
+ */
23
+ function splitCmdTokens(line) {
24
+ const tokens = [];
25
+ let cur = "";
26
+ let inQuote = false;
27
+ let started = false;
28
+ for (let i = 0; i < line.length; i++) {
29
+ const c = line[i];
30
+ if (inQuote) {
31
+ if (c === '"') {
32
+ if (line[i + 1] === '"') {
33
+ cur += '"';
34
+ i++;
35
+ continue;
36
+ }
37
+ inQuote = false;
38
+ continue;
39
+ }
40
+ cur += c;
41
+ continue;
42
+ }
43
+ if (c === '"') {
44
+ inQuote = true;
45
+ started = true;
46
+ continue;
47
+ }
48
+ if (c === " ") {
49
+ if (started) {
50
+ tokens.push(cur);
51
+ cur = "";
52
+ started = false;
53
+ }
54
+ continue;
55
+ }
56
+ started = true;
57
+ cur += c;
58
+ }
59
+ if (started) {
60
+ tokens.push(cur);
61
+ }
62
+ return tokens;
63
+ }
64
+
65
+ describe("shellSplit", () => {
66
+ it("keeps quoted free-text (apostrophes, spaces, metachars) as one token", () => {
67
+ assert.deepEqual(shellSplit(`release --summary "It's a & test"`), [
68
+ "release",
69
+ "--summary",
70
+ "It's a & test",
71
+ ]);
72
+ });
73
+ });
74
+
75
+ describe("quoteWin32Arg", () => {
76
+ it("passes safe tokens through unquoted", () => {
77
+ assert.equal(quoteWin32Arg("release"), "release");
78
+ assert.equal(quoteWin32Arg("--summary=fixed"), "--summary=fixed");
79
+ });
80
+
81
+ it("double-quotes whitespace, quotes, and cmd.exe metacharacters", () => {
82
+ assert.equal(quoteWin32Arg("a b"), '"a b"');
83
+ assert.equal(quoteWin32Arg("a&b"), '"a&b"');
84
+ assert.equal(quoteWin32Arg("a|b"), '"a|b"');
85
+ assert.equal(quoteWin32Arg("a>b"), '"a>b"');
86
+ assert.equal(quoteWin32Arg("a<b"), '"a<b"');
87
+ assert.equal(quoteWin32Arg("(a)"), '"(a)"');
88
+ assert.equal(quoteWin32Arg("%PATH%"), '"%PATH%"');
89
+ assert.equal(quoteWin32Arg("a^b"), '"a^b"');
90
+ assert.equal(quoteWin32Arg("a!b"), '"a!b"');
91
+ });
92
+
93
+ it("escapes embedded double quotes by doubling", () => {
94
+ assert.equal(quoteWin32Arg('a"b'), '"a""b"');
95
+ });
96
+
97
+ it("regex flags every cmd.exe separator", () => {
98
+ for (const meta of [" ", '"', "&", "|", "<", ">", "^", "(", ")", "%", "!"]) {
99
+ assert.ok(WIN32_CMD_METACHAR_RE.test(`x${meta}y`), meta);
100
+ }
101
+ });
102
+ });
103
+
104
+ describe("buildSpawnPlan — win32 global (subprocess-scm-01 / #2911)", () => {
105
+ it("never uses shell:true and routes through cmd.exe /d /s /c", () => {
106
+ const plan = buildSpawnPlan("global", "deft", ["release"], WIN32);
107
+ assert.equal(plan.shell, false);
108
+ assert.equal(plan.command, "cmd.exe");
109
+ assert.deepEqual(plan.args.slice(0, 3), ["/d", "/s", "/c"]);
110
+ assert.equal(plan.args[3], "deft release");
111
+ });
112
+
113
+ it("keeps injection-shaped free-text args as a single quoted token", () => {
114
+ const injections = [
115
+ "& calc.exe",
116
+ "&calc",
117
+ "| whoami",
118
+ "&& shutdown /s",
119
+ "; rm -rf /",
120
+ "$(reboot)",
121
+ "`reboot`",
122
+ "> C:\\pwn.txt",
123
+ "< C:\\secret",
124
+ "(malicious)",
125
+ "%USERPROFILE%",
126
+ "^escaped",
127
+ "!DELAYED!",
128
+ ];
129
+ for (const evil of injections) {
130
+ const plan = buildSpawnPlan("global", "deft", ["release", "--summary", evil], WIN32);
131
+ assert.equal(plan.shell, false, evil);
132
+ const commandLine = plan.args[3];
133
+ const tokens = splitCmdTokens(commandLine);
134
+ // deft + release + --summary + evil == 4 top-level tokens, evil intact.
135
+ assert.deepEqual(tokens, ["deft", "release", "--summary", evil], `injection ${evil}`);
136
+ // Any cmd.exe metacharacter must be neutralised inside a quoted span so it
137
+ // can never act as a bare command separator (POSIX-only chars like the
138
+ // backtick are literal to cmd.exe and need no quoting).
139
+ if (WIN32_CMD_METACHAR_RE.test(evil)) {
140
+ assert.ok(commandLine.includes(`"${evil.replace(/"/g, '""')}"`), `quoted ${evil}`);
141
+ }
142
+ }
143
+ });
144
+
145
+ it("routes end-to-end from a quoted DEFT_ENGINE_CMD string", () => {
146
+ const argv = shellSplit('release --summary "pwn & calc | whoami"');
147
+ const plan = buildSpawnPlan("global", "directive", argv, WIN32);
148
+ assert.equal(plan.shell, false);
149
+ assert.deepEqual(splitCmdTokens(plan.args[3]), [
150
+ "directive",
151
+ "release",
152
+ "--summary",
153
+ "pwn & calc | whoami",
154
+ ]);
155
+ });
156
+
157
+ it("leaves safe args unquoted for readability", () => {
158
+ const plan = buildSpawnPlan("global", "deft", ["session:start", "--json"], WIN32);
159
+ assert.equal(plan.args[3], "deft session:start --json");
160
+ });
161
+ });
162
+
163
+ describe("buildSpawnPlan — other paths keep shell:false", () => {
164
+ it("win32 vendored spawns node directly (no cmd.exe, no shell)", () => {
165
+ const plan = buildSpawnPlan("vendored", "/bin.js", ["release", "a&b"], WIN32);
166
+ assert.equal(plan.shell, false);
167
+ assert.equal(plan.command, "/node");
168
+ assert.deepEqual(plan.args, ["/bin.js", "release", "a&b"]);
169
+ });
170
+
171
+ it("posix global spawns the shim directly with shell:false", () => {
172
+ const plan = buildSpawnPlan("global", "deft", ["release", "a&b"], POSIX);
173
+ assert.equal(plan.shell, false);
174
+ assert.equal(plan.command, "deft");
175
+ assert.deepEqual(plan.args, ["release", "a&b"]);
176
+ });
177
+
178
+ it("posix vendored spawns node with shell:false", () => {
179
+ const plan = buildSpawnPlan("vendored", "/bin.js", ["release"], POSIX);
180
+ assert.equal(plan.shell, false);
181
+ assert.equal(plan.command, "/node");
182
+ assert.deepEqual(plan.args, ["/bin.js", "release"]);
183
+ });
184
+
185
+ it("returns null for unknown modes (caller exits 2)", () => {
186
+ assert.equal(buildSpawnPlan("bogus", "deft", ["release"], WIN32), null);
187
+ });
188
+ });
@@ -36,7 +36,7 @@ tasks:
36
36
  ENGINE_CMD: 'triage-queue queue --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'
37
37
 
38
38
  show:
39
- desc: "Per-issue triage detail (#1128 / D11). -- task triage:show -- <N> [--repo OWNER/NAME]"
39
+ desc: "Per-issue triage detail (#1128 / D11, #2890). -- task triage:show -- <N> [--format=default|operator] [--repo OWNER/NAME]"
40
40
  internal: true
41
41
  deps: [":engine:_ts-build"]
42
42
  dir: '{{.USER_WORKING_DIR}}'
package/tasks/verify.yml CHANGED
@@ -59,6 +59,13 @@ tasks:
59
59
  vars:
60
60
  ENGINE_CMD: 'verify-content-manifest --project-root "{{.DEFT_ROOT}}"'
61
61
 
62
+ license-sync:
63
+ desc: "Drift guard for root LICENSE ↔ content/LICENSE.md and published package.json license fields (#2902). Three-state exit (0 clean / 1 drift / 2 config). Framework-source only."
64
+ # Framework-source-only gate: reads THIS repo's LICENSE + package manifests.
65
+ # Plain node script (no engine build required).
66
+ cmds:
67
+ - node "{{.DEFT_ROOT}}/scripts/verify-license-sync.mjs" --project-root "{{.DEFT_ROOT}}"
68
+
62
69
  skill-external-fetch-gate:
63
70
  desc: "Verify shipped skills do not pair external fetch/follow-through with execute/install without Security context mitigation (#1936 / #1532)."
64
71
  deps:
@@ -91,6 +98,18 @@ tasks:
91
98
  vars:
92
99
  ENGINE_CMD: 'verify-cursor-tier1 --project-root "{{.DEFT_ROOT}}"'
93
100
 
101
+ openclaw-tier1:
102
+ desc: "Framework-source content gate (#2875): assert OpenClaw (openclaw descriptor, sessions_spawn primitive) is enumerated as Tier 1 in the swarm Phase 3 capability matrix AND routing accepts openclaw as a dispatch_provider. Fails when a doc/code edit drops the OpenClaw descriptor and silently re-opens grok-build / generic-terminal misclassification. Three-state exit (0 clean / 1 missing marker / 2 config error)."
103
+ deps:
104
+ - task: :engine:_ts-build
105
+ # Framework-source-only gate: it scans THIS repo's swarm skill + routing
106
+ # module, so it targets DEFT_ROOT (not USER_WORKING_DIR). Mirrors
107
+ # verify:cursor-tier1 / verify:content-manifest.
108
+ cmds:
109
+ - task: :engine:invoke
110
+ vars:
111
+ ENGINE_CMD: 'verify-openclaw-tier1 --project-root "{{.DEFT_ROOT}}"'
112
+
94
113
  go-freeze:
95
114
  desc: "Tier-1 freeze gate for the legacy Go-installer bridge (#1912). Advisory while the Tier-0 SoT (lastGoInstaller) is null; once the operator pins it, fails when cmd/deft-install is bumped above the frozen tag. Three-state exit (0 ok / 1 violation / 2 config error). Emergency bypass: DEFT_ALLOW_GO_INSTALLER_BUMP=1."
96
115
  deps:
@@ -73,15 +73,15 @@ When the operator supplies an ordered plan (delivery sequence, cohort, checklist
73
73
 
74
74
  ## 2.6 Provider-neutral worker metadata (#1531)
75
75
 
76
- Heterogeneous swarm dispatch (#1531) assigns each worker a **dispatch provider** (the runtime primitive that launched the agent), a **worker role** (what the agent is allowed to do), and a **selected backend** or **routing policy** (how the harness maps that role to a concrete agent). These fields are provider-neutral: Composer-class coding agents, Grok Build (`spawn_subagent`), Cursor/cloud agents, and future adapters share the same contract.
76
+ Heterogeneous swarm dispatch (#1531) assigns each worker a **dispatch provider** (the runtime primitive that launched the agent), a **worker role** (what the agent is allowed to do), and a **selected backend** or **routing policy** (how the harness maps that role to a concrete agent). These fields are provider-neutral: Composer-class coding agents, Grok Build (`spawn_subagent`), Cursor/cloud agents, OpenClaw (`sessions_spawn`, #2874 / #2879), and future adapters share the same contract.
77
77
 
78
78
  ! Every intentional backend-routed dispatch MUST carry a separate `## Worker metadata` section in the dispatch envelope, placed AFTER `## Allocation context` and BEFORE the task body. This section is advisory metadata for the worker and for audit; it does NOT replace, extend, or reorder the five-field #1378 `## Allocation context` recognition contract above.
79
79
 
80
80
  When present, the section documents these fields in order:
81
81
 
82
- - `dispatch_provider`: the runtime primitive that launched this worker -- e.g. `spawn_subagent`, `start_agent`, `cursor-composer`, `cursor-cloud-agent`, or a future adapter id. Names the harness surface, not the model.
82
+ - `dispatch_provider`: the runtime primitive that launched this worker -- e.g. `spawn_subagent`, `start_agent`, `sessions_spawn` (OpenClaw host; platform descriptor `openclaw` per #2874 / #2875), `cursor-composer`, `cursor-cloud-agent`, or a future adapter id. Names the harness surface, not the model.
83
83
  - `worker_role`: the role boundary for this dispatch -- one of `leaf-implementation`, `orchestrator`, `review-monitor`, or `merge-release` (stable ids from `packages/core/src/swarm/routing.ts` `SWARM_WORKER_ROLES`). Tells the worker which preamble rules and skill surfaces apply.
84
- - `selected_backend`: the stable backend id from `plan.policy.swarmSubagentBackend` / `task policy:subagent-backends` (e.g. `composer`, `grok-build`, `cursor-cloud`) | null -- which catalogued coding backend the operator selected for this role.
84
+ - `selected_backend`: the stable backend id from `plan.policy.swarmSubagentBackend` / `task policy:subagent-backends` (accepted set today: `composer`, `grok-build`, `cursor-cloud` only — see `KNOWN_SUBAGENT_BACKEND_IDS`) | null -- which catalogued **coding** backend the operator selected for this role. OpenClaw is a **host / dispatch_provider** (`sessions_spawn` / descriptor `openclaw`), not a `swarmSubagentBackend` enum value; do not write `selected_backend: openclaw` into policy (#2879 Greptile P1).
85
85
  - `routing_policy`: <path or reference to the operator's routing file / tiering policy> | null -- when backend selection is delegated to harness routing instead of a typed policy field, cite the policy handle here so postmortems can reconstruct the route. The canonical handle is the gitignored, per-machine `.deft/routing.local.json` (#1739), keyed by `(dispatch_provider, worker_role)`; set decisions with `task swarm:routing-set -- --role <role> (--model <slug> | --harness-default)`.
86
86
  - `resolved_model` (#1739): the concrete model slug the operator pinned for this `(provider, role)` | null for an explicit harness default. Resolved from `.deft/routing.local.json` and stamped into the `task swarm:launch` manifest. **This is the field the dispatch primitive must actually honor** -- see the threading rule below.
87
87
  - `model_source` (#1739): provenance of `resolved_model` -- e.g. `cursor-route`, `harness-default explicit`. Lets a postmortem tell a pinned model from a harness default.
@@ -94,7 +94,7 @@ Populate `selected_backend` OR `routing_policy` (or both when the operator sets
94
94
  - `orchestrator`, `review-monitor`, or `merge-release` + explicit backend routing: at least one MUST be non-null so strong-tier audit traces stay reconstructable.
95
95
  - Any role on the harness-default agent with no tiering decision: both MAY be null; `dispatch_provider` and `worker_role` remain required.
96
96
 
97
- **Role-boundary expectations (all providers):** the same boundaries apply whether the worker runs on Composer, Grok Build, Cursor/cloud, or a future adapter:
97
+ **Role-boundary expectations (all providers):** the same boundaries apply whether the worker runs on Composer, Grok Build, Cursor/cloud, OpenClaw, or a future adapter:
98
98
 
99
99
  - ! `leaf-implementation` workers implement scoped xBRIEF work in their assigned worktree only -- gates (`task check`, file-scope audit, Greptile review cycle) are model-agnostic and MUST still pass.
100
100
  - ! `orchestrator`, `review-monitor`, and `merge-release` roles MUST run on strong or review-capable agents; dispatchers MUST NOT route these roles to cheap leaf backends.
@@ -460,6 +460,7 @@ These rules bind **orchestrators** dispatching implementation, fix, or review-cy
460
460
 
461
461
  - ! Long-running workers (expected >~3 min: implementation, fix batches, review-cycle owners, pollers) MUST be dispatched independently / in the background so the parent conversation channel stays interactive and the orchestrator is notified on completion (`DONE` / `BLOCKED` / `FAILED` per §11).
462
462
  - ! On Cursor, background dispatch means the Task tool's background path (`run_in_background: true` on the Task invocation) — NOT blocking the orchestrator's turn for the worker's full wall-clock.
463
+ - ! On OpenClaw, background dispatch means `sessions_spawn` (optionally with `visible` so the Control UI can watch the subagent) so the parent session stays interactive; the completion channel is **parent push / announce**, not `get_command_or_subagent_output` and not Cursor Task completion (#2874 / #2879). Nested leaf-spawn-leaf limits mirror Cursor #2797 when the platform does not support reliable nested `sessions_spawn`.
463
464
  - ⊗ Foreground/blocking dispatch for long-running implementation, fix, or review-cycle workers when a background/independent dispatch primitive is available — blocking locks the conversation and prevents user steerability (#1878 / Gap D).
464
465
  - ~ Foreground dispatch is reserved for short tasks (<~3 min): quick probes, single-command checks, terse status reads.
465
466
 
@@ -490,7 +491,7 @@ Reference: scope-expansion comment 4399553752 on issue #954.
490
491
 
491
492
  ## 10.5 Heartbeat contract (#1365)
492
493
 
493
- Long-running `spawn_subagent` review-cycle agents on the Grok Build hybrid swarm path can go completely dark from the monitor's perspective -- no commits, no PR comments, no completion notifications. The #1166 swarm session demonstrated the failure mode: two of three dispatched pollers produced zero observable signals; the monitor could not distinguish stalled from healthy.
494
+ Long-running `spawn_subagent` review-cycle agents on the Grok Build hybrid swarm path can go completely dark from the monitor's perspective -- no commits, no PR comments, no completion notifications. The same visibility gap applies to OpenClaw `sessions_spawn` and Cursor `Task` pollers. The #1166 swarm session demonstrated the failure mode: two of three dispatched pollers produced zero observable signals; the monitor could not distinguish stalled from healthy.
494
495
 
495
496
  The heartbeat contract closes that gap. Any sub-agent whose tool loop is expected to run for more than ~3 minutes (review-cycle pollers, watchdogs, long-running implementation agents) MUST emit a small JSON heartbeat at `<project-root>/.deft-scratch/subagent-status/<agent-id>.json` per `docs/subagent-heartbeat.md`.
496
497
 
@@ -506,6 +507,10 @@ The parent monitor watches the heartbeat file directly (three-state exit 0 ok /
506
507
 
507
508
  ! **Cursor false-alive / REDISPATCH_OK (#2824):** On the Cursor `Task` path, the host may report a leaf as "still running" after it has gone silent (empty transcript, no heartbeats, no DONE/FAILED). When `task verify:subagent-alive` exits `1` for a registered in-flight `drive-to: merge*` worker — missing heartbeat, STALE heartbeat, or no recent git/PR activity — the monitor MUST treat the worker as dead and print `REDISPATCH_OK` to authorize takeover re-dispatch. Do NOT block on host resume when the liveness gate has failed closed.
508
509
 
510
+ ! **OpenClaw `sessions_spawn` / heartbeat mapping (#2879):** Same file-heartbeat contract applies to OpenClaw review-monitors and long-running leaves. OpenClaw host session liveness, Control UI presence, or gateway channel reachability does NOT replace periodic heartbeats — those signals only prove the session exists, not that the tool loop is progressing. OpenClaw pollers write `.deft-scratch/subagent-status/<agent-id>.json` so `task agent:monitor` / `task verify:subagent-alive` can detect stalled monitors; OpenClaw-native session status MAY be a *supplementary* signal only. Host "still running" + missing/STALE heartbeat authorizes the same `REDISPATCH_OK` posture as Cursor #2824.
511
+
512
+ ! **Parent ensures scratch dir + startup grace before REDISPATCH_OK (#2879):** `task verify:subagent-alive` exits `2` (config error, no `REDISPATCH_OK`) when the scratch directory is **missing** and has no records. Parents MUST `mkdir` the worker worktree's `.deft-scratch/subagent-status/` at dispatch time so a later missing record is exit `1` + `REDISPATCH_OK`. Parents MUST ALSO wait a **startup grace** (default 3 minutes from dispatch, or until the first `phase: "starting"` heartbeat is observed) before treating a missing required-agent as takeover-eligible — probing an empty parent-created dir immediately races a healthy worker still writing its first heartbeat and can spawn a duplicate. Exit `2` remains reserved for true config errors (bad args / wrong path).
513
+
509
514
  - Monitors run `task verify:subagent-alive -- --require-agent <agent-id> [--scratch-dir <worktree>/.deft-scratch/subagent-status]` each poll iteration.
510
515
  - Workers run `task agent:monitor` (raw sweep) or the gate verb above; both wrap `subagent-monitor` (#1365).
511
516
 
@@ -524,6 +529,12 @@ Every worker MUST send a final status message before exiting its tool loop, rega
524
529
 
525
530
  ⊗ Emit `DONE` from a `drive-to: merge-ready` worker while merge-ready is false — a false-terminal `DONE` pulls the cohort monitor into inline Greptile fixes and violates Gap D (#2843 monitor-as-implementer recurrence).
526
531
 
532
+ ! **Thin DONE is not success (#2943):** A terminal message that lacks PR URL / merge evidence (no `PR #N`, no PR URL, no merge confirmation) is a **thin DONE** / failed-leaf signal for the parent monitor — re-dispatch or take over after ground truth. Prefer structured completion fields when the host supplies them (`prUrl`, `mergeStatus`, `emptyDiff`). Workers MUST NOT exit with mid-edit prose and call it `DONE` when the envelope required a PR or merge-ready outcome.
533
+
534
+ ! **Parent tool-first after leaf completion (#2943):** When a parent / monitor receives a leaf completion event (`subagent_announce`, parent-push, or host completion notify), its **first response** MUST be a **tool-first** ground-truth batch (`gh` / `git` / worktree or file status) **or** a host **yield** (`sessions_yield` on OpenClaw, or equivalent). ⊗ Multi-sentence progress-only first response with zero tools / yield — the OpenClaw text-repetition hang class (#2943).
535
+
536
+ ⊗ Treat thin DONE (no PR URL / merge evidence) as success (#2943).
537
+
527
538
  Per-step acks during the run are noise. ONE start message, ONE final message; intermediate messages only on `BLOCKED` / `FAILED`. The final message lets the dispatcher distinguish a clean exit from a silent timeout when the lifecycle event arrives.
528
539
 
529
540
  ## 12. Session ritual + `task verify:cache-fresh` gates before `start_agent` (#1348 / #1127)
@@ -6,13 +6,14 @@ Canonical Greptile review-cycle / poller sub-agent prompt body.
6
6
  Used by parent monitor agents when delegating post-PR work via the platform
7
7
  dispatch primitive per the parent's Phase 3 runtime capability detection
8
8
  (`spawn_subagent` for "grok-build" / Grok Build TUI, `start_agent` for
9
- Warp-orchestrated, or equivalent in other environments). See
10
- `skills/deft-directive-swarm/SKILL.md` Phase 3 + #1342 slices 1-3 and the
11
- review-cycle skill's updated Approach 1.
9
+ Warp-orchestrated, `sessions_spawn` for OpenClaw, Cursor `Task` with
10
+ `run_in_background: true`, or equivalent in other environments). See
11
+ `skills/deft-directive-swarm/SKILL.md` Phase 3 + #1342 slices 1-3 / #2874
12
+ OpenClaw Tier 1 and the review-cycle skill's updated Approach 1.
12
13
 
13
14
  The parent reads this file and applies Python `str.format(...)` to substitute
14
15
  five placeholders, then passes the formatted prompt via the chosen dispatch
15
- primitive (spawn_subagent, start_agent, etc.):
16
+ primitive (spawn_subagent, start_agent, sessions_spawn, Cursor Task, etc.):
16
17
 
17
18
  from pathlib import Path
18
19
  prompt = Path("templates/swarm-greptile-poller-prompt.md").read_text(encoding="utf-8").format(
@@ -23,7 +24,9 @@ primitive (spawn_subagent, start_agent, etc.):
23
24
  parent_agent_id="<parent-id>",
24
25
  )
25
26
  # Dispatch via the detected primitive (example for start_agent shown;
26
- # use spawn_subagent + the launch adapter for grok-build per #1342)
27
+ # use spawn_subagent + the launch adapter for grok-build per #1342;
28
+ # use sessions_spawn for OpenClaw per #2874 / #2879 -- completion is
29
+ # parent push / announce, not get_command_or_subagent_output)
27
30
  start_agent(name=f"greptile-poller-{{N}}", prompt=prompt, execution_mode="local")
28
31
 
29
32
  This file is the proven prompt body. Hand-authored variants have repeatedly
@@ -31,10 +34,11 @@ missed two specific parsing bugs (markdown-link `Last reviewed commit:`,
31
34
  raw `\b(P0|P1)\b` substring scan with negation false-positive); the body
32
35
  below encodes the fixes inline. See #727 (canonical encoding) and
33
36
  `skills/deft-directive-swarm/SKILL.md` Phase 6 Sub-Agent Role Separation
34
- (#1342 platform adapter unification: the platform adapter dispatches via
35
- `spawn_subagent` on grok-build and `start_agent` on Warp-orchestrated, so
36
- this template is platform-agnostic by construction) for the rules that
37
- mandate using this template instead of hand-authoring.
37
+ (#1342 / #2879 platform adapter unification: the platform adapter dispatches
38
+ via `spawn_subagent` on grok-build, `start_agent` on Warp-orchestrated, and
39
+ `sessions_spawn` on OpenClaw, so this template is platform-agnostic by
40
+ construction) for the rules that mandate using this template instead of
41
+ hand-authoring.
38
42
 
39
43
  NOTE on `.format()` escaping: every literal curly brace in this file is
40
44
  doubled (`{{` / `}}`) so it survives the `str.format(...)` pass. The five
@@ -48,8 +52,12 @@ DO NOT STOP until ONE of the six terminal exit conditions below fires.
48
52
  ## Role posture
49
53
 
50
54
  - Single role: review-cycle agent. Read `skills/deft-directive-review-cycle/SKILL.md` and follow Phase 2 (Review/Fix Loop) end-to-end.
51
- - Parent agent ID for status messages: `{parent_agent_id}`. Send status updates via `send_message_to_agent` at start, on each terminal exit condition, and on any blocker.
52
- - Execution: local. Working directory: the worktree the parent gave you (or your `--cwd` if running under `oz agent run --cwd`).
55
+ - Parent agent ID for status messages: `{parent_agent_id}`. Send status updates at start, on each terminal exit condition, and on any blocker via the **host completion channel** for your dispatch primitive:
56
+ - Warp / `start_agent`: `send_message_to_agent`
57
+ - grok-build / `spawn_subagent`: spawn result channel / parent announce (parent may poll `get_command_or_subagent_output`)
58
+ - **OpenClaw / `sessions_spawn` (#2879):** parent push / announce (session announce or Control UI–visible completion) -- **not** `get_command_or_subagent_output`, and not Cursor Task completion semantics
59
+ - Cursor `Task` (background): Task completion-notification path
60
+ - Execution: local. Working directory: the worktree the parent gave you (or your `--cwd` if running under `oz agent run --cwd` / OpenClaw session cwd).
53
61
 
54
62
  ## Bounded poll loop
55
63
 
@@ -533,7 +541,7 @@ Send:
533
541
  - ! Set `$env:GIT_EDITOR = "true"` (Windows PowerShell) or `GIT_EDITOR=true` (Unix) BEFORE any git command that could open an editor (rebase, commit --amend) to prevent terminal lockup.
534
542
  - ! Use Python scripts (single `run_shell_command` call) for the poll loop, NEVER shell `Start-Sleep` + repeated tool calls. The Python script handles `time.sleep({poll_interval_seconds})` between polls and exits when a terminal condition fires.
535
543
  - ! Always pass `do_not_summarize_output: true` semantics when fetching `gh pr view --comments` -- summarizers silently drop the Outside-Diff section.
536
- - ! Send a status message to `{parent_agent_id}` at start (acknowledging the task) and at every terminal exit (CLEAN / NEW P0/P1 FINDINGS escalation / ERRORED / TIMEOUT / STALL). Do NOT silently complete.
544
+ - ! Send a status message to `{parent_agent_id}` at start (acknowledging the task) and at every terminal exit (CLEAN / NEW P0/P1 FINDINGS escalation / ERRORED / TIMEOUT / STALL / INFORMAL-CLEAN). Route it through the host completion channel for your primitive (see Role posture -- OpenClaw `sessions_spawn` uses parent push / announce). Do NOT silently complete.
537
545
 
538
546
  ## Implementation Notes
539
547
 
@@ -554,3 +562,4 @@ Dogfood lessons captured during the #727 self-review cycle. The template body ab
554
562
  - #727 -- this template's acceptance issue and the full anti-pattern record (rm-chaining, parsing-bug recurrence, role-conflation in implementation-agent prompts).
555
563
  - #1039 -- (5) STALL terminal exit + Tier 1 instrumentation + Tier 3 per-condition fail-loud (`clean_gate_holdout`); the third recurrence in this template's detector-gap chain after #910 (triple-tier) and #1035 (Tier 2.5 + confidence-heading).
556
564
  - #1364 -- cohort-level CLEAN verification gate (`task swarm:verify-review-clean`, `scripts/swarm_verify_review_clean.py`). The (1) CLEAN section's swarm-orchestrated terminal contract block declares that only the exact `PR #{pr_number} CLEAN -- ready for merge` subject with `confidence > 3` on current HEAD is an acceptable "review complete" signal for the swarm monitor's Phase 5 -> 6 transition; the cohort verifier picks up any other terminal exit ((2) NEW P0/P1 FINDINGS escalation, (3) ERRORED, (4) TIMEOUT, (5) STALL) and holds the merge gate until fresh poller re-dispatch or explicit user escalation resolves it. Recurrence record: #1166 swarm execution where multiple pollers exited with `clean_gate_holdout=confidence` (confidence == 3) and the monitor still raised the Phase 5 -> 6 gate because the trigger keyed on "all pollers have reported back" rather than "every PR in the cohort is objectively CLEAN".
565
+ - #2879 -- OpenClaw `sessions_spawn` + parent push/announce completion channel named in Role posture (alongside Warp `start_agent` and grok-build `spawn_subagent`); heartbeat mapping in `docs/subagent-heartbeat.md` and preamble §10.5. Epic #2874.
package/vbrief/vbrief.md CHANGED
@@ -1,10 +1,12 @@
1
1
  # vBRIEF Usage in Deft
2
2
 
3
- Canonical reference for vBRIEF file conventions within Deft-managed projects.
3
+ > **Public name (#2907):** The sole current work-state name is **xBRIEF** under `xbrief/` (`.xbrief.json`). **vBRIEF** / `vbrief/` / `.vbrief.json` in this document are **legacy / schema-lineage** names (this path still hosts core schemas). Prefer xbrief in product docs and new guidance. Authoritative rename/history: [UPGRADING.md — xBRIEF rename](../UPGRADING.md#xbrief-rename-2034--2110--2907). Migrate on-disk layouts with `deft migrate:xbrief`.
4
+
5
+ Canonical **schema and convention** reference for durable work-state files within Deft-managed projects (historical filename: vBRIEF).
4
6
 
5
7
  Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
6
8
 
7
- **⚠️ See also**: [context/working-memory.md](../context/working-memory.md) | [resilience/continue-here.md](../resilience/continue-here.md) | [context/long-horizon.md](../context/long-horizon.md) | [glossary.md](../glossary.md)
9
+ **⚠️ See also**: [glossary.md](../glossary.md) | [UPGRADING.md — xBRIEF rename](../UPGRADING.md#xbrief-rename-2034--2110--2907) | [context/working-memory.md](../context/working-memory.md) | [resilience/continue-here.md](../resilience/continue-here.md)
8
10
 
9
11
  ---
10
12