@kylecheng3146/agent-ops 0.10.0 → 0.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/packages/cli/src/args.js +10 -10
- package/dist/packages/cli/src/cli.js +14 -4
- package/dist/packages/cli/src/commands/advance.js +2 -1
- package/dist/packages/cli/src/commands/parallel.js +1 -1
- package/dist/packages/cli/src/commands/review.js +3 -1
- package/dist/packages/cli/src/commands/run.js +10 -3
- package/dist/packages/cli/src/context.js +22 -4
- package/dist/packages/cli/src/office-entry.js +8 -4
- package/dist/packages/cli/src/owned-run-step.js +4 -4
- package/dist/packages/cli/src/parallel-deps.js +0 -2
- package/dist/packages/cli/src/run-deps.js +12 -9
- package/dist/packages/cli/src/run-entry.js +76 -17
- package/dist/packages/cli/src/run-setup-entry.js +2 -2
- package/dist/runtime/src/install/harness.js +5 -2
- package/dist/runtime/src/office/scene.js +1 -1
- package/dist/runtime/src/office/server.js +43 -16
- package/dist/runtime/src/office/snapshot.js +1 -1
- package/dist/runtime/src/parallel/finish.js +139 -65
- package/dist/runtime/src/parallel/service.js +5 -1
- package/dist/runtime/src/review/execute.js +9 -7
- package/dist/runtime/src/review/packet.js +17 -6
- package/dist/runtime/src/review/runner.js +2 -0
- package/dist/runtime/src/run/controls.js +99 -1
- package/dist/runtime/src/run/hosts/codex.js +28 -1
- package/dist/runtime/src/run/hosts/protocol.js +5 -2
- package/dist/runtime/src/run/integration.js +35 -13
- package/dist/runtime/src/run/policy.js +65 -10
- package/dist/runtime/src/run/scheduler.js +10 -5
- package/dist/runtime/src/run/service.js +68 -21
- package/dist/runtime/src/run/supervisor.js +11 -2
- package/dist/runtime/src/run/transport.js +1 -0
- package/dist/runtime/src/security/permissions.js +85 -0
- package/dist/runtime/src/task/debug-state.js +9 -3
- package/dist/runtime/src/task/debug-user-event.js +82 -17
- package/dist/runtime/src/task/debug.js +35 -15
- package/docs/en/guides/configuration.md +8 -4
- package/docs/en/guides/task-debug.md +14 -2
- package/docs/en/spec/acceptance-and-evidence.md +4 -0
- package/docs/en/spec/harness-adapters.md +26 -1
- package/docs/en/spec/loop-engineering.md +34 -6
- package/docs/en/spec/review.md +6 -0
- package/docs/zh-TW/guides/configuration.md +6 -3
- package/docs/zh-TW/guides/task-debug.md +9 -2
- package/docs/zh-TW/spec/acceptance-and-evidence.md +2 -1
- package/docs/zh-TW/spec/harness-adapters.md +21 -1
- package/docs/zh-TW/spec/loop-engineering.md +9 -3
- package/docs/zh-TW/spec/review.md +5 -0
- package/package.json +1 -1
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { lstat, open, readdir, realpath } from "node:fs/promises";
|
|
2
3
|
import { homedir } from "node:os";
|
|
3
4
|
import { isAbsolute, join, relative, sep } from "node:path";
|
|
4
5
|
import { sha256 } from "../fs/hash.js";
|
|
5
6
|
import { AgentOpsError } from "../fs/paths.js";
|
|
6
7
|
export function debugUserControl(text) {
|
|
7
8
|
const value = text.trim();
|
|
9
|
+
if (/^(?:不要給預算時間(?:\s+直接完整驗證與review)?|取消時間限制|no time limit)[。.!!]?$/iu.test(value))
|
|
10
|
+
return "unlimited";
|
|
8
11
|
if (/^(?:暫停|暂停|先暫停|先暂停|停止|stop|pause)[。.!!]?$/iu.test(value))
|
|
9
12
|
return "pause";
|
|
10
13
|
if (/^(?:先)?只診斷(?:,等我補失敗時證據)?[。.!!]?$/u.test(value) || /^(?:先)?只诊断[。.!!]?$/u.test(value) || /^diagnos(?:e|is)[ -]only[.!]?$/iu.test(value))
|
|
@@ -37,6 +40,57 @@ function codexUserText(row) {
|
|
|
37
40
|
catch { /* An ambiguous reply cannot authorize a control change. */ }
|
|
38
41
|
return undefined;
|
|
39
42
|
}
|
|
43
|
+
/** Bound each row, not the transcript: assistant/tool output must not hide later human input. */
|
|
44
|
+
async function* nativeLines(path) {
|
|
45
|
+
const file = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
46
|
+
try {
|
|
47
|
+
if (!(await file.stat()).isFile())
|
|
48
|
+
return;
|
|
49
|
+
let pending = Buffer.alloc(0), oversized = false, index = 0;
|
|
50
|
+
const started = Date.now();
|
|
51
|
+
for await (const chunk of file.createReadStream({ autoClose: false })) {
|
|
52
|
+
if (Date.now() - started > 10_000)
|
|
53
|
+
throw new Error("Native transcript read timed out");
|
|
54
|
+
const bytes = chunk;
|
|
55
|
+
let start = 0;
|
|
56
|
+
while (start < bytes.length) {
|
|
57
|
+
const end = bytes.indexOf(10, start), stop = end < 0 ? bytes.length : end;
|
|
58
|
+
if (!oversized) {
|
|
59
|
+
if (pending.length + stop - start > 1024 * 1024) {
|
|
60
|
+
pending = Buffer.alloc(0);
|
|
61
|
+
oversized = true;
|
|
62
|
+
}
|
|
63
|
+
else
|
|
64
|
+
pending = Buffer.concat([pending, bytes.subarray(start, stop)]);
|
|
65
|
+
}
|
|
66
|
+
if (end < 0)
|
|
67
|
+
break;
|
|
68
|
+
if (!oversized)
|
|
69
|
+
yield { line: pending.toString("utf8"), index };
|
|
70
|
+
index++;
|
|
71
|
+
pending = Buffer.alloc(0);
|
|
72
|
+
oversized = false;
|
|
73
|
+
start = end + 1;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (!oversized && pending.length > 0)
|
|
77
|
+
yield { line: pending.toString("utf8"), index };
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
await file.close();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async function ownedNativeSession(root, worktreeRoot, sessionId) {
|
|
84
|
+
if (process.env.CODEX_THREAD_ID !== sessionId || worktreeRoot === undefined)
|
|
85
|
+
return false;
|
|
86
|
+
const { readWorktreeRecord } = await import("../parallel/service.js");
|
|
87
|
+
const record = await readWorktreeRecord(worktreeRoot);
|
|
88
|
+
if (record === null || record.sessionId !== sessionId || record.agentId !== undefined || record.runId !== undefined)
|
|
89
|
+
return false;
|
|
90
|
+
const [main, worktree, recordMain, recordPath] = await Promise.all([root, worktreeRoot, record.mainRoot, record.path].map(path => realpath(path)));
|
|
91
|
+
const inside = relative(join(main, ".worktrees"), worktree);
|
|
92
|
+
return main === recordMain && worktree === recordPath && inside.length > 0 && !inside.startsWith("..") && !isAbsolute(inside);
|
|
93
|
+
}
|
|
40
94
|
/** Host transcripts are the user-event authority; CLI actor/text fields are never accepted. */
|
|
41
95
|
/** `worktreeRoot` is the session's owned worktree: Claude stamps each row with the shell's cwd, which moves there after `cd`. */
|
|
42
96
|
export async function readDebugUserEvent(root, sessionId, home = homedir(), kind = "latest", worktreeRoot) {
|
|
@@ -59,35 +113,46 @@ export async function readDebugUserEvent(root, sessionId, home = homedir(), kind
|
|
|
59
113
|
catch { /* The host need not be installed. */ }
|
|
60
114
|
}
|
|
61
115
|
await visit(codex, 3);
|
|
116
|
+
const owned = await ownedNativeSession(root, worktreeRoot, sessionId).catch(() => false);
|
|
62
117
|
for (const path of paths) {
|
|
63
118
|
try {
|
|
64
119
|
const stat = await lstat(path), canonical = await realpath(path);
|
|
65
120
|
const fromHome = relative(canonicalHome, canonical);
|
|
66
|
-
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 16 * 1024 * 1024 || fromHome.startsWith("..") || isAbsolute(fromHome))
|
|
67
|
-
continue;
|
|
68
|
-
const lines = (await readFile(path, "utf8")).split("\n");
|
|
69
|
-
const rows = lines.map(line => { try {
|
|
70
|
-
return JSON.parse(line);
|
|
71
|
-
}
|
|
72
|
-
catch {
|
|
73
|
-
return null;
|
|
74
|
-
} });
|
|
75
121
|
const codexSource = path.startsWith(codex + sep);
|
|
76
|
-
if (
|
|
122
|
+
if (!stat.isFile() || stat.isSymbolicLink() || (!codexSource && stat.size > 16 * 1024 * 1024) || fromHome.startsWith("..") || isAbsolute(fromHome))
|
|
77
123
|
continue;
|
|
78
|
-
|
|
79
|
-
|
|
124
|
+
let matchedSession = false, event;
|
|
125
|
+
for await (const { line, index } of nativeLines(path)) {
|
|
126
|
+
let row;
|
|
127
|
+
try {
|
|
128
|
+
row = JSON.parse(line);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (codexSource && row?.type === "session_meta") {
|
|
134
|
+
if (row.payload?.id !== sessionId || (row.payload?.cwd !== root && !owned)) {
|
|
135
|
+
event = undefined;
|
|
136
|
+
matchedSession = false;
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
matchedSession = true;
|
|
140
|
+
}
|
|
80
141
|
const text = codexSource ? codexUserText(row) :
|
|
81
142
|
row?.type === "user" && row.sessionId === sessionId && (row.cwd === root || (worktreeRoot !== undefined && row.cwd === worktreeRoot)) && row.message?.role === "user" &&
|
|
82
143
|
row.isMeta !== true && (row.origin === undefined || row.origin?.kind === "human") ? row.message.content : undefined;
|
|
83
144
|
if (typeof text !== "string" || text.length > 64 * 1024 || (kind === "control" && debugUserControl(text) === null))
|
|
84
145
|
continue;
|
|
85
146
|
const timestamp = Date.parse(row.timestamp);
|
|
86
|
-
if (!Number.isFinite(timestamp))
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
147
|
+
if (!Number.isFinite(timestamp)) {
|
|
148
|
+
event = undefined;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const sourceDigest = sha256(line);
|
|
152
|
+
event = { id: sha256(relative(home, path) + ":" + index + ":" + sourceDigest), sourceDigest, text, timestamp };
|
|
90
153
|
}
|
|
154
|
+
if (event !== undefined && (!codexSource || matchedSession))
|
|
155
|
+
return event;
|
|
91
156
|
}
|
|
92
157
|
catch { /* An unavailable native source cannot grant time. */ }
|
|
93
158
|
}
|
|
@@ -14,7 +14,7 @@ import { resolveReviewScope } from "../review/scope.js";
|
|
|
14
14
|
import { runVerificationCommand } from "../verify/spawn.js";
|
|
15
15
|
import { checkTaskVerificationEvidence } from "./completion.js";
|
|
16
16
|
import { taskContractHash, criterionContractHash } from "./contract.js";
|
|
17
|
-
import { DEBUG_EXTENSION_MS, debugProofDigest, debugRemainingMs, debugRounds, debugStopCause } from "./debug-state.js";
|
|
17
|
+
import { DEBUG_EXTENSION_MS, debugSpentMs, debugProofDigest, debugRemainingMs, debugRounds, debugStopCause } from "./debug-state.js";
|
|
18
18
|
import { FileTaskStore } from "./store.js";
|
|
19
19
|
import { debugUserControl, readDebugUserEvent } from "./debug-user-event.js";
|
|
20
20
|
import { readWorktreeRecord } from "../parallel/service.js";
|
|
@@ -144,7 +144,13 @@ export class DebugService {
|
|
|
144
144
|
await this.#mutate(root, next);
|
|
145
145
|
return await this.root(root.task.id);
|
|
146
146
|
}
|
|
147
|
-
|
|
147
|
+
/**
|
|
148
|
+
* `sealed` admits a strategy switch's citation of this issue's own sealed
|
|
149
|
+
* regression history: a scope revision stales every current reference while
|
|
150
|
+
* verification stays stopped until the switch is recorded. Hypotheses and
|
|
151
|
+
* delivery still need evidence for the current contract and candidate.
|
|
152
|
+
*/
|
|
153
|
+
async #evidence(taskId, reference, sealed = false) {
|
|
148
154
|
const { task, config, sourceFingerprint } = await this.#scope(taskId);
|
|
149
155
|
const store = new FileEvidenceStore(this.options.root, this.options.root);
|
|
150
156
|
const checked = validateEvidence(await store.load(reference));
|
|
@@ -154,9 +160,16 @@ export class DebugService {
|
|
|
154
160
|
const root = await this.root(taskId);
|
|
155
161
|
const head = await this.options.gitRunner.run(["rev-parse", "HEAD"]);
|
|
156
162
|
const candidate = new TextDecoder().decode(head.stdout).trim();
|
|
157
|
-
|
|
158
|
-
e.sourceFingerprint
|
|
159
|
-
|
|
163
|
+
const current = p !== undefined && e.taskContractHash === taskContractHash(task.task) && e.configHash === calculateConfigHash(config) &&
|
|
164
|
+
e.sourceFingerprint === sourceFingerprint && p.pairedCommit === (p.phase === "baseline" ? candidate : root.debug.baselineCommit) &&
|
|
165
|
+
p.commit === (p.phase === "baseline" ? root.debug.baselineCommit : candidate);
|
|
166
|
+
const historical = sealed && p !== undefined && (p.phase === "baseline" ? p.commit : p.pairedCommit) === root.debug.baselineCommit &&
|
|
167
|
+
root.debug.receipts.some(r => r.kind === "verify" && r.taskId === taskId && r.references.includes(reference));
|
|
168
|
+
if (head.exitCode !== 0 || e.taskId !== taskId || p === undefined || !(current || historical) ||
|
|
169
|
+
!task.task.criteria.some(c => c.id === e.criterionId && c.acceptance?.mode === "behavioral" && c.acceptance.baselineCommit === root.debug.baselineCommit &&
|
|
170
|
+
(current
|
|
171
|
+
? p.criterionContractHash === criterionContractHash(c) && c.acceptance.bindings.some(b => b.runnerId === e.commandId && sha256(canonicalJson({ runnerId: b.runnerId, materials: b.materials })) === p.bindingHash)
|
|
172
|
+
: c.acceptance.bindings.some(b => b.runnerId === e.commandId))))
|
|
160
173
|
return fail("TASK_DEBUG_EVIDENCE_STALE", "Evidence must discriminate the original issue at its immutable baseline and current candidate.");
|
|
161
174
|
const source = await readPrivateFile(join(this.options.root, p.executionArtifact), this.options.root);
|
|
162
175
|
if (source === null || p.executionArtifact !== ".agent-ops/tasks/acceptance/" + sha256(source) + ".json")
|
|
@@ -242,7 +255,7 @@ export class DebugService {
|
|
|
242
255
|
fail("TASK_DEBUG_STRATEGY_INVALID", "Document a substantive different verification method and evidence-backed reason.");
|
|
243
256
|
if (debugStopCause(root.debug) !== "strategy-required" || root.debug.strategy !== null || root.debug.operation !== null)
|
|
244
257
|
fail("TASK_DEBUG_STRATEGY_NOT_ALLOWED", "Exactly one strategy switch is allowed after two stagnant repair rounds.");
|
|
245
|
-
await this.#evidence(taskId, input.reference);
|
|
258
|
+
await this.#evidence(taskId, input.reference, true);
|
|
246
259
|
await this.#mutate(root, { ...root.debug, strategy: { from: input.from, to: input.to, reason: input.reason, references: [input.reference], afterRound: debugRounds(root.debug) }, checkpoint: null });
|
|
247
260
|
}
|
|
248
261
|
/** Escape Loop: where the confirmed cause came from, bound into the contract before review. */
|
|
@@ -300,9 +313,9 @@ export class DebugService {
|
|
|
300
313
|
const attached = await this.options.tasks.status({ sessionId });
|
|
301
314
|
if (attached.task.debug?.rootTaskId !== root.task.id || root.status !== "active")
|
|
302
315
|
fail("TASK_DEBUG_CONTROL_INVALID", "Human control must belong to the active attached original issue.");
|
|
303
|
-
if (action === "resume" && debugRemainingMs(root.debug) === 0) {
|
|
316
|
+
if (action === "unlimited" || ((action === "resume" || action === "repair") && debugRemainingMs(root.debug) === 0)) {
|
|
304
317
|
try {
|
|
305
|
-
await this.extend(taskId, sessionId, event.id);
|
|
318
|
+
await this.extend(taskId, sessionId, event.id, home);
|
|
306
319
|
}
|
|
307
320
|
catch (error) {
|
|
308
321
|
if (!(error instanceof AgentOpsError) || !["TASK_DEBUG_GRANT_INVALID", "TASK_DEBUG_GRANT_UNSUPPORTED"].includes(error.code))
|
|
@@ -374,10 +387,14 @@ export class DebugService {
|
|
|
374
387
|
// EVIDENCE-SURFACE-001: intake may start without a surface, the first repair may not.
|
|
375
388
|
if (kind === "verify" && task.task.surfaceRequired === true && task.task.surface === undefined && productDigest !== sha256(canonicalJson([])))
|
|
376
389
|
fail("SURFACE_MISSING", "Add the surface audit with task revise --surface-file before verifying the first repair.");
|
|
377
|
-
const
|
|
390
|
+
const previousVerify = root.debug.receipts.filter(r => r.kind === "verify" && (r.outcome === "PASS" || r.outcome === "FAIL")).at(-1);
|
|
391
|
+
const previousCandidate = previousVerify?.productDigest ?? sha256("[]");
|
|
378
392
|
const cause = debugStopCause(root.debug);
|
|
379
393
|
// Recovery reruns the original verifier; changed product content still needs fresh proof.
|
|
380
|
-
|
|
394
|
+
// At the round limit an unchanged PASS candidate may be re-verified (not a new round), e.g. after an
|
|
395
|
+
// escape-ledger update moved the source fingerprint; a FAIL cannot be re-run into proof at the cap.
|
|
396
|
+
if (cause !== null && !(["environment", "permission"].includes(cause) && (kind === "investigate" || kind === "verify")) &&
|
|
397
|
+
!(cause === "iterations" && productDigest === previousCandidate && (kind === "review" || (kind === "verify" && previousVerify?.outcome === "PASS"))))
|
|
381
398
|
fail("TASK_DEBUG_STOP_REQUIRED", `Runtime stop predicate: ${cause}. Record a checkpoint; resume alone cannot reset it.`);
|
|
382
399
|
const processIdentity = await nativeProcessIdentity(process.pid);
|
|
383
400
|
if (processIdentity === null)
|
|
@@ -587,17 +604,20 @@ export class DebugService {
|
|
|
587
604
|
fail("TASK_DEBUG_RECOVERY_CONFIRMATION_REQUIRED", "Owned debug checkpoint/path/source/contract/config mismatch. Preserve the checkout and obtain user confirmation before recovery.");
|
|
588
605
|
await this.options.tasks.attach(sessionId, root.task.id);
|
|
589
606
|
}
|
|
590
|
-
async extend(taskId, sessionId, eventId) {
|
|
607
|
+
async extend(taskId, sessionId, eventId, home) {
|
|
591
608
|
const root = await this.root(taskId), attached = await this.options.tasks.status({ sessionId });
|
|
592
609
|
if (attached.task.debug?.rootTaskId !== root.task.id || root.debug.operation !== null)
|
|
593
610
|
fail("TASK_DEBUG_GRANT_INVALID", "Extension belongs to the original issue and its native session; a running operation cannot be extended.");
|
|
594
|
-
const event = await this.#userEvent(sessionId);
|
|
611
|
+
const event = await this.#userEvent(sessionId, home);
|
|
595
612
|
if (eventId !== "latest" && eventId !== event.id)
|
|
596
613
|
fail("TASK_DEBUG_GRANT_INVALID", "The requested event is not the current native human event.");
|
|
597
614
|
if (root.debug.grants.some(g => g.eventId === event.id))
|
|
598
615
|
return;
|
|
599
|
-
const otherCause = debugStopCause({ ...root.debug, consumedMs: 0, budgetMs: 86_400_000,
|
|
616
|
+
const otherCause = debugStopCause({ ...root.debug, consumedMs: 0, foreground: null, operation: null, budgetMs: 86_400_000,
|
|
600
617
|
...(root.debug.control === undefined ? {} : { control: { ...root.debug.control, paused: false } }) });
|
|
618
|
+
const unlimited = debugUserControl(event.text) === "unlimited";
|
|
619
|
+
if (unlimited && (root.status !== "active" || event.timestamp < Date.parse(root.createdAt)))
|
|
620
|
+
fail("TASK_DEBUG_GRANT_INVALID", "Only a current native human event can waive the active original issue time limit.");
|
|
601
621
|
const foregroundActive = root.debug.foreground !== undefined && root.debug.foreground !== null;
|
|
602
622
|
const remainingAtEvent = debugRemainingMs(root.debug, event.timestamp);
|
|
603
623
|
const sealedAtCandidates = [Date.parse(root.updatedAt), root.debug.receipts.at(-1)?.finishedAt ?? Number.NaN].filter(Number.isFinite);
|
|
@@ -605,8 +625,8 @@ export class DebugService {
|
|
|
605
625
|
const exhaustedAtEvent = foregroundActive
|
|
606
626
|
? remainingAtEvent === 0
|
|
607
627
|
: debugRemainingMs(root.debug) === 0 && event.timestamp > lastSealedAt;
|
|
608
|
-
if (!exhaustedAtEvent || (otherCause !== null && !["environment", "permission"].includes(otherCause)) ||
|
|
628
|
+
if ((!unlimited && !exhaustedAtEvent) || (otherCause !== null && !["environment", "permission"].includes(otherCause)) || !["resume", "repair", "unlimited"].includes(debugUserControl(event.text) ?? ""))
|
|
609
629
|
fail("TASK_DEBUG_GRANT_INVALID", "Only a contemporaneous human continue event extends exhausted time; other blockers remain authoritative.");
|
|
610
|
-
await this.#mutate(root, { ...root.debug, grants: [...root.debug.grants, { eventId: event.id, sessionId, sourceDigest: event.sourceDigest, grantedMs: DEBUG_EXTENSION_MS }], checkpoint: null });
|
|
630
|
+
await this.#mutate(root, { ...root.debug, grants: [...root.debug.grants, { eventId: event.id, sessionId, sourceDigest: event.sourceDigest, grantedMs: unlimited ? null : DEBUG_EXTENSION_MS, spentAtGrantMs: debugSpentMs(root.debug, event.timestamp) }], checkpoint: null });
|
|
611
631
|
}
|
|
612
632
|
}
|
|
@@ -275,7 +275,7 @@ evidence. Give each editing conversation its own worktree instead:
|
|
|
275
275
|
(Claude Code refuses it inside an isolated worktree session). `agent-ops
|
|
276
276
|
worktree finish <name>` completes every non-archived task in the worktree,
|
|
277
277
|
subtasks before their parents, each against the base its PASS review
|
|
278
|
-
recorded (the worktree's base when none was recorded), before
|
|
278
|
+
recorded (the worktree's base when none was recorded), before merging.
|
|
279
279
|
If one cannot complete, nothing merges and the error names the task.
|
|
280
280
|
Creating a subtask keeps the session attached to the top of its tree, and
|
|
281
281
|
each task keeps its own review record, so a parent and a subtask reviewed
|
|
@@ -295,9 +295,13 @@ evidence. Give each editing conversation its own worktree instead:
|
|
|
295
295
|
its files, so uncommitted changes there block it. When the main checkout is
|
|
296
296
|
on another branch or detached, only the target's ref moves: the checkout, its
|
|
297
297
|
files and its uncommitted changes are left alone. A target checked out in
|
|
298
|
-
another worktree is refused, naming that path. If the target moved, it rebases
|
|
299
|
-
|
|
300
|
-
the
|
|
298
|
+
another worktree is refused, naming that path. If the target moved, it rebases the worktree and
|
|
299
|
+
stops with `WORKTREE_REBASED` before completing any task: earlier evidence
|
|
300
|
+
covered the old base, not the combination that would merge. Verify and
|
|
301
|
+
review again with the printed `--base`, then finish again; a task that was
|
|
302
|
+
already complete cannot take new evidence, so cover the rebased change with
|
|
303
|
+
a follow-up task. A conflict is reset and reported with the
|
|
304
|
+
`refs/notes/agent-ops` intent of the work that landed first. The note
|
|
301
305
|
records every task of the worktree, subtasks indented under their parent,
|
|
302
306
|
and a `session: <id>` line naming the session that finished it. Finish
|
|
303
307
|
also resets that session's gate baseline to the merged source, so the
|
|
@@ -73,8 +73,9 @@ Use `task debug --task <id> --debug-action <action>`:
|
|
|
73
73
|
- `resume --session <id>` recovers a matching owned worktree checkpoint. Mismatches
|
|
74
74
|
require human confirmation and remain preserved; this command cannot waive them.
|
|
75
75
|
- `extend --session <native-id> --event-id latest` reads the current native human
|
|
76
|
-
continue event.
|
|
77
|
-
|
|
76
|
+
continue or resume-repair event. After time exhaustion it grants a full twenty
|
|
77
|
+
minutes from that event, retaining consumed time and overrun in the grant snapshot
|
|
78
|
+
without waiving any other blocker. No actor/text flag can
|
|
78
79
|
authorize a grant. Missing native transcripts fail closed as unsupported.
|
|
79
80
|
|
|
80
81
|
Limits belong to the original root across revisions, children and sessions.
|
|
@@ -97,6 +98,11 @@ rounds. Test/fixture edits do not. New paired original-regression outcomes or
|
|
|
97
98
|
new discriminating hypothesis outcomes count as progress; renaming a task or
|
|
98
99
|
hypothesis does not. Two stagnant rounds require the strategy switch; two more
|
|
99
100
|
stop, subject to the global round/time ceilings.
|
|
101
|
+
The switch may cite this issue's own sealed regression evidence from an earlier
|
|
102
|
+
contract revision, because a scope revision stales current evidence while
|
|
103
|
+
verification stays stopped; hypotheses and delivery still need fresh evidence.
|
|
104
|
+
At the round limit an unchanged candidate may still be reviewed, and re-verified
|
|
105
|
+
when its last verification passed; a failing or changed candidate stays stopped.
|
|
100
106
|
|
|
101
107
|
Success requires baseline assertion red and candidate green for the original
|
|
102
108
|
regression, required related checks, a supported confirmed hypothesis, and two
|
|
@@ -121,3 +127,9 @@ persists foreground progress, and does not promise
|
|
|
121
127
|
execution after the native conversation closes. Native transcripts are trusted
|
|
122
128
|
host evidence, not protection against an attacker with the same filesystem
|
|
123
129
|
authority.
|
|
130
|
+
|
|
131
|
+
An explicit native human `no time limit` / `取消時間限制` instruction removes the
|
|
132
|
+
original issue time cap via the same native-only `extend --event-id latest`
|
|
133
|
+
authority. The grant records `grantedMs: null` and the spent snapshot; original
|
|
134
|
+
budget, consumption, prior grants, iteration limits and evidence gates remain.
|
|
135
|
+
Ordinary command and review stall timeouts remain in force.
|
|
@@ -18,6 +18,10 @@ Executable acceptance MUST bind the current contract to immutable replay evidenc
|
|
|
18
18
|
build and test build. Tasks provide stable check IDs and explicit committed
|
|
19
19
|
test/fixture/helper materials. Each checkout installs and builds its own version
|
|
20
20
|
before candidate materials are overlaid. Tasks cannot override runner commands.
|
|
21
|
+
- A generic runner must emit exactly the union of `checkIds` requested by criteria
|
|
22
|
+
sharing its runner, baseline and materials; unexpected rows are UNKNOWN. If a
|
|
23
|
+
runner always emits its full suite, bind the full emitted set while keeping
|
|
24
|
+
`redCheckIds` limited to the actual regression assertions.
|
|
21
25
|
- Fixture, collection, build, timeout, cancellation, truncated output, missing or
|
|
22
26
|
duplicate IDs, retries and skips MUST NOT establish red or green. Complete raw
|
|
23
27
|
execution artifacts are digest-bound to the contract, materials and commits.
|
|
@@ -109,7 +109,7 @@ The current registration matrix is intentionally asymmetric:
|
|
|
109
109
|
| office-presence (Preview) | degraded | degraded | supported | degraded |
|
|
110
110
|
| lifecycle-summary | degraded | supported | supported | degraded |
|
|
111
111
|
| command-policy | supported | unknown | supported | supported |
|
|
112
|
-
| completion-gate | supported | unsupported |
|
|
112
|
+
| completion-gate | supported | unsupported | supported | unsupported |
|
|
113
113
|
| optional-stop-verify | degraded | unsupported | supported | degraded |
|
|
114
114
|
|
|
115
115
|
For runtime-failure handling, only `command-policy` is fail-closed. Claude
|
|
@@ -191,6 +191,31 @@ removes only `run` and keeps `loop`.
|
|
|
191
191
|
- Positive: `A Claude Code session with the run profile writes .agent-ops/state/run-goal.md in the main checkout and starts agent-ops run --host claude --wait in the background.`
|
|
192
192
|
- Negative: `Split an eight-criteria change into subtasks while the run profile is active, add auto-run text to GEMINI.md, or resume a blocked run without the user.`
|
|
193
193
|
|
|
194
|
+
`run status` includes `recovery` in JSON and the same guidance in text: phase
|
|
195
|
+
(unknown for legacy records), original bounded redacted diagnostic, sealed proof
|
|
196
|
+
(`recorded-unchecked` is an observation, not validation), cleanup state,
|
|
197
|
+
resumability and concrete remediation. An unchanged cleanup failure retains its
|
|
198
|
+
original cause. Trust, native identity, generation and denial failures remain
|
|
199
|
+
fail-closed; a sealed proof never erases a native or organization denial.
|
|
200
|
+
|
|
201
|
+
Manual resume of a receipt-bound integration validates the exact sealed proof,
|
|
202
|
+
receipt digest and transaction, owner/generation, candidate/target ancestry and
|
|
203
|
+
Git note before cleanup. It executes only missing note, gate, trust and checkout
|
|
204
|
+
cleanup steps; it preserves existing receipt bytes and completed task/proof work.
|
|
205
|
+
A missing unrecorded note may be bound to the sealed receipt; a missing or changed
|
|
206
|
+
recorded note blocks. Completed cleanup requires the owned checkouts to be gone.
|
|
207
|
+
The target is never moved again. A changed target is accepted only as a descendant
|
|
208
|
+
of the receipt-bound candidate with recorded integration history.
|
|
209
|
+
|
|
210
|
+
Cleanup records a bounded redacted original failure and a retry-input digest of
|
|
211
|
+
the transaction boundary, checkout status, untracked file metadata and heartbeat
|
|
212
|
+
liveness. Identical inputs return `RUN_CLEANUP_UNCHANGED` before repeating cleanup;
|
|
213
|
+
a remediation delta permits a fresh attempt after all safety checks. This digest
|
|
214
|
+
is a retry guard, not verification reuse or new authority. Finalization failure
|
|
215
|
+
does not immediately replay the failed integration: it preserves the original
|
|
216
|
+
cause for manual recovery. Live/unknown/forged/symlink heartbeats and unrelated
|
|
217
|
+
user files retain the existing non-force removal protections.
|
|
218
|
+
|
|
194
219
|
## HARNESS-ADAPTER-008
|
|
195
220
|
|
|
196
221
|
`agent-ops office` (Preview) MUST show progress read-only from agent-ops state alone. The
|
|
@@ -14,8 +14,12 @@
|
|
|
14
14
|
goals. Same-host writers use isolated worktrees and current generation leases;
|
|
15
15
|
the coordinator counts toward the maximum two writers. Dependencies become
|
|
16
16
|
available only through frozen deliveries; child baselines follow those commits.
|
|
17
|
-
-
|
|
18
|
-
setup, proof, review and integration.
|
|
17
|
+
- New runs have no total time cap (`budget.limitMs: null`). Active interval unions
|
|
18
|
+
still measure setup, proof, review and integration. Legacy finite runs upgrade
|
|
19
|
+
only on manual resume, retaining the old limit, intervals, elapsed time, usage
|
|
20
|
+
epochs and blocker history. Reads and installation never upgrade or awaken runs.
|
|
21
|
+
Command/review timeouts, provider quotas, DEBUG budgets and no-progress/stop
|
|
22
|
+
controls remain independent and effective. Explicit --time-budget is rejected.
|
|
19
23
|
Missing counters remain UNKNOWN; partial usage is not a complete cost estimate.
|
|
20
24
|
- Questions pause work and require an explicit answer. Repair preserves the goal
|
|
21
25
|
and records failures; two identical rounds without progress stop that worker
|
|
@@ -38,17 +42,41 @@
|
|
|
38
42
|
setup requires explicit resume and retains its checkout and failure artifact.
|
|
39
43
|
- A coordinator may assess additional fixed non-dangerous capabilities only under
|
|
40
44
|
a run-scoped policy bound to the original repository trust, executable runtime
|
|
41
|
-
and
|
|
45
|
+
and a finite one-hour authorization window. Explicit native or organization denial cannot be bypassed.
|
|
42
46
|
Policy changes fence all writers, preserve frozen deliveries and journal contract
|
|
43
47
|
synchronization before new generations acquire leases. This never grants global
|
|
44
48
|
repository trust. Native decisions retain redacted command scope and rationale;
|
|
45
49
|
absent provider evidence remains UNKNOWN.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
50
|
+
Active execution automatically renews an expired grant only when the original
|
|
51
|
+
repository trust, config, executable runtime and operation scope still match.
|
|
52
|
+
Renewal is digest-bound to its predecessor, preserves assessments, rejects
|
|
53
|
+
invalid/backward clocks, and loses to concurrent stop, denial or scope changes.
|
|
54
|
+
Changed/revoked authority requires genuine authorization through the existing
|
|
55
|
+
trust/resume process; renewal never grants permanent trust or clears denial.
|
|
56
|
+
Explicit resume reconciles immutable renewal lineage before policy synchronization.
|
|
57
|
+
An answer uses the same recovery checks; neither action can revive an explicit denial.
|
|
58
|
+
`run resume <id> --retry-approval <auth-event-id>` MAY explicitly retry a
|
|
59
|
+
Codex automatic approval review that failed with its usage-limit wrapper.
|
|
60
|
+
Each selected failure MUST retain its original private artifact, digest,
|
|
61
|
+
worker identity and policy lineage; unrelated or genuine denials still block.
|
|
62
|
+
Repeat the flag for multiple failures. Successful acknowledgements are audited
|
|
63
|
+
and revalidated on later resumes. This grants no command permission: native
|
|
64
|
+
approval runs again. Existing writer-death, checkout, trust and budget checks apply.
|
|
65
|
+
Initial recovery after a current manual resume MAY ignore an owned Codex limit
|
|
66
|
+
snapshot received before activation whose native update second strictly predates
|
|
67
|
+
that resume. Automatic restarts, unknown/equal/future timestamps, clock rollback,
|
|
68
|
+
and every limit received after activation starts remain authoritative. Native
|
|
69
|
+
activation MUST acknowledge an active goal before starting or steering a turn.
|
|
70
|
+
Manual resume releases derivative `RUN_POLICY_DISABLED` task blocks while
|
|
71
|
+
preserving IDs, accounting, history and unrelated product failures.
|
|
49
72
|
- Convergence compares actual per-check statuses and outstanding finding pins.
|
|
50
73
|
Source fingerprints, timing and diagnostics alone cannot establish progress.
|
|
51
74
|
|
|
75
|
+
With `jobs=1`, child execution MUST wait for confirmed coordinator process death.
|
|
76
|
+
The coordinator retains its task and native session identity, and resumes under a
|
|
77
|
+
new generation after child deliveries. Native registration, recovery and start
|
|
78
|
+
MUST enforce at most one active agent; changing task slot accounting alone is insufficient.
|
|
79
|
+
|
|
52
80
|
This module defines the bounded loop used to plan, implement, verify, and hand off work.
|
|
53
81
|
|
|
54
82
|
## LOOP-START-001
|
package/docs/en/spec/review.md
CHANGED
|
@@ -10,6 +10,8 @@ An independent reviewer MUST receive a minimal packet containing the request, cr
|
|
|
10
10
|
- Positive: `Packet contains recorded task intent, criteria, files, and evidence requirements.`
|
|
11
11
|
- Negative: `Forward the complete session transcript and environment.`
|
|
12
12
|
|
|
13
|
+
Large surface audits, including complete revision history, discovery and post-block exclusions, may use a digest-bound reference to an exact normalized match in the validated snapshot contract manifest. Both review rounds must read the complete manifest surface. The 64 KiB inline packet and 512 KiB manifest bounds remain enforced; small and legacy packets retain inline data.
|
|
14
|
+
|
|
13
15
|
## REVIEW-RESULT-001
|
|
14
16
|
|
|
15
17
|
A review result MUST preserve PASS, FAIL, or NOT_RUN and MUST NOT convert NOT_RUN into PASS.
|
|
@@ -57,6 +59,10 @@ Raw reviewer output remains bounded and unstreamed. SIGINT or SIGTERM aborts
|
|
|
57
59
|
the active process tree without fallback or attestation; timeout remains a
|
|
58
60
|
distinct NOT_RUN reason rather than being flattened to `missing-cli`.
|
|
59
61
|
|
|
62
|
+
agy may remain silent throughout a generation. Its review and resumed answer
|
|
63
|
+
MUST NOT be cancelled solely for missing output; the configured round timeout
|
|
64
|
+
and caller cancellation remain effective.
|
|
65
|
+
|
|
60
66
|
## REVIEW-CHAIN-001
|
|
61
67
|
|
|
62
68
|
The two planned sessions MUST be ordered as a necessary review followed by an
|
|
@@ -236,7 +236,7 @@ fingerprint,因此兩個對話修改同一個 checkout 會互相作廢對方
|
|
|
236
236
|
詢問一次。
|
|
237
237
|
- 在 worktree 裡對每個 task 執行 verify 與 review,但不要執行 `task complete`
|
|
238
238
|
(Claude Code 在隔離的 worktree session 中會拒絕它)。`agent-ops worktree finish
|
|
239
|
-
<name>`
|
|
239
|
+
<name>` 會在合併之前 complete worktree 內每個未封存的 task,先 subtask
|
|
240
240
|
後 parent,各自使用其 PASS review 記錄的 base(沒有記錄時使用 worktree 的 base)。
|
|
241
241
|
任一 task 無法完成時不會合併,錯誤訊息會指出是哪個 task。建立 subtask 時 session
|
|
242
242
|
維持附著在 task 樹最上層,且每個 task 各自保留 review 紀錄,所以在同一份原始碼上
|
|
@@ -254,8 +254,11 @@ fingerprint,因此兩個對話修改同一個 checkout 會互相作廢對方
|
|
|
254
254
|
finish。主 checkout 就在 target branch 上時,合併會更新它的檔案,所以那裡有未
|
|
255
255
|
commit 的變更會擋住合併;主 checkout 在其他 branch 或 detached 時,只移動
|
|
256
256
|
target 的 ref,checkout、檔案與未 commit 的變更都不會被動到。Target 若被其他
|
|
257
|
-
worktree checkout 則拒絕,並指出該路徑。Target
|
|
258
|
-
|
|
257
|
+
worktree checkout 則拒絕,並指出該路徑。Target 若已前進,會 rebase worktree 後以
|
|
258
|
+
`WORKTREE_REBASED` 停下,不 complete 任何 task:先前的證據涵蓋的是舊 base,
|
|
259
|
+
不是即將合併的組合。以印出的 `--base` 重新 verify 與 review,再執行一次 finish;
|
|
260
|
+
已 complete 的 task 不能再記錄證據,需以 follow-up task 涵蓋 rebase 後的變更。
|
|
261
|
+
衝突則重設並連同先合併那份工作在 `refs/notes/agent-ops` 的意圖一起
|
|
259
262
|
回報。Note 會記錄 worktree 的每個 task,subtask 縮排在其 parent 之下,並附上
|
|
260
263
|
一行 `session: <id>`,標明完成這次 finish 的 session。Finish 也會把該 session
|
|
261
264
|
的 gate baseline 重設為合併後的原始碼,所以即使 task 現在只存在於 note 裡,
|
|
@@ -48,6 +48,8 @@ Claude、Codex 與 agy 都使用可信 native session lifecycle,在 `UserPromp
|
|
|
48
48
|
中斷且沒有封存結果的操作,必須等持久化 process identity 證實程序已死亡後,
|
|
49
49
|
才封存 UNKNOWN environment receipt;這段經過時間仍會保留,不會退還不明時間。
|
|
50
50
|
測試材料修改不算修復輪;兩輪停滯需要一次實質策略切換,再兩輪停滯停止。
|
|
51
|
+
範圍改版會讓現有證據失效、而驗證在登記策略切換前仍停止,因此策略切換可引用同一原始任務已封存的歷史回歸證據;假設確認與最終交付仍需新版契約的新證據。
|
|
52
|
+
達到回合上限後,未變更的候選仍可審查;上次驗證通過時也可重新驗證(例如只更新 escape ledger),不計為新回合。驗證失敗或產品內容變更的候選仍維持停止。
|
|
51
53
|
|
|
52
54
|
假設確認後以 `origin --payload-file <json>` 宣告 bug 來源:`{"kind": "escape-of",
|
|
53
55
|
"commit": "<commit>", "label": "<最早關卡>"}`(可選 `"receipt"`),或 `{"kind":
|
|
@@ -61,8 +63,8 @@ verify 並重新記錄已確認的假設;沒有它時 finish 以 `ESCAPE_ORIGI
|
|
|
61
63
|
|
|
62
64
|
checkpoint 保存 task/session/source/contract/config/cause;resume 只接受同一
|
|
63
65
|
擁有者 worktree 的匹配 checkpoint,保留 dirty 內容及預算。失配需要人類確認。
|
|
64
|
-
|
|
65
|
-
|
|
66
|
+
時間耗盡後明確「繼續」或「繼續修復」可透過 `extend --event-id latest` 讀取原生人類事件,
|
|
67
|
+
從該事件取得完整 20 分鐘,並以 grant 快照保留既有消耗與超時;沒有可信事件來源則回報 unsupported,其他阻礙不被解除。
|
|
66
68
|
|
|
67
69
|
Claude / agy / 原生 Codex 的 Stop 都會檢查無 Git 差異的 active DEBUG task,不論是否安裝 completion gate。
|
|
68
70
|
DEBUG gate 故障先針對 task/config 重新載入一次;仍失敗則明確回報 `UNKNOWN/未完成`
|
|
@@ -74,3 +76,8 @@ Codex 的一般 completion gate 仍 unsupported;DEBUG 專用 Stop 以原生
|
|
|
74
76
|
只承諾前景對話與持久化進度,不承諾對話關閉後繼續,也不宣稱抵擋同檔案權限攻擊。
|
|
75
77
|
|
|
76
78
|
完整命令及細節見英文 [Task DEBUG](../../en/guides/task-debug.md)。
|
|
79
|
+
|
|
80
|
+
原生人類明確要求「取消時間限制」或 `no time limit` 時,可透過同一個
|
|
81
|
+
`extend --event-id latest` 原生授權入口取消原任務總時限。grant 記錄
|
|
82
|
+
`grantedMs: null` 與已用時間快照;原預算、消耗、舊 grant、回合限制及
|
|
83
|
+
驗證與 review 關卡均保留。個別命令與 review 的防卡死逾時仍有效。
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
- `behavioral`:指定 red checks 在 baseline 都因斷言失敗,candidate 的必要 checks 全部通過。`invariant`:candidate 的必要 checks 通過,baseline 原本 PASS 有效。`review-only`:兩輪 fresh review 對照原始目標驗收,不宣稱機械證明。
|
|
14
14
|
- repo 授權的 `verification.acceptanceRunners` 定義 argv、setup、產品與 test build。task 提供穩定 check IDs 與明列且已 commit 的 test/fixture/helper;兩側各自安裝並 build 其版本,再覆蓋 candidate 材料。task 不能改 runner 命令。
|
|
15
|
+
- generic runner 輸出的 check IDs 必須恰好等於共用相同 runner、baseline 與材料之 criteria 所要求的 `checkIds` 聯集;多出的列為 UNKNOWN。若 runner 固定輸出整套 checks,就綁定完整輸出集合,並將 `redCheckIds` 限於實際回歸斷言。
|
|
15
16
|
- fixture、collection、build、timeout、取消、輸出截斷、缺少或重複 ID、retry、skip MUST NOT 算 red 或 green。完整 execution artifacts 以摘要綁定契約、材料與 commits。
|
|
16
17
|
- 原生 Jest JSON 與 Vitest task errors 無法完整區分 test-call 斷言與 hook/fixture cleanup。除非已 review 的 repo collector 提供明確斷言分類,失敗列 MUST 保持 UNKNOWN;AssertionError 名稱、matcher 資料與 pass 的 hook 狀態都不足。green 需要明確的單次 attempt 證據:attempts、Jest invocations,或 Vitest retryCount 與 repeatCount 兩者。原生 Vitest JSON 省略這些 counters,不能單獨提供機械證明。pytest 每個 nodeid 必須有一組依序的 setup/call/teardown;Rust protocol 必須提供 attempts。缺 metadata MUST NOT 預設為一次成功。
|
|
17
18
|
- baseline 固定為建立 commit;finding pin 使用被 FAIL 的 candidate。revise/replan 保留原始目標、全部映射要求與先前契約/證據。新契約必須重新取證並取得兩輪 fresh review。
|
|
@@ -20,7 +21,7 @@
|
|
|
20
21
|
|
|
21
22
|
task state 寫入 version 2、讀取 version 1;evidence version 4 讀取舊證據,attestation version 3 綁定契約與 coverage。legacy criteria 維持下列 verifier 規則;typed/revised 契約不能沿用舊 review。coverage 分列機械證明、review-only、legacy;runner 綁定本身不算證明。明確 no-change proof 列出固定 candidate 上已 commit 的 regular source files;整合時重新解析支持路徑。一般 empty-diff review 與 empty-branch direct finish 仍拒絕。
|
|
22
23
|
|
|
23
|
-
English source version: 2026-10-
|
|
24
|
+
English source version: 2026-10-20. Revalidate: when the English specification changes.
|
|
24
25
|
|
|
25
26
|
## EVIDENCE-CRITERION-001
|
|
26
27
|
|
|
@@ -93,7 +93,7 @@ script,也不得改變一般 permission request。
|
|
|
93
93
|
| office-presence (Preview) | degraded | degraded | supported | degraded |
|
|
94
94
|
| lifecycle-summary | degraded | supported | supported | degraded |
|
|
95
95
|
| command-policy | supported | unknown | supported | supported |
|
|
96
|
-
| completion-gate | supported | unsupported |
|
|
96
|
+
| completion-gate | supported | unsupported | supported | unsupported |
|
|
97
97
|
| optional-stop-verify | degraded | unsupported | supported | degraded |
|
|
98
98
|
|
|
99
99
|
Runtime-failure 處理中,只有 `command-policy` 為 fail-closed。當已安裝的 config
|
|
@@ -164,6 +164,26 @@ byte-identical。Update MUST 保留此 profile,除非提供 `--auto-run on|off
|
|
|
164
164
|
- Positive: `啟用 run profile 的 Claude Code session 在 main checkout 寫入 .agent-ops/state/run-goal.md,並在 background 啟動 agent-ops run --host claude --wait。`
|
|
165
165
|
- Negative: `run profile 啟用時仍將八項 criteria 的變更拆成 subtask、在 GEMINI.md 加入 auto-run 文字,或未經使用者即 resume blocked run。`
|
|
166
166
|
|
|
167
|
+
`run status` 的 JSON 提供 `recovery`,文字輸出也呈現相同資訊:phase(舊紀錄為
|
|
168
|
+
unknown)、有界且遮蔽敏感資料的原始診斷、sealed proof(`recorded-unchecked`
|
|
169
|
+
僅表示紀錄存在,不代表驗證通過)、cleanup 狀態、能否 resume 與具體修復方式。
|
|
170
|
+
相同 cleanup blocker 保留原始 cause。Trust、native identity、generation 與 denial
|
|
171
|
+
仍 fail-closed;sealed proof 不得抹除 native 或組織的明確拒絕。
|
|
172
|
+
|
|
173
|
+
人工 resume 已寫入 receipt 的 integration,必須先驗證原 sealed proof、receipt
|
|
174
|
+
digest 與 transaction、owner/generation、candidate/target ancestry 及 Git note。
|
|
175
|
+
只執行尚缺的 note、gate、trust 與 checkout cleanup,保留原 receipt bytes 與已完成的
|
|
176
|
+
task/proof。尚未記錄完成且缺失的 note 可補寫;已記錄完成的 note 缺失或變更則阻擋。
|
|
177
|
+
確認所屬 checkout 已移除後才可標記 cleaned,不再次移動 target。Target 若已前進,
|
|
178
|
+
僅在它是 receipt-bound candidate 的後代且有原整合歷史時接受。
|
|
179
|
+
|
|
180
|
+
Cleanup 保存有界且遮蔽敏感資料的原始錯誤,以及 transaction boundary、checkout
|
|
181
|
+
status、untracked file metadata 與 heartbeat liveness 的 retry-input digest。
|
|
182
|
+
相同輸入回 `RUN_CLEANUP_UNCHANGED`,不重複 cleanup;修復條件改變後仍須重新通過所有
|
|
183
|
+
安全檢查,才可嘗試一次。此 digest 僅防止盲重試,不是 proof reuse 或新授權。
|
|
184
|
+
Finalization 失敗不立即重跑同一 integration,保留原 cause 供人工恢復。
|
|
185
|
+
Live/unknown/偽造/symlink heartbeat 與無關使用者檔案仍受既有 non-force removal 保護。
|
|
186
|
+
|
|
167
187
|
## HARNESS-ADAPTER-008
|
|
168
188
|
|
|
169
189
|
`agent-ops office`(Preview)MUST 只依據 agent-ops state 以 read-only 方式呈現進度。
|
|
@@ -11,17 +11,23 @@
|
|
|
11
11
|
- Negative: 只因 native goal completion 就把 run 標為 complete。
|
|
12
12
|
|
|
13
13
|
- macOS supervisor 在啟動 goal 前保存 Claude/Codex native 身份。同 host writers 使用獨立 worktrees 與目前 generation lease;coordinator 計入最多兩個 writer。依賴以固定交付提供,child baseline 在依賴 commits 進入 checkout 後建立。
|
|
14
|
-
-
|
|
14
|
+
- 新 run 不設總時間上限(`budget.limitMs: null`),仍以 active intervals 聯集記錄 setup、verify、review、整合的消耗。舊有限 run 僅在下次人工 resume 升級,保留舊上限、intervals、消耗、usage epochs 與 blocker 歷史;讀取或安裝不會升級或喚醒舊 run。單次命令/review timeout、provider quota、DEBUG 預算、無進度與 pause/stop 控制保持獨立有效。明確指定 --time-budget 會被拒絕。缺少計數維持 UNKNOWN,partial usage 不算完整成本。
|
|
15
15
|
- 實質疑問暫停並要求明確回答。修復保留目標與失敗紀錄;連續兩次相同失敗且無進展停止該 worker 及其依賴,獨立 task 仍可執行。review findings 在下次 final gate 前必須有回歸 pin 或記錄 review-only fallback。
|
|
16
16
|
- native completion 僅是觀察。完成必須有必要驗證、兩輪 fresh review、完成的 task state、綁定 target 的 receipt。整合在移動 target 前封存 candidate,記錄 task、receipt、note、cleanup 進度;恢復驗證封存證據,不重做成功的 target mutation。不一致的 target 維持 blocked。
|
|
17
17
|
- Stop 先保存禁止續跑,再確認受管 process group 死亡。crash recovery 不得取代仍存活或身份不明的 writer。dirty checkout、版本漂移、restart storm 保留現場。重開機/login 改變必須明確 resume;不修改全域 host policy。
|
|
18
18
|
- Native handshake 前先保存啟動程序身份;尚未開啟的 Codex session 必須證明程序死亡,才可在新 lease 重新初始化。
|
|
19
19
|
|
|
20
20
|
- Setup 與 native agent 發起的 local proof MUST 先登記 dormant process group 再執行;Stop 也要取消子程序。中斷的 setup 必須明確 resume,保留 checkout 與失敗 artifact。
|
|
21
|
-
- Coordinator 只能在綁定原始 repository trust、可執行 runtime
|
|
22
|
-
-
|
|
21
|
+
- Coordinator 只能在綁定原始 repository trust、可執行 runtime、一小時有限授權期限的 run-scoped policy 下,評估新增固定且非危險的能力。不得繞過 native 或組織的明確拒絕。Policy 變更先停止所有 writer,保留固定交付,以 journal 記錄 contract 同步,再啟動新 generation;不授予永久 repository trust。Native decision 保存遮罩後的命令範圍與理由,provider 未提供的證據維持 UNKNOWN。
|
|
22
|
+
- 執行中的 run 僅在原始 repository trust、config、可執行 runtime 與操作範圍完全相同時,自動續期過期的有限授權。續期以 digest 綁定前一版本並保留既有評估,拒絕無效/倒退時鐘;並行 stop、denial 或 scope 變更使續期失敗。權限已變更或撤銷時須沿既有 trust/resume 流程取得真正授權;續期不授予永久 trust,也不消除 denial。明確 resume 先核對不可變 renewal lineage,再同步 policy。回答問題也沿用相同恢復檢查;兩者皆不得復活已被明確拒絕的工作。
|
|
23
|
+
- `run resume <id> --retry-approval <auth-event-id>` MAY 明確重試以額度不足 wrapper 失敗的 Codex 自動審批。每筆指定失敗 MUST 保有原始 private artifact、digest、worker identity 與 policy lineage;真正或未指定的拒絕仍阻擋。多筆失敗可重複旗標。成功確認會留存並在後續 resume 重新驗證;此操作不授予命令權限,原生審批必須重跑,既有 writer 死亡、checkout、trust 與預算檢查仍適用。
|
|
24
|
+
- 當次人工 resume 的初始恢復 MAY 忽略啟用前收到、且 native 更新秒數嚴格早於該次 resume 的本執行緒 Codex 額度快照。自動重啟、未知/同秒/未來時間、時鐘倒退,以及啟用開始後收到的所有額度事件仍有效。Native 啟用 MUST 確認 goal 為 active,才能啟動或轉向 turn。人工 resume 可解除衍生的 `RUN_POLICY_DISABLED` task 阻擋,但保留 ID、耗用、歷史及其他產品失敗。
|
|
23
25
|
- 收斂判斷比較實際 per-check 狀態及未解除的 finding pins;source fingerprint、時間或診斷文字本身不能算進展。
|
|
24
26
|
|
|
27
|
+
`jobs=1` 時,child MUST 等 coordinator 程序確認死亡後才執行。coordinator 保留
|
|
28
|
+
task 與 native session identity,在 child 交付後以新 generation 接續。native
|
|
29
|
+
註冊、恢復與啟動 MUST 都限制最多一個 active agent;僅扣除 task slot 不足以符合。
|
|
30
|
+
|
|
25
31
|
English source version: 2026-07-23. Revalidate: when the English specification changes.
|
|
26
32
|
|
|
27
33
|
## LOOP-START-001
|