cowork-flow 1.9.1 → 2.0.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 +31 -1
- package/docs/architecture.md +1 -34
- package/docs/direction.md +1 -1
- package/docs/hosts.md +13 -9
- package/package.json +1 -1
- package/presets/claude-code/.claude-plugin/plugin.json +1 -1
- package/presets/codex/.codex-plugin/plugin.json +1 -1
- package/presets/dsh/plugins/workflow-state.js +31 -16
- package/presets/kimi-code/.kimi-plugin/plugin.json +1 -1
- package/presets/opencode/cowork-flow/plugin-core.js +42 -87
- package/presets/opencode/plugins/cowork-flow.js +7 -9
- package/presets/qoder/.qoder-plugin/plugin.json +1 -1
- package/presets/qoder/hooks/hooks.json +1 -1
- package/presets/zcode/.zcode-plugin/plugin.json +1 -1
- package/src/commands/host.js +23 -2
- package/src/commands/registry.js +4 -2
- package/template/.cowork-flow/.version +1 -1
- package/template/.cowork-flow/adapters/claude-code/adapter.yaml +1 -1
- package/template/.cowork-flow/adapters/codex/adapter.yaml +1 -1
- package/template/.cowork-flow/adapters/dsh/adapter.yaml +1 -1
- package/template/.cowork-flow/adapters/opencode/adapter.yaml +1 -1
- package/template/.cowork-flow/adapters/qoder/adapter.yaml +1 -1
- package/template/.cowork-flow/scripts/adapters/cli/subagent.py +111 -7
- package/template/.cowork-flow/scripts/adapters/cli/task_lifecycle_commands.py +0 -1
- package/template/.cowork-flow/scripts/adapters/cli/task_parser.py +0 -7
- package/template/.cowork-flow/scripts/adapters/host/claude_code_policy.py +6 -4
- package/template/.cowork-flow/scripts/adapters/host/codex_policy.py +8 -6
- package/template/.cowork-flow/scripts/adapters/host/edit_scope.py +113 -0
- package/template/.cowork-flow/scripts/adapters/host/opencode_policy.py +12 -0
- package/template/.cowork-flow/scripts/adapters/host/qoder_policy.py +7 -5
- package/template/.cowork-flow/scripts/adapters/host/workflow_state_hook.py +0 -91
- package/template/.cowork-flow/scripts/adapters/host/zcode_policy.py +15 -77
- package/template/.cowork-flow/scripts/run.py +0 -2
- package/template/.cowork-flow/scripts/runtime/session_state.py +19 -0
- package/template/.cowork-flow/scripts/services/context_paths.py +2 -2
- package/template/.cowork-flow/scripts/services/fact_view.py +49 -21
- package/template/.cowork-flow/scripts/services/lifecycle_checks.py +1 -216
- package/template/.cowork-flow/scripts/services/lifecycle_policy.py +0 -5
- package/template/.cowork-flow/scripts/services/task_gate_profiles.py +1 -1
- package/template/.cowork-flow/scripts/services/task_lifecycle.py +14 -31
- package/template/.cowork-flow/scripts/services/workflow_runtime.py +140 -0
- package/template/.cowork-flow/spec/backend/error-handling.md +0 -14
- package/template/.cowork-flow/spec/contracts/context-injection.md +49 -22
- package/template/.cowork-flow/spec/contracts/decision-anchor.md +3 -3
- package/template/.cowork-flow/spec/contracts/error-codes.md +4 -5
- package/template/.cowork-flow/spec/contracts/evidence-completion.md +6 -8
- package/template/.cowork-flow/spec/contracts/index.md +1 -2
- package/template/.cowork-flow/spec/contracts/plan-binding.md +3 -2
- package/template/.cowork-flow/spec/contracts/subagent-dispatch.md +31 -22
- package/template/.cowork-flow/spec/contracts/task-adopt.md +1 -1
- package/template/.cowork-flow/spec/runtime/contract-registry.json +0 -13
- package/template/.cowork-flow/spec/runtime/host-assets.json +10 -7
- package/template/.cowork-flow/spec/runtime/scope-rules.json +2 -2
- package/template/.opencode/cowork-flow/plugin-core.js +42 -87
- package/template/.opencode/plugins/cowork-flow.js +7 -9
- package/template/AGENTS.md +0 -1
- package/template/skills/agent-dispatch/SKILL.md +2 -15
- package/template/skills/cowork-flow/SKILL.md +9 -0
- package/template/skills/runtime-health/scripts/doctor.py +57 -49
- package/template/skills/spec-sync/SKILL.md +0 -1
- package/template/skills/task-planning/SKILL.md +1 -1
- package/template/skills/task-review/SKILL.md +3 -3
- package/template/.cowork-flow/scripts/adapters/cli/spec_check.py +0 -178
- package/template/.cowork-flow/scripts/services/spec_check.py +0 -573
- package/template/.cowork-flow/spec/contracts/spec-checks.md +0 -281
|
@@ -20,6 +20,7 @@ from runtime.session_state import (
|
|
|
20
20
|
FIELD_RUNTIME_CONTEXT_ID,
|
|
21
21
|
FIELD_SCOPE,
|
|
22
22
|
SCOPE_SUBAGENT,
|
|
23
|
+
iter_subagent_contexts,
|
|
23
24
|
logical_subagent_context_key,
|
|
24
25
|
platform_from_context_key,
|
|
25
26
|
resolve_context_key,
|
|
@@ -38,6 +39,38 @@ class RuntimeContextError(RuntimeError):
|
|
|
38
39
|
super().__init__(f"{code}: {detail}")
|
|
39
40
|
|
|
40
41
|
|
|
42
|
+
def normalize_relative_path(path: str) -> str:
|
|
43
|
+
"""Repository-relative form used to compare ownership claims."""
|
|
44
|
+
normalized = path.strip().replace("\\", "/")
|
|
45
|
+
while normalized.startswith("./"):
|
|
46
|
+
normalized = normalized[2:]
|
|
47
|
+
return normalized.strip("/")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def slice_paths_overlap(left: str, right: str) -> bool:
|
|
51
|
+
"""True when two owned paths are equal or one contains the other."""
|
|
52
|
+
a = normalize_relative_path(left)
|
|
53
|
+
b = normalize_relative_path(right)
|
|
54
|
+
if not a or not b:
|
|
55
|
+
return False
|
|
56
|
+
return a == b or a.startswith(b + "/") or b.startswith(a + "/")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def owned_paths_overlap(
|
|
60
|
+
left: tuple[str, ...] | list[str],
|
|
61
|
+
right: tuple[str, ...] | list[str],
|
|
62
|
+
) -> list[tuple[str, str]]:
|
|
63
|
+
"""Every overlapping (left, right) pair of owned-path claims."""
|
|
64
|
+
overlaps: list[tuple[str, str]] = []
|
|
65
|
+
for a in left:
|
|
66
|
+
if not isinstance(a, str):
|
|
67
|
+
continue
|
|
68
|
+
for b in right:
|
|
69
|
+
if isinstance(b, str) and slice_paths_overlap(a, b):
|
|
70
|
+
overlaps.append((a, b))
|
|
71
|
+
return overlaps
|
|
72
|
+
|
|
73
|
+
|
|
41
74
|
@dataclass(frozen=True)
|
|
42
75
|
class RuntimeContextInitResult:
|
|
43
76
|
"""Files created for one runtime-context initialization."""
|
|
@@ -105,6 +138,17 @@ class RuntimeContextService:
|
|
|
105
138
|
logical_context_key=logical_context_key,
|
|
106
139
|
)
|
|
107
140
|
|
|
141
|
+
conflicts = self.slice_conflicts(runtime_context_id, context)
|
|
142
|
+
if conflicts:
|
|
143
|
+
raise RuntimeContextError(
|
|
144
|
+
"SUBAGENT-SLICE-001",
|
|
145
|
+
"owned_paths overlap an open implement context of this task: "
|
|
146
|
+
+ "; ".join(
|
|
147
|
+
f"{other_id} owns {other_path}, overlaps {claimed_path}"
|
|
148
|
+
for other_id, claimed_path, other_path in conflicts
|
|
149
|
+
),
|
|
150
|
+
)
|
|
151
|
+
|
|
108
152
|
unit = self._unit(
|
|
109
153
|
self._operation_id("init", runtime_context_id),
|
|
110
154
|
"runtime-context-init",
|
|
@@ -120,6 +164,102 @@ class RuntimeContextService:
|
|
|
120
164
|
logical_context_key=logical_context_key,
|
|
121
165
|
)
|
|
122
166
|
|
|
167
|
+
def slice_conflicts(
|
|
168
|
+
self,
|
|
169
|
+
runtime_context_id: str,
|
|
170
|
+
context: dict,
|
|
171
|
+
) -> list[tuple[str, str, str]]:
|
|
172
|
+
"""(other_id, claimed_path, conflicting_path) for open sibling slices.
|
|
173
|
+
|
|
174
|
+
Only implement contexts that declare `owned_paths` take part: an
|
|
175
|
+
init without an ownership claim can neither conflict nor be
|
|
176
|
+
conflicted with, and a closed context has released its paths.
|
|
177
|
+
"""
|
|
178
|
+
if context.get("agent_type") != "cowork-implement":
|
|
179
|
+
return []
|
|
180
|
+
claimed = self._owned_paths(context)
|
|
181
|
+
if not claimed:
|
|
182
|
+
return []
|
|
183
|
+
task_dir = self._normalized_task_dir(str(context.get("task_dir") or ""))
|
|
184
|
+
if not task_dir:
|
|
185
|
+
return []
|
|
186
|
+
conflicts: list[tuple[str, str, str]] = []
|
|
187
|
+
for other in iter_subagent_contexts(self.repo_root):
|
|
188
|
+
other_id = str(other.get("runtime_context_id") or "")
|
|
189
|
+
if not other_id or other_id == runtime_context_id:
|
|
190
|
+
continue
|
|
191
|
+
if other.get("agent_type") != "cowork-implement":
|
|
192
|
+
continue
|
|
193
|
+
if other.get("status") == "closed":
|
|
194
|
+
continue
|
|
195
|
+
if self._normalized_task_dir(str(other.get("task_dir") or "")) != task_dir:
|
|
196
|
+
continue
|
|
197
|
+
for claimed_path, other_path in owned_paths_overlap(
|
|
198
|
+
claimed,
|
|
199
|
+
self._owned_paths(other),
|
|
200
|
+
):
|
|
201
|
+
conflicts.append((other_id, claimed_path, other_path))
|
|
202
|
+
return conflicts
|
|
203
|
+
|
|
204
|
+
@staticmethod
|
|
205
|
+
def _task_dir_is_absolute(value: str) -> bool:
|
|
206
|
+
"""Decide on the raw value, before any slash stripping.
|
|
207
|
+
|
|
208
|
+
Stripping first would turn a slash-rooted POSIX path into a
|
|
209
|
+
relative-looking one and silently disable the same-task comparison.
|
|
210
|
+
"""
|
|
211
|
+
raw = value.strip().replace("\\", "/")
|
|
212
|
+
if raw.startswith("/"):
|
|
213
|
+
return True
|
|
214
|
+
return Path(raw).is_absolute()
|
|
215
|
+
|
|
216
|
+
def _normalized_task_dir(self, value: str) -> str:
|
|
217
|
+
"""One task, one key: `--execution-task-dir` may arrive absolute."""
|
|
218
|
+
raw = value.strip().replace("\\", "/")
|
|
219
|
+
if self._task_dir_is_absolute(raw):
|
|
220
|
+
try:
|
|
221
|
+
return normalize_relative_path(
|
|
222
|
+
str(
|
|
223
|
+
Path(raw)
|
|
224
|
+
.resolve()
|
|
225
|
+
.relative_to(self.repo_root.resolve())
|
|
226
|
+
)
|
|
227
|
+
)
|
|
228
|
+
except ValueError:
|
|
229
|
+
return normalize_relative_path(raw)
|
|
230
|
+
return normalize_relative_path(raw)
|
|
231
|
+
|
|
232
|
+
@staticmethod
|
|
233
|
+
def _owned_paths(context: dict) -> list[str]:
|
|
234
|
+
assignment = context.get("assignment")
|
|
235
|
+
owned = (
|
|
236
|
+
assignment.get("owned_paths")
|
|
237
|
+
if isinstance(assignment, dict)
|
|
238
|
+
else None
|
|
239
|
+
)
|
|
240
|
+
if not isinstance(owned, list):
|
|
241
|
+
return []
|
|
242
|
+
return [path for path in owned if isinstance(path, str) and path.strip()]
|
|
243
|
+
|
|
244
|
+
def list_contexts(
|
|
245
|
+
self,
|
|
246
|
+
*,
|
|
247
|
+
task_dir: str | None = None,
|
|
248
|
+
open_only: bool = False,
|
|
249
|
+
) -> list[dict]:
|
|
250
|
+
"""Subagent runtime contexts for inventory views (read-only)."""
|
|
251
|
+
wanted = self._normalized_task_dir(task_dir) if task_dir else None
|
|
252
|
+
contexts: list[dict] = []
|
|
253
|
+
for context in iter_subagent_contexts(self.repo_root):
|
|
254
|
+
if open_only and context.get("status") == "closed":
|
|
255
|
+
continue
|
|
256
|
+
if wanted is not None and self._normalized_task_dir(
|
|
257
|
+
str(context.get("task_dir") or "")
|
|
258
|
+
) != wanted:
|
|
259
|
+
continue
|
|
260
|
+
contexts.append(context)
|
|
261
|
+
return contexts
|
|
262
|
+
|
|
123
263
|
def update(
|
|
124
264
|
self,
|
|
125
265
|
runtime_context_id: str,
|
|
@@ -1,19 +1,5 @@
|
|
|
1
1
|
# 后端异常处理规范
|
|
2
2
|
|
|
3
|
-
<!--
|
|
4
|
-
本 spec 支持 frontmatter `checks:` 声明检查命令(规范挂命令契约见
|
|
5
|
-
spec/contracts/spec-checks.md)。模板自带的声明由上游维护、sync 会覆盖
|
|
6
|
-
本地修改;请把实际声明写进自建 spec 文件。示例:
|
|
7
|
-
|
|
8
|
-
---
|
|
9
|
-
checks:
|
|
10
|
-
- cmd: python scripts/check_error_codes.py
|
|
11
|
-
files: "src/"
|
|
12
|
-
when: lifecycle
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
-->
|
|
16
|
-
|
|
17
3
|
## 目标
|
|
18
4
|
|
|
19
5
|
- 调用方能稳定感知错误
|
|
@@ -23,7 +23,7 @@ would make the digest self-referential).
|
|
|
23
23
|
| opencode | plugin `experimental.chat.system.transform` → shim (`cowork-flow/plugin-core.js`) → `inject.py --host opencode`, bare-text stdout | system-prompt section push; `shell.env` exports the context key to shell commands |
|
|
24
24
|
| dsh | preset plugin system-prompt section | named section, replace semantics |
|
|
25
25
|
| kimi-code | user-level `config.toml` (`$KIMI_CODE_HOME`, default `~/.kimi-code/`) `[[hooks]]` row on `UserPromptSubmit` → shim (`cowork-flow-inject.mjs`) → `inject.py --host kimi-code` | bare-text stdout appended to the prompt context, no `hookSpecificOutput` envelope |
|
|
26
|
-
| qoder | machine-level plugin `hooks/hooks.json` (node, `${QODER_PLUGIN_ROOT}/hooks/inject-context.mjs`) → project `.cowork-flow/scripts/.../inject.py --host qoder`, stdout JSON | `hookSpecificOutput.{hookEventName, additionalContext}` plus a top-level `systemMessage` (a CLI-visible trace; the model context is unchanged)
|
|
26
|
+
| qoder | machine-level plugin `hooks/hooks.json` (node, `${QODER_PLUGIN_ROOT}/hooks/inject-context.mjs`) → project `.cowork-flow/scripts/.../inject.py --host qoder`, stdout JSON | `hookSpecificOutput.{hookEventName, additionalContext}` plus a top-level `systemMessage` (a CLI-visible trace; the model context is unchanged) |
|
|
27
27
|
|
|
28
28
|
The qoder row carries one delta beyond the shared envelope: `systemMessage`,
|
|
29
29
|
emitted only on the two injecting events (`SessionStart`, `UserPromptSubmit`)
|
|
@@ -260,7 +260,7 @@ review time:
|
|
|
260
260
|
<stage-contract task=".cowork-flow/tasks/08-30-demo">
|
|
261
261
|
Scope: src/demo.py; src/next.py (+1 more in implement.jsonl) [agent-mutable]
|
|
262
262
|
Specs: .cowork-flow/spec/backend/index.md (+1 more)
|
|
263
|
-
Gates: edits outside Scope are review blockers; CLAUDE.md and workflow files are protected; spec/ edits may be allowed by review policy; scope is agent-mutable (
|
|
263
|
+
Gates: edits outside Scope are review blockers; CLAUDE.md and workflow files are protected; spec/ edits may be allowed by review policy; scope is agent-mutable (declare more in implement.jsonl)
|
|
264
264
|
Verify: npm run test:fast; python3 -m pytest tests/ -q
|
|
265
265
|
</stage-contract>
|
|
266
266
|
```
|
|
@@ -270,38 +270,65 @@ Verify: npm run test:fast; python3 -m pytest tests/ -q
|
|
|
270
270
|
absolute paths, drive letters, wildcards, unsupported types — authorize
|
|
271
271
|
nothing and are dropped, matching `context_paths.normalize_context_file_scope_entry`),
|
|
272
272
|
first 8 + `(+N more)`. The scope is **agent-mutable** on main
|
|
273
|
-
(
|
|
274
|
-
hard fence; the label says so. Delegated
|
|
273
|
+
(appending a scope entry to `implement.jsonl` extends it) — it is a
|
|
274
|
+
self-declared boundary, not a hard fence; the label says so. Delegated
|
|
275
|
+
sessions inherit the parent
|
|
275
276
|
task's scope and see it as `[read-only]` — the child must not believe it
|
|
276
277
|
can self-declare scope on the parent's behalf.
|
|
277
278
|
- **Specs**: `.cowork-flow/spec/` pointer entries from implement.jsonl
|
|
278
279
|
(first 4, same canonicality filter as Scope). Read them before coding; the
|
|
279
|
-
full files are not injected.
|
|
280
|
+
full files are not injected. Once the task is `in_progress`, the line
|
|
281
|
+
upgrades to `Specs: <spec>(<h2 heading tree>); …`: at most 6 h2 entries,
|
|
282
|
+
each truncated to 24 characters with `();` characters stripped; a missing
|
|
283
|
+
file adds no annotation. The digest is an entry-name index, not the prose,
|
|
284
|
+
and is hard-capped by `scope-rules.json` `stageContract.budget`; over
|
|
285
|
+
budget the whole line yields. The python/zcode output comes from a single
|
|
286
|
+
Python entrypoint, locked by the `test/stage-contract.test.js` matrix;
|
|
287
|
+
opencode transports that same entrypoint's output, so byte-exact equality
|
|
288
|
+
holds by construction.
|
|
280
289
|
- **Gates**: static preview text, identical on all hosts; the delegated
|
|
281
290
|
variant replaces the self-declaration sentence with "scope is inherited
|
|
282
291
|
from the parent task (read-only reference)".
|
|
283
|
-
- **Verify**: `## 验证命令` lines from decision-anchor (first
|
|
292
|
+
- **Verify**: `## 验证命令` lines from decision-anchor (first 10, 120 chars
|
|
284
293
|
each) — the agent's own declared self-checks; omitted when none.
|
|
285
|
-
- **Per-edit warning
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
294
|
+
- **Per-edit warning**: PostToolUse on an edit tool injects at most one
|
|
295
|
+
line when the edited file is outside the task's declared scope —
|
|
296
|
+
a frozen single line shared by every host, differing only in transport:
|
|
297
|
+
zcode / claude-code / codex / qoder return it as
|
|
298
|
+
`hookSpecificOutput.additionalContext`, opencode (bare-text `emit_text`)
|
|
299
|
+
appends it to the finished tool output, and dsh attaches it to the tool
|
|
300
|
+
result as a plugin notice. `editScopeWarning: plugin` is declared for
|
|
301
|
+
those six hosts; kimi-code declares `unsupported` because the host drops
|
|
302
|
+
PostToolUse stdout. Silence rules: no active in_progress/review task, a
|
|
303
|
+
subagent session, an in-scope path, or no extractable path; every failure
|
|
304
|
+
stays silent and every host exits 0 — the edit has already happened. No
|
|
305
|
+
host refreshes the workflow-state digest from this hook: the advisory is
|
|
306
|
+
the whole payload (state refreshes on the next injecting event, or on
|
|
307
|
+
dsh's `tools/result`).
|
|
308
|
+
Bash-initiated writes (redirection, tee) are a declared residual gap: the
|
|
309
|
+
warning covers edit tool invocations only. Each host's matcher text
|
|
310
|
+
(`Edit|Write|MultiEdit`, codex's `apply_patch|Write|Edit`, qoder's
|
|
311
|
+
`Edit|Write`) is asserted by template tests but depends on that host's
|
|
312
|
+
runtime's tool names — there is no external runtime contract to verify
|
|
313
|
+
them against, so a runtime rename would silently disable the warning; the
|
|
314
|
+
matcher is the single switch.
|
|
315
|
+
- Budget: the whole block stays ≤ 3400 characters. Over-budget inputs
|
|
298
316
|
degrade by dropping Verify, then Specs, then shrinking Scope entries
|
|
299
|
-
(min 1)
|
|
300
|
-
the
|
|
317
|
+
(min 1), then truncating the Scope row's own text. The opening tag, the
|
|
318
|
+
closing tag and the Gates row always survive intact — a body-wide cut
|
|
319
|
+
would either truncate the tag every consumer matches on (making the block
|
|
320
|
+
invisible to them) or silently drop the edit-policy row, so neither is
|
|
321
|
+
allowed. Below the size those fixed rows plus a minimal `Scope: X` row
|
|
322
|
+
need, or when the cut would shave a `Scope: ` label off any row, the block
|
|
323
|
+
is returned whole and may exceed the budget: guard rows and parseability
|
|
324
|
+
outrank the budget.
|
|
325
|
+
The scope-filter rules and the stage-contract
|
|
301
326
|
budget/limits come from `.cowork-flow/spec/runtime/scope-rules.json`, the
|
|
302
327
|
single source consumed at runtime; a missing or malformed file degrades to
|
|
303
328
|
embedded defaults that are byte-identical with the shipped file (locked by
|
|
304
|
-
`tests/test_scope_rules.py`).
|
|
329
|
+
`tests/test_scope_rules.py`). The file is template-managed
|
|
330
|
+
(`syncPolicy.safeFiles`): `cwf project sync` refreshes it, so local edits to
|
|
331
|
+
it do not survive a sync.
|
|
305
332
|
- Absent for `no_task` / `planning` / `completed` states. Block content is
|
|
306
333
|
byte-identical across host lines for the whole fixture matrix
|
|
307
334
|
(`test/fixtures/stage-contract-matrix.json` drives the equality test:
|
|
@@ -52,7 +52,7 @@ translated heading would simply stop being read.
|
|
|
52
52
|
fact view (`services/fact_view.parse_decision_anchor`). Line-format conventions:
|
|
53
53
|
|
|
54
54
|
- `## 验证命令`: one command per line (an optional `- ` prefix), truncated to 120
|
|
55
|
-
characters, at most
|
|
55
|
+
characters, at most 10 lines; the implementing agent runs them itself
|
|
56
56
|
(self-declared, self-checked) and the reviewer verifies the execution evidence.
|
|
57
57
|
- `## 范围边界`: only the first non-empty line is taken, truncated to 160
|
|
58
58
|
characters.
|
|
@@ -110,14 +110,14 @@ After merging (5 files): `decision-anchor.md` + `implement.jsonl` + `check.jsonl
|
|
|
110
110
|
component.
|
|
111
111
|
A **consequence** (expected cost, not a defect): **any byte change in
|
|
112
112
|
`implement.jsonl`** (appending or removing a scope entry, rewording a `reason`,
|
|
113
|
-
a domain-guide entry
|
|
113
|
+
a domain-guide entry) **or a rewrite of a
|
|
114
114
|
referenced spec's prose invalidates an existing receipt**, requiring one more
|
|
115
115
|
review run against the final state. Appending evidence, ticking ACs, and runtime
|
|
116
116
|
metadata are not in that set.
|
|
117
117
|
- **Light profile `meta.taskType: "tiny"`**: applies to `strict` tasks only
|
|
118
118
|
(`legacy` + `tiny` stays a compatibility path). A light task **is still strict**
|
|
119
119
|
— what it waives are three things: the review receipt, AC evidence, and
|
|
120
|
-
`dev_type`. It does **not** waive
|
|
120
|
+
`dev_type`. It does **not** waive the file-scope gate or "AC rows
|
|
121
121
|
are non-empty" (`LIFECYCLE-AC-003`/`004` still apply). Plan-file binding is not
|
|
122
122
|
among the three, but the pre-existing field-level exemption for tiny applies, so
|
|
123
123
|
a light task does not require `planFile` either. `tiny` **can only be declared at
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Runtime Error Codes
|
|
2
2
|
|
|
3
|
-
Every
|
|
4
|
-
|
|
3
|
+
Every runtime error code that can reach a result's `code` field, with the
|
|
4
|
+
condition that produces it. A code is a stable contract: consumers match on the
|
|
5
5
|
code, never on the message text.
|
|
6
6
|
|
|
7
7
|
Codes are defined in `scripts/services/*.py` and surface in CLI output, `--json`
|
|
@@ -28,6 +28,7 @@ results, and the closeout blockers.
|
|
|
28
28
|
| `LIFECYCLE-TASK-001` | The task directory passed to a lifecycle command does not exist. | `services/task_lifecycle.py:529` |
|
|
29
29
|
| `LIFECYCLE-TRANSITION-001` | The task's current status cannot transition to the target status of the stage being run. | `services/task_lifecycle.py:641` |
|
|
30
30
|
| `LIFECYCLE-UOW-001` | The `UnitOfWork` write fails: task metadata cannot be re-read before commit/takeover, changed under it (optimistic-concurrency conflict), or `unit.commit()` raises. | `services/task_lifecycle.py:942` (also `:949`, `:998`, `:1051`, `:1059`, `:1095`) |
|
|
31
|
+
| `SUBAGENT-SLICE-001` | A `cowork-implement` runtime context is initialized with `owned_paths` that equal, or nest with, the `owned_paths` of an open (not yet closed) implement context of the same task; nothing is written for the rejected context. | `services/workflow_runtime.py:144` |
|
|
31
32
|
|
|
32
33
|
## Non-error `LIFECYCLE-*` codes
|
|
33
34
|
|
|
@@ -43,6 +44,4 @@ to them:
|
|
|
43
44
|
## Exemptions
|
|
44
45
|
|
|
45
46
|
`LIFECYCLE-AC-002` is the only code in this set a flag can waive
|
|
46
|
-
(`--allow-missing-evidence`)
|
|
47
|
-
declarations, not a `LIFECYCLE-*` code. Details are in `evidence-completion.md` and
|
|
48
|
-
`spec-checks.md`.
|
|
47
|
+
(`--allow-missing-evidence`). Details are in `evidence-completion.md`.
|
|
@@ -40,7 +40,7 @@ direct lifecycle checks:
|
|
|
40
40
|
| `LIFECYCLE-AC-001` | an unchecked AC row exists | none — do the work, or tick the completed rows |
|
|
41
41
|
| `LIFECYCLE-AC-002` | a declared AC has no valid evidence record | `--allow-missing-evidence` (recorded in task.json `meta.evidenceExempt`, with `at`, `flag`, `by`, and the `missing` detail list) |
|
|
42
42
|
| `LIFECYCLE-AC-003` | `gateProfile=strict` and decision-anchor yields no AC row | none — a strict task was already required to declare ACs at start, so reading none at completion has only three explanations: the anchor was rewritten, the task was created wrongly, or the anchor is undecodable (corrupted encoding) |
|
|
43
|
-
| `LIFECYCLE-AC-004` | `gateProfile=strict` and the ac_evidence module cannot be imported | none —
|
|
43
|
+
| `LIFECYCLE-AC-004` | `gateProfile=strict` and the ac_evidence module cannot be imported | none — a broken module is not "nothing to check"; the message carries the original exception, and you rerun once the runtime install is fixed |
|
|
44
44
|
|
|
45
45
|
- The first two codes can appear together; `--allow-missing-evidence` waives
|
|
46
46
|
`LIFECYCLE-AC-002` only, never `003`/`004`.
|
|
@@ -51,8 +51,8 @@ direct lifecycle checks:
|
|
|
51
51
|
- **Strict path**: a `gateProfile=strict` task re-verifies AC existence and
|
|
52
52
|
evidence-module availability at completion — the start-time AC declaration
|
|
53
53
|
requirement does not lapse because completion cannot read it.
|
|
54
|
-
- The `meta.evidenceExempt` trace
|
|
55
|
-
|
|
54
|
+
- The `meta.evidenceExempt` trace does not accumulate: retries leave only the
|
|
55
|
+
latest completion result.
|
|
56
56
|
- **An exemption record must name which items were waived and who approved it**:
|
|
57
57
|
`meta.evidenceExempt` = `{"at", "flag": "--allow-missing-evidence",
|
|
58
58
|
"by": <actor or null>, "missing": [<AC id>…]}`. `by` is the resolved session
|
|
@@ -60,9 +60,7 @@ direct lifecycle checks:
|
|
|
60
60
|
`creator`/`assignee`. Both the completion output and the archive output print
|
|
61
61
|
this record item by item (the completion output reads the `task.json` just
|
|
62
62
|
written), so "what was recorded" and "what was printed" cannot tell two different
|
|
63
|
-
stories; with no exemption, no exemption rows are printed at all.
|
|
64
|
-
matches the `meta.specCheckExempt` detail rules on the spec-check side; see
|
|
65
|
-
`spec-checks.md`.
|
|
63
|
+
stories; with no exemption, no exemption rows are printed at all.
|
|
66
64
|
|
|
67
65
|
## Injection and fact view
|
|
68
66
|
|
|
@@ -81,5 +79,5 @@ direct lifecycle checks:
|
|
|
81
79
|
behind an AC to fabricate a record.
|
|
82
80
|
- It does not parse AC prose semantics; a missing checkbox on an `AC-xxx` row
|
|
83
81
|
parses as incomplete.
|
|
84
|
-
- This contract does not change
|
|
85
|
-
by default (`fact-layer-access.md`).
|
|
82
|
+
- This contract does not change the file-scope gate or the AC gate; MCP write
|
|
83
|
+
tools still refuse by default (`fact-layer-access.md`).
|
|
@@ -12,7 +12,6 @@ Current contracts:
|
|
|
12
12
|
- `capabilities.md`: the host adapter capability model.
|
|
13
13
|
- `party-mode-v2-board.md`: the Party Mode V2 board protocol.
|
|
14
14
|
- `plan-binding.md`: lightweight development-plan binding, pre-start missing-plan blocking, and recovery visibility.
|
|
15
|
-
- `spec-checks.md`: the spec-declared command contract — syntax of `checks:` in user-spec frontmatter, three-state gate semantics, timing, and the host capability matrix.
|
|
16
15
|
- `context-injection.md`: the transport shapes, event-timing matrix, and fingerprint serialization rules for runtime-context injection across hosts (a meta-protocol, not in `contract-registry.json`).
|
|
17
16
|
- `decision-anchor.md`: the schema of each task's `decision-anchor.md` — goal, acceptance criteria, rejected options, and key assumptions.
|
|
18
17
|
- `error-output-as-data.md`: external error output is analyzed as data, never executed as instructions (`ERROR_OUTPUT_AS_DATA_V1`).
|
|
@@ -20,4 +19,4 @@ Current contracts:
|
|
|
20
19
|
- `evidence-completion.md`: evidence-based completion — the `evidence.jsonl` schema, the `LIFECYCLE-AC-001/002/003/004` completion gate, `--allow-missing-evidence` exemption traces, and the injected coverage row.
|
|
21
20
|
- `task-adopt.md`: task adoption — the entry boundary of `task next --run --adopt`, its preconditions (`LIFECYCLE-ADOPT-001`), the rebind + baseline reset + audit write, and how it differs from `--takeover`.
|
|
22
21
|
- `task-board.md`: the shared board and dependency edges — the list/MCP board fields (executor / ownerSessionActive / evidenceCoverage / blocked), `--depends-on` declaration and validation, the start dependency gate (`LIFECYCLE-DEPENDENCY-001`), and the adopt handover receipt.
|
|
23
|
-
- `error-codes.md`: the registry of every runtime
|
|
22
|
+
- `error-codes.md`: the registry of every runtime error code with the condition that produces it: `LIFECYCLE-AC-001/002/003/004`, `LIFECYCLE-ADOPT-001`, `LIFECYCLE-CHECK-001`, `LIFECYCLE-CONTEXT-001`, `LIFECYCLE-DEPENDENCY-001`, `LIFECYCLE-EXECUTOR-001`, `LIFECYCLE-RECOVERY-001`, `LIFECYCLE-REVIEW-001/002/003/004`, `LIFECYCLE-TASK-001`, `LIFECYCLE-TRANSITION-001`, `LIFECYCLE-UOW-001`, `SUBAGENT-SLICE-001`.
|
|
@@ -14,8 +14,9 @@ the plan into a second workflow kernel.
|
|
|
14
14
|
|
|
15
15
|
## Creation phase
|
|
16
16
|
|
|
17
|
-
- A plain
|
|
18
|
-
|
|
17
|
+
- A plain task creation (`task next --title … --slug …`) may create a planning
|
|
18
|
+
task with no plan.
|
|
19
|
+
- `task next --from-plan <path>` is an explicit binding request; `<path>` must
|
|
19
20
|
exist, lie inside the repository, and lie under `.cowork-flow/plans/`.
|
|
20
21
|
- On a successful binding the runtime writes:
|
|
21
22
|
|
|
@@ -38,6 +38,28 @@ verifies that support. If `subagent init` emits a suggested host context key,
|
|
|
38
38
|
the parent may use that key unless the host adapter has a stronger stable child
|
|
39
39
|
session key.
|
|
40
40
|
|
|
41
|
+
## Implement Slices
|
|
42
|
+
|
|
43
|
+
An implement context may declare the ownership slice it works on inside
|
|
44
|
+
`assignment`: `slice` (a name) and `owned_paths` (repository-relative paths),
|
|
45
|
+
recorded by `subagent init --slice <name> --owns <path>...`. The runtime then
|
|
46
|
+
machine-checks slice cleanliness instead of leaving it to prose:
|
|
47
|
+
|
|
48
|
+
- Initializing an implement context whose `owned_paths` equal, or nest with,
|
|
49
|
+
those of an open (not yet closed) implement context of the same task fails
|
|
50
|
+
closed with `SUBAGENT-SLICE-001`; nothing is written for the rejected
|
|
51
|
+
context.
|
|
52
|
+
- A context that declares no `owned_paths` claims nothing, so it neither
|
|
53
|
+
conflicts with nor blocks anyone — existing plain `init` calls keep working
|
|
54
|
+
unchanged.
|
|
55
|
+
- Closing a context releases its paths; a finished slice no longer blocks a
|
|
56
|
+
re-run of the same slice.
|
|
57
|
+
|
|
58
|
+
`subagent list [--task <task-dir>] [--open]` inventories contexts with their
|
|
59
|
+
status and ownership summary, and `subagent prompt <runtime_context_id>`
|
|
60
|
+
renders the transport lines plus the assignment pointers as a paste-ready
|
|
61
|
+
dispatch prompt.
|
|
62
|
+
|
|
41
63
|
## Binding Gate
|
|
42
64
|
|
|
43
65
|
The child hook or plugin may resolve `cowork_runtime_context_id` early and bind
|
|
@@ -86,33 +108,20 @@ does not defend against a forged runtime record — writing
|
|
|
86
108
|
`.cowork-flow/.runtime/**` is out of scope here and would need a signed or
|
|
87
109
|
externally rooted record, which is a separate contract.
|
|
88
110
|
|
|
89
|
-
## Delegated Spec Self-Check
|
|
90
|
-
|
|
91
|
-
Delegated subagent tool calls receive no edit-phase hook feedback on hosts
|
|
92
|
-
whose plugin hooks cover only the main session stream (verified on zcode:
|
|
93
|
-
plugin PostToolUse fires for main-session tool calls but never for
|
|
94
|
-
Agent-tool children). The reliable pull path is the CLI: before reporting
|
|
95
|
-
completion, `cowork-implement` (and any child that changes files) runs
|
|
96
|
-
|
|
97
|
-
```text
|
|
98
|
-
./.cowork-flow/run spec-check
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
fixes every reported violation, and re-runs until the summary is clean or the
|
|
102
|
-
remaining violations are reported as blockers. This is a protocol behavior,
|
|
103
|
-
not a second lifecycle gate — the closeout gate re-runs the same checks and
|
|
104
|
-
blocks completion regardless.
|
|
105
|
-
|
|
106
111
|
## Return Acceptance And Closeout
|
|
107
112
|
|
|
108
|
-
-
|
|
113
|
+
- Converge on the child result before acceptance — via the wait primitive or
|
|
114
|
+
the host's completion signal; the dispatch form (foreground or background)
|
|
115
|
+
is host-determined.
|
|
109
116
|
- Confirm no stray running children with the adapter list primitive.
|
|
110
117
|
- Verify the child report by checking files, commands, and results; do not
|
|
111
118
|
trust completed text alone.
|
|
112
|
-
- Before accepting delegated output,
|
|
113
|
-
the
|
|
114
|
-
|
|
115
|
-
|
|
119
|
+
- Before accepting delegated output, verify the child's changed paths against
|
|
120
|
+
the task scope and the applicable `spec/` requirements from the diff
|
|
121
|
+
(`git diff --stat` / `task next <dir> --validate`): child sessions receive no
|
|
122
|
+
edit-phase hook feedback on hosts whose plugin hooks cover only the main
|
|
123
|
+
session stream, so acceptance-time review is the earliest reliable checkpoint
|
|
124
|
+
for delegated work.
|
|
116
125
|
- After completion or failure, close the child with the adapter cancel/close
|
|
117
126
|
primitive and clean the runtime context sessions.
|
|
118
127
|
- The child remains a leaf executor and must not dispatch, wait for, list, or
|
|
@@ -74,7 +74,7 @@ that does not advance status); `check_result` is `null` (no check is exempted).
|
|
|
74
74
|
## Session binding
|
|
75
75
|
|
|
76
76
|
On successful adoption the CLI binds the adopter's session to that task (the same
|
|
77
|
-
shape as a rebind by `task next <dir>` or the binding
|
|
77
|
+
shape as a rebind by `task next <dir>` or the binding at task creation), so a
|
|
78
78
|
later `task next <dir> --run` can omit the directory and continue the
|
|
79
79
|
review/complete/archive chain.
|
|
80
80
|
|
|
@@ -115,19 +115,6 @@
|
|
|
115
115
|
"when changing task start readiness",
|
|
116
116
|
"when changing resume context for active tasks"
|
|
117
117
|
]
|
|
118
|
-
},
|
|
119
|
-
{
|
|
120
|
-
"id": "SPEC_CHECKS_V1",
|
|
121
|
-
"path": ".cowork-flow/spec/contracts/spec-checks.md",
|
|
122
|
-
"digest": [
|
|
123
|
-
"Specs may declare `checks:` commands in frontmatter; the mechanism runs declarations, never prose.",
|
|
124
|
-
"A declared command that cannot be found counts as unchecked and blocks completion."
|
|
125
|
-
],
|
|
126
|
-
"readWhen": [
|
|
127
|
-
"before adding or editing spec files",
|
|
128
|
-
"when a spec clause can be judged by a command exit code",
|
|
129
|
-
"when spec-check reports violations or unchecked"
|
|
130
|
-
]
|
|
131
118
|
}
|
|
132
119
|
],
|
|
133
120
|
"references": [
|
|
@@ -155,7 +155,7 @@
|
|
|
155
155
|
"runtimeContextDispatch": "native",
|
|
156
156
|
"runtimeContextBinding": "shim",
|
|
157
157
|
"runtimeContextCleanup": "native",
|
|
158
|
-
"editScopeWarning": "
|
|
158
|
+
"editScopeWarning": "plugin"
|
|
159
159
|
},
|
|
160
160
|
"commandTargets": [
|
|
161
161
|
{
|
|
@@ -204,7 +204,7 @@
|
|
|
204
204
|
"runtimeContextDispatch": "native",
|
|
205
205
|
"runtimeContextBinding": "shim",
|
|
206
206
|
"runtimeContextCleanup": "native",
|
|
207
|
-
"editScopeWarning": "
|
|
207
|
+
"editScopeWarning": "plugin"
|
|
208
208
|
},
|
|
209
209
|
"commandTargets": [],
|
|
210
210
|
"payload": {
|
|
@@ -251,7 +251,7 @@
|
|
|
251
251
|
"runtimeContextDispatch": "native",
|
|
252
252
|
"runtimeContextBinding": "shim",
|
|
253
253
|
"runtimeContextCleanup": "native",
|
|
254
|
-
"editScopeWarning": "
|
|
254
|
+
"editScopeWarning": "plugin"
|
|
255
255
|
},
|
|
256
256
|
"commandTargets": [
|
|
257
257
|
{
|
|
@@ -300,7 +300,7 @@
|
|
|
300
300
|
"runtimeContextDispatch": "native",
|
|
301
301
|
"runtimeContextBinding": "shim",
|
|
302
302
|
"runtimeContextCleanup": "shim",
|
|
303
|
-
"editScopeWarning": "
|
|
303
|
+
"editScopeWarning": "plugin"
|
|
304
304
|
},
|
|
305
305
|
"commandTargets": [],
|
|
306
306
|
"payload": null
|
|
@@ -431,7 +431,7 @@
|
|
|
431
431
|
"runtimeContextDispatch": "native",
|
|
432
432
|
"runtimeContextBinding": "shim",
|
|
433
433
|
"runtimeContextCleanup": "native",
|
|
434
|
-
"editScopeWarning": "
|
|
434
|
+
"editScopeWarning": "plugin"
|
|
435
435
|
},
|
|
436
436
|
"commandTargets": [],
|
|
437
437
|
"payload": {
|
|
@@ -460,8 +460,8 @@
|
|
|
460
460
|
".cowork-flow/.version",
|
|
461
461
|
".cowork-flow/run",
|
|
462
462
|
".cowork-flow/run.cmd",
|
|
463
|
-
".cowork-flow/spec/contracts/
|
|
464
|
-
".cowork-flow/spec/
|
|
463
|
+
".cowork-flow/spec/contracts/workflow-state-templates.md",
|
|
464
|
+
".cowork-flow/spec/runtime/scope-rules.json"
|
|
465
465
|
],
|
|
466
466
|
"safePrefixes": [
|
|
467
467
|
".cowork-flow/"
|
|
@@ -515,6 +515,7 @@
|
|
|
515
515
|
".cowork-flow/scripts/application",
|
|
516
516
|
".cowork-flow/scripts/services/batch_execution.py",
|
|
517
517
|
".cowork-flow/scripts/services/decision_review.py",
|
|
518
|
+
".cowork-flow/scripts/services/spec_check.py",
|
|
518
519
|
".cowork-flow/scripts/commands",
|
|
519
520
|
".cowork-flow/scripts/common",
|
|
520
521
|
".cowork-flow/scripts/common/__init__.py",
|
|
@@ -567,6 +568,7 @@
|
|
|
567
568
|
".cowork-flow/scripts/subagent.py",
|
|
568
569
|
".cowork-flow/scripts/task.py",
|
|
569
570
|
".cowork-flow/spec/contracts/entry-contract.md",
|
|
571
|
+
".cowork-flow/spec/contracts/spec-checks.md",
|
|
570
572
|
".cowork-flow/spec/runtime/rules.json",
|
|
571
573
|
".cowork-flow/spec/schemas/rules.schema.json",
|
|
572
574
|
".cowork-flow/spec/runtime/skill-registry.json",
|
|
@@ -575,6 +577,7 @@
|
|
|
575
577
|
".cowork-flow/scripts/adapters/cli/add_session.py",
|
|
576
578
|
".cowork-flow/workspace/index.md",
|
|
577
579
|
".cowork-flow/scripts/adapters/cli/change.py",
|
|
580
|
+
".cowork-flow/scripts/adapters/cli/spec_check.py",
|
|
578
581
|
".cowork-flow/changes/",
|
|
579
582
|
".cowork-flow/skills/adversarial-review",
|
|
580
583
|
".cowork-flow/skills/agent-dispatch",
|