mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-27 02:44:50 +01:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Product E2E checks real provider behavior under a campaign budget. > - Its request and trace limits also determine the operating-cost forecast. > - The initial limits greatly exceed the measured marker and feedback cases. > - This pull request lowers those defaults while keeping the same selected cases and full model context limits. > - The benefit is a smaller bounded resource envelope with explicit quota failures and readable historical evidence. ## Linked Issues or Issue Description Refs: #17365 Refs: #17371 **What existing behavior does this improve?** The resource ceilings for bounded Product E2E campaigns and their OTLP relay. **Current behavior** Daily and weekly campaigns allow 60 and 120 model requests. The native relay defaults to 256 MiB of expanded source and 16,384 records per attempt. Both October 23 two-case qualification campaigns used ten model requests. The largest native attempt used 6,531,801 expanded source bytes and 2,455 records. **Proposed behavior** Use 30 daily and 60 weekly model requests. Default native exports to 16 MiB of expanded source and 4,096 records. Preserve the older explicit relay maxima so prior receipts remain valid and deliberate controller overrides still work. **Reason and benefit** The smaller limits leave headroom for the measured cases and reduce the maximum source traffic in the operating forecast. They do not change the daily or weekly dollar cap, model context ceiling, per-request reservation, selected cases, or attempt policy. **Breaking changes** A new bounded attempt that exceeds the smaller default trace allowance receives a quota error. It cannot silently omit required evidence. Existing receipts remain readable. No paid recurrence is enabled by this change. ## What Changed - Set scheduled model request caps to 30 daily and 60 weekly. - Add smaller native relay defaults while preserving the existing explicit maximum and historical receipt validation. - Exercise default count and expanded-source exhaustion through the HTTP relay. Keep a regression for larger historical receipt policies. - Document the measured basis, default capacities, and failure behavior. ## Verification - `pnpm test:e2e:runner:unit`: 4,095 Vitest tests passed, two existing skips; 137 Node contract tests passed. - `pnpm test:e2e:runner:typecheck`: passed. - `git diff --check`: passed. - The relay test accepts two 3-MiB log payloads, rejects the third before upstream dispatch, and retains its quota receipt. - Read-only replay of the prior real hosted campaign preserved all five ingestion receipts and 123,044 payload bytes exactly. Its accounting journal remained byte-for-byte unchanged; zero provider calls. - Full repository typecheck, tests, build and reviews are pending hosted CI. No paid model call was made for this change. Live qualification of the smaller defaults remains open. ## Risks - More verbose future cases may exhaust the new defaults. Such a failure remains visible and requires evidence before increasing a limit. - Source-byte ceilings are not Braintrust invoice units. Operating-cost qualification and reconciliation remain separate requirements. - Manual controller overrides can still use the earlier larger maxima; their cost audit must account for the limits actually recorded in the receipt. ## Model Used OpenAI Codex, GPT-6 family, with reasoning, code execution and tool use. The host does not expose an exact model build identifier or context window size for this session. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge Co-authored-by: Paperclip <noreply@paperclip.ing>
122 lines
8.3 KiB
TypeScript
122 lines
8.3 KiB
TypeScript
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { pathToFileURL } from "node:url";
|
|
import { runnerMatrix } from "./catalog.js";
|
|
import { validateEvalOperationsManifest, type EvalOperationsManifest } from "./operations.js";
|
|
import { evalRequestReservation } from "./eval-gateway.js";
|
|
import prices from "./scheduled-prices.json" with { type: "json" };
|
|
import { scheduledOperatingOverhead, validateOperatingOverhead, type EvalOperatingOverhead } from "./operating-overhead.js";
|
|
import { usdToUnits } from "../../packages/shared/src/money.js";
|
|
import { scheduledForecast } from "./scheduled-forecast.js";
|
|
|
|
export type EvalCadence = "daily" | "weekly";
|
|
export const EVAL_SCHEDULES = { daily: "47 8 * * *", weekly: "47 9 * * 0" } as const;
|
|
const budget = (cadence: EvalCadence) => cadence === "daily" ? "20" : "100";
|
|
const marker = (profile: string) => `core-compatibility.${profile}.local.message-marker`;
|
|
const feedback = (profile: string) => `feedback-persistence.${profile}.local.complaint-and-suggestion`;
|
|
|
|
export function scheduledSelection(cadence: EvalCadence) {
|
|
if (!["daily", "weekly"].includes(cadence)) throw new Error("Unknown eval cadence");
|
|
return [marker("runner-acpx-claude"), feedback("runner-acpx-claude"),
|
|
...(cadence === "weekly" ? [marker("runner-codex"), feedback("runner-codex")] : [])];
|
|
}
|
|
|
|
export function scheduledCadence(event: string, schedule: string, requested = "manual"): EvalCadence | undefined {
|
|
if (event === "schedule") {
|
|
const found = Object.entries(EVAL_SCHEDULES).find(([, expression]) => expression === schedule);
|
|
if (!found) throw new Error("Unrecognized paid eval schedule");
|
|
return found[0] as EvalCadence;
|
|
}
|
|
if (event !== "workflow_dispatch" || !["manual", "daily", "weekly"].includes(requested)) throw new Error("Unrecognized eval trigger");
|
|
return requested === "manual" ? undefined : requested as EvalCadence;
|
|
}
|
|
|
|
export function validateScheduledBudget(input: { cadence: EvalCadence; now: number; overhead: EvalOperatingOverhead }) {
|
|
if (!Number.isFinite(input.now) || input.now < Date.parse(prices.verifiedAt) || input.now >= Date.parse(prices.expiresAt)) {
|
|
throw new Error("Scheduled eval pricing needs review before paid admission");
|
|
}
|
|
const executionIds = scheduledSelection(input.cadence);
|
|
const executions = executionIds.map(id => {
|
|
const execution = runnerMatrix.find(row => row.id === id);
|
|
if (!execution) throw new Error(`Scheduled execution is absent: ${id}`);
|
|
return execution;
|
|
});
|
|
const models = prices.models.filter(model => executions.some(execution => execution.profile.model === model.price.model)) as EvalOperationsManifest["models"];
|
|
if (executions.some(execution => !models.some(model => model.price.model === execution.profile.model
|
|
&& model.provider === (execution.profile.credential === "ANTHROPIC_API_KEY" ? "anthropic" : "openai")))) {
|
|
throw new Error("Missing exact scheduled model pricing");
|
|
}
|
|
const reserved = validateOperatingOverhead(input.overhead, budget(input.cadence));
|
|
if (input.now < Date.parse(input.overhead.verifiedAt) || input.now + 30 * 60_000 >= Date.parse(input.overhead.expiresAt)) {
|
|
throw new Error("Operating overhead allowance does not cover the campaign window");
|
|
}
|
|
if (models.some(model => reserved + usdToUnits(evalRequestReservation({ ...model, apiKey: "forecast", mode: "braintrust" })) > usdToUnits(budget(input.cadence)))) {
|
|
throw new Error("Operating overhead leaves insufficient allowance for the selected model");
|
|
}
|
|
return { executionIds, executions, models };
|
|
}
|
|
|
|
export function scheduledManifest(input: {
|
|
cadence: EvalCadence; campaignId: string; ledgerPath: string; projectId: string; now: number; runAttempt: string;
|
|
overhead: EvalOperatingOverhead;
|
|
}): EvalOperationsManifest {
|
|
if (input.runAttempt !== "1") throw new Error("Paid campaign reruns require the retained journal; a fresh allowance is refused");
|
|
const { executionIds, executions, models } = validateScheduledBudget(input);
|
|
const manifest: EvalOperationsManifest = {
|
|
version: 1, limits: { campaignId: input.campaignId, maxUsd: budget(input.cadence), overhead: input.overhead,
|
|
deadlineAt: new Date(input.now + 30 * 60_000).toISOString(), maxRequests: input.cadence === "daily" ? 30 : 60,
|
|
maxConcurrent: 1, maxAttemptsPerExecution: 1 },
|
|
ledgerPath: input.ledgerPath, executionIds, maxAttemptsPerExecution: 1, route: "braintrust", projectId: input.projectId,
|
|
models,
|
|
};
|
|
validateEvalOperationsManifest(manifest, executions, 0);
|
|
return manifest;
|
|
}
|
|
|
|
export function scheduledMatrix(cadence: EvalCadence) {
|
|
return { include: [{ executionId: "scheduled-campaign", suiteId: "core-compatibility", profileId: "runner-acpx-claude",
|
|
credentialName: "", environmentId: "local", caseId: "scheduled-campaign", timeoutMinutes: 60, needsDaytona: false }],
|
|
needsDaytona: false, executionIds: scheduledSelection(cadence) };
|
|
}
|
|
|
|
export async function prepareScheduledCampaign(env: NodeJS.ProcessEnv, now = Date.now()) {
|
|
const cadence = scheduledCadence(env.EVAL_EVENT ?? "", env.EVAL_SCHEDULE ?? "", env.EVAL_CADENCE);
|
|
if (!cadence || env.EVAL_OPERATIONS_ENABLED !== "true") throw new Error("Bounded eval operations are not enabled");
|
|
if (!/^[1-9][0-9]*$/.test(env.GITHUB_RUN_ID ?? "") || !env.GITHUB_WORKSPACE) throw new Error("Missing workflow campaign identity");
|
|
const campaignId = `gha-${env.GITHUB_RUN_ID}-1-scheduled-campaign`;
|
|
const directory = path.join(env.GITHUB_WORKSPACE, "tests/runner-e2e/results/scheduled");
|
|
const manifest = scheduledManifest({ cadence, campaignId, ledgerPath: path.join(directory, "ledger.jsonl"),
|
|
projectId: env.PAPERCLIP_E2E_BRAINTRUST_PROJECT_ID ?? "", now, runAttempt: env.GITHUB_RUN_ATTEMPT ?? "",
|
|
overhead: scheduledOperatingOverhead(env.EVAL_OPERATING_OVERHEAD_JSON, env.EVAL_TEST_RUNNER, budget(cadence), now, cadence) });
|
|
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
const manifestPath = path.join(directory, "manifest.json");
|
|
await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, { mode: 0o600, flag: "wx" });
|
|
const forecast = { cadence, executionIds: manifest.executionIds, maxUsd: manifest.limits.maxUsd,
|
|
overhead: manifest.limits.overhead, budgetScope: "model requests plus declared operating overhead reservations",
|
|
measurements: scheduledForecast(runnerMatrix.filter(execution => manifest.executionIds.includes(execution.id)), manifest.models),
|
|
basis: "Retained local pilot measurements; successful samples and matching configurations are reported separately",
|
|
uncertainty: "Incomplete provider receipts and no invoice reconciliation; estimates are not a guarantee",
|
|
maximumRequestReservations: manifest.models.map(model => ({ model: model.price.model,
|
|
usd: evalRequestReservation({ ...model, apiKey: "forecast", mode: "braintrust" }) })),
|
|
maximumMonthlyTotalAllowanceUsd: "1120", pricingExpiresAt: prices.expiresAt };
|
|
await writeFile(path.join(directory, "forecast.json"), `${JSON.stringify(forecast, null, 2)}\n`, { mode: 0o600, flag: "wx" });
|
|
return { manifest, manifestPath, campaignId, cadence };
|
|
}
|
|
|
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
const command = process.argv[2];
|
|
if (command === "matrix") {
|
|
const cadence = scheduledCadence(process.env.EVAL_EVENT ?? "", process.env.EVAL_SCHEDULE ?? "", process.env.EVAL_CADENCE);
|
|
if (!cadence || process.env.EVAL_OPERATIONS_ENABLED !== "true") throw new Error("Bounded eval operations are not enabled");
|
|
const now = Date.now();
|
|
const overhead = scheduledOperatingOverhead(process.env.EVAL_OPERATING_OVERHEAD_JSON, process.env.EVAL_TEST_RUNNER, budget(cadence), now, cadence);
|
|
validateScheduledBudget({ cadence, overhead, now });
|
|
console.log(JSON.stringify({ ...scheduledMatrix(cadence), overhead }));
|
|
} else if (command === "prepare") {
|
|
const prepared = await prepareScheduledCampaign(process.env);
|
|
if (!process.env.GITHUB_ENV) throw new Error("Missing workflow environment file");
|
|
const prior = await readFile(process.env.GITHUB_ENV, "utf8");
|
|
await writeFile(process.env.GITHUB_ENV, `${prior}PAPERCLIP_E2E_OPERATIONS_MANIFEST=${prepared.manifestPath}\nPAPERCLIP_E2E_CAMPAIGN_ID=${prepared.campaignId}\nPAPERCLIP_E2E_ACCOUNTING_CAMPAIGN_ID=${prepared.campaignId}\n`);
|
|
} else throw new Error("Usage: scheduled-operations.ts <matrix|prepare>");
|
|
}
|