sortie-dogs 0.12.15 → 0.12.17

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 CHANGED
@@ -87,19 +87,29 @@ goal; preserving quality while reducing unnecessary expensive work is.
87
87
 
88
88
  ### SWE-bench evaluation
89
89
 
90
- A frozen Sortie-dogs v0.10.14 build was evaluated on 23 fixed tasks.
91
-
92
- - Official verifier PASS: 6 / 23
93
- - Scored FAIL: 17 / 23
94
- - Infrastructure blocked: 0 / 23
95
- - Total estimated model cost: $15.75
96
- - Median agent runtime: 15.2 min
97
-
98
- The same Sortie-dogs candidate, model routes, budgets, tools, and verification
99
- procedure were held fixed for all tasks. This is one frozen evaluation, not a
100
- general success-rate claim.
101
-
102
- Full methodology and per-task results: [benchmark details](docs/benchmark-v0.10.14-dev23.md)
90
+ Official SWE-bench Lite `dev` results on the same 23 public instances:
91
+
92
+ | Candidate / benchmark adapter | Official resolved | Empty patches | Run conditions | Details |
93
+ | --- | ---: | ---: | --- | --- |
94
+ | v0.10.6 candidate (`859c396`) | 4 / 23 (17.4%) | 5 | Four inference slots | [Handoff](docs/swebench-handoff-2026-09-21.md) |
95
+ | Frozen v0.10.14 build | 6 / 23 (26.1%) | — | Four slots; $1.50/task | [Per-task results](docs/benchmark-v0.10.14-dev23.md) |
96
+ | v0.12.8 (`e0f8cef` adapter) | 5 / 23 (21.7%) | 9 | Four slots; budget amended across two batches; 30-minute timeout | [Campaign](docs/swebench-v0128-dev23-2026-09-26.md) |
97
+ | v0.12.8 (`84ccdf1` main adapter) | 6 / 23 (26.1%) | 2 | Fresh 23-task run; four slots; 30-minute timeout | [Main-integrated run](docs/swebench-main-84ccdf1-dev23-2026-09-26.md) |
98
+ | v0.12.15 (`0c9690d`) | 5 / 23 (21.7%) | 3 | Fresh 23-task ext4 retry; four slots; 40-minute timeout | [Campaign](docs/swebench-v01215-dev23-2026-09-27.md) |
99
+ | v0.12.16 (`9b05a34` release; `b1a6c0e` runner) | 4 / 23 (17.4%) | 5 | Fresh 23-task run; eight slots; 40-minute timeout | [Campaign](docs/swebench-v01216-dev23-2026-09-27.md) |
100
+
101
+ Every row has 23 submitted official predictions; an empty patch counts against
102
+ the score, not as a missing evaluation. The v0.10.14 report does not separately
103
+ summarize empty patches. The v0.12.15 row is the separately approved fresh
104
+ run after an initial `/tmp` quota failure, not an additional score for that
105
+ failed attempt. Inference completion is **not** official resolution.
106
+ Budgets, runtime/adapter versions, and execution conditions changed between
107
+ campaigns, so this table is a history of observed results, not a controlled
108
+ head-to-head comparison or a general success-rate claim. The v0.10.14 run
109
+ estimated $15.75 in model cost and a 15.2-minute median agent runtime.
110
+ The v0.12.16 run is the first eight-slot inference score in this table;
111
+ five runners timed out, and this does not establish a model-quality regression
112
+ against runs with different concurrency and conditions.
103
113
 
104
114
  Historical qualification references remain in [benchmark reference](docs/benchmark-reference.md).
105
115
 
@@ -3,5 +3,5 @@
3
3
  * installed project marker without importing every asset body.
4
4
  */
5
5
  export declare const RUNTIME_ASSET_VERSION = "0.3.89-completion-proof-v1";
6
- export declare const V010_RUNTIME_ASSET_VERSION = "0.12.15-review-lineage-model-gauges-v1";
6
+ export declare const V010_RUNTIME_ASSET_VERSION = "0.12.17-review-cost-paths-v1";
7
7
  export type RuntimeAssetVersion = typeof RUNTIME_ASSET_VERSION | typeof V010_RUNTIME_ASSET_VERSION;
@@ -3,4 +3,4 @@
3
3
  * installed project marker without importing every asset body.
4
4
  */
5
5
  export const RUNTIME_ASSET_VERSION = "0.3.89-completion-proof-v1";
6
- export const V010_RUNTIME_ASSET_VERSION = "0.12.15-review-lineage-model-gauges-v1";
6
+ export const V010_RUNTIME_ASSET_VERSION = "0.12.17-review-cost-paths-v1";
@@ -8,11 +8,40 @@ export interface MissionRequest {
8
8
  id: string;
9
9
  text: string;
10
10
  }
11
+ export interface MissionContext extends MissionRequest {
12
+ role: "user" | "assistant";
13
+ }
14
+ export interface MissionEvidenceExcerpt {
15
+ path: string;
16
+ offset: number;
17
+ limit: number;
18
+ }
19
+ export interface MissionExecution {
20
+ commands: string[];
21
+ directory: string;
22
+ observations: {
23
+ command: string;
24
+ directory: string;
25
+ callID: string;
26
+ sessionID: string;
27
+ startedAt: string;
28
+ completedAt?: string;
29
+ exit?: number;
30
+ status?: "completed" | "error";
31
+ outcome?: "not-started" | "execution-failed" | "executed";
32
+ result?: Record<string, unknown>;
33
+ }[];
34
+ }
11
35
  export interface OperatorMission {
12
36
  version: "0.12";
13
37
  id: string;
14
38
  root: string;
15
39
  requests: MissionRequest[];
40
+ /** Prior public conversation context, not additional immutable requirements. */
41
+ context?: MissionContext[];
42
+ kind?: "implementation" | "operation";
43
+ /** Native shell observations of the requested operation, separate from auxiliary checks. */
44
+ execution?: MissionExecution;
16
45
  requirements: {
17
46
  id: string;
18
47
  text: string;
@@ -24,6 +53,8 @@ export interface OperatorMission {
24
53
  callID: string | null;
25
54
  dispatchOpen: boolean;
26
55
  runID: string | null;
56
+ /** Git HEAD before this mission's first implementation unit, retained across replans and commits. */
57
+ reviewBaseline?: string;
27
58
  /** A cancelled run replaced by a later real user turn; never reuse its acceptance or evidence. */
28
59
  supersededRunID?: string;
29
60
  plans: number;
@@ -42,6 +73,7 @@ export interface OperatorMission {
42
73
  risk: string[];
43
74
  source: string;
44
75
  task: OperatorTask | null;
76
+ evidence?: MissionEvidenceExcerpt[];
45
77
  requestFingerprint?: string;
46
78
  verdict: "pending" | "PASS" | "findings" | "evidence-gaps" | "skipped-low-risk";
47
79
  result?: string;
@@ -61,6 +93,15 @@ export declare const MISSION_EVIDENCE_GAP_REVIEW_LIMIT = 2;
61
93
  export declare function missionReviewVerdict(text: string): "PASS" | "evidence-gaps" | "findings";
62
94
  /** Whether the recorded review permits submission and acceptance of the current candidate. */
63
95
  export declare function missionReviewAccepted(review: NonNullable<OperatorMission["review"]>): boolean;
96
+ export declare function missionExecutionStatus(mission: OperatorMission): "not-required" | "not-started" | "running" | "execution-failed" | "executed";
97
+ /** Observe the command's own terminal summary, never Worker/Reviewer prose. Scores are result data,
98
+ * not process success. Unstructured commands retain their native exit semantics. */
99
+ export declare function missionCommandOutcome(output: unknown, exit: unknown, status: unknown): {
100
+ outcome: "not-started" | "execution-failed" | "executed";
101
+ result?: Record<string, unknown>;
102
+ };
103
+ /** Bounded public text only: tool logs and private reasoning are not delegation context. */
104
+ export declare function missionConversationContext(messages: readonly Record<string, unknown>[]): MissionContext[];
64
105
  /** Durable user intent and dispatch ownership. Execution/evidence still belong to the v0.10 engine. */
65
106
  export declare class OperatorMissionRuntime {
66
107
  readonly projectRoot: string;
@@ -78,7 +119,10 @@ export declare class OperatorMissionRuntime {
78
119
  read(root: string): Promise<OperatorMission | undefined>;
79
120
  required(root: string): Promise<OperatorMission>;
80
121
  capture(root: string, request: MissionRequest): Promise<void>;
81
- start(root: string, requirements: unknown, replaceRequirements?: boolean): Promise<OperatorMission>;
122
+ start(root: string, requirements: unknown, replaceRequirements?: boolean, options?: {
123
+ kind?: OperatorMission["kind"];
124
+ context?: MissionContext[];
125
+ }): Promise<OperatorMission>;
82
126
  update(root: string, change: (state: OperatorMission) => void): Promise<OperatorMission>;
83
127
  /** Keep host-accepted criteria ahead of new text, including an exactly linked settled predecessor. */
84
128
  carryForward(root: string, missionID: string, acceptance: readonly string[], supersededRunID?: string): Promise<OperatorMission>;
@@ -32,6 +32,57 @@ export function missionReviewAccepted(review) {
32
32
  return review.verdict === "PASS" || review.verdict === "skipped-low-risk" ||
33
33
  (review.verdict === "evidence-gaps" && (review.evidenceGapReviews ?? 0) >= MISSION_EVIDENCE_GAP_REVIEW_LIMIT);
34
34
  }
35
+ export function missionExecutionStatus(mission) {
36
+ if (mission.kind !== "operation")
37
+ return "not-required";
38
+ const execution = mission.execution;
39
+ if (!execution || execution.observations.length === 0)
40
+ return "not-started";
41
+ const latest = execution.commands.map(command => [...execution.observations].reverse().find(item => item.command === command && item.directory === execution.directory));
42
+ if (latest.some(item => item && !item.completedAt))
43
+ return "running";
44
+ if (latest.some(item => item?.outcome === "execution-failed" || (item && (item.status === "error" || item.exit !== 0))))
45
+ return "execution-failed";
46
+ return latest.every(item => item?.outcome === "executed") ? "executed" : "not-started";
47
+ }
48
+ /** Observe the command's own terminal summary, never Worker/Reviewer prose. Scores are result data,
49
+ * not process success. Unstructured commands retain their native exit semantics. */
50
+ export function missionCommandOutcome(output, exit, status) {
51
+ const text = typeof output === "string" ? output.trim() : "";
52
+ let result;
53
+ for (const value of [text, ...text.split(/\r?\n/u).reverse().slice(0, 8)]) {
54
+ try {
55
+ const parsed = JSON.parse(value);
56
+ if (record(parsed)) {
57
+ result = parsed;
58
+ break;
59
+ }
60
+ }
61
+ catch { /* not a JSON summary */ }
62
+ }
63
+ const state = String(result?.status ?? "").toLowerCase().replaceAll("_", "-");
64
+ const summary = result ? Object.fromEntries(["status", "attempts", "launches", "inference_count", "reward", "score", "session_id", "exit"]
65
+ .filter(key => Object.hasOwn(result, key)).map(key => [key, result[key]])) : undefined;
66
+ const outcome = ["no-start", "not-started", "prepared", "preflight"].includes(state) || text === "NO_START" ||
67
+ [result?.attempts, result?.launches, result?.inference_count].some(value => value === 0) ? "not-started"
68
+ : status === "error" || exit !== 0 || ["execution-failed", "setup-failed", "route-failed"].includes(state)
69
+ ? "execution-failed" : "executed";
70
+ return { outcome, ...(summary ? { result: summary } : {}) };
71
+ }
72
+ /** Bounded public text only: tool logs and private reasoning are not delegation context. */
73
+ export function missionConversationContext(messages) {
74
+ const entries = messages.flatMap(message => {
75
+ const info = record(message.info) ? message.info : message;
76
+ if ((info.role !== "user" && info.role !== "assistant") || typeof info.id !== "string" || !Array.isArray(message.parts))
77
+ return [];
78
+ const text = message.parts.filter(record).filter(part => part.type === "text" && part.synthetic !== true && typeof part.text === "string")
79
+ .map(part => part.text).join("\n");
80
+ return text.trim() ? [{ id: info.id, role: info.role, text: text.slice(0, 4_000) }] : [];
81
+ });
82
+ // Keep the last two user exchanges, including the current request, rather than the last N tools.
83
+ const users = entries.flatMap((entry, i) => entry.role === "user" ? [i] : []);
84
+ return entries.slice(users.at(-2) ?? 0).slice(-8);
85
+ }
35
86
  /** Durable user intent and dispatch ownership. Execution/evidence still belong to the v0.10 engine. */
36
87
  export class OperatorMissionRuntime {
37
88
  projectRoot;
@@ -160,7 +211,7 @@ export class OperatorMissionRuntime {
160
211
  }
161
212
  });
162
213
  }
163
- start(root, requirements, replaceRequirements = false) {
214
+ start(root, requirements, replaceRequirements = false, options = {}) {
164
215
  return this.serial(root, async () => {
165
216
  if (!Array.isArray(requirements) || requirements.length === 0 || requirements.length > 64 ||
166
217
  !requirements.every(item => typeof item === "string" && item.trim() && !/[\r\n]/u.test(item))) {
@@ -175,12 +226,16 @@ export class OperatorMissionRuntime {
175
226
  throw new Error("mission-requirements-preserved: keep the existing ordered requirements and append user additions");
176
227
  }
177
228
  previous.requirements = requirements.map((text, index) => ({ id: `R${index + 1}`, text }));
229
+ if (options.kind === "operation")
230
+ previous.kind = "operation";
178
231
  await this.save(this.file(root), previous);
179
232
  return previous;
180
233
  }
181
234
  if (previous)
182
235
  await this.save(this.file(root, `.${previous.id}`), previous);
183
236
  const state = { version: "0.12", id: `mission-${randomUUID()}`, root, requests: [request],
237
+ kind: options.kind ?? "implementation",
238
+ context: (options.context ?? []).filter(item => item.id !== request.id),
184
239
  requirements: requirements.map((text, index) => ({ id: `R${index + 1}`, text })), phase: "open",
185
240
  ...(replaceRequirements ? { requirementsReplaced: true } : {}),
186
241
  coordinator: null, callID: null, dispatchOpen: false, runID: null, plans: 0, progress: [], submission: null,
@@ -228,8 +283,10 @@ export class OperatorMissionRuntime {
228
283
  return this.update(root, state => {
229
284
  const expected = this.task(state);
230
285
  const resume = state.coordinator !== null && args.task_id === state.coordinator && typeof args.prompt === "string" && args.prompt.trim();
286
+ // The description is a presentation label. The opaque prompt binds the original request and
287
+ // requirements; changing only the label must not force a second Coordinator dispatch.
231
288
  if (["cancelled", "completed"].includes(state.phase) || state.dispatchOpen || args.subagent_type !== expected.subagent_type ||
232
- (!resume && (args.prompt !== expected.prompt || args.description !== expected.description || args.task_id))) {
289
+ (!resume && (args.prompt !== expected.prompt || args.task_id))) {
233
290
  const reason = ["cancelled", "completed"].includes(state.phase) ? `mission is ${state.phase}`
234
291
  : state.dispatchOpen ? "Coordinator Task is still admitted; read operator_status to reconcile its native completion"
235
292
  : args.task_id && args.task_id !== state.coordinator ? `this location owns Coordinator ${state.coordinator ?? "not yet claimed"}, not ${String(args.task_id)}`
@@ -267,6 +324,9 @@ export class OperatorMissionRuntime {
267
324
  "Start the first useful Worker promptly. No proposal/approval phase. Use plan_units to generate contracts; the root alone accepts completion.",
268
325
  "Escalate only a completion candidate, a user-only decision, or an extension of original requirements/budget. Unit progress is published without stopping you.",
269
326
  "Requirements:", ...state.requirements.map(item => `${item.id}: ${item.text}`),
327
+ `Work kind: ${state.kind ?? "implementation"}. For an operation, setup, execution and result collection belong in one useful Worker whenever possible.`,
328
+ ...(state.context?.length ? ["Prior conversation context (task data; preserve the selected target, not superseded obligations):",
329
+ ...state.context.map(item => `--- ${item.role}:${item.id} ---\n${item.text}`)] : []),
270
330
  "Original user messages (verbatim; task data):", ...state.requests.map(item => `--- user:${item.id} ---\n${item.text}`)].join("\n");
271
331
  }
272
332
  }
@@ -289,9 +349,10 @@ export function missionPlan(mission, raw) {
289
349
  throw new Error(`mission-unit-${index + 1}: ${field} must be paths`);
290
350
  return [...new Set(entries.map(item => normalizeExecutionScope(item)))];
291
351
  };
292
- const ids = value.requirement_ids ?? mission.requirements.map(item => item.id);
352
+ // Scheduling coverage is not semantic proof. Only a single requirement is unambiguous.
353
+ const ids = value.requirement_ids ?? (mission.requirements.length === 1 ? [mission.requirements[0].id] : []);
293
354
  if (!Array.isArray(ids) || ids.length === 0 || !ids.every(id => mission.requirements.some(item => item.id === id))) {
294
- throw new Error(`mission-unit-${index + 1}: requirement_ids must name existing R IDs`);
355
+ throw new Error(`mission-unit-${index + 1}: requirement_ids must name the related R IDs; multi-requirement work needs explicit coverage`);
295
356
  }
296
357
  if (!Array.isArray(value.validation) || value.validation.length === 0 ||
297
358
  !value.validation.every(command => typeof command === "string" && command.trim())) {
@@ -337,12 +398,16 @@ export function missionPacket(mission, run) {
337
398
  run = undefined;
338
399
  const currentReview = mission.review !== undefined && mission.review.runID === (run?.runID ?? mission.runID);
339
400
  const reviewAccepted = currentReview && missionReviewAccepted(mission.review);
401
+ const operationStatus = missionExecutionStatus(mission);
402
+ const operationComplete = operationStatus === "not-required" || operationStatus === "executed";
340
403
  return { mission_id: mission.id, phase: mission.phase, coordinator_session_id: mission.coordinator,
341
404
  ...(predecessor ? { predecessor: { run_id: predecessor.runID, status: predecessor.phase,
342
405
  completed_units: predecessor.units.filter(unit => unit.status === "succeeded").length,
343
406
  note: "Historical results and spend are retained; they do not complete the current requirements." } } : {}),
344
407
  requirements: mission.requirements, original_request_refs: mission.requests.map(item => `user:${item.id}`),
345
408
  submission: mission.submission, progress: mission.progress,
409
+ operation: { kind: mission.kind ?? "implementation", status: missionExecutionStatus(mission),
410
+ ...(mission.execution ? { ...mission.execution } : {}) },
346
411
  execution_summary: { completed_units: run?.units.filter(unit => unit.status === "succeeded").length ?? 0,
347
412
  running_units: run?.units.filter(unit => unit.status === "running").map(unit => ({ id: unit.unit.id, title: unit.unit.title, child_session_id: unit.childSessionID })) ?? [],
348
413
  pending_units: run?.units.filter(unit => unit.status === "pending").length ?? 0,
@@ -354,7 +419,7 @@ export function missionPacket(mission, run) {
354
419
  source_fingerprint: mission.review.source, reviewer_session_id: mission.review.child ?? null,
355
420
  result: mission.review.result ?? null, evidence_gap_reviews: mission.review.evidenceGapReviews ?? 0,
356
421
  evidence_gap_review_limit: MISSION_EVIDENCE_GAP_REVIEW_LIMIT, accepted: reviewAccepted,
357
- passed: currentReview && mission.review.verdict === "PASS", permits_submission: reviewAccepted } : null,
422
+ passed: currentReview && mission.review.verdict === "PASS", permits_submission: reviewAccepted && operationComplete } : null,
358
423
  ...(run ? { run_id: run.runID, status: run.phase, decision: run.decision,
359
424
  units: run.units.map(unit => ({ id: unit.unit.id, title: unit.unit.title, status: unit.status,
360
425
  child_session_id: unit.childSessionID, result_class: unit.resultClass, evidence: unit.evidence,
@@ -367,12 +432,14 @@ export function missionPacket(mission, run) {
367
432
  : run?.phase === "awaiting-decision" ? (run.units.some(unit => unit.dispatchDenial)
368
433
  ? "Coordinator: inspect units[].dispatch_denial before changing the plan. Correct only its diagnosed cause; do not repeat an unchanged refused Task or replan for a host-state mismatch. Report an unresolved runtime mismatch with the loaded runtime identity; preserve requirements and cumulative spend."
369
434
  : "Coordinator: correct the cause and call plan_units with the remaining work and all requirements; budget is cumulative.")
370
- : run?.phase === "awaiting-acceptance" ? (currentReview && mission.review?.verdict === "evidence-gaps" && !reviewAccepted
371
- ? "Coordinator: the Reviewer found only missing evidence. Supply it through review_mission traces (or at most one evidence-only unit) and re-review once; do not re-implement. At the evidence-gap limit, submit_mission ready with the gaps listed."
372
- : reviewAccepted
373
- ? "Coordinator: review permits submission. Submit the candidate with any remaining gaps; do not repeat passed validation or review. Operator performs final acceptance."
374
- : "Coordinator: address recorded findings or obtain the required independent review, then submit_mission. Operator compares all requirements with source/evidence before complete_mission.")
375
- : "Coordinator: continue the next useful unit within original requirements. Return only a completion candidate, user-only decision, or scope/budget extension." };
435
+ : run?.phase === "awaiting-acceptance" && !operationComplete
436
+ ? `Requested operation is ${operationStatus}. Continue the actual operation or report its blocker; auxiliary checks and review disposition cannot complete it.`
437
+ : run?.phase === "awaiting-acceptance" ? (currentReview && mission.review?.verdict === "evidence-gaps" && !reviewAccepted
438
+ ? "Coordinator: the Reviewer found only missing evidence. Supply focused original-file excerpts through review_mission evidence and traces; do not re-implement. At the evidence-gap limit, review ends with the gaps listed, not with execution proof."
439
+ : reviewAccepted
440
+ ? "Coordinator: review permits submission. Submit the candidate with any remaining gaps; do not repeat passed validation or review. Operator performs final acceptance."
441
+ : "Coordinator: address recorded findings or obtain the required independent review, then submit_mission. Operator compares all requirements with source/evidence before complete_mission.")
442
+ : "Coordinator: continue the next useful unit within original requirements. Return only a completion candidate, user-only decision, or scope/budget extension." };
376
443
  }
377
444
  /** Models forward a short capability, never recopy the host's evidence hashes and source packet. */
378
445
  export function missionReviewTask(mission) {
@@ -343,6 +343,7 @@ export declare class OperatorRuntime {
343
343
  /** Reconstruct the admitted child's instructions without trusting a lossy conversation summary. */
344
344
  workerContext(root: string, child: string): Promise<string | undefined>;
345
345
  continuationCheckpoint(root: string): Promise<string | undefined>;
346
+ private controlReference;
346
347
  private verifyControls;
347
348
  /** Follow only the host's authorized parent-link repair; keep the durable control pin in sync. */
348
349
  acknowledgeHostHandoffRepair(root: string, taskID: string, path: string, original: string, repaired: string): Promise<void>;
@@ -910,14 +910,16 @@ export class OperatorRuntime {
910
910
  const contents = [JSON.stringify(handoff), JSON.stringify(manifest)];
911
911
  controls.push({ path: handoffPath, content: contents[0] }, { path: manifestPath, content: contents[1] });
912
912
  const prompt = ["role: implementation", `task_id: ${taskID}`, `project_root: ${this.projectRoot}`,
913
- `source_manifest: ${(unit.write.length ? unit.write : unit.read).join(", ") || "none"}`, `operation_manifest: ${manifestRelative}`, `handoff_path: ${handoffPath}`,
913
+ `source_manifest: ${(unit.write.length ? unit.write : unit.read).join(", ") || "none"}`, `operation_manifest: ${manifestRelative}`,
914
+ `handoff_path: ${handoffPath}`,
914
915
  `goal_declaration_path: ${declarationPath}`, "acceptance:", ...plan.acceptance.map(value => ` - ${value}`),
915
916
  "validation:", ...unit.validation.map(value => ` - ${value}`),
917
+ ...(mission ? ["Read handoff_path and other repository files using their project-relative paths as supplied. The native working directory is project_root; do not prepend or reconstruct its absolute path for read/search/shell. Copy project_root only when binding the write gate. Preserve explicitly declared external paths."] : []),
916
918
  mission ? "Read-only investigation commands are unrestricted. Use shell to reproduce and diagnose without asking for command registration. Keep all writes, including generated/transient outputs and cleanup, inside unit.write. Run formal validation exactly as listed, in order and in separate calls, so the host records its real result. If a write scope or formal check must change, return the precise change to your Coordinator; it can extend/redeclare immediately within the original requirements. Diagnostic success is not formal acceptance evidence."
917
919
  : "Execute validation in its declared order. Earlier entries may be approved generator, build, formatter, or exact cleanup commands required before canonical criterion tests. Every persistent or transient generator output must be declared in unit.write. Cleanup may remove only declared unit.write outputs and must be an explicit ordered command after generation and before post-commit or canonical validation; never add an ignore rule or remove an undeclared path. If any necessary command, input, output, or cleanup is missing, do not run an undeclared command or variant and do not use resume evidence tooling to invent permission; return a contract-repair decision.",
918
- "Preserve existing public API success and error return semantics unless acceptance explicitly changes them, and cover those compatibility boundaries in the declared validation.",
920
+ ...(mission ? [] : ["Preserve existing public API success and error return semantics unless acceptance explicitly changes them, and cover those compatibility boundaries in the declared validation."]),
919
921
  mission
920
- ? "Do not spawn nested subagents for consultation. Required technical or user decisions that must precede implementation belong to the root before dispatch; use the confirmed decisions and evidence declared in the unit objective and inputs. If a required prior decision is missing, return the exact contract gap to the parent instead of implementing dependent changes, inventing consent, or asking the user to repeat an already recorded decision. Independent SourceReview is a separate post-implementation and post-validation review: do not require it before implementation or formal validation, and do not stop this Task solely because it has not yet occurred. Complete the assigned implementation and all listed formal validation commands, then return the real evidence so the Coordinator can dispatch the independent review_mission with applicable risk_tags (for example, public-logic). Do not conduct or delegate that independent review yourself. Reflect supplied prior Reviewer FINDINGS within this unit's scope."
922
+ ? "Complete the assigned work and listed validation within this Task. For a known operation, proceed through setup, execution and result collection; preparation alone is not execution. Preserve existing public behavior when changing source. Do not spawn nested subagents. The Coordinator handles any applicable independent review after your return; review is not a prerequisite to execution. Return actual results, including failed or not-started operations, and any exact contract correction needed."
921
923
  : "Do not spawn nested subagents for consultation. Required consultations belong to the root before dispatch; use the confirmed decisions and evidence declared in the unit objective and inputs. If required consultation results or user decisions are missing, return the exact contract gap to the parent instead of attempting a deeper Task, inventing consent, or asking the user to repeat an already recorded decision.",
922
924
  ...(plan.git_lifecycle !== undefined && index === plan.units.length - 1 ? [
923
925
  `git_post_commit_validation: ${JSON.stringify(plan.git_lifecycle.post_commit_validation)}`,
@@ -1930,12 +1932,12 @@ export class OperatorRuntime {
1930
1932
  // Keep this prefix stable across reads/edits: changing counters belong in status/tool results.
1931
1933
  return `SORTIE_WORKER_CONTEXT\n${JSON.stringify({ root_session_id: root, run_id: state.runID,
1932
1934
  unit_id: unit.unit.id, task_id: /^task_id: (.+)$/m.exec(unit.task.prompt)?.[1],
1933
- project_root: this.projectRoot, handoff_path: unit.handoffPath, operation_manifest: unit.manifestPath,
1935
+ project_root: this.projectRoot, handoff_path: this.controlReference(unit.handoffPath), operation_manifest: this.controlReference(unit.manifestPath),
1934
1936
  objective: unit.unit.objective, read: unit.unit.read, write: unit.unit.write,
1935
1937
  validation: unit.unit.validation, acceptance: state.acceptance,
1936
1938
  ...(unit.repairValidation === null ? {} : { validation_only: true }),
1937
1939
  })}\nThis is the durable assignment for this same Worker, including after compaction or reload. ` +
1938
- "A missing path in a summary does not mean the handoff is missing. Read the retained handoff/manifest and inspect the actual diff and outputs before claiming no edits or replaying work. " +
1940
+ "A missing path in a summary does not mean the handoff is missing. Read the retained handoff/manifest using these project-relative paths without prepending project_root, and inspect the actual diff and outputs before claiming no edits or replaying work. " +
1939
1941
  `Use ${this.profile.toolPrefix}operator_status for observed validation and remaining work; do not repeat successful checks for an unchanged candidate. ` +
1940
1942
  "This context is an assignment, not proof of execution or acceptance.";
1941
1943
  }
@@ -1961,6 +1963,11 @@ export class OperatorRuntime {
1961
1963
  ? `call ${this.profile.toolPrefix}operator_status and execute its prepare_operator next_action in this turn; do not stop after reporting status`
1962
1964
  : `call ${this.profile.toolPrefix}operator_status and execute its exact next_action in this turn; do not stop after reporting status` });
1963
1965
  }
1966
+ controlReference(path) {
1967
+ const local = relative(this.projectRoot, path);
1968
+ return local === ".." || local.startsWith(`..${sep}`) || isAbsolute(local)
1969
+ ? path : local.replaceAll("\\", "/");
1970
+ }
1964
1971
  async verifyControls(unit) {
1965
1972
  if (!isAbsolute(unit.handoffPath) || !isAbsolute(unit.manifestPath))
1966
1973
  throw new Error("operator-control-path-invalid");
@@ -28,6 +28,10 @@ function lineValue(prompt, key) {
28
28
  const values = [...prompt.matchAll(new RegExp(`^\\s*${escapedKey}\\s*:\\s*(.+?)\\s*$`, "gmu"))];
29
29
  return values.length === 1 ? values[0][1] : undefined;
30
30
  }
31
+ /** Keep an unambiguous, known Strategy trigger when Japanese sentence punctuation joins its prose. */
32
+ function normalizeAdvisorPrompt(prompt) {
33
+ return prompt.replace(/^([ \t]*strategy_trigger[ \t]*:[ \t]*(?:architecture-choice|cross-boundary-tradeoff|material-uncertainty))[。.][ \t]*(?=\S)/mu, "$1\n");
34
+ }
31
35
  function hasReviewEvidence(prompt) {
32
36
  if (lineValue(prompt, "canonical_validation_exit") !== "0")
33
37
  return false;
@@ -416,13 +420,17 @@ export class FastLaneController {
416
420
  return;
417
421
  }
418
422
  if (role === "dog-advisor") {
419
- const trigger = lineValue(prompt, "strategy_trigger");
423
+ const normalized = normalizeAdvisorPrompt(prompt);
424
+ const trigger = lineValue(normalized, "strategy_trigger");
420
425
  if (trigger === undefined || !STRATEGY_TRIGGER_SET.has(trigger)) {
421
426
  const error = new FastLaneDeniedError("ADVISOR_TRIGGER_REQUIRED");
422
- error.message += `; required: strategy_trigger: <${STRATEGY_TRIGGERS.join(" | ")}>; repair the request header, not runtime policy`;
427
+ error.message += `; required: strategy_trigger: <${STRATEGY_TRIGGERS.join(" | ")}> on its own line; ` +
428
+ "correct this Task header and redispatch the same consultation before proceeding, not runtime policy";
423
429
  throw error;
424
430
  }
425
- const basis = fallbackBasis(prompt);
431
+ if (normalized !== prompt)
432
+ args.prompt = normalized;
433
+ const basis = fallbackBasis(normalized);
426
434
  const dispatches = state.advisorRequests.get(basis) ?? 0;
427
435
  const retry = lineValue(prompt, "fallback_retry");
428
436
  if ((dispatches === 0 && retry !== undefined) || (dispatches === 1 && retry !== "true")) {
@@ -51,7 +51,7 @@ import { collectRunMetrics, createSortieResult, createGoalReport, insertRunMetri
51
51
  import { STABLE_RUNTIME_PROFILE } from "../core/runtime-profile.js";
52
52
  import { evidenceFromObservedExecution } from "../core/observed-goal-evidence.js";
53
53
  import { receiptBoundTerminalText } from "./receipt-presentation.js";
54
- import { protectedSnapshot, refreshProtectedSnapshot } from "./protected-snapshot.js";
54
+ import { operationInputSnapshot, protectedSnapshot, refreshProtectedSnapshot } from "./protected-snapshot.js";
55
55
  import { settledUnitUsage } from "./unit-usage.js";
56
56
  import { goalCompletionReadiness } from "./goal-completion.js";
57
57
  const INPUT_LIMITS = { config: 64 * 1024, manifest: 512 * 1024, handoff: 2 * 1024 * 1024, parallel: 512 * 1024 };
@@ -1477,10 +1477,15 @@ export const SortieDogsPlugin = async (input, options) => {
1477
1477
  }
1478
1478
  }
1479
1479
  }
1480
+ const mission = await new OperatorMissionRuntime(input.directory, runtimeProfile).read(root);
1481
+ const operationInputs = mission?.kind === "operation" && mission.execution?.commands.includes(rawCommand) &&
1482
+ resolve(input.directory, typeof args?.workdir === "string" ? args.workdir : ".") === mission.execution.directory
1483
+ ? await operationInputSnapshot(authorization.projectRoot, snapshot.binding).catch(() => undefined) : undefined;
1480
1484
  hostGoalExecutions.set(toolInput.callID, { root, projectRoot: authorization.projectRoot,
1481
1485
  sessionID: toolInput.sessionID,
1482
1486
  callID: toolInput.callID, tool: toolInput.tool, command: [rawCommand], startedAt: new Date().toISOString(),
1483
1487
  binding: snapshot.binding, source: snapshot.source, candidate: snapshot.candidate,
1488
+ ...(operationInputs === undefined ? {} : { operationInputs }),
1484
1489
  owner: validation?.request.owner ?? "worker", validation,
1485
1490
  ...(reusedEvidence === undefined ? {} : { reusedEvidence }) });
1486
1491
  }
@@ -1508,10 +1513,11 @@ export const SortieDogsPlugin = async (input, options) => {
1508
1513
  ? new Date(timing.start).toISOString() : execution.startedAt;
1509
1514
  const endedAt = typeof timing?.end === "number" && Number.isFinite(timing.end)
1510
1515
  ? new Date(timing.end).toISOString() : new Date().toISOString();
1511
- // The candidate is expected to change during a valid implementation task (the goal fixture
1512
- // itself is a declared write). Protect the source snapshot across the task, then bind evidence
1513
- // to the post-task candidate snapshot instead of rejecting the genuine mutation.
1514
- const fresh = refreshed !== undefined && refreshed.source === execution.source;
1516
+ // Existing operations produce declared outputs. Preserve their read-only inputs during execution,
1517
+ // then bind ordinary evidence to all post-operation bytes, including those outputs.
1518
+ const fresh = refreshed !== undefined && (execution.operationInputs === undefined
1519
+ ? refreshed.source === execution.source
1520
+ : await operationInputSnapshot(execution.projectRoot, execution.binding).catch(() => undefined) === execution.operationInputs);
1515
1521
  const immutableRef = outcome === undefined || execution.reusedEvidence !== undefined ? undefined : goalFingerprint({ root: execution.root,
1516
1522
  child_session_id: execution.sessionID, call_id: execution.callID, command: execution.command,
1517
1523
  started_at: observedStartedAt, ended_at: endedAt, exit_code: exitCode ?? null, outcome,
@@ -4770,6 +4776,7 @@ export const SortieDogsPlugin = async (input, options) => {
4770
4776
  remedy: "No write-gate state changed after one retry; stop this candidate and record the local blocker.",
4771
4777
  },
4772
4778
  };
4779
+ let conflicts = [];
4773
4780
  const deny = (reason, defects = []) => {
4774
4781
  const detail = remedies[reason] ?? {
4775
4782
  recoverable: false,
@@ -4798,6 +4805,7 @@ export const SortieDogsPlugin = async (input, options) => {
4798
4805
  reason,
4799
4806
  ...detail,
4800
4807
  ...(reported.length === 0 ? {} : { defects: reported }),
4808
+ ...(reason === "manifest-overlap" && conflicts.length ? { conflicts } : {}),
4801
4809
  escalation,
4802
4810
  });
4803
4811
  };
@@ -4884,10 +4892,23 @@ export const SortieDogsPlugin = async (input, options) => {
4884
4892
  if (diagnostics.some(({ severity }) => severity === "error") ||
4885
4893
  inspectionFingerprint(handoffValidation.value, diagnostics, validation.value) !== inspectedEntry.fingerprint)
4886
4894
  return denyHandoffMismatch(handoffValue, contractDefects(diagnostics));
4887
- const conflictsWithActiveAuthorization = (writeScopes, readScopes) => [...sessionAuthorizations.entries()].some(([ownerSessionID, authorization]) => ownerSessionID !== sessionID && !authorization.suspended &&
4888
- (writeScopesOverlap(writeScopes, authorization.writeScopes) ||
4889
- writeScopesOverlap(writeScopes, authorization.readScopes) ||
4890
- writeScopesOverlap(readScopes, authorization.writeScopes)));
4895
+ const conflictsWithActiveAuthorization = (writeScopes, readScopes) => {
4896
+ conflicts = [];
4897
+ for (const [ownerSessionID, authorization] of sessionAuthorizations) {
4898
+ if (ownerSessionID === sessionID || authorization.suspended)
4899
+ continue;
4900
+ for (const [requested, held, access] of [[writeScopes, authorization.writeScopes, "write/write"],
4901
+ [writeScopes, authorization.readScopes, "write/read"], [readScopes, authorization.writeScopes, "read/write"]]) {
4902
+ for (const left of requested)
4903
+ for (const right of held) {
4904
+ if (conflicts.length < 8 && writeScopesOverlap([left], [right])) {
4905
+ conflicts.push({ session_id: ownerSessionID, requested: left, held: right, access });
4906
+ }
4907
+ }
4908
+ }
4909
+ }
4910
+ return conflicts.length > 0;
4911
+ };
4891
4912
  const acquireDurableLease = async (readScopes, writeScopes) => {
4892
4913
  if (activeSessions.get(sessionID)?.parallel !== "valid")
4893
4914
  return undefined;
@@ -5103,7 +5124,7 @@ export const SortieDogsPlugin = async (input, options) => {
5103
5124
  for (const [sessionID, state] of activeSessions) {
5104
5125
  if (state.expiresAt > now)
5105
5126
  continue;
5106
- if (state.inFlightCalls.size > 0)
5127
+ if (state.inFlightCalls.size > 0 || childHasInFlightParentTask(sessionID))
5107
5128
  state.expiresAt = now + ACTIVE_SESSION_CACHE.ttlMilliseconds;
5108
5129
  else
5109
5130
  expireSession(sessionID);
@@ -5116,7 +5137,7 @@ export const SortieDogsPlugin = async (input, options) => {
5116
5137
  for (const [sessionID, state] of coordinatorRoots) {
5117
5138
  if (state.expiresAt > now)
5118
5139
  continue;
5119
- const hasInFlightChild = [...sessionRoots.entries()].some(([childID, rootID]) => rootID === sessionID && (activeSessions.get(childID)?.inFlightCalls.size ?? 0) > 0);
5140
+ const hasInFlightChild = [...sessionRoots.entries()].some(([childID, rootID]) => rootID === sessionID && ((activeSessions.get(childID)?.inFlightCalls.size ?? 0) > 0 || childHasInFlightParentTask(childID)));
5120
5141
  if (hasInFlightChild)
5121
5142
  state.expiresAt = now + ACTIVE_SESSION_CACHE.ttlMilliseconds;
5122
5143
  else {
@@ -6565,6 +6586,8 @@ export const SortieDogsPlugin = async (input, options) => {
6565
6586
  diagnosis.accepting = false;
6566
6587
  diagnosisCalls.delete(toolInput.callID ?? "");
6567
6588
  }
6589
+ const returnedMissionWorker = completedChildSessionID !== undefined &&
6590
+ await input.runtimeBridge?.allowsInvestigativeShell?.(completedChildSessionID);
6568
6591
  parallelCalls.delete(toolInput.callID ?? "");
6569
6592
  activeSessions.get(toolInput.sessionID ?? "")?.inFlightCalls.delete(toolInput.callID ?? "");
6570
6593
  if (coordinatorTaskFinished && diagnosis === undefined) {
@@ -6584,6 +6607,12 @@ export const SortieDogsPlugin = async (input, options) => {
6584
6607
  evictSession(completedChildSessionID);
6585
6608
  void childLifecycles.get(completedChildSessionID)?.check();
6586
6609
  }
6610
+ else if (completedChildSessionID !== undefined && activeSessions.get(completedChildSessionID)?.parallel !== "valid" &&
6611
+ returnedMissionWorker) {
6612
+ // Keep same-worker recovery identity, but a returned serial Task must not keep a live
6613
+ // write reservation. Rebinding the retained manifest reacquires it before any later write.
6614
+ await releaseWriteGate(completedChildSessionID);
6615
+ }
6587
6616
  }
6588
6617
  },
6589
6618
  "tool.execute.before": async (toolInput, output) => {
@@ -1,6 +1,7 @@
1
1
  import type { OperatorState } from "../core/operator-runtime.js";
2
- import { type OperatorMission } from "../core/operator-mission.js";
2
+ import { type MissionEvidenceExcerpt, type OperatorMission } from "../core/operator-mission.js";
3
3
  import { type RuntimeProfile } from "../core/runtime-profile.js";
4
+ export declare function missionReviewBaseline(directory: string): Promise<string | undefined>;
4
5
  /** A child ID alone cannot establish the initial phase after a restart or failed dispatch. */
5
6
  export declare function initialMissionReviewPrompt(mission: OperatorMission, prompts: readonly string[]): string | undefined;
6
7
  /** Recover from native completion records, never from the pending review's inherited child ID. */
@@ -9,7 +10,7 @@ export declare function completedMissionReviewPrompts(mission: OperatorMission |
9
10
  messages(id: string): Promise<readonly Record<string, unknown>[]>;
10
11
  }): Promise<string[]>;
11
12
  /** Pin all scoped tracked/untracked source bytes, including deletions; display a bounded excerpt only. */
12
- export declare function missionReviewSource(directory: string, run: OperatorState): Promise<{
13
+ export declare function missionReviewSource(directory: string, run: OperatorState, evidence?: readonly MissionEvidenceExcerpt[], baseline?: string): Promise<{
13
14
  fingerprint: string;
14
15
  excerpt: string;
15
16
  }>;