Consolidate native completion constraints with matched eval gates

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-03 14:27:51 -05:00
1 parent 2a8a99e4a5
commit 20f26bad15
16 files changed
+456 -18

No files matched your search

+8
View File
@@ -3,6 +3,14 @@
Paperclip has two live eval families with different questions, owners, and
evidence. Choose the family before selecting a model, profile, or case.
The explicit-only [native instruction consolidation comparison](plans/2026-10-03-native-completion-consolidation.md)
uses six Product E2E cells per source variant. It measures the completion
constraint reduction separately from the earlier native tool-description
trial. Provider-free start/resume payload capture is a byte measurement;
behavioral qualification requires the original paired live outcomes and
retained content. Neither source admission nor a scripted pass proves model
behavior.
- **Runner Evals:** real Runner/provider behavior against a seeded mock control
plane. Definitions live in `paperclip-evals/evals/paperclip-runner`; see the
[direct live protocol evals](../packages/paperclip-runner/docs/runner-protocol-live-evals.md).
@@ -0,0 +1,55 @@
# Native completion constraint consolidation
Date: 2026-10-03. Roadmap item: 2.3. Status: implementation and provider-free calibration; live behavior is unqualified.
## Change and comparison axis
The first slice replaces the repeated final-response procedure in native task constraints with a short requirement to obtain an accepted `paperclip_finish` or `paperclip_block` result and follow that tool's reporting instructions. It removes the extra completion sentence in the Codex/runnerd and direct OpenCode backends. The existing finish/block tool descriptions still own rejection, retry, approval, link/action, explicit wait, and final-response behavior.
The production comparison axis is exactly these three files:
- `packages/paperclip-runner/src/backends/runtime-context.ts`
- `packages/paperclip-runner/src/backends/codex-native-backend.ts`
- `packages/paperclip-runner/src/backends/opencode-native-backend.ts`
Both variants start from master `2a8a99e4a5f69aa803b3f10b982f583e75a87042`. The baseline retains those three original files. The candidate changes only those three production files. All fixture, grader, admission, test and documentation repairs must be common to both variants. The fixed execution prompt, production AGENTS bundle, completion tool descriptions/schemas, operational skills, hiring, connections and delivery guidance stay identical. The prior #14961 trial measured a different change and cannot qualify this one.
## Provider-free measurement
`src/backends/native-instruction-measurement.test.ts` in the Runner package captures actual `thread/start`, `thread/resume` and `turn/start` parameters from the real runnerd facade with a scripted transport. It captures native Codex, ACPX Claude and OpenCode for input v4 and v5: twelve receipts per variant. Every receipt records exact UTF-8 bytes, character counts and hashes of the instruction string, fixture tool schemas, turn input and their full projection. It checks finish/block descriptions, document/file rules, approval mode and identical start/resume tools and instructions. The fixture uses the actual production AGENTS file and a fixed projection of the core semantic catalog plus the reserved completion tools.
Run from `packages/paperclip-runner`:
```sh
PAPERCLIP_NATIVE_INSTRUCTION_REPORT=/absolute/path/measurement.json pnpm exec vitest run src/backends/native-instruction-measurement.test.ts
```
The receipt declares its source SHA, source file hashes, fixture hash and dirty state. A dirty overlay can aid diagnosis but is rejected for live admission. Temporary workspace paths are normalized to a fixed path for comparison. This is the complete declared Paperclip projection at that boundary, not the production server's full granted catalog or a capture of provider-owned stock prompts. Token counts, billing, provider preprocessing and cognitive consumption remain unknown. Scripted resume capture does not qualify live resume behavior.
## Bounded live comparison
The explicit-only Product E2E suite is `native-instruction-consolidation`. Discovery requires no credentials:
```sh
pnpm test:e2e:runner -- --list --suite native-instruction-consolidation
```
Each source variant declares these original tasks on the same local native profiles:
| Profile | Model | Tasks |
| --- | --- | --- |
| Codex | `gpt-5.6-sol` | assigned-skill document completion; whole-task blocker |
| ACPX Claude | `claude-sonnet-5` | assigned-skill document completion; whole-task blocker |
| OpenCode | `openrouter/deepseek/deepseek-v4-flash-0731` | assigned-skill document completion; whole-task blocker |
Six cells per variant means twelve actual runs for the pair, each with one attempt. Use exact IDs with parallelism one and no automatic retry. Each cell uses the original task deadline and 1,000-cent company and agent hard stops. These budgets bound recorded Paperclip spend; they do not guarantee final provider invoice amounts. Do not run providers until the human authorizes the live scope. Stop to inspect a usable behavior failure; do not reroll it to obtain a pass.
The new source gate requires clean committed sources, the declared base ancestry, one complete known source variant, no unrelated paths, the current fixture digest and all twelve provider-free capture receipts. It rejects missing, duplicated, dirty, stale or paid capture evidence. It verifies the requested immutable source SHA when supplied. Before live execution also verify the selected compiled Runner/source identity through the existing launcher build path. Hosted shallow sources are not admitted by this local gate; do not substitute a relaxed ancestry check.
The suite uses the original assigned-skill durable-document oracle and strict native completion grading. It retains named call-ID joins, acceptance/termination, final-response order, provider-final persistence, exact blocker owner/action/scope, workspace and issue/agent side effects, default instructions and budget evidence. Unknown or absent evidence cannot pass. No relaxed source-read matcher or provider-call join is introduced.
## Decision and evidence
Freeze both source refs, the common definition hash, models/profiles, permissions, default instructions, input construction, deadlines, budgets and compiled source identity before admission. Preserve every attempt, original grade, failure and usage receipt. Compare all six exact pairs and retained content; report new failures, new passes, unchanged failures, unchanged passes and uncomparable pairs separately. A passing total does not conceal a lost document, incomplete blocker report, extra work or a missing final response.
The reduction remains unqualified until the paired live evidence exists. Passing deterministic checks proves transport/catalog and control-plane behavior under the tested scripted inputs. A single live pair per cell gives bounded evidence, not general equivalence, causation, cost/speed trends, general coding quality, or live continuation coverage. Approval feedback and yielded continuation need their own retained cases before making broader claims. If any behavior worsens, hold this slice and inspect the original evidence before changing source or expanding paid scope.
@@ -140,7 +140,6 @@ function createTransportBackedNativeSessionBackend(
]
: []),
...nativeTaskConstraints(input),
"Return one semantic completion result.",
];
return new HarnessDriverBackend(
@@ -0,0 +1,143 @@
import { createHash } from "node:crypto";
import { execFileSync } from "node:child_process";
import { readFileSync, writeFileSync } from "node:fs";
import { afterAll, describe, expect, it } from "vitest";
import { PAPERCLIP_SEMANTIC_ACTION_CATALOG } from "../catalog/semantic-action-catalog.js";
import { buildNativeModelEnvelope, parseNativeExecutionInput, type NativeExecutionInput } from "../contracts/native-execution.js";
import { NATIVE_RUNTIME_ASSET_SCHEMA, PAPERCLIP_EXECUTION_PROMPT, PAPERCLIP_EXECUTION_PROMPT_REVISION, nativeRuntimePromptDigest, canonicalNativeRuntimeContextDigest } from "../contracts/runtime-context.js";
import { PRP_BLOCK_TOOL_DESCRIPTION, PRP_COMPLETION_TOOL_DESCRIPTION } from "../contracts/completion-result.js";
import { FakeCodexTransport, WORKSPACE } from "../drivers/codex/codex-app-server-driver.test-support.js";
import { resolveQualifiedAcpxProfile } from "../drivers/acpx/qualified-profiles.js";
import { createRunnerdNativeSessionBackend } from "./codex-native-backend.js";
// This captures Paperclip's real runnerd RPC boundary with a scripted transport.
// It measures complete Paperclip-supplied instruction/tool/message projections,
// not provider-owned stock prompts, tokenization, billing or model behavior.
const tools = PAPERCLIP_SEMANTIC_ACTION_CATALOG
.filter(tool => tool.placement === "core")
.map(tool => ({ name: tool.operationId, description: tool.description, inputSchema: tool.inputSchema }));
const identity = { companyId: "company", agentId: "agent", issueId: "issue", runId: "run", sessionId: "session" };
const { qualificationModel: _qualificationModel, reportedModelId: _reportedModelId, permissionPolicy: _permissionPolicy, ...profile } = resolveQualifiedAcpxProfile("claude", "claude-sonnet-5");
const providers: NativeExecutionInput["provider"][] = [
{ kind: "codex", model: "gpt-5.6-sol", approvalPolicy: "never" },
{ kind: "acpx", agent: "claude", model: "claude-sonnet-5", permissionMode: "approve-all", profile },
{ kind: "opencode", model: "openrouter/deepseek/deepseek-v4-flash-0731", permissionMode: "allow" },
];
const receipts: unknown[] = [];
const sha256 = (text: string | Buffer) => createHash("sha256").update(text).digest("hex");
function execution(provider: NativeExecutionInput["provider"], schema: "v4" | "v5"): NativeExecutionInput {
const rootPath = WORKSPACE;
const entry = readFileSync(new URL("../../../../server/src/onboarding-assets/default/AGENTS.md", import.meta.url), "utf8");
writeFileSync(`${rootPath}/AGENTS.md`, entry);
const context = {
prompt: { revision: PAPERCLIP_EXECUTION_PROMPT_REVISION, text: PAPERCLIP_EXECUTION_PROMPT, digest: nativeRuntimePromptDigest() },
instructions: { entryPath: "AGENTS.md", bundle: { schema: NATIVE_RUNTIME_ASSET_SCHEMA, rootPath, digest: sha256(entry), manifestDigest: sha256(entry), fileCount: 1, totalBytes: Buffer.byteLength(entry) } },
skills: [], mcp: { assignmentSetId: "none", digest: "0".repeat(64), bindingId: null },
};
return parseNativeExecutionInput({
schema: `paperclip.native-execution-input.${schema}`,
binding: { companyId: identity.companyId, agentId: identity.agentId, issueId: identity.issueId, runId: identity.runId, executionWorkspaceId: "workspace" },
task: { identifier: "TASK-1", title: "Save the requested document", description: "Save the requested document", prompt: "Save the requested document.", workMode: "standard" },
workspace: { cwd: rootPath, repoUrl: null, repoRef: null, branchName: null },
session: { normalizedSessionId: "session", driverKind: provider.kind === "acpx" ? "acpx_runtime" : provider.kind === "opencode" ? "opencode_server" : "codex_app_server", protocolVersion: 1 },
provider,
executionMode: "default",
planningContext: null,
completionContract: { id: "contract", sha256: `sha256:${"a".repeat(64)}`, schemaVersion: "paperclip.completion-contract.v1", contract: { revision: "1", objective: "Save the requested document", criteria: [{ id: "objective", requirement: "Document saved" }] } },
interactionResponses: [], credentialBindings: [],
runtimeContext: { ...context, aggregateDigest: canonicalNativeRuntimeContextDigest(context) },
});
}
function transport(provider: NativeExecutionInput["provider"]) {
return new FakeCodexTransport("thread-1", "provider-session-1", provider.kind === "acpx" ? {
kind: "acpx", normalizedSessionId: "session", acpxRecordId: "fixture-record", backendSessionId: "fixture-backend",
agentSessionId: "fixture-agent", profileDigest: `sha256:${"a".repeat(64)}`, workspaceDigest: `sha256:${"b".repeat(64)}`,
requestedModel: provider.model, effectiveModel: provider.model, permissionMode: "approve-all",
providerLifetimeFenceCandidates: [60001, 60002, 60003],
} : undefined);
}
function measure(value: unknown) {
// Temporary fixture workspace names must not create false before/after deltas.
const json = JSON.stringify(value).replaceAll(WORKSPACE, "/workspace");
return { utf8Bytes: Buffer.byteLength(json), characters: json.length, sha256: sha256(json) };
}
describe("native instruction payload measurement", () => {
for (const provider of providers) for (const schema of ["v4", "v5"] as const) {
it(`captures ${provider.kind} ${schema} instructions and tools on start and resume`, async () => {
const input = execution(provider, schema);
const fresh = transport(provider), resumed = transport(provider);
const backend = createRunnerdNativeSessionBackend(input, {
transportFactory: () => fresh, dynamicTools: tools,
environment: { PAPERCLIP_WORKSPACE_CWD: WORKSPACE },
});
const session = await backend.openSession({ identity, workingDirectory: WORKSPACE });
const checkpoint = await session.snapshot();
const envelope = buildNativeModelEnvelope(input);
if ("requestedSkills" in envelope && backend.preparedTaskConstraints) envelope.constraints = [...backend.preparedTaskConstraints];
await session.startTurn({ message: { role: "user", text: JSON.stringify(envelope) } });
await session.close({ reason: "measurement" });
resumed.readResponse = { thread: { id: "thread-1", sessionId: "provider-session-1", cwd: WORKSPACE, turns: [] } };
const resumeBackend = createRunnerdNativeSessionBackend(input, {
transportFactory: () => resumed, dynamicTools: tools,
environment: { PAPERCLIP_WORKSPACE_CWD: WORKSPACE },
});
const recovery = await resumeBackend.recoverSession!(checkpoint, { signal: new AbortController().signal });
expect(recovery.recovered).toBe(true);
await recovery.session!.startTurn({ message: { role: "user", text: JSON.stringify(envelope) } });
await recovery.session!.close({ reason: "measurement" });
for (const [phase, captured, method] of [["start", fresh, "thread/start"], ["resume", resumed, "thread/resume"]] as const) {
const setup = captured.calls.find(call => call.method === method)!.params;
const turn = captured.calls.find(call => call.method === "turn/start")!.params;
const instructions = setup.developerInstructions ?? setup.baseInstructions;
expect(instructions).toContain(PAPERCLIP_EXECUTION_PROMPT);
expect(instructions).toContain("You are an agent in a Paperclip company.");
const deliveredTools = setup.dynamicTools as Array<{ name: string; description: string }>;
expect(deliveredTools.filter(tool => tool.name === "paperclip_finish")).toEqual([
expect.objectContaining({ description: PRP_COMPLETION_TOOL_DESCRIPTION }),
]);
expect(deliveredTools.filter(tool => tool.name === "paperclip_block")).toEqual([
expect.objectContaining({ description: PRP_BLOCK_TOOL_DESCRIPTION }),
]);
expect(deliveredTools).toHaveLength(tools.length + 2);
expect(JSON.stringify(turn.input)).toContain("Obtain one accepted result");
expect(JSON.stringify(turn.input)).toContain("Document saved");
expect(JSON.stringify(turn.input)).toContain("write_document");
expect(JSON.stringify(turn.input)).toContain("register_deliverable");
expect(setup.approvalPolicy).toBe("never");
receipts.push({ provider: provider.kind, schema, phase,
instructions: measure(instructions), tools: measure(setup.dynamicTools), input: measure(turn.input),
fullProjection: measure({ instructions, tools: setup.dynamicTools, input: turn.input }),
deliveredToolCount: deliveredTools.length,
});
}
const start = fresh.calls.find(call => call.method === "thread/start")!.params;
const resume = resumed.calls.find(call => call.method === "thread/resume")!.params;
expect(resume.dynamicTools).toEqual(start.dynamicTools);
expect(resume.developerInstructions ?? resume.baseInstructions).toEqual(start.developerInstructions ?? start.baseInstructions);
});
}
});
afterAll(() => {
const output = process.env.PAPERCLIP_NATIVE_INSTRUCTION_REPORT;
if (!output) return;
if (receipts.length !== 12) throw new Error("Incomplete native instruction measurement; refusing a partial receipt");
const sourcePaths = ["runtime-context.ts", "codex-native-backend.ts", "opencode-native-backend.ts"];
writeFileSync(output, `${JSON.stringify({
schema: "paperclip.native-instruction-measurement.v1",
sourceSha: execFileSync("git", ["rev-parse", "HEAD"], { encoding: "utf8" }).trim(),
sourceDirty: Boolean(execFileSync("git", ["status", "--porcelain", "--untracked-files=normal"], { encoding: "utf8" }).trim()),
sourceHashes: Object.fromEntries(sourcePaths.map(file => [file, sha256(readFileSync(new URL(file, import.meta.url)))])),
fixtureSha256: sha256(readFileSync(new URL(import.meta.url))),
boundary: "scripted runnerd RPC; complete Paperclip instructions, fixture core tool schemas and turn input",
providerCalls: 0, tokenCount: null, vendorStockPrompt: "unavailable", modelBehavior: "not_measured",
receipts,
}, null, 2)}\n`);
});
@@ -14,10 +14,7 @@ export function createOpenCodeNativeSessionBackend(
throw new Error("OpenCode native backend requires a persisted OpenCode provider/model selection");
}
const preparedContext = input.schema === NATIVE_EXECUTION_INPUT_SCHEMA;
const constraints = [
...nativeTaskConstraints(input),
"Return one semantic completion result through paperclip_finish or paperclip_block.",
];
const constraints = nativeTaskConstraints(input);
return new HarnessDriverBackend(new OpenCodeServerDriver({
...options,
@@ -10,6 +10,10 @@ import { join } from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { createCodexTaskEnvelope } from "../contracts/codex.js";
import {
PRP_BLOCK_TOOL_DESCRIPTION,
PRP_COMPLETION_TOOL_DESCRIPTION,
} from "../contracts/completion-result.js";
import {
buildNativeModelEnvelope,
type NativeExecutionInput,
@@ -63,7 +67,16 @@ describe("native runtime context files", () => {
expect(constraints).toContain(
"Obtain one accepted result from paperclip_finish or paperclip_block before writing",
);
expect(constraints).toContain("do not call another tool");
expect(constraints).toContain("before writing the complete user-facing final response.");
for (const description of [PRP_COMPLETION_TOOL_DESCRIPTION, PRP_BLOCK_TOOL_DESCRIPTION]) {
expect(description).toContain("If rejected, correct the report and retry.");
expect(description).toContain("After acceptance, read the returned outcome");
expect(description).toContain("end the turn without further tool calls");
}
expect(PRP_COMPLETION_TOOL_DESCRIPTION).toContain("do not claim completion while gated");
expect(PRP_COMPLETION_TOOL_DESCRIPTION).toContain("supplied link and action");
expect(PRP_COMPLETION_TOOL_DESCRIPTION).toContain("explicit wait for the next response");
expect(PRP_BLOCK_TOOL_DESCRIPTION).toContain("its owner, and the action needed to unblock it");
expect(constraints).not.toContain(
"final response exactly once before invoking",
);
@@ -31,8 +31,10 @@ export function nativeSystemInstructions(input: NativeExecutionInput): string {
}
export function nativeTaskConstraints(input: NativeExecutionInput): string[] {
// Keep the discovery/ordering rule in each turn. The completion tools own
// reporting, rejection feedback, approval handling and final-response details.
const finalResponseConstraint =
"Obtain one accepted result from paperclip_finish or paperclip_block before writing the complete user-facing final response. Use paperclip_finish with yielded and a response_wake continuation only when explicitly waiting for the next response. If the tool rejects an incomplete report, correct it and retry. When it succeeds, read its outcome and explain any pending approval with the supplied link and required action. Do not claim the task is done when completion is still gated. Then write the final response exactly once and do not call another tool.";
"Obtain one accepted result from paperclip_finish or paperclip_block before writing the complete user-facing final response. Follow that tool's reporting and final-response instructions.";
const answeredQuestions = Array.isArray(input.interactionResponses)
? input.interactionResponses.flatMap((response, responseIndex) => {
if (
+2
View File
@@ -424,6 +424,8 @@ pnpm test:e2e:runner -- --id agent-chat-hardening.runner-codex.local.stop-startu
The independent, explicit-only `native-completion` suite qualifies native finish/block descriptions on unchanged master defaults. It preserves the original assigned-skill document journey and pairs it with whole-task blocking across three native profiles, with enforced single attempts. See [NATIVE-COMPLETION.md](NATIVE-COMPLETION.md) for admission, provenance and limits.
The separate, explicit-only `native-instruction-consolidation` suite reuses those original tasks and strict graders to compare completion constraints on the production defaults at `2a8a99e4a5f69aa803b3f10b982f583e75a87042`. It declares six local cells: document completion and whole-task blocking on native Codex, ACPX Claude, and OpenCode. Each cell allows one attempt and applies a 1,000-cent company and agent budget hard stop. Its source gate rejects dirty, mixed, unknown, or unrelated source changes before credentials load. A provider-free fixture captures the complete Paperclip instruction/tool/message projection at the scripted runnerd RPC boundary on start and resume for native input v4 and v5. That capture measures bytes; it does not measure vendor-owned prompts, tokens, billing, or model behavior. See the [comparison plan](../../doc/plans/2026-10-03-native-completion-consolidation.md) for exact scope and live qualification limits. Existing `native-completion` results do not qualify this new reduction.
`context-integrity` is an explicit-only local suite with two bounded cases across
ten listed legacy/native profiles (20 cells). Six cells are pending-prerequisite
profiles and are listed for discovery but rejected before provider credentials are
+3 -3
View File
@@ -136,10 +136,10 @@ describe("runner E2E catalog", () => {
expect(localIntegrityTasks).toHaveLength(2);
expect(openRouterBreadthTasks).toHaveLength(3);
expect(runnerSuites.map((suite) => suite.expectedMatrixSize)).toEqual([
6, 30, 3, 16, 16, 2, 6, 8, 46, 23, 50, 6, 20, 26, 52, 28, 18, 2, 6, 6, 12, 10, 48, 16, 10, 2, 1, 1,
6, 30, 3, 16, 16, 2, 6, 8, 46, 23, 50, 6, 6, 20, 26, 52, 28, 18, 2, 6, 6, 12, 10, 48, 16, 10, 2, 1, 1,
]);
expect(validateRunnerCatalog()).toHaveLength(470);
expect(new Set(runnerMatrix.map((entry) => entry.id)).size).toBe(470);
expect(validateRunnerCatalog()).toHaveLength(476);
expect(new Set(runnerMatrix.map((entry) => entry.id)).size).toBe(476);
expect(
runnerMatrix.filter((entry) => entry.suite.id === "core-compatibility"),
).toHaveLength(48);
+12
View File
@@ -1,4 +1,5 @@
import { nativeCompletionTasks, nativeCompletionDefinitionDigest } from "./native-completion-cases.js";
import { NATIVE_INSTRUCTION_SUITE, NATIVE_INSTRUCTION_BASE_SHA, nativeInstructionDefinitionDigest } from "./native-instruction-consolidation.js";
import { nativeCompletionProfile, NATIVE_COMPLETION_BUDGET_CENTS } from "./native-completion-defaults.js";
import { chatConfirmationTasks } from "./chat-cases.js";
import { hiringTemplateTasks, hiringTemplateProfile, hiringTemplateDefinitionDigest } from "./hiring-template-cases.js";
@@ -1197,6 +1198,17 @@ export const runnerSuites: readonly RunnerSuiteFixture[] = [
.map(task => `everyday-workflows.${profile.id}.daytona.${task.id}`))],
definitionMetadata: { version: 4, instructions: "production", grading: "outcome-and-invariants", scheduling: "explicit-only" },
},
{
id: NATIVE_INSTRUCTION_SUITE, label: "Native completion instruction consolidation", manualOnly: true,
description: "Matched production-default durable-document and concrete-blocker checks for the completion constraint reduction.",
groups: ["native", "local"],
profiles: runnerProfiles.filter(profile => ["runner-codex", "runner-acpx-claude", "runner-opencode"].includes(profile.id)).map(nativeCompletionProfile),
environments: [localEnvironment], tasks: nativeCompletionTasks, expectedMatrixSize: 6,
definitionMetadata: { version: 1, fixtureDigest: nativeInstructionDefinitionDigest(), baseSha: NATIVE_INSTRUCTION_BASE_SHA,
instructions: "production-default", maximumAttemptsPerCell: 1, automaticRetryPolicy: "single_attempt",
budgetMonthlyCents: NATIVE_COMPLETION_BUDGET_CENTS, scheduling: "explicit-only",
grading: "original-assigned-skill-and-strict-native-completion", },
},
{
id: "native-completion", label: "Native completion guidance", manualOnly: true,
description: "Production-default assigned-skill document completion and concrete whole-task blocking, with independent native result/final ordering.",
+5
View File
@@ -52,6 +52,7 @@ import {
} from "./types.js";
import { assertRunnerE2EPrerequisites } from "./prerequisites.js";
import { assertNativeCompletionSelection, prepareNativeCompletionPreflight, NATIVE_COMPLETION_PREFLIGHT_ENV } from "./native-completion-admission.js";
import { assertNativeInstructionSelection, prepareNativeInstructionPreflight, NATIVE_INSTRUCTION_PREFLIGHT_ENV, NATIVE_INSTRUCTION_SUITE } from "./native-instruction-consolidation.js";
import { prepareStockHarnessPreflight, STOCK_PREFLIGHT_ENV } from "./stock-harness-admission.js";
import {
@@ -1086,6 +1087,7 @@ async function main() {
// profiles remain discoverable, but cannot reach a provider.
assertRunnerE2EPrerequisites(executions);
assertNativeCompletionSelection(executions);
assertNativeInstructionSelection(executions);
const campaignId = cleanId(
process.env.PAPERCLIP_E2E_CAMPAIGN_ID ??
`local-${new Date().toISOString().replace(/[:.]/g, "-")}`,
@@ -1095,6 +1097,9 @@ async function main() {
if (executions.some(execution => execution.suite.id === "native-completion")) {
process.env[NATIVE_COMPLETION_PREFLIGHT_ENV] = prepareNativeCompletionPreflight(summaryDir);
}
if (executions.some(execution => execution.suite.id === NATIVE_INSTRUCTION_SUITE)) {
process.env[NATIVE_INSTRUCTION_PREFLIGHT_ENV] = prepareNativeInstructionPreflight(summaryDir);
}
if (executions.some(execution => execution.suite.id === "stock-harness")) {
process.env[STOCK_PREFLIGHT_ENV] = prepareStockHarnessPreflight(summaryDir);
}
+1 -1
View File
@@ -136,7 +136,7 @@ export async function setupLiveFixtures(input: {
return api.post<CompanyRecord>("/api/companies", {
name: `Runner E2E ${execution.id} ${input.executionNonce}`,
description: "Ephemeral paid full-stack runner acceptance fixture",
budgetMonthlyCents: execution.suite.id === "native-completion" ? NATIVE_COMPLETION_BUDGET_CENTS
budgetMonthlyCents: ["native-completion", "native-instruction-consolidation"].includes(execution.suite.id) ? NATIVE_COMPLETION_BUDGET_CENTS
: execution.suite.id === "task-titles" ? TASK_TITLE_BUDGET_CENTS
: execution.suite.id === "stock-harness" ? 1_000 : 0,
});
@@ -40,12 +40,12 @@ export interface NativeDefaultReceipt {
budgets: { company: unknown; agent: unknown };
}
export function gradeNativeDefault(receipt: NativeDefaultReceipt) {
export function gradeNativeDefault(receipt: NativeDefaultReceipt, expectedDefaultSha256 = NATIVE_MASTER_DEFAULT_SHA256) {
const checks = [
{ id: "production-default-bundle", passed: receipt.entryFile === "AGENTS.md"
&& receipt.files.length === 1 && receipt.files[0]?.path === "AGENTS.md"
&& receipt.files[0]?.sha256 === NATIVE_MASTER_DEFAULT_SHA256,
detail: "The served public hire bundle is the unchanged master default, with no QA or reduced-manual injection." },
&& receipt.files[0]?.sha256 === expectedDefaultSha256,
detail: "The served public hire bundle matches the declared production default, with no fixture instruction injection." },
{ id: "bounded-company-and-agent", passed: receipt.budgets.company === NATIVE_COMPLETION_BUDGET_CENTS
&& receipt.budgets.agent === NATIVE_COMPLETION_BUDGET_CENTS,
detail: "Both public budgets enforce the declared 1,000-cent hard stop." },
@@ -0,0 +1,69 @@
import { execFileSync } from "node:child_process";
import { createHash } from "node:crypto";
import { readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";
import { runnerSuites, buildRunnerMatrix } from "./catalog.js";
import { gradeNativeDefault, NATIVE_MASTER_DEFAULT_SHA256 } from "./native-completion-defaults.js";
import { nativeCompletionTasks } from "./native-completion-cases.js";
import { assertNativeInstructionSelection, nativeInstructionVariant, validateNativeInstructionMeasurement, NATIVE_INSTRUCTION_BASE_SHA, NATIVE_INSTRUCTION_DEFAULT_SHA256, NATIVE_INSTRUCTION_SUITE, NATIVE_INSTRUCTION_VARIANTS } from "./native-instruction-consolidation.js";
describe("native instruction comparison admission", () => {
it("accepts each complete source variant and rejects a mixed variant", () => {
const files = Object.keys(NATIVE_INSTRUCTION_VARIANTS.baseline);
const baseline = new Map(files.map(file => [file, execFileSync("git", ["show", `${NATIVE_INSTRUCTION_BASE_SHA}:${file}`])]));
expect(nativeInstructionVariant(file => baseline.get(file)!)).toBe("baseline");
const candidate = new Map(files.map(file => [file, readFileSync(new URL(`../../${file}`, import.meta.url))]));
const currentVariant = nativeInstructionVariant(file => candidate.get(file)!);
expect(["baseline", "candidate"]).toContain(currentVariant);
candidate.set(files[0]!, currentVariant === "candidate" ? baseline.get(files[0]!)! : Buffer.from("unknown source"));
expect(() => nativeInstructionVariant(file => candidate.get(file)!)).toThrow("Mixed or unknown");
baseline.set(files[0]!, Buffer.from("unknown source"));
expect(() => nativeInstructionVariant(file => baseline.get(file)!)).toThrow("Mixed or unknown");
});
it("declares six explicit single-attempt cells with the original task prompts and oracle", () => {
const suite = runnerSuites.find(value => value.id === NATIVE_INSTRUCTION_SUITE)!;
expect(suite.tasks).toEqual(nativeCompletionTasks);
expect(suite.manualOnly).toBe(true);
expect(suite.environments.map(value => value.id)).toEqual(["local"]);
const executions = buildRunnerMatrix([suite]);
expect(executions).toHaveLength(6);
expect(() => assertNativeInstructionSelection(executions)).not.toThrow();
for (const execution of executions) {
expect(execution.task.expectedRunCount).toBe(1);
expect(execution.task.automaticRetryPolicy).toBe("single_attempt");
expect(() => assertNativeInstructionSelection([{ ...execution, environment: { ...execution.environment, id: "daytona" } }])).toThrow();
expect(() => assertNativeInstructionSelection([{ ...execution, task: { ...execution.task, automaticRetryPolicy: undefined } }])).toThrow();
}
});
it("uses the new common production default without qualifying old default evidence", () => {
const receipt = { schema: "paperclip.native-completion-default.v1" as const, companyId: "company", agentId: "agent", entryFile: "AGENTS.md",
files: [{ path: "AGENTS.md", sha256: NATIVE_INSTRUCTION_DEFAULT_SHA256, bytes: 40 }], budgets: { company: 1000, agent: 1000 } };
expect(gradeNativeDefault(receipt, NATIVE_INSTRUCTION_DEFAULT_SHA256).passed).toBe(true);
expect(gradeNativeDefault(receipt).passed).toBe(false);
receipt.files[0]!.sha256 = NATIVE_MASTER_DEFAULT_SHA256;
expect(gradeNativeDefault(receipt, NATIVE_INSTRUCTION_DEFAULT_SHA256).passed).toBe(false);
receipt.files[0]!.sha256 = NATIVE_INSTRUCTION_DEFAULT_SHA256;
receipt.budgets.company = 0;
expect(gradeNativeDefault(receipt, NATIVE_INSTRUCTION_DEFAULT_SHA256).passed).toBe(false);
});
it("rejects duplicate or incomplete capture, dirty or paid source, and stale fixture evidence", () => {
const measurement = {
schema: "paperclip.native-instruction-measurement.v1", sourceSha: "frozen", sourceDirty: false, providerCalls: 0,
fixtureSha256: createHash("sha256").update(readFileSync(new URL("../../packages/paperclip-runner/src/backends/native-instruction-measurement.test.ts", import.meta.url))).digest("hex"),
sourceHashes: Object.fromEntries(Object.entries(NATIVE_INSTRUCTION_VARIANTS.baseline).map(([file, digest]) => [file.split('/').at(-1)!, digest])),
receipts: ['codex', 'acpx', 'opencode'].flatMap(provider => ['v4', 'v5'].flatMap(schema => ['start', 'resume'].map(phase => ({ provider, schema, phase })))),
};
const source = { sourceSha: "frozen", variant: "baseline" };
expect(() => validateNativeInstructionMeasurement(measurement, source)).not.toThrow();
for (const invalid of [
{ ...measurement, receipts: measurement.receipts.slice(1) },
{ ...measurement, receipts: measurement.receipts.map(() => measurement.receipts[0]!) },
{ ...measurement, sourceDirty: true }, { ...measurement, providerCalls: 1 },
{ ...measurement, sourceSha: "other" }, { ...measurement, fixtureSha256: "stale" },
{ ...measurement, sourceHashes: { ...measurement.sourceHashes, "runtime-context.ts": "wrong" } },
]) expect(() => validateNativeInstructionMeasurement(invalid, source)).toThrow();
});
});
@@ -0,0 +1,128 @@
import { createHash } from "node:crypto";
import { execFileSync } from "node:child_process";
import { readFileSync, writeFileSync } from "node:fs";
import { join, resolve } from "node:path";
import type { MatrixExecution } from "./types.js";
import { nativeCompletionDefinitionDigest } from "./native-completion-cases.js";
export const NATIVE_INSTRUCTION_SUITE = "native-instruction-consolidation";
export const NATIVE_INSTRUCTION_BASE_SHA = "2a8a99e4a5f69aa803b3f10b982f583e75a87042";
export const NATIVE_INSTRUCTION_DEFAULT_SHA256 = "c6318bf7e425cc0dd7acd4ff2905dab5c46ffddedef979afdf06720c82f6e95a";
export const NATIVE_INSTRUCTION_PREFLIGHT_ENV = "PAPERCLIP_NATIVE_INSTRUCTION_PREFLIGHT";
const root = resolve(import.meta.dirname, "../..");
const hash = (bytes: Buffer | string) => createHash("sha256").update(bytes).digest("hex");
export const NATIVE_INSTRUCTION_VARIANTS = {
baseline: {
"packages/paperclip-runner/src/backends/runtime-context.ts": "e7c46e81cc9c93f9a4f805b5091ef08c59ea70c62f208cfda70d6edeed363898",
"packages/paperclip-runner/src/backends/codex-native-backend.ts": "f276d436391c9e2f0a7e58ce1302b5c9f1a39ac01c148be257f9d455515a31b9",
"packages/paperclip-runner/src/backends/opencode-native-backend.ts": "d54ddde3a3fdffcda3490d813d27b3b4891448e11cd05f61f9bb4b292d9fd6c2",
},
candidate: {
"packages/paperclip-runner/src/backends/runtime-context.ts": "cae9075fac25f168972c2be4ddb58052ef2940554458e2757d88a5d0e39805c2",
"packages/paperclip-runner/src/backends/codex-native-backend.ts": "affecc515a623e0dfeb338f553ea53ebb7d18baa3d13e4395d17b21000dbee93",
"packages/paperclip-runner/src/backends/opencode-native-backend.ts": "d5dc4cc2c06b37d22be47c9f15f828c06b439d8241a8d34498ca4339273eed96",
},
} as const;
// These are common comparison setup, not additional production changes.
const comparisonFiles = new Set([
...Object.keys(NATIVE_INSTRUCTION_VARIANTS.baseline),
"packages/paperclip-runner/src/backends/runtime-context.test.ts",
"packages/paperclip-runner/src/backends/native-instruction-measurement.test.ts",
"tests/runner-e2e/native-instruction-consolidation.ts",
"tests/runner-e2e/native-instruction-consolidation.test.ts",
"tests/runner-e2e/native-completion-defaults.ts",
"tests/runner-e2e/native-completion-defaults.test.ts",
"tests/runner-e2e/catalog.ts", "tests/runner-e2e/catalog.test.ts", "tests/runner-e2e/launch.ts",
"tests/runner-e2e/runner.spec.ts", "tests/runner-e2e/live-fixtures.ts",
"tests/runner-e2e/README.md", "doc/evals.md",
"doc/plans/2026-10-03-native-completion-consolidation.md",
]);
const git = (...args: string[]) => execFileSync("git", args, { cwd: root, encoding: "utf8" }).trim();
export function nativeInstructionVariant(read = (file: string) => readFileSync(join(root, file))) {
const matches = Object.entries(NATIVE_INSTRUCTION_VARIANTS).filter(([, files]) =>
Object.entries(files).every(([file, digest]) => hash(read(file)) === digest));
if (matches.length !== 1) throw new Error("Mixed or unknown native instruction source");
return matches[0]![0];
}
export function nativeInstructionDefinitionDigest() {
return hash(nativeCompletionDefinitionDigest() + hash(readFileSync(new URL(import.meta.url)))
+ hash(readFileSync(join(root, "packages/paperclip-runner/src/backends/native-instruction-measurement.test.ts"))));
}
export function assertNativeInstructionSelection(executions: readonly MatrixExecution[]) {
for (const execution of executions.filter(value => value.suite.id === NATIVE_INSTRUCTION_SUITE)) {
if (!['runner-codex', 'runner-acpx-claude', 'runner-opencode'].includes(execution.profile.id)
|| execution.profile.generation !== "native" || execution.environment.id !== "local"
|| execution.profile.qualificationCandidate !== undefined || execution.task.expectedRunCount !== 1
|| (execution.task.minimumExpectedRunCount !== undefined && execution.task.minimumExpectedRunCount !== 1)
|| !['assigned-skill-explicit-invocation', 'native-blocked-report'].includes(execution.task.id)
|| execution.task.automaticRetryPolicy !== "single_attempt"
|| execution.id !== `${NATIVE_INSTRUCTION_SUITE}.${execution.profile.id}.local.${execution.task.id}`
|| execution.suite.manualOnly !== true || execution.suite.expectedMatrixSize !== 6
|| execution.suite.tasks.length !== 2
|| !['assigned-skill-explicit-invocation', 'native-blocked-report'].every(id =>
execution.suite.tasks.filter(task => task.id === id && task.expectedRunCount === 1
&& task.automaticRetryPolicy === "single_attempt").length === 1)) {
throw new Error("Native instruction comparison requires the declared local single-attempt cells");
}
}
}
function sourceReceipt() {
const sourceSha = git("rev-parse", "HEAD");
const requestedSource = process.env.PAPERCLIP_RUNNER_E2E_SOURCE_SHA?.trim();
if (requestedSource && requestedSource !== sourceSha) throw new Error("Native instruction source differs from requested immutable revision");
if (git("status", "--porcelain", "--untracked-files=normal")) throw new Error("Native instruction comparison requires clean committed source");
// Do not silently relax shallow history or admit unrelated production edits.
git("merge-base", "--is-ancestor", NATIVE_INSTRUCTION_BASE_SHA, sourceSha);
const changed = git("diff", "--name-only", NATIVE_INSTRUCTION_BASE_SHA, sourceSha).split("\n").filter(Boolean);
if (changed.some(file => !comparisonFiles.has(file))) throw new Error("Native instruction comparison contains unrelated source changes");
return { sourceSha, variant: nativeInstructionVariant(), fixtureDigest: nativeInstructionDefinitionDigest() };
}
export function validateNativeInstructionMeasurement(measurement: {
schema: string; sourceSha: string; sourceDirty: boolean; providerCalls: number; fixtureSha256: string;
sourceHashes: Record<string, string>; receipts: Array<{ provider: string; schema: string; phase: string }>;
}, source: { sourceSha: string; variant: string }) {
const expected = ['codex', 'acpx', 'opencode'].flatMap(provider => ['v4', 'v5'].flatMap(schema =>
['start', 'resume'].map(phase => `${provider}/${schema}/${phase}`))).sort();
const actual = measurement.receipts.map(value => `${value.provider}/${value.schema}/${value.phase}`).sort();
const files = NATIVE_INSTRUCTION_VARIANTS[source.variant as keyof typeof NATIVE_INSTRUCTION_VARIANTS];
if (measurement.schema !== "paperclip.native-instruction-measurement.v1"
|| measurement.sourceSha !== source.sourceSha || measurement.sourceDirty !== false || measurement.providerCalls !== 0
|| measurement.fixtureSha256 !== hash(readFileSync(join(root, "packages/paperclip-runner/src/backends/native-instruction-measurement.test.ts")))
|| JSON.stringify(actual) !== JSON.stringify(expected) || !files
|| Object.entries(files).some(([file, digest]) => measurement.sourceHashes[file.split('/').at(-1)!] !== digest))
throw new Error("Invalid or incomplete native instruction measurement");
}
export function prepareNativeInstructionPreflight(directory: string) {
const source = sourceReceipt();
const measurementPath = join(directory, "native-instruction-measurement.json");
execFileSync(process.execPath, [join(root, "node_modules/vitest/vitest.mjs"), "run", "src/backends/native-instruction-measurement.test.ts"], {
cwd: join(root, "packages/paperclip-runner"), timeout: 60_000,
env: { PATH: process.env.PATH, PAPERCLIP_NATIVE_INSTRUCTION_REPORT: measurementPath }, stdio: "inherit",
});
const measurement = JSON.parse(readFileSync(measurementPath, "utf8"));
validateNativeInstructionMeasurement(measurement, source);
const output = join(directory, "native-instruction-preflight.json");
writeFileSync(output, `${JSON.stringify({ schema: "paperclip.native-instruction-preflight.v1", ...source,
measurementPath, measurementSha256: hash(readFileSync(measurementPath)) }, null, 2)}\n`);
return output;
}
export function verifyNativeInstructionPreflight(path: string | undefined) {
if (!path) throw new Error("Missing native instruction source admission");
const receipt = JSON.parse(readFileSync(path, "utf8"));
const source = sourceReceipt();
if (receipt.schema !== "paperclip.native-instruction-preflight.v1"
|| receipt.sourceSha !== source.sourceSha || receipt.variant !== source.variant
|| receipt.fixtureDigest !== source.fixtureDigest
|| receipt.measurementSha256 !== hash(readFileSync(receipt.measurementPath)))
throw new Error("Stale or mismatched native instruction source admission");
validateNativeInstructionMeasurement(JSON.parse(readFileSync(receipt.measurementPath, "utf8")), source);
return receipt;
}
+9 -4
View File
@@ -1,4 +1,5 @@
import { assertNativeCompletionSelection, NATIVE_COMPLETION_PREFLIGHT_ENV, verifyNativeCompletionPreflight } from "./native-completion-admission.js";
import { assertNativeInstructionSelection, verifyNativeInstructionPreflight, NATIVE_INSTRUCTION_PREFLIGHT_ENV, NATIVE_INSTRUCTION_SUITE, NATIVE_INSTRUCTION_DEFAULT_SHA256 } from "./native-instruction-consolidation.js";
import { captureNativeDefault, gradeNativeDefault, nativeCompletionWorkspaceDigest } from "./native-completion-defaults.js";
import { gradeNativeCompletion } from "./native-completion-scoring.js";
import { assertNativeBlockerReply } from "./native-blocker-visible.js";
@@ -562,6 +563,10 @@ for (const execution of executions) {
assertNativeCompletionSelection([execution]);
verifyNativeCompletionPreflight(process.env[NATIVE_COMPLETION_PREFLIGHT_ENV]);
}
if (execution.suite.id === NATIVE_INSTRUCTION_SUITE) {
assertNativeInstructionSelection([execution]);
verifyNativeInstructionPreflight(process.env[NATIVE_INSTRUCTION_PREFLIGHT_ENV]);
}
const credentials = credentialValues();
const secrets = normalizedSecrets(Object.values(credentials));
const api = new RunnerApi(request);
@@ -859,9 +864,9 @@ for (const execution of executions) {
daytonaImage: process.env.PAPERCLIP_E2E_DAYTONA_IMAGE,
});
if (execution.suite.id === "native-completion") {
if (["native-completion", NATIVE_INSTRUCTION_SUITE].includes(execution.suite.id)) {
const receipt = await captureNativeDefault({ api, agentId: fixtures.agent.id, companyId: fixtures.company.id });
const grade = gradeNativeDefault(receipt);
const grade = gradeNativeDefault(receipt, execution.suite.id === NATIVE_INSTRUCTION_SUITE ? NATIVE_INSTRUCTION_DEFAULT_SHA256 : undefined);
await writeSanitizedJson(snapshotsDir, "native-default-before-execution.json", { receipt, grade }, secrets);
if (!grade.passed) throw new Error("Native production-default hire admission failed before execution");
const [issues, agents, workspaceDigest] = await Promise.all([
@@ -2615,7 +2620,7 @@ for (const execution of executions) {
await expect(proofLink).toHaveAttribute("href", `/api/attachments/${downloadedResponseProof!.attachmentId}/content`);
} else if (execution.suite.id === "task-titles") {
await expect(visibleAgentReplies.filter({ hasText: marker }).last()).toBeVisible({ timeout: 30_000 });
} else if (execution.suite.id === "native-completion" && execution.task.id === "native-blocked-report") {
} else if (["native-completion", NATIVE_INSTRUCTION_SUITE].includes(execution.suite.id) && execution.task.id === "native-blocked-report") {
await assertNativeBlockerReply(visibleAgentReplies, marker);
} else if (execution.task.flow === "warm_three_turn") {
// Prove the persisted user-facing response is visible, independently
@@ -2676,7 +2681,7 @@ for (const execution of executions) {
);
}
}
if (execution.suite.id === "native-completion") {
if (["native-completion", NATIVE_INSTRUCTION_SUITE].includes(execution.suite.id)) {
if (!issue || !nativeInitial) throw new Error("Missing native qualification issue or pre-execution receipt");
const [currentIssue, companyRuns, issues, agents, comments, documents, interactions, workspaceDigest] = await Promise.all([
api.get<IssueRecord>(`/api/issues/${issue.id}`),