diff --git a/examples/adversarial-implementation/README.md b/examples/adversarial-implementation/README.md
index 19dfac715..b17609066 100644
--- a/examples/adversarial-implementation/README.md
+++ b/examples/adversarial-implementation/README.md
@@ -63,7 +63,8 @@ retains the selected providers and all three budgets through continuation.
consumer-driven API repairs, each from zero through ten. `maxResponseCorrections`
bounds extra response-format calls per completed assignment, from zero through
three. Zero admits no extra response call. It applies to ordinary planning,
-implementation, selected feedback reviews and foundation replies, including
+implementation, selected feedback reviews, independent delivery reviews and
+foundation replies, including
repairs by their original owner. It authorizes no transport retry or repetition
of uncertain productive work. Continuing a Ticket keeps its consumed source
correction count and recorded allocation; it does not restart a failed attempt.
@@ -304,19 +305,45 @@ Independent review summaries allow 1500 characters; detailed observations belong
in `findings`. The request states this bound alongside the exact revision and
clause IDs. Ordinary phase and API-foundation summaries retain their own bounds.
+A joined reply containing only `{"status":"in_progress"}` gets one completion
+turn in the same native conversation, with the same reviewer, resolved model,
+effort and exact source revision. The workflow saves the original reply, checks
+that the checkout is clean and reserves `completion/` before sending a prompt
+with a five-minute deadline. A second progress reply, unavailable conversation,
+failed turn or changed source holds the review. Unmarked prose and malformed
+verdicts do not authorize a completion turn. Status and reconciliation read the
+saved evidence without prompting; a later invocation never starts an unused or
+interrupted completion turn.
+
+The completion turn has its own allowance. If it returns a review whose only
+defect is summary length, the original formatting allowance still applies.
+
+An otherwise-valid joined review with only an oversized summary can consume the
+work item's recorded `maxResponseCorrections` allowance. Each correction stays
+in the selected reviewer conversation with the same resolved model and effort.
+It shortens only `summary`; every other result field is frozen. Zero permits no
+extra call. The workflow records the original raw reply and rejection, reserves
+each correction before dispatch, and saves its request, native outcome, reply,
+source observations and disposition separately. Exhaustion, changed substance,
+changed source or uncertain work holds explicitly; none selects a fallback
+reviewer or repeats the completed review. Reconciliation validates this saved
+chain without prompting. Historical assignments without a recorded allowance
+have zero; a new workflow does not retroactively accept their rejected replies.
+
+
Use `--props-action reconcile` when the selected joined review has a saved
execution but no accepted receipt. Only the selection's attempt qualifies: the
original reviewer, or Astra after a retained transition from an eligible Fable
failure. It checks that attempt's assignment, joined execution, unchanged raw
-reply, current schema, clauses and source before creating the missing receipt,
+reply, any saved completion, current schema, clauses and source before creating the missing receipt,
and saves a raw reply the execution joined but never wrote. It never shortens text, changes a verdict, repeats
a model call or overwrites a contradictory receipt. Missing or uncertain execution
continues to hold. Status uses this same validation boundary to read receipts.
Reconciliation is limited to independent review from a work item launched under
the same response contract. Its assignment retains the exact response schema; a
-missing or different schema refuses rather than reinterpreting an old reply. It cannot repair or adopt an ordinary rejected role
-verdict, rewrite its raw rejection or partial work, or reset a correction budget.
+missing or different schema refuses rather than reinterpreting an old reply. It validates only corrections recorded under the original review assignment.
+It cannot adopt an ordinary rejected role verdict, rewrite its raw rejection or partial work, or reset a correction budget.
A current schema accepting older text does not retroactively approve that work.
Preserve the original rejection and require an explicit applicable
schema-amendment decision, recording the old bound, amended bound, exact raw
@@ -361,13 +388,46 @@ from claiming readiness or merging independently.
## Prepare requests without metadata repair turns
-These rules apply to separate phase requests and Ticket role calls.
-`Phase.Request` renders the complete prompt before entering an Agent, including
-initial repository instructions, role instructions, assignment, selected text and
-response schema. A missing selected component or input fails there; a packet
-above 256000 characters refuses before provider dispatch. The selected evidence
-keeps its separate 64000-character bound; the complete-prompt ceiling also accommodates
-mandatory initialization instructions and the response schema. Nothing is truncated.
+Claude workflow-role calls use the CLI's scoped `claude-workflow-role` provider.
+Each role installs it directly around its authored session, covering initial and
+continued prompts. It disables native shell/subagent backgrounding and denies
+`ScheduleWakeup` and `Monitor`;
+ordinary interactive Sidekick calls keep their defaults. A retained session that
+lacks matching creation settings refuses before another prompt. Each role joins
+its owned processes and returns its result in the same call. The upfront brief
+contains work, evidence and lifetime rules. Identical role instructions selected
+as input are delivered once; a different captured version retains the current
+role brief too. The frozen submission guide contains response schema, limits and
+routing. Reading defaults to result preparation; an earlier read must resolve a
+concrete reporting requirement that blocks a work decision. Every result,
+including an early blocker, requires the complete guide within the same call.
+
+These rules apply to separate phase requests and Ticket role calls. Codex authors
+prose committed to workflow documents, including guides, component instructions
+and prompt templates. Claude roles may write internal runtime handoffs, hold
+prompts and summaries.
+`Phase.Run` freezes the submission guide in its reserved attempt directory.
+`Phase.Request` checks that guide and prepares the phase prompt before entering
+an Agent. The prompt includes initial repository and role instructions,
+the assignment, selected text and an absolute reference to `submission-guide.md`
+in the attempt directory. The guide contains the complete submission instructions
+and the current role- and phase-specific JSON contract. Read it before returning
+any result, including an early blocker, source conflict or unanswered decision.
+The same retained native call reads the file and returns the result; staging the
+guide adds no model call.
+
+The guide and its hash receipt are runner-owned. Preparation refuses occupied
+paths and confirms that the published text is readable under current File
+permissions and protection. After saving each raw reply, the workflow verifies
+the guide and receipt against the expected submission instructions before it
+accepts a result or starts a correction. Missing, unreadable or changed files
+hold the attempt with its raw reply preserved. Corrections retain their existing
+inline repair packet, original session and allowance.
+
+A missing selected component or input fails before provider dispatch. A prompt
+above 256000 characters refuses without truncation. The guide has its own
+256000-character and UTF-8-byte bounds. Selected evidence keeps its separate
+64000-character allowance; moving submission instructions does not enlarge it.
Provider configuration and client compatibility retain their existing checks before a turn starts.
No paid prompt probes installation or repairs launch metadata.
diff --git a/examples/adversarial-implementation/components/Adversarial/PlannerRole.md b/examples/adversarial-implementation/components/Adversarial/PlannerRole.md
index 7138e5499..ff3b300a7 100644
--- a/examples/adversarial-implementation/components/Adversarial/PlannerRole.md
+++ b/examples/adversarial-implementation/components/Adversarial/PlannerRole.md
@@ -12,7 +12,7 @@ props:
-
+
+
+
diff --git a/examples/adversarial-implementation/components/Delivery/Dispatch.md b/examples/adversarial-implementation/components/Delivery/Dispatch.md
index a81e117ad..b11fb96b1 100644
--- a/examples/adversarial-implementation/components/Delivery/Dispatch.md
+++ b/examples/adversarial-implementation/components/Delivery/Dispatch.md
@@ -16,7 +16,7 @@ A release that fails ends the run and leaves the attempt reserved, so it holds
for reconciliation and can never select a replacement.
-
+
diff --git a/examples/adversarial-implementation/components/Delivery/Finish.md b/examples/adversarial-implementation/components/Delivery/Finish.md
new file mode 100644
index 000000000..8e0c43404
--- /dev/null
+++ b/examples/adversarial-implementation/components/Delivery/Finish.md
@@ -0,0 +1,58 @@
+---
+description: 'Finish an incomplete review once in its original conversation, or read that saved turn. Use .'
+props:
+ type: object
+ properties:
+ delivery: {type: object}
+ revision: {type: string, pattern: '^[0-9a-f]{40}$'}
+ reviewer: {type: object}
+ catalog: {type: object}
+ attempt: {type: string, pattern: '^/'}
+ dispatch: {type: boolean}
+ required: [delivery, revision, reviewer, catalog, attempt, dispatch]
+ additionalProperties: false
+returns: {type: string}
+---
+
+The initial turn has returned an explicit progress reply. Reserve the one
+completion turn before prompting. Its reply and source evidence stay separate
+from the first turn and any later summary corrections.
+
+
+
+
+
+
+
+
+
+
+```bash exec as="reserved"
+mkdir -- {quoted}
+```
+
+
+
+Continue your unfinished independent review of {props.revision} from this conversation. Finish only the checks still needed. Keep source unchanged, run checks in the foreground and join them before returning. Return the final verdict for the original acceptance clauses as one JSON object. If you cannot finish, report the concrete blocker in the verdict. This is the only completion turn.
+
+
+
+
+{turn.reply}
+
+
+
+
+{savedRequest}
+
+
+{savedOutcome}
+
+
+
+
+{beforeText}
+{afterText}
+{reply}
+
+
diff --git a/examples/adversarial-implementation/components/Delivery/Prepare.md b/examples/adversarial-implementation/components/Delivery/Prepare.md
index 8798a687c..69a9da1a1 100644
--- a/examples/adversarial-implementation/components/Delivery/Prepare.md
+++ b/examples/adversarial-implementation/components/Delivery/Prepare.md
@@ -15,7 +15,7 @@ returns: {type: object}
clause.id)).size !== policy.acceptance.length || policy.acceptance.some(clause => !props.task.inputs.includes(clause.source))}>
clause.id).filter((id,index,ids) => ids.indexOf(id) !== index).join(", ") + ". Missing source references: " + [...new Set(policy.acceptance.filter(clause => !props.task.inputs.includes(clause.source)).map(clause => clause.source))].join(", ") + ". No provider prompt began."} />
-
+
Object.values(props.task.sessions).includes(entry.reviewer.session))}>
diff --git a/examples/adversarial-implementation/components/Delivery/Reconcile.md b/examples/adversarial-implementation/components/Delivery/Reconcile.md
index e04ee59dd..bf5daaf8a 100644
--- a/examples/adversarial-implementation/components/Delivery/Reconcile.md
+++ b/examples/adversarial-implementation/components/Delivery/Reconcile.md
@@ -14,13 +14,14 @@ returns: {type: object}
Only the retained selection's attempt can supply the receipt: the original
reviewer, or its replacement after a retained transition from an eligible
failure. A joined reply that was never saved as `raw.txt` is completed from the
-attempt's retained outcome.
+attempt's retained outcome. If it was a progress reply, only the saved
+completion turn can supply the review.
-
+
{assignmentText}
@@ -36,9 +37,8 @@ attempt's retained outcome.
-
-
-{extracted.json}
+
+
!result.acceptance.includes(clause.id)) || result.verdict === "pass" && result.findings.some(finding => finding.blocking)}>
diff --git a/examples/adversarial-implementation/components/Delivery/Response.md b/examples/adversarial-implementation/components/Delivery/Response.md
new file mode 100644
index 000000000..62fae6f89
--- /dev/null
+++ b/examples/adversarial-implementation/components/Delivery/Response.md
@@ -0,0 +1,93 @@
+---
+description: 'Finish a review after an explicit progress reply, repair an oversized summary, or validate the saved replies without prompting. Use .'
+props:
+ type: object
+ properties:
+ delivery: {type: object}
+ revision: {type: string}
+ reviewer: {type: object}
+ attempt: {type: string}
+ raw: {type: string}
+ dispatch: {type: boolean, default: false}
+ required: [delivery, revision, reviewer, attempt, raw]
+ additionalProperties: false
+returns: {type: object}
+---
+
+A progress reply can use one separate completion turn. Formatting corrections
+start with the completed review and consume only the recorded formatting
+allowance. Reconciliation reads both records and sends no prompt.
+
+
+
+{assignmentText}
+
+ 3 || limit !== (props.delivery.maxResponseCorrections ?? 0)}>
+
+{outcomeText}
+
+
+
+
+{props.raw}
+
+
+
+
+
+
+
+
+
+
+
+
+= limit}>
+
+
+ [key,key === "summary" ? value : {const:original[key]}]))}} />
+
+ 64000}>
+
+
+
+
+
+```bash exec as="reserved"
+mkdir -- {quoted}
+```
+
+
+
+Shorten only the summary of your completed independent review to at most {schemas.review.properties.summary.maxLength} characters. This is response-only correction {round}, with {limit-round} remaining. Do not inspect source, run commands, repeat the review, publish or change any other field. Preserve the verdict, findings, clause IDs and revision exactly. Return one JSON object satisfying the frozen schema below.
+
+
+
+
+{turn.reply}
+
+
+
+
+
+{requestText}
+
+
+{returnedText}
+
+
+
+
+{beforeText}
+{afterText}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/adversarial-implementation/components/Delivery/Review.md b/examples/adversarial-implementation/components/Delivery/Review.md
index 330830114..daae17bd9 100644
--- a/examples/adversarial-implementation/components/Delivery/Review.md
+++ b/examples/adversarial-implementation/components/Delivery/Review.md
@@ -44,7 +44,19 @@ mkdir -- {quotedReplacement}
-
+
+
+
+
+
+
+
+{raw}
+
+
+
+
+
diff --git a/examples/adversarial-implementation/components/Delivery/ReviewState.ts b/examples/adversarial-implementation/components/Delivery/ReviewState.ts
index 2206912ea..1d05e792f 100644
--- a/examples/adversarial-implementation/components/Delivery/ReviewState.ts
+++ b/examples/adversarial-implementation/components/Delivery/ReviewState.ts
@@ -216,6 +216,9 @@ function assignmentFor(selection: Selection, reviewer: Reviewer) {
reviewer,
acceptance: selection.policy.acceptance,
responseSchema: selection.responseSchema,
+ ...(selection.maxResponseCorrections === undefined
+ ? {}
+ : { maxResponseCorrections: selection.maxResponseCorrections }),
};
}
@@ -238,12 +241,27 @@ export default function* ReviewState(value: unknown): Operation {
);
}
-function* observe(directory: string, expected: Selection, replacement: boolean): Operation {
+function* observe(
+ directory: string,
+ expected: Selection,
+ replacement: boolean,
+): Operation {
const selectionText = yield* read(directory, "selection.json");
const recorded = parsed(selectionSchema, selectionText);
const selection =
- selectionText === null ? "absent" : recorded !== null && same(recorded, expected) ? "matches" : "mismatch";
- const empty = { selection, chain: expected.chain, attempts: [], candidate: null, effective: null, transition: null };
+ selectionText === null
+ ? "absent"
+ : recorded !== null && same(recorded, expected)
+ ? "matches"
+ : "mismatch";
+ const empty = {
+ selection,
+ chain: expected.chain,
+ attempts: [],
+ candidate: null,
+ effective: null,
+ transition: null,
+ };
if (selection !== "matches") {
return { ...empty, next: "uncertain", hold: null };
}
@@ -274,7 +292,8 @@ function* observe(directory: string, expected: Selection, replacement: boolean):
next = "uncertain";
} else {
const transitionText = yield* read(replacementDirectory, "transition.json");
- const proven = transitionText !== null && same(parsed(z.unknown(), transitionText), transition);
+ const proven =
+ transitionText !== null && same(parsed(z.unknown(), transitionText), transition);
const replaced = yield* attempt(
replacementDirectory,
1,
@@ -298,7 +317,9 @@ function* observe(directory: string, expected: Selection, replacement: boolean):
: "The replacement transition differs from the primary attempt's eligible failure.",
};
attempts.push(settled);
- candidate = proven ? { position: 1, directory: replacementDirectory, reviewer: second.reviewer } : null;
+ candidate = proven
+ ? { position: 1, directory: replacementDirectory, reviewer: second.reviewer }
+ : null;
next = !proven
? "uncertain"
: settled.state === "review"
@@ -331,7 +352,8 @@ function* observe(directory: string, expected: Selection, replacement: boolean):
holdRecord.work === expected.work &&
holdRecord.revision === expected.revision &&
holdRecord.chain === expected.chain &&
- (dispositionText === null || (disposition !== null && same(disposition.hold, holdRecord))),
+ (dispositionText === null ||
+ (disposition !== null && same(disposition.hold, holdRecord))),
answer: disposition?.answer ?? null,
evidence: [
`${directory}/hold.json`,
diff --git a/examples/adversarial-implementation/components/Delivery/Role.md b/examples/adversarial-implementation/components/Delivery/Role.md
index 03c89edf8..52cb79ed8 100644
--- a/examples/adversarial-implementation/components/Delivery/Role.md
+++ b/examples/adversarial-implementation/components/Delivery/Role.md
@@ -19,6 +19,8 @@ saved to `outcome.json` before that release, including when the turn fails.
+Finish this native call with its review JSON result. Run required checks in the foreground and join every process you start before returning. A progress message, monitor or future notification does not complete the review.
+
Perform one independent correctness review of {props.revision} in {props.delivery.repository}. Read the repository instructions and inspect that exact commit and its affected consumers. Keep source unchanged. Reuse applicable evidence; run only focused evidence that distinguishes a finding. Do not run broad confidence suites or wait for CI. Do not delegate, implement, publish comments, or merge. Report a concrete blocker rather than substituting a provider or repeating an uncertain call.
Review these recorded acceptance clauses against their original selected sources. A derived check cannot strengthen or replace them. Report a material ambiguity as blocked.
@@ -29,17 +31,24 @@ Original captured source text:
Keep summary within {schemas.review.properties.summary.maxLength} characters, including spaces and newlines. Put detailed observations in findings; do not compress the whole report into summary. Check revision against the assigned full SHA and acceptance against the recorded clause IDs before returning.
-Return exactly one JSON object satisfying this schema. acceptance lists every clause ID actually examined. pass requires no blocking finding. The workflow supplies work, reviewer, attempt and completion metadata.
+If you must end the turn before the verdict is ready, return exactly {JSON.stringify(schemas.progress.const)} on its own. The workflow can then give this same conversation one chance to finish.
+
+Otherwise, return exactly one JSON object satisfying this schema. acceptance lists every clause ID actually examined. pass requires no blocking finding. The workflow supplies work, reviewer, attempt and completion metadata.
64000}>
-
+
+
{instructions}
+
+{turn.reply}
+
+
diff --git a/examples/adversarial-implementation/components/Delivery/Schema.md b/examples/adversarial-implementation/components/Delivery/Schema.md
index bcfe715ac..009d4438c 100644
--- a/examples/adversarial-implementation/components/Delivery/Schema.md
+++ b/examples/adversarial-implementation/components/Delivery/Schema.md
@@ -5,4 +5,4 @@ returns: {type: object}
-
+
diff --git a/examples/adversarial-implementation/components/Delivery/Selection.md b/examples/adversarial-implementation/components/Delivery/Selection.md
index b591c5c86..3f7bb6438 100644
--- a/examples/adversarial-implementation/components/Delivery/Selection.md
+++ b/examples/adversarial-implementation/components/Delivery/Selection.md
@@ -19,4 +19,4 @@ reviewer is the only one.
-
+
diff --git a/examples/adversarial-implementation/components/Delivery/Validate.md b/examples/adversarial-implementation/components/Delivery/Validate.md
new file mode 100644
index 000000000..b0adfc0e5
--- /dev/null
+++ b/examples/adversarial-implementation/components/Delivery/Validate.md
@@ -0,0 +1,38 @@
+---
+description: 'Check one independent review without changing it. Use .'
+props:
+ type: object
+ properties:
+ text: {type: string}
+ revision: {type: string}
+ acceptance: {type: array}
+ frozen: {type: [object, 'null'], default: null}
+ required: [text, revision, acceptance]
+ additionalProperties: false
+returns: {type: object}
+---
+
+
+
+
+
+
+
+{extracted.json}
+
+
+
+
+
+ [key,key === "summary" ? value : {const:frozen[key]}]))}} />
+
+ !result.acceptance.includes(clause.id)) || result.verdict === "pass" && result.findings.some(finding => finding.blocking) || !correspondence.ok}>
+
+
+ schemas.review.properties.summary.maxLength,result,reason:result.summary.length > schemas.review.properties.summary.maxLength ? "The review summary exceeds its recorded limit." : null}} />
+
+
+
+
+
+
diff --git a/examples/adversarial-implementation/components/Delivery/records.ts b/examples/adversarial-implementation/components/Delivery/records.ts
index 94b463c36..fe7a3eccc 100644
--- a/examples/adversarial-implementation/components/Delivery/records.ts
+++ b/examples/adversarial-implementation/components/Delivery/records.ts
@@ -81,6 +81,7 @@ export const selectionSchema = z.strictObject({
policy: z.record(z.string(), z.unknown()),
inputs: z.array(z.record(z.string(), z.unknown())),
responseSchema: z.record(z.string(), z.unknown()),
+ maxResponseCorrections: z.number().int().min(0).max(3).optional(),
chain: z.enum(["fable-astra", "astra-final", "single"]),
reviewers: z.array(entrySchema).min(1).max(2),
});
@@ -98,7 +99,7 @@ export const holdSchema = z.strictObject({
schema: z.literal("delivery-review-hold.v1"),
work: z.string().min(1),
revision: z.string().regex(/^[0-9a-f]{40}$/),
- chain: z.enum(["fable-astra", "astra-final"]),
+ chain: selectionSchema.shape.chain,
summary: z.string().min(1),
unknown: z.string().min(1),
attempts: z.array(attemptSchema).min(1).max(2),
diff --git a/examples/adversarial-implementation/components/Phase/Correct.md b/examples/adversarial-implementation/components/Phase/Correct.md
index 61734ee93..cd69431ab 100644
--- a/examples/adversarial-implementation/components/Phase/Correct.md
+++ b/examples/adversarial-implementation/components/Phase/Correct.md
@@ -80,6 +80,7 @@ shasum -a 256 -- {quoted}/request.json {quoted}/diagnostic.json {quoted}/origina
{raw}
+
diff --git a/examples/adversarial-implementation/components/Phase/Instructions.md b/examples/adversarial-implementation/components/Phase/Instructions.md
index f763ce210..e56ead10a 100644
--- a/examples/adversarial-implementation/components/Phase/Instructions.md
+++ b/examples/adversarial-implementation/components/Phase/Instructions.md
@@ -16,26 +16,35 @@ props:
default: []
assignment:
type: object
+ submission:
+ type: object
initialize:
type: boolean
default: false
required:
- role
- assignment
+ - submission
additionalProperties: false
---
diff --git a/examples/adversarial-implementation/components/Phase/ProviderName.md b/examples/adversarial-implementation/components/Phase/ProviderName.md
new file mode 100644
index 000000000..7329fd836
--- /dev/null
+++ b/examples/adversarial-implementation/components/Phase/ProviderName.md
@@ -0,0 +1,12 @@
+---
+description: 'Select the registered provider name for a workflow role. Use and wrap the authored role session in .'
+props:
+ type: object
+ properties:
+ agent: {type: string}
+ required: [agent]
+ additionalProperties: false
+returns: {type: string}
+---
+
+
diff --git a/examples/adversarial-implementation/components/Phase/Request.md b/examples/adversarial-implementation/components/Phase/Request.md
index c4de4c76d..613cd7d3e 100644
--- a/examples/adversarial-implementation/components/Phase/Request.md
+++ b/examples/adversarial-implementation/components/Phase/Request.md
@@ -13,6 +13,7 @@ props:
returns: {type: string}
---
-
+
+
256000}> input.path + " (" + input.content.length + ")").join(", ") + ". Preserve the prepared assignment and reduce the request before dispatch; nothing was truncated or sent. No provider prompt began."} />
diff --git a/examples/adversarial-implementation/components/Phase/Role.md b/examples/adversarial-implementation/components/Phase/Role.md
index 5cda85d04..1f9f6f046 100644
--- a/examples/adversarial-implementation/components/Phase/Role.md
+++ b/examples/adversarial-implementation/components/Phase/Role.md
@@ -68,4 +68,5 @@ props:
-
+
+
diff --git a/examples/adversarial-implementation/components/Phase/Run.md b/examples/adversarial-implementation/components/Phase/Run.md
index c076b3c98..946e45632 100644
--- a/examples/adversarial-implementation/components/Phase/Run.md
+++ b/examples/adversarial-implementation/components/Phase/Run.md
@@ -128,12 +128,14 @@ mkdir -- {quotedAttempt}
+
{raw}
+
diff --git a/examples/adversarial-implementation/components/Phase/SubmissionGuide.ts b/examples/adversarial-implementation/components/Phase/SubmissionGuide.ts
new file mode 100644
index 000000000..a6bac6e80
--- /dev/null
+++ b/examples/adversarial-implementation/components/Phase/SubmissionGuide.ts
@@ -0,0 +1,52 @@
+import { Component } from "@executablemd/core/api";
+import { z } from "zod";
+import { clear, describe, descriptor, read, same, unused, write } from "../Ticket/text-storage.ts";
+
+export const props = {
+ type: "object",
+ properties: {
+ repository: { type: "string" },
+ directory: { type: "string" },
+ mode: { enum: ["prepare", "check"], default: "prepare" },
+ },
+ required: ["repository", "directory"],
+ additionalProperties: false,
+};
+const input = z
+ .object({
+ repository: z.string(),
+ directory: z.string().startsWith("/"),
+ mode: z.enum(["prepare", "check"]).default("prepare"),
+ })
+ .strict();
+const limit = 256_000;
+
+export default function* SubmissionGuide(value: unknown) {
+ const { repository, directory, mode } = input.parse(value);
+ const acquired = yield* Component.operations.tryContent();
+ if (acquired.failure !== undefined) {
+ throw acquired.failure;
+ }
+ const text = acquired.text;
+ const expected = describe(directory + "/submission-guide.md", text);
+ if (text.length > limit || expected.utf8Bytes > limit) {
+ throw new Error("The submission guide exceeds 256000 characters or UTF-8 bytes.");
+ }
+ yield* clear(text);
+ const receipt = directory + "/submission-guide.json";
+ if (mode === "prepare") {
+ if (!(yield* unused(repository, expected.path)) || !(yield* unused(repository, receipt))) {
+ throw new Error("The submission guide is occupied or unavailable; preserve this attempt.");
+ }
+ yield* write(repository, expected.path, text);
+ yield* write(repository, receipt, JSON.stringify(expected));
+ }
+ const recorded = descriptor.parse(JSON.parse(yield* read(repository, receipt, limit)));
+ const retained = yield* read(repository, expected.path, limit);
+ if (!same(expected, recorded) || !same(expected, describe(expected.path, retained))) {
+ throw new Error(
+ "The frozen submission guide changed; preserve the raw reply. No successor is admitted.",
+ );
+ }
+ return expected;
+}
diff --git a/examples/adversarial-implementation/components/Phase/SubmissionInstructions.md b/examples/adversarial-implementation/components/Phase/SubmissionInstructions.md
new file mode 100644
index 000000000..441327d8a
--- /dev/null
+++ b/examples/adversarial-implementation/components/Phase/SubmissionInstructions.md
@@ -0,0 +1,40 @@
+---
+props:
+ type: object
+ properties:
+ role: {enum: [architect, planner, implementor]}
+ assignment: {type: object}
+ required: [role, assignment]
+ additionalProperties: false
+---
+
+
diff --git a/examples/adversarial-implementation/components/Ticket/PreparationPrompt.md b/examples/adversarial-implementation/components/Ticket/PreparationPrompt.md
index b5044f4b2..5331a2214 100644
--- a/examples/adversarial-implementation/components/Ticket/PreparationPrompt.md
+++ b/examples/adversarial-implementation/components/Ticket/PreparationPrompt.md
@@ -36,4 +36,5 @@ Return exactly one JSON object satisfying:
256000}>
-
+
+
diff --git a/examples/adversarial-implementation/delivery.md b/examples/adversarial-implementation/delivery.md
index 1637a3133..ea366dadc 100644
--- a/examples/adversarial-implementation/delivery.md
+++ b/examples/adversarial-implementation/delivery.md
@@ -10,9 +10,10 @@ props:
---
Read the original delivery record and perform only its computed next action.
-A status read starts no agent. Review preserves every attempt and never repeats
-one: an unavailable Fable reviewer is replaced once by Codex Astra, and a review
-nobody could complete is held and asked about. Readiness permits no merge.
+A status read starts no agent. An explicit progress reply gets one continuation
+in the same conversation. An unavailable Fable reviewer is replaced once by
+Codex Astra. The workflow preserves each attempt and asks about a review it
+cannot complete. Readiness permits no merge.
{recordText}
diff --git a/examples/adversarial-implementation/tests/Delivery.test.md b/examples/adversarial-implementation/tests/Delivery.test.md
index 42e209302..fc8a5ca04 100644
--- a/examples/adversarial-implementation/tests/Delivery.test.md
+++ b/examples/adversarial-implementation/tests/Delivery.test.md
@@ -87,7 +87,9 @@ printf '%s' "$XMD_EXAMPLE_COMMAND"
-
+
+
+
@@ -178,3 +180,113 @@ pwd -P
+
+
+
+
+
+
+
+```bash exec as="head"
+git rev-parse HEAD
+```
+
+
+
+
+
+
+
+
+
+
+
+
+
+{raw}
+
+
+
+
+
+
+
+
+{execution.output}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```bash exec as="head"
+git rev-parse HEAD
+```
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ question.held)} expected={[true]} />
+
+
+
+
+
+{raw}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/adversarial-implementation/tests/Friction.test.md b/examples/adversarial-implementation/tests/Friction.test.md
index 7fefbd9a1..f0caff020 100644
--- a/examples/adversarial-implementation/tests/Friction.test.md
+++ b/examples/adversarial-implementation/tests/Friction.test.md
@@ -128,10 +128,11 @@ git rev-parse HEAD
-
+
{raw}
+
{correction.output}
diff --git a/examples/adversarial-implementation/tests/ProductionRole.test.md b/examples/adversarial-implementation/tests/ProductionRole.test.md
index 987e3756c..dbaf20e74 100644
--- a/examples/adversarial-implementation/tests/ProductionRole.test.md
+++ b/examples/adversarial-implementation/tests/ProductionRole.test.md
@@ -18,9 +18,13 @@ pwd -P
-
+
+
+
-
+
+
+
@@ -37,13 +41,21 @@ pwd -P
-
+
+
+
-
+
+
+
-
+
+
+
-
+
+
+
@@ -70,9 +82,10 @@ pwd -P
```
-
+
{raw}
+
@@ -155,10 +168,12 @@ false
-
-
-
-
+
+
+
+
+
+
{schemaText}
diff --git a/examples/adversarial-implementation/tests/Submission.test.md b/examples/adversarial-implementation/tests/Submission.test.md
new file mode 100644
index 000000000..111eb87c2
--- /dev/null
+++ b/examples/adversarial-implementation/tests/Submission.test.md
@@ -0,0 +1,98 @@
+```bash exec as="location"
+pwd -P
+```
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{schemaText}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```bash exec as="head"
+git -C {quoted} rev-parse HEAD
+```
+
+
+
+
+
+
+
+
+
+
+
+
+ 0} expected={true} />
+
+{deliveryText}
+
+
+
+
+ 0} expected={true} />
+
+{execution.output}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Changed before dispatch.
+A directory cannot supply the guide.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/adversarial-implementation/tests/agents/api-conflict-correction.md b/examples/adversarial-implementation/tests/agents/api-conflict-correction.md
index d1c3949a4..5fa7dc813 100644
--- a/examples/adversarial-implementation/tests/agents/api-conflict-correction.md
+++ b/examples/adversarial-implementation/tests/agents/api-conflict-correction.md
@@ -1,5 +1,5 @@
-
+
{text}
{assignment.request}
diff --git a/examples/adversarial-implementation/tests/agents/decision-planner-held.md b/examples/adversarial-implementation/tests/agents/decision-planner-held.md
index b7197ad34..76796ff15 100644
--- a/examples/adversarial-implementation/tests/agents/decision-planner-held.md
+++ b/examples/adversarial-implementation/tests/agents/decision-planner-held.md
@@ -1,4 +1,4 @@
-
+
{text}
diff --git a/examples/adversarial-implementation/tests/agents/decision-planner.md b/examples/adversarial-implementation/tests/agents/decision-planner.md
index 38527ce4b..f01778c98 100644
--- a/examples/adversarial-implementation/tests/agents/decision-planner.md
+++ b/examples/adversarial-implementation/tests/agents/decision-planner.md
@@ -1,9 +1,9 @@
-
+
{text}
-
+
{text}
diff --git a/examples/adversarial-implementation/tests/agents/initialized-role.md b/examples/adversarial-implementation/tests/agents/initialized-role.md
index a5d4e2e00..6ed6e1f74 100644
--- a/examples/adversarial-implementation/tests/agents/initialized-role.md
+++ b/examples/adversarial-implementation/tests/agents/initialized-role.md
@@ -1,3 +1,4 @@
- request.instructions.includes(text))}>
+ request.instructions.includes(text))}>
+
initialization received
diff --git a/examples/adversarial-implementation/tests/agents/preparation-architect.md b/examples/adversarial-implementation/tests/agents/preparation-architect.md
index 25c928fa3..cc908ce88 100644
--- a/examples/adversarial-implementation/tests/agents/preparation-architect.md
+++ b/examples/adversarial-implementation/tests/agents/preparation-architect.md
@@ -1,5 +1,5 @@
-
+
{text}
{assignment.request}
diff --git a/examples/adversarial-implementation/tests/agents/preparation-implementor.md b/examples/adversarial-implementation/tests/agents/preparation-implementor.md
index 15dc51713..5f2c976bd 100644
--- a/examples/adversarial-implementation/tests/agents/preparation-implementor.md
+++ b/examples/adversarial-implementation/tests/agents/preparation-implementor.md
@@ -3,7 +3,7 @@
-
+
{text}
{assignment.request}
diff --git a/examples/adversarial-implementation/tests/agents/preparation-planner.md b/examples/adversarial-implementation/tests/agents/preparation-planner.md
index 00e1712d6..d00f8754e 100644
--- a/examples/adversarial-implementation/tests/agents/preparation-planner.md
+++ b/examples/adversarial-implementation/tests/agents/preparation-planner.md
@@ -1,10 +1,10 @@
-
+
{text}
{assignment.request}
-
+
{text}
{assignment.request}
diff --git a/examples/adversarial-implementation/tests/agents/review-completion.md b/examples/adversarial-implementation/tests/agents/review-completion.md
new file mode 100644
index 000000000..27f21af9c
--- /dev/null
+++ b/examples/adversarial-implementation/tests/agents/review-completion.md
@@ -0,0 +1,27 @@
+
+{initial.text}
+
+
+The checks are still running.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{text}
+
+
+
diff --git a/examples/adversarial-implementation/tests/agents/submission-result.md b/examples/adversarial-implementation/tests/agents/submission-result.md
new file mode 100644
index 000000000..03441b9c6
--- /dev/null
+++ b/examples/adversarial-implementation/tests/agents/submission-result.md
@@ -0,0 +1,15 @@
+
+
+
+{text}
+
+not a phase result
+
+
+
+
+
+
+{packetText}
+
+
diff --git a/examples/adversarial-implementation/tests/agents/summary-correction.md b/examples/adversarial-implementation/tests/agents/summary-correction.md
new file mode 100644
index 000000000..6f8a70118
--- /dev/null
+++ b/examples/adversarial-implementation/tests/agents/summary-correction.md
@@ -0,0 +1,10 @@
+
+{initial.text}
+
+
+
+
+{text}
+
+
+
diff --git a/examples/adversarial-implementation/tests/components/Delivery/Role.md b/examples/adversarial-implementation/tests/components/Delivery/Role.md
index 841597a75..e2d17bf88 100644
--- a/examples/adversarial-implementation/tests/components/Delivery/Role.md
+++ b/examples/adversarial-implementation/tests/components/Delivery/Role.md
@@ -9,6 +9,11 @@ props: {type: object, properties: {delivery: {type: object}, revision: {type: st
clause.id),findings:["repair","false-pass"].includes(work) ? [{blocking:true,description:"A blocking regression remains."}] : []}} />
+
+
+{raw}
+
+
@@ -20,3 +25,5 @@ props: {type: object, properties: {delivery: {type: object}, revision: {type: st
+
+
diff --git a/examples/adversarial-implementation/tests/components/Fixture/Composition.ts b/examples/adversarial-implementation/tests/components/Fixture/Composition.ts
new file mode 100644
index 000000000..489368059
--- /dev/null
+++ b/examples/adversarial-implementation/tests/components/Fixture/Composition.ts
@@ -0,0 +1,18 @@
+import { Component } from "@executablemd/core/api";
+import { useCompositionComponents } from "@executablemd/git";
+import { useRunComposition } from "@executablemd/git/deno";
+import { z } from "zod";
+
+export const props = {
+ type: "object",
+ properties: { repository: { type: "string" }, directory: { type: "string" } },
+ required: ["repository", "directory"],
+ additionalProperties: false,
+};
+
+export default function* Composition(input: unknown) {
+ const selected = z.strictObject({ repository: z.string(), directory: z.string() }).parse(input);
+ yield* useCompositionComponents();
+ yield* useRunComposition({ root: selected.directory, cwd: selected.repository });
+ yield* Component.operations.content();
+}
diff --git a/examples/adversarial-implementation/tests/components/Fixture/Fallback.md b/examples/adversarial-implementation/tests/components/Fixture/Fallback.md
index cb9a071c6..5ed33b62b 100644
--- a/examples/adversarial-implementation/tests/components/Fixture/Fallback.md
+++ b/examples/adversarial-implementation/tests/components/Fixture/Fallback.md
@@ -45,7 +45,7 @@ git symbolic-ref --short HEAD
-
+
@@ -105,7 +105,7 @@ printf '%s' "$reached"
{primaryText}
-
+
{replacementText}
@@ -210,7 +210,7 @@ fi
-{JSON.stringify(stray)}
+{JSON.stringify(stray)}
diff --git a/examples/adversarial-implementation/tests/production-components/NativeGuide.ts b/examples/adversarial-implementation/tests/production-components/NativeGuide.ts
new file mode 100644
index 000000000..0c16ca9e3
--- /dev/null
+++ b/examples/adversarial-implementation/tests/production-components/NativeGuide.ts
@@ -0,0 +1,55 @@
+import { Agent, Component } from "@executablemd/core/api";
+import { ensureDir, rm } from "@effectionx/fs";
+import { z } from "zod";
+import { read, write } from "../../components/Ticket/text-storage.ts";
+
+export const props = {
+ type: "object",
+ properties: { variant: { type: "string" } },
+ required: ["variant"],
+ additionalProperties: false,
+};
+
+export default function* NativeGuide(input: unknown) {
+ const { variant } = z.object({ variant: z.string() }).parse(input);
+ let calls = 0;
+ let path: string | undefined;
+ yield* Agent.around({
+ *prompt(args, next) {
+ calls++;
+ const text = args[0];
+ path ??= text
+ .split("The frozen UTF-8 file at ")[1]
+ ?.split(" contains the submission schema")[0];
+ if (path === undefined || (calls === 1 && text.includes('"$defs"'))) {
+ throw new Error("The native request did not stage its submission guide.");
+ }
+ const guide = yield* read("/", path);
+ if (
+ ![
+ '"$defs"',
+ "including an early blocker",
+ "Test-only changes can affect acceptance.",
+ "A mechanical assessment has no architecture, acceptance or complexity impact.",
+ "a public boundary is not that reason",
+ ].every((rule) => guide.includes(rule))
+ ) {
+ throw new Error("The staged guide is unreadable or incomplete.");
+ }
+ if (variant === "changed" || (variant === "correction-changed" && calls === 2)) {
+ yield* write("/", path, "Changed during the native turn.");
+ }
+ if (variant === "missing" || variant === "unreadable") {
+ yield* rm(path);
+ }
+ if (variant === "unreadable") {
+ yield* ensureDir(path);
+ }
+ return yield* next(...args);
+ },
+ });
+ yield* Component.operations.content();
+ if (calls !== (variant === "correction-changed" ? 2 : 1)) {
+ throw new Error("The staged submission used an additional native call.");
+ }
+}
diff --git a/examples/adversarial-implementation/tests/production-components/Phase/ProviderName.md b/examples/adversarial-implementation/tests/production-components/Phase/ProviderName.md
new file mode 100644
index 000000000..ed72ec7ae
--- /dev/null
+++ b/examples/adversarial-implementation/tests/production-components/Phase/ProviderName.md
@@ -0,0 +1,11 @@
+---
+props:
+ type: object
+ properties:
+ agent: {type: string}
+ required: [agent]
+ additionalProperties: false
+returns: {type: string}
+---
+
+
diff --git a/packages/cli/tests/adversarial-session-continuity.test.ts b/packages/cli/tests/adversarial-session-continuity.test.ts
index 4f279cfa2..6360be674 100644
--- a/packages/cli/tests/adversarial-session-continuity.test.ts
+++ b/packages/cli/tests/adversarial-session-continuity.test.ts
@@ -34,7 +34,7 @@ import { openReplSession, submitReplEntry } from "../src/repl/session.ts";
import type { ReplSession, ReplSessionOptions } from "../src/repl/session.ts";
import type { ReplQuestion } from "../src/repl/elicitation.ts";
import { createFakeAcp, makeStore } from "./support/fake-acp.ts";
-import type { FakeAcp } from "./support/fake-acp.ts";
+import type { FakeAcp, FakeStore } from "./support/fake-acp.ts";
import { AGENT_SOURCE, world } from "../../acp/tests/released-peer.ts";
import PhaseSession, {
props as phaseSessionProps,
@@ -381,6 +381,7 @@ const captured = z.strictObject({
interface ReviewWorld {
root: string;
+ store: FakeStore;
delivery: Record;
asked: unknown[];
run(source: string, props: Record): Operation<{ ok: boolean; text: string }>;
@@ -391,6 +392,7 @@ function* reviewWorld(fake: FakeAcp, create = fake.create): Operation' +
"";
const reviewSource =
'';
+const workflowControls = {
+ env: { CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: "1" },
+ disallowedTools: ["ScheduleWakeup", "Monitor"],
+};
+
+const scopedRoleSource =
+ '' +
+ '';
+const continuedRoleSource =
+ '';
+
+describe("production workflow provider scope", () => {
+ it("applies controls through each actual Claude role and retains them across cold reentry", function* () {
+ for (const role of ["phase", "foundation", "preparation", "delivery"]) {
+ const fake = createFakeAcp();
+ fake.configuration(catalog(["fixture-default", "opus", "fable"], ["medium", "high"]));
+ const harness = yield* reviewWorld(fake);
+ const assignment = {
+ repository: harness.delivery.repository,
+ artifactDirectory: join(harness.root, "submission"),
+ identityDirectory: join(harness.root, "sessions"),
+ phase: "implementation",
+ work: "provider-scope",
+ revision,
+ request: "Use the saved evidence.",
+ assessment: {
+ mechanical: false,
+ architecture: false,
+ acceptance: true,
+ risk: null,
+ reason: "Inspect the accepted behavior.",
+ evidence: ["accepted.md"],
+ },
+ };
+ yield* ensureDir(assignment.artifactDirectory);
+ const props = { assignment };
+ let source = scopedRoleSource;
+ let continued = continuedRoleSource;
+ if (role === "foundation") {
+ source =
+ '';
+ continued =
+ '';
+ } else if (role === "preparation") {
+ fake.script({ reply: "Seeded the original owner." });
+ expect(
+ yield* harness.run(`${source}`, props),
+ ).toMatchObject({ ok: true });
+ source =
+ '';
+ continued = source;
+ } else if (role === "delivery") {
+ source = roleSource;
+ continued = source;
+ }
+ const selected = role === "delivery" ? "fable" : "opus";
+ const callProps =
+ role === "delivery"
+ ? {
+ delivery: harness.delivery,
+ revision,
+ reviewer: {
+ provider: "claude",
+ model: selected,
+ effort: "high",
+ session: "controlled-owner",
+ },
+ choices: [selected],
+ attempt: join(harness.root, "delivery", "attempt"),
+ }
+ : props;
+ for (const text of [source, continued]) {
+ fake.script({ reply: role === "delivery" ? review : "Completed the assigned work." });
+ const ran = yield* harness.run(
+ `${text}`,
+ callProps,
+ );
+ if (!ran.ok) {
+ throw new Error(`${role}: ${ran.text}`);
+ }
+ const ensured = fake.ensured.at(-1)!;
+ expect(ensured.sessionOptions).toEqual(workflowControls);
+ expect(ensured.requestedModel).toBe(selected);
+ expect(harness.store.records.get(ensured.sessionKey)?.acpx?.session_options).toEqual({
+ model: selected,
+ env: workflowControls.env,
+ disallowed_tools: workflowControls.disallowedTools,
+ });
+ expect(fake.running(ensured.sessionKey)).toEqual({ model: selected, effort: "high" });
+ }
+ expect(fake.turns).toHaveLength(role === "preparation" ? 3 : 2);
+ expect(new Set(fake.turns.map((entry) => entry.handle.sessionKey)).size).toBe(1);
+ }
+ });
+
+ it("keeps ordinary acpx unchanged and refuses to retrofit its retained owner", function* () {
+ const fake = createFakeAcp();
+ fake.configuration(catalog(["opus"], ["high"]));
+ const harness = yield* reviewWorld(fake);
+ fake.script({ reply: "Original ordinary owner." });
+ const ordinary =
+ 'Use the original ordinary provider.';
+ expect(yield* harness.run(ordinary, {})).toMatchObject({ ok: true });
+ expect(fake.ensured[0]?.sessionOptions).toBeUndefined();
+ const before = JSON.stringify([...harness.store.records]);
+ const controlled =
+ '' +
+ ordinary +
+ "";
+ const refused = yield* harness.run(controlled, {});
+ expect(refused).toMatchObject({ ok: false });
+ expect(refused.text).toContain("does not retrofit existing conversations");
+ expect(fake.prompts).toHaveLength(1);
+ expect(fake.ensured).toHaveLength(1);
+ expect(JSON.stringify([...harness.store.records])).toBe(before);
+ });
+});
+
function catalog(models: string[], efforts: string[] = ["low", "high"]) {
return {
model: { selected: models[0]!, choices: models.map((id) => ({ id })) },
@@ -543,7 +664,7 @@ describe(
choices: ["fable", "claude-fable-5-1[1m]"],
attempt,
});
- expect(ran.ok).toBe(true);
+ expect(ran).toMatchObject({ ok: true });
const settled = outcome.parse(
JSON.parse(yield* readTextFile(join(attempt, "outcome.json"))),
);
@@ -555,6 +676,10 @@ describe(
expect(settled.turn).toEqual({ state: "reply", reply: review });
expect(fake.prompts).toHaveLength(1);
expect(fake.prompts[0]).toContain(`independent correctness review of ${revision}`);
+ expect(fake.prompts[0]).toContain("Finish this native call with its review JSON result.");
+ expect(fake.prompts[0]).toContain(
+ "Run required checks in the foreground and join every process you start before returning.",
+ );
expect(fake.prompts[0]).toContain(
JSON.stringify(acceptance, null, 2).split("\n")[2]!.trim(),
);
@@ -763,11 +888,6 @@ describe(
);
function* reviewAt(sha: string) {
const attempt = join(harness.delivery.directory as string, `review-${sha}`);
- yield* ensureDir(attempt);
- yield* writeTextFile(
- join(attempt, "assignment.json"),
- JSON.stringify({ work: "production-review", revision: sha, reviewer }),
- );
const ran = yield* harness.run(roleSource, {
delivery: harness.delivery,
revision: sha,
@@ -786,13 +906,14 @@ describe(
).toBe("unavailable");
expect(fake.prompts).toEqual([]);
fake.configuration(catalog(["fable"]));
- fake.script({ reply: review });
+ const currentReview = review.replace(revision, "c".repeat(40));
+ fake.script({ reply: currentReview });
const first = yield* reviewAt("c".repeat(40));
- expect(first.ran.ok).toBe(true);
+ expect(first.ran).toMatchObject({ ok: true });
expect(fake.prompts).toHaveLength(1);
expect(
outcome.parse(JSON.parse(yield* readTextFile(join(first.attempt, "outcome.json")))).turn,
- ).toEqual({ state: "reply", reply: review });
+ ).toEqual({ state: "reply", reply: currentReview });
const established = z
.object({
name: z.string(),
diff --git a/packages/cli/tests/adversarial-submission.test.ts b/packages/cli/tests/adversarial-submission.test.ts
new file mode 100644
index 000000000..85aca680e
--- /dev/null
+++ b/packages/cli/tests/adversarial-submission.test.ts
@@ -0,0 +1,7 @@
+import { describe, it } from "@executablemd/test-support/bdd";
+import { runAdversarialJourney } from "./support/adversarial-journey.ts";
+
+describe("adversarial implementation example", () => {
+ it("runs the Submission Markdown journeys without native models", () =>
+ runAdversarialJourney("Submission"));
+});
diff --git a/packages/cli/tests/support/adversarial-journey.ts b/packages/cli/tests/support/adversarial-journey.ts
index c4a97f469..a92a40e73 100644
--- a/packages/cli/tests/support/adversarial-journey.ts
+++ b/packages/cli/tests/support/adversarial-journey.ts
@@ -3,7 +3,7 @@ import { fileURLToPath } from "node:url";
import { env } from "@executablemd/runtime";
import { readTextFile } from "@effectionx/fs";
-const testedSuites = ["ProductionRole", "Preparation"];
+const testedSuites = ["ProductionRole", "Preparation", "Submission", "Delivery"];
export function* runAdversarialJourney(suite: string) {
const retainedEvidence = suite.startsWith("ArtifactReturn")
@@ -45,8 +45,8 @@ export function* runAdversarialJourney(suite: string) {
`\n${source}\n`,
]),
"--include",
- testedSuites.includes(suite)
- ? "examples/adversarial-implementation/components"
+ testedSuites.includes(suite) && suite !== "Delivery"
+ ? "examples/adversarial-implementation/tests/production-components"
: "examples/adversarial-implementation/tests/components",
"--include",
"examples/adversarial-implementation/components",
diff --git a/packages/cli/tests/support/fake-acp.ts b/packages/cli/tests/support/fake-acp.ts
index fd7bc6a7c..454ef9726 100644
--- a/packages/cli/tests/support/fake-acp.ts
+++ b/packages/cli/tests/support/fake-acp.ts
@@ -779,6 +779,19 @@ export function createFakeAcp(): FakeAcp {
cumulative_token_usage: {},
request_token_usage: {},
};
+ const sessionOptions = input.sessionOptions;
+ if (sessionOptions !== undefined || input.requestedModel !== undefined) {
+ record.acpx = {
+ session_options: {
+ model: input.requestedModel ?? sessionOptions?.model,
+ allowed_tools: sessionOptions?.allowedTools,
+ disallowed_tools: sessionOptions?.disallowedTools,
+ max_turns: sessionOptions?.maxTurns,
+ system_prompt: sessionOptions?.systemPrompt,
+ env: sessionOptions?.env,
+ },
+ };
+ }
if (deferred) {
record.sessionMaterialization = {
state: "pending",
diff --git a/scripts/tests/runtime-exclusions.test.ts b/scripts/tests/runtime-exclusions.test.ts
index 06eadfb62..0b812d97d 100644
--- a/scripts/tests/runtime-exclusions.test.ts
+++ b/scripts/tests/runtime-exclusions.test.ts
@@ -163,6 +163,7 @@ describe("the applicable corpus", () => {
"adversarial-preparation",
"adversarial-production-role",
"adversarial-session-continuity",
+ "adversarial-submission",
"adversarial-workflow",
].map((name) => `packages/cli/tests/${name}.test.ts`);
expect(
diff --git a/specs/acp-client-spec.md b/specs/acp-client-spec.md
index 77b1504f1..2533f2776 100644
--- a/specs/acp-client-spec.md
+++ b/specs/acp-client-spec.md
@@ -772,8 +772,11 @@ until one is installed as a root provider or by AgentProvider.
### Claude workflow-role profile
The CLI registers `claude-workflow-role` alongside ordinary `acpx`. The
-profile uses the same carried adapter, session store and host identity
-coordination as ordinary calls. Interactive Sidekick configuration is unchanged.
+adversarial workflow selects this provider only around Claude role calls,
+including foundation, document preparation, independent review and its completion
+and response-only correction turns. It uses the same carried adapter, session
+store and host identity coordination as ordinary calls. Interactive Sidekick
+configuration is unchanged.
The profile sets `CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1` in the child session
environment and supplies `disallowedTools: ["ScheduleWakeup", "Monitor"]`