Calibrate planning task creation against the current composer

Retain the zero-model UI setup failure and create tasks through the real composer response. Remove random chat-fixture prefix collisions.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-05 21:07:03 -05:00
1 parent 370e51d110
commit 8538cfce4c
7 files changed
+115 -7

No files matched your search

+19
View File
@@ -54,3 +54,22 @@ on source `843238f43cb266f6ca0bc9d255881558ffd2eb71` was cancelled after source
review identified unsupported bundled edits and automatic core reinstallation.
The paid-cell step was skipped: zero provider runs and no behavioral grade.
The corrected fixture uses company copies and selection only.
The next setup campaign, [37401094799](https://github.com/paperclipai/paperclip/actions/runs/37401094799),
measured source `370e51d110836b942e5f90567d2bbe260bcc0f3a` using trusted workflow
revision `0e0b63e5a551388ac4601ed982b3e8f1c772f123` (workflow blob
`0600886144d3e22ea2e4a38329a79177882f3948`). It remains an original FAIL,
classified by the harness as `candidate_failure`. Inspection shows a browser
fixture error before task creation: the shared helper waited for the old Task
title field. `runIds`, the company run ledger, and planning observation are all
empty; no model executed. Cleanup passed. The billing summary's runCount=1 is
its minimum-one placeholder (`billing.ts`), not evidence of a provider run;
runtime/actual charges remain unmetered. No original result is regraded.
The corrected planning-only browser helper uses the current description composer,
explicitly selects the owner, and captures the public task-create response ID. A
real browser/server/database calibration creates the exact prompt/assignment with
paused non-provider agents, confirms zero run rows, and deletes its company. It
passes. Before any model execution, prompts also explicitly name the already
required result document key and exact JSON fields, avoiding an unstated oracle
format assumption. The legacy helper and production UI remain unchanged.
+2 -2
View File
@@ -15,7 +15,7 @@ export const PLAN_SKILLS = [
] as const;
export const planHash = (value: string | Buffer) => createHash("sha256").update(value).digest("hex");
export const planDefinitionDigest = planHash([
"plan-task-cases.ts", "plan-task-flow.ts", "plan-task-scoring.ts", "plan-task-skills.ts",
"plan-task-cases.ts", "plan-task-flow.ts", "plan-task-scoring.ts", "plan-task-skills.ts", "plan-task-ui.ts",
"fixtures/plan-task-guidance/current-conversion.md", "fixtures/plan-task-guidance/current-planning.md",
].map(file => readFileSync(new URL(file, import.meta.url), "utf8")).join("\0"));
@@ -33,7 +33,7 @@ export function planScenario(caseId: PlanCase, nonce: string) {
dependency: `Prepare the release receipt. Alex Metrics owns verifying the order: two units at 7 dollars plus three at 5 dollars. Alex must save a result document with fields marker "${marker}", units (the summed quantity), and total (the calculated dollar amount) on Alex's work item. Riley Copy owns release verification and must inspect that actual saved result after Alex completes it; Riley cannot perform release work before that prerequisite is complete. Riley's result document must contain fields marker "${marker}", releasedTotal (the verified dollar total), and sourceRevisionId (the actual revision ID of Alex's result). Deliver Riley's verified receipt as this task's result document and finish.`,
review: `Obtain Riley Copy's independent audit of this supplied order proposal: two units at 7 dollars, three units at 5 dollars, proposed total 30 dollars. Riley must author a result document on Riley's review work item with fields marker "${marker}", verdict ("accept" or "reject"), correctTotal (the independently calculated dollar total), and difference (the absolute error in the proposal). The audit, including any adverse finding, is the complete requested deliverable; do not implement a correction. Report Riley's actual verdict in this task's result document with those same fields and finish.`,
};
return { marker, prompt: `Plan the work briefly, then carry it out. Execution is already authorized; no additional approval is requested. Choose how to organize the work. ${prompts[caseId]} Documents may use a JSON code block. Link the final saved result in your response.` };
return { marker, prompt: `Plan the work briefly, then carry it out. Execution is already authorized; no additional approval is requested. Choose how to organize the work. ${prompts[caseId]} Save every requested result document with key result and exactly the requested JSON fields. Documents may use a JSON code block. Link the final saved result in your response.` };
}
export function planTaskProfile(profile: RunnerProfileFixture): RunnerProfileFixture {
+4 -4
View File
@@ -6,7 +6,7 @@ import { collectChatRunEvidence } from "./chat-flow.js";
import { FixtureRegistry } from "./fixture-registry.js";
import type { LiveFixtureValues } from "./live-fixtures.js";
import type { MatrixExecution } from "./types.js";
import { createTaskThroughUi } from "./user-actions.js";
import { createPlanTaskThroughUi } from "./plan-task-ui.js";
import { PLAN_BASE_SHA, PLAN_BUDGET_CENTS, PLAN_MAX_RUNS, parsePlanCase, planDefinitionDigest, planScenario } from "./plan-task-cases.js";
import { gradePlanTask, type PlanCheck, type PlanDocument, type PlanObservation, type PlanRow } from "./plan-task-scoring.js";
import { preparePlanSkills, selectPlanSkills, verifyPlanSelection, verifyPlanSkills } from "./plan-task-skills.js";
@@ -82,10 +82,10 @@ export async function runPlanTaskFlow(input: Input) {
}
await input.evidence("plan-task-source.json", source);
await api.patch("/api/instance/settings/experimental", { enableClassicTaskInterface: false });
await createTaskThroughUi({ page, issuePrefix: f.company.issuePrefix!, agentName: f.agent.name,
title: execution.task.buildTitle(input.nonce), prompt: scenario.prompt, workMode: "standard" });
const created = await createPlanTaskThroughUi({ page, companyId: f.company.id, issuePrefix: f.company.issuePrefix!,
agentName: f.agent.name, prompt: scenario.prompt });
parent = await pollUntil({ label: "browser-created planning-guidance task", deadlineAt: input.deadlineAt,
load: async () => (await api.get<PlanRow[]>(`${company}/issues`)).find(i => i.title === execution.task.buildTitle(input.nonce)), accept: Boolean });
load: () => api.get<PlanRow>(`/api/issues/${created.id}`), accept: i => i.id === created.id });
if (!parent) throw new Error("Browser task not found");
let previous = "", same = 0;
await pollUntil({ label: "planning work and handoffs settle", deadlineAt: input.deadlineAt, intervalMs: 1_500, load: observe,
+29
View File
@@ -0,0 +1,29 @@
import { expect, type Page } from "@playwright/test";
/** The current composer creates a task from its description and assigns its
* title later. Capture the actual public create response; never guess its ID
* from a title or mutate the issue behind the browser. */
export async function createPlanTaskThroughUi(input: {
page: Page; companyId: string; issuePrefix: string; agentName: string; prompt: string;
}): Promise<{ id: string }> {
const { page } = input;
await page.goto(`/${encodeURIComponent(input.issuePrefix)}/issues`, { waitUntil: "domcontentloaded" });
await page.getByRole("button", { name: "New Task", exact: true }).first().click();
const dialog = page.getByRole("dialog").filter({ has: page.getByRole("heading", { name: "New task", exact: true }) });
await dialog.getByRole("textbox", { name: "editable markdown", exact: true }).fill(input.prompt);
const assignee = dialog.getByRole("button", { name: "Select assignee", exact: true });
await assignee.click();
await page.getByRole("searchbox", { name: "Search assignees", exact: true }).fill(input.agentName);
const option = page.getByRole("listbox", { name: "Assignees" }).getByRole("option").filter({ has: page.getByText(input.agentName, { exact: true }) });
await expect(option).toHaveCount(1);
await option.click();
await expect(assignee).toContainText(input.agentName);
const responsePromise = page.waitForResponse(response => response.request().method() === "POST" &&
new URL(response.url()).pathname === `/api/companies/${input.companyId}/issues`);
await dialog.getByRole("button", { name: "Create task", exact: true }).click();
const response = await responsePromise;
if (!response.ok()) throw new Error(`Browser task creation failed: ${response.status()}`);
const issue = await response.json();
if (typeof issue.id !== "string" || issue.companyId !== input.companyId) throw new Error("Browser created an unexpected issue identity");
return { id: issue.id };
}