Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions packages/opencode/src/dag/blocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,14 @@ function node(input: {
outputSchema?: Record<string, unknown>
}): NodeConfig {
const instruction = input.instruction?.trim() ? "Block-specific instruction:\n{{instruction}}" : ""
// issue #323: a reporting checkpoint adjudicates a direction, so its
// prompt must demand adversarial independent verification. The production
// incident: a gate confirmed parent-supplied "defect evidence" that was a
// production no-op because it re-read the parent's narrative instead of
// the source. Upstream claims are hypotheses, not facts.
const adversarial = input.reportToParent
? "As a reporting checkpoint you adjudicate this direction: treat upstream and parent-supplied claims as hypotheses to verify, never facts to confirm. Independently read the relevant source before endorsing, spot-check at least three of the most load-bearing claims (name the count you actually inspected), cite what you actually inspected, and replan or reject the direction when a load-bearing claim does not survive inspection."
: ""
return {
id: input.id,
name: input.name,
Expand All @@ -339,6 +347,7 @@ function node(input: {
"Workflow objective:\n{{objective}}",
instruction,
input.contract,
adversarial,
"Use dependency outputs as evidence and return a concise artifact that downstream blocks can consume. Do not ask the user questions from this child session.",
]
.filter(Boolean)
Expand Down
50 changes: 50 additions & 0 deletions packages/opencode/test/dag/blocks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,56 @@ describe("workflow blocks", () => {
])
})

// issue #323: a reporting checkpoint adjudicates a direction — its prompt
// must demand adversarial independent verification, not self-confirmation
// of upstream claims. The production incident: cp-after-exploration
// confirmed a parent-supplied "defect" that was a production no-op because
// the gate re-read the parent's narrative instead of reading the source.
it("compiles reporting checkpoints with an adversarial verification clause", () => {
const nodes = DagBlocks.compileWorkflowBlocks({
objective: "Ship the feature",
blocks: [
{ id: "cp-after-exploration", kind: "explore", report_to_parent: true },
{ id: "stage", kind: "coding", depends_on: ["cp-after-exploration"] },
],
})
const checkpoint = nodes.find((node) => node.id === "cp-after-exploration")
expect(checkpoint?.report_to_parent).toBe(true)
// Upstream claims are hypotheses to verify, not facts to confirm.
expect(checkpoint?.prompt_template.inline).toContain("hypotheses to verify")
// Independent source inspection is mandatory for load-bearing claims.
expect(checkpoint?.prompt_template.inline).toContain("Independently read the relevant source")
// A quantified spot-check floor on the most load-bearing claims.
expect(checkpoint?.prompt_template.inline).toContain("at least three")
// A claim that fails inspection must fail the direction, not pass it.
expect(checkpoint?.prompt_template.inline).toContain("replan or reject")
})

it("keeps the adversarial clause off non-reporting nodes", () => {
const nodes = DagBlocks.compileWorkflowBlocks({
objective: "Ship the feature",
blocks: [
{ id: "map", kind: "explore" },
{ id: "stage", kind: "coding", depends_on: ["map"] },
],
})
// Only adjudicating checkpoints pay the adversarial cost; evidence
// producers (a plain explore) and executors (coding) do not.
for (const node of nodes) {
expect(node.report_to_parent).toBe(false)
expect(node.prompt_template.inline).not.toContain("hypotheses to verify")
}
})

it("gives default-reporting synthesize nodes the adversarial clause", () => {
const nodes = DagBlocks.compileWorkflowBlocks({
objective: "Ship the feature",
blocks: [{ id: "closing", kind: "synthesize" }],
})
expect(nodes[0]?.report_to_parent).toBe(true)
expect(nodes[0]?.prompt_template.inline).toContain("hypotheses to verify")
})

it("composes configured design delivery capabilities without new lifecycle kinds", () => {
const nodes = DagBlocks.compileWorkflowBlocks({
objective: "Design, implement, and review project-owned memory",
Expand Down
Loading