426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
2285 lines
79 KiB
TypeScript
2285 lines
79 KiB
TypeScript
#!/usr/bin/env bun
|
|
/**
|
|
* Seed-dataset GEPA optimizer for a LifeOps per-capability task, run against
|
|
* gpt-oss-120b on Cerebras (#9299, Scope 5).
|
|
*
|
|
* Unlike `lifeops:gepa` (which buckets recorded trajectories), this entrypoint
|
|
* carries a small, hand-curated seed dataset so the GEPA loop can run before
|
|
* any trajectories have been captured. It reuses the real building blocks:
|
|
* - the GEPA optimizer (`runGepa`) + LifeOps scorer (`scoreLifeOpsTask`),
|
|
* - plugin-training's Cerebras client (`getTrainingUseModelAdapter`),
|
|
* - the standard optimized-prompt store (`OptimizedPromptService.setPrompt`),
|
|
* so the artifact auto-loads at boot.
|
|
*
|
|
* It prints the measured before/after score and persists the optimized prompt
|
|
* only when `--apply` is passed and the optimized prompt beats the baseline.
|
|
*
|
|
* TRAIN_MODEL_PROVIDER=cerebras CEREBRAS_API_KEY=... \
|
|
* bun run --cwd plugins/plugin-training scripts/lifeops-gepa-seed.ts \
|
|
* --task calendar_extract [--apply] [--generations 2] [--population 4]
|
|
*
|
|
* Providers (#11384): `cerebras` and `anthropic` use API keys via
|
|
* `cerebras-eval-model`; `cli` runs on a subscription-only host through
|
|
* plugin-cli-inference's `claude --print` lane (#10757, no API key):
|
|
*
|
|
* TRAIN_MODEL_PROVIDER=cli EVAL_MODEL_PROVIDER=cli \
|
|
* bun run --cwd plugins/plugin-training scripts/lifeops-gepa-seed.ts \
|
|
* --task reminder_dispatch --apply
|
|
*
|
|
* Scoring: the structured planner tasks (calendar_extract, schedule_plan,
|
|
* inbox_triage, health_checkin) use the deterministic field-match scorer; the
|
|
* prose/NL tasks (reminder_dispatch, meeting_prep, morning_brief,
|
|
* screentime_recap) are graded by a live judge against per-example rubrics
|
|
* (`createLifeOpsJudgeCompare`).
|
|
*
|
|
* Without `--apply` it runs the optimization and reports metrics but does not
|
|
* persist (dry run).
|
|
*/
|
|
import { join, resolve } from "node:path";
|
|
import { pathToFileURL } from "node:url";
|
|
import { parseArgs } from "node:util";
|
|
import {
|
|
type OptimizedPromptArtifact,
|
|
OptimizedPromptService,
|
|
type OptimizedPromptTask,
|
|
} from "@elizaos/core";
|
|
import { CALENDAR_PLAN_INSTRUCTIONS } from "../../plugin-calendar/src/actions/optimized-prompt-instructions.ts";
|
|
import {
|
|
HEALTH_PLAN_INSTRUCTIONS,
|
|
SCREENTIME_RECAP_INSTRUCTIONS,
|
|
} from "../../plugin-health/src/actions/optimized-prompt-instructions.ts";
|
|
import { INBOX_TRIAGE_INSTRUCTIONS } from "../../plugin-inbox/src/inbox/triage-classifier.ts";
|
|
import {
|
|
BRIEF_NARRATIVE_INSTRUCTIONS,
|
|
MEETING_PREP_INSTRUCTIONS,
|
|
REMINDER_DISPATCH_INSTRUCTIONS,
|
|
SCHEDULE_PLAN_INSTRUCTIONS,
|
|
} from "../../plugin-personal-assistant/src/lifeops/optimized-prompt-instructions.ts";
|
|
import {
|
|
type EvalModelClient,
|
|
getEvalModelClient,
|
|
getTrainingUseModelAdapter,
|
|
} from "../src/core/cerebras-eval-model.ts";
|
|
import {
|
|
createLifeOpsJudgeCompare,
|
|
encodeJudgeExpectation,
|
|
LIFEOPS_JUDGE_TASKS,
|
|
} from "../src/core/lifeops-judge-scorer.ts";
|
|
import {
|
|
createPromptScorer,
|
|
createRuntimeAdapter,
|
|
type OptimizationExample,
|
|
type OptimizerResult,
|
|
runGepa,
|
|
scoreLifeOpsTask,
|
|
} from "../src/optimizers/index.ts";
|
|
import { getCliModelClient, resolveCliModel } from "./lib/cli-model.ts";
|
|
|
|
const DEFAULT_GENERATIONS = 2;
|
|
const DEFAULT_POPULATION = 4;
|
|
const MAX_GENERATIONS = 20;
|
|
const MAX_POPULATION = 50;
|
|
const MIN_PERSIST_DELTA = 0.0001;
|
|
|
|
export interface SeedTask {
|
|
task: OptimizedPromptTask;
|
|
baseline: string;
|
|
dataset: OptimizationExample[];
|
|
}
|
|
|
|
interface CalendarPlannerInputArgs {
|
|
currentMessage: string;
|
|
intent?: string;
|
|
recentConversation?: string;
|
|
}
|
|
|
|
function calendarPlannerInput(args: CalendarPlannerInputArgs): string {
|
|
const intent = args.intent ?? args.currentMessage;
|
|
return [
|
|
"Current timezone: America/Los_Angeles",
|
|
"LOCAL DATE ANCHORS (authoritative - IGNORE UTC day for date arithmetic): yesterday = 2026-06-23, today = 2026-06-24, tomorrow = 2026-06-25.",
|
|
"Current local datetime: Wednesday, June 24, 2026 at 9:00:00 AM PDT",
|
|
"Current ISO datetime (informational only - do NOT use for 'today/tomorrow/yesterday'): 2026-06-24T16:00:00.000Z",
|
|
"When the user says 'today', 'tomorrow', 'yesterday', or similar, resolve the calendar day from the LOCAL DATE ANCHORS above (not from the UTC datetime) and build timeMin/timeMax as a full local-day window in the current timezone.",
|
|
"",
|
|
`Current request:\n${args.currentMessage}`,
|
|
`Resolved intent:\n${intent}`,
|
|
`Recent conversation:\n${args.recentConversation ?? "(none)"}`,
|
|
].join("\n");
|
|
}
|
|
|
|
function expectedCalendarPlan(fields: Record<string, unknown>): string {
|
|
return JSON.stringify(fields);
|
|
}
|
|
|
|
interface SchedulingPlannerInputArgs {
|
|
currentMessage: string;
|
|
intent?: string;
|
|
params?: Record<string, unknown>;
|
|
recentConversation?: string;
|
|
}
|
|
|
|
function schedulingPlannerInput(args: SchedulingPlannerInputArgs): string {
|
|
const intent = args.intent ?? args.currentMessage;
|
|
const params = args.params ?? {};
|
|
const paramLines = Object.entries(params)
|
|
.map(([key, value]) => `${key}: ${String(value)}`)
|
|
.join("\n");
|
|
return [
|
|
`Current request:\n${args.currentMessage}`,
|
|
`Resolved intent:\n${intent}`,
|
|
`Structured parameters:\n${paramLines || "(none)"}`,
|
|
`Recent conversation:\n${args.recentConversation ?? "(none)"}`,
|
|
].join("\n");
|
|
}
|
|
|
|
function expectedSchedulePlan(fields: Record<string, unknown>): string {
|
|
return JSON.stringify(fields);
|
|
}
|
|
|
|
function inboxClassificationInput(args: {
|
|
senderName: string;
|
|
channel?: string;
|
|
text: string;
|
|
ownerContext?: string;
|
|
}): string {
|
|
return [
|
|
"Owner context:",
|
|
args.ownerContext ??
|
|
"The owner prioritizes legal, finance, and calendar-critical messages.",
|
|
"",
|
|
"Messages:",
|
|
JSON.stringify(
|
|
[
|
|
{
|
|
id: "msg-1",
|
|
senderName: args.senderName,
|
|
channel: args.channel ?? "email",
|
|
text: args.text,
|
|
},
|
|
],
|
|
null,
|
|
2,
|
|
),
|
|
].join("\n");
|
|
}
|
|
|
|
function expectedInboxClassification(fields: Record<string, unknown>): string {
|
|
return JSON.stringify(fields);
|
|
}
|
|
|
|
interface HealthPlannerInputArgs {
|
|
currentMessage: string;
|
|
intent?: string;
|
|
params?: Record<string, unknown>;
|
|
recentConversation?: string;
|
|
}
|
|
|
|
// Mirrors resolveHealthPlanWithLlm's composed prompt body
|
|
// (plugin-health/src/actions/health.ts) so GEPA optimizes the production
|
|
// health_checkin planner, not a divergent shape.
|
|
function healthPlannerInput(args: HealthPlannerInputArgs): string {
|
|
const intent = args.intent ?? args.currentMessage;
|
|
const params = args.params ?? {};
|
|
const paramLines = Object.entries(params)
|
|
.map(([key, value]) => `${key}: ${String(value)}`)
|
|
.join("\n");
|
|
return [
|
|
"Current request:",
|
|
args.currentMessage,
|
|
"Resolved intent:",
|
|
intent,
|
|
"Structured parameters:",
|
|
paramLines || "(none)",
|
|
"Recent conversation:",
|
|
args.recentConversation ?? "(none)",
|
|
].join("\n");
|
|
}
|
|
|
|
function expectedHealthPlan(fields: Record<string, unknown>): string {
|
|
return JSON.stringify(fields);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Prose/NL task input builders (#11384). These mirror the production prompt
|
|
// composition that FOLLOWS the resolved instructions at each call site, since
|
|
// the GEPA scorer composes `${candidatePrompt}\n\n${input.user}`. Expected
|
|
// outputs are judge rubrics (encodeJudgeExpectation), graded live by
|
|
// `createLifeOpsJudgeCompare`, not exact-matched.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// Mirrors buildReminderDispatchPrompt (plugin-personal-assistant
|
|
// lifeops/domains/reminders-service.ts): everything after the resolved
|
|
// instructions, ending with the "Reminder text:" cue.
|
|
function reminderDispatchInput(args: {
|
|
title: string;
|
|
due: string;
|
|
channel: string;
|
|
urgency: "low" | "medium" | "high" | "critical";
|
|
lifecycle: "plan" | "escalation";
|
|
voice?: string;
|
|
recentConversation?: string[];
|
|
nearbyReminderTitles?: string[];
|
|
}): string {
|
|
return [
|
|
"Character voice:",
|
|
args.voice ?? "No extra character context.",
|
|
"",
|
|
"Current reminder:",
|
|
`- title: ${args.title}`,
|
|
`- due: ${args.due}`,
|
|
`- channel: ${args.channel}`,
|
|
`- urgency: ${args.urgency}`,
|
|
`- lifecycle: ${args.lifecycle}`,
|
|
"",
|
|
"Recent conversation:",
|
|
args.recentConversation && args.recentConversation.length > 0
|
|
? args.recentConversation.join("\n")
|
|
: "No recent conversation available.",
|
|
"",
|
|
"Other reminders around this time:",
|
|
args.nearbyReminderTitles && args.nearbyReminderTitles.length > 0
|
|
? args.nearbyReminderTitles.map((title) => `- ${title}`).join("\n")
|
|
: "None.",
|
|
"",
|
|
"Reminder text:",
|
|
].join("\n");
|
|
}
|
|
|
|
// Mirrors buildNarrativePrompt (plugin-personal-assistant actions/brief.ts)
|
|
// for both morning_brief and meeting_prep. Production interleaves the header
|
|
// BEFORE the resolved instructions ("header\n\ninstructions\n\nData:"); the
|
|
// GEPA composition puts the candidate instructions first, so the header leads
|
|
// input.user instead — the data payload and header text are identical.
|
|
function briefNarrativeInput(args: {
|
|
kind: "morning" | "evening" | "weekly";
|
|
period: "today" | "tomorrow" | "this_week";
|
|
sections: Record<string, unknown>;
|
|
}): string {
|
|
const payload = JSON.stringify(
|
|
{ kind: args.kind, period: args.period, sections: args.sections },
|
|
null,
|
|
2,
|
|
);
|
|
return [
|
|
`You are composing the owner's ${args.kind} briefing for ${args.period}.`,
|
|
"",
|
|
"Data:",
|
|
payload,
|
|
].join("\n");
|
|
}
|
|
|
|
// Mirrors the screen-time recap render context (plugin-health
|
|
// actions/screen-time.ts respond → renderReply with
|
|
// buildScreenTimeRecapRules): the optimizable policy is the candidate prompt;
|
|
// the reply is rendered from the owner request + screen-time context below.
|
|
function screentimeRecapInput(args: {
|
|
request: string;
|
|
thisPeriodLabel: string;
|
|
thisPeriod: Array<{ app: string; minutes: number }>;
|
|
priorPeriodLabel?: string;
|
|
priorPeriod?: Array<{ app: string; minutes: number }>;
|
|
usageNotes?: string[];
|
|
}): string {
|
|
const lines = [
|
|
"Owner request:",
|
|
args.request,
|
|
"",
|
|
"Screen-time context:",
|
|
`This period (${args.thisPeriodLabel}):`,
|
|
...args.thisPeriod.map((row) => `- ${row.app}: ${row.minutes}m`),
|
|
];
|
|
if (args.priorPeriod && args.priorPeriodLabel) {
|
|
lines.push(
|
|
`Prior period (${args.priorPeriodLabel}):`,
|
|
...args.priorPeriod.map((row) => `- ${row.app}: ${row.minutes}m`),
|
|
);
|
|
} else {
|
|
lines.push("Prior period: no data recorded (first tracked period).");
|
|
}
|
|
if (args.usageNotes && args.usageNotes.length > 0) {
|
|
lines.push("Usage notes:", ...args.usageNotes.map((note) => `- ${note}`));
|
|
}
|
|
return lines.join("\n");
|
|
}
|
|
|
|
function judgeExpectation(reference: string, rubric: string[]): string {
|
|
return encodeJudgeExpectation({ reference, rubric });
|
|
}
|
|
|
|
export const SEED_TASKS: Record<string, SeedTask> = {
|
|
// calendar_extract is the live calendar action planner, not the downstream
|
|
// create-event field extractor. Keep the baseline and rows aligned with
|
|
// CALENDAR_PLAN_INSTRUCTIONS and CalendarLlmPlan.
|
|
calendar_extract: {
|
|
task: "calendar_extract",
|
|
baseline: CALENDAR_PLAN_INSTRUCTIONS,
|
|
dataset: [
|
|
{
|
|
input: {
|
|
user: calendarPlannerInput({
|
|
currentMessage: "What's on my calendar today?",
|
|
}),
|
|
},
|
|
expectedOutput: expectedCalendarPlan({
|
|
subaction: "feed",
|
|
shouldAct: true,
|
|
queries: [],
|
|
timeMin: "2026-06-24T00:00:00-07:00",
|
|
timeMax: "2026-06-25T00:00:00-07:00",
|
|
windowLabel: "today",
|
|
}),
|
|
},
|
|
{
|
|
input: {
|
|
user: calendarPlannerInput({
|
|
currentMessage: "What's my next meeting?",
|
|
}),
|
|
},
|
|
expectedOutput: expectedCalendarPlan({
|
|
subaction: "next_event",
|
|
shouldAct: true,
|
|
queries: [],
|
|
}),
|
|
},
|
|
{
|
|
input: {
|
|
user: calendarPlannerInput({
|
|
currentMessage: "Find my return flight to Denver.",
|
|
}),
|
|
},
|
|
expectedOutput: expectedCalendarPlan({
|
|
subaction: "search_events",
|
|
shouldAct: true,
|
|
queries: ["return flight to denver", "denver"],
|
|
}),
|
|
},
|
|
{
|
|
input: {
|
|
user: calendarPlannerInput({
|
|
currentMessage:
|
|
"Set up a dentist appointment on March 3rd at 9am for 30 minutes at Downtown Dental.",
|
|
}),
|
|
},
|
|
expectedOutput: expectedCalendarPlan({
|
|
subaction: "create_event",
|
|
shouldAct: true,
|
|
title: "dentist appointment",
|
|
}),
|
|
},
|
|
{
|
|
input: {
|
|
user: calendarPlannerInput({
|
|
currentMessage:
|
|
"Block 2-3pm tomorrow for a 1:1 with Priya in the small conference room.",
|
|
}),
|
|
},
|
|
expectedOutput: expectedCalendarPlan({
|
|
subaction: "create_event",
|
|
shouldAct: true,
|
|
title: "1:1 with Priya",
|
|
}),
|
|
},
|
|
{
|
|
input: {
|
|
user: calendarPlannerInput({
|
|
currentMessage: "Move the dentist appointment to Friday afternoon.",
|
|
}),
|
|
},
|
|
expectedOutput: expectedCalendarPlan({
|
|
subaction: "update_event",
|
|
shouldAct: true,
|
|
queries: ["dentist appointment"],
|
|
windowLabel: "Friday afternoon",
|
|
}),
|
|
},
|
|
{
|
|
input: {
|
|
user: calendarPlannerInput({
|
|
currentMessage: "Cancel the team meeting tomorrow.",
|
|
}),
|
|
},
|
|
expectedOutput: expectedCalendarPlan({
|
|
subaction: "delete_event",
|
|
shouldAct: true,
|
|
queries: ["team meeting"],
|
|
timeMin: "2026-06-25T00:00:00-07:00",
|
|
timeMax: "2026-06-26T00:00:00-07:00",
|
|
windowLabel: "tomorrow",
|
|
}),
|
|
},
|
|
{
|
|
input: {
|
|
user: calendarPlannerInput({
|
|
currentMessage: "What do I have while I'm in Tokyo?",
|
|
}),
|
|
},
|
|
expectedOutput: expectedCalendarPlan({
|
|
subaction: "trip_window",
|
|
shouldAct: true,
|
|
queries: ["tokyo"],
|
|
tripLocation: "Tokyo",
|
|
}),
|
|
},
|
|
{
|
|
input: {
|
|
user: calendarPlannerInput({
|
|
currentMessage:
|
|
"Réserve un rendez-vous chez le médecin mardi à 10h pour une heure à la clinique du centre.",
|
|
}),
|
|
},
|
|
expectedOutput: expectedCalendarPlan({
|
|
subaction: "create_event",
|
|
shouldAct: true,
|
|
title: "rendez-vous médecin",
|
|
}),
|
|
},
|
|
{
|
|
input: {
|
|
user: calendarPlannerInput({
|
|
currentMessage:
|
|
"Termin beim Friseur am Samstag um 14 Uhr, eine halbe Stunde, im Salon Schmidt.",
|
|
}),
|
|
},
|
|
expectedOutput: expectedCalendarPlan({
|
|
subaction: "create_event",
|
|
shouldAct: true,
|
|
title: "Friseurtermin",
|
|
}),
|
|
},
|
|
{
|
|
input: {
|
|
user: calendarPlannerInput({
|
|
currentMessage: "Can you help me with my calendar?",
|
|
}),
|
|
},
|
|
expectedOutput: expectedCalendarPlan({
|
|
subaction: null,
|
|
shouldAct: false,
|
|
}),
|
|
},
|
|
],
|
|
},
|
|
// schedule_plan is the live scheduling-negotiation planner
|
|
// (buildSchedulingPlanPrompt → SCHEDULE_PLAN_INSTRUCTIONS). It returns a JSON
|
|
// object with subaction / shouldAct / response. Rows cover every subaction,
|
|
// the wrong-tool and vague guards (shouldAct=false), and multilingual,
|
|
// formal+informal phrasing per the GEPA-real-conversation requirement (#9299).
|
|
schedule_plan: {
|
|
task: "schedule_plan",
|
|
baseline: SCHEDULE_PLAN_INSTRUCTIONS,
|
|
dataset: [
|
|
{
|
|
input: {
|
|
user: schedulingPlannerInput({
|
|
currentMessage:
|
|
"Set up a time to meet with Jordan next week for a 30-minute sync.",
|
|
}),
|
|
},
|
|
expectedOutput: expectedSchedulePlan({
|
|
subaction: "start",
|
|
shouldAct: true,
|
|
}),
|
|
},
|
|
{
|
|
input: {
|
|
user: schedulingPlannerInput({
|
|
currentMessage:
|
|
"Propose Thursday at 2pm for the budget review negotiation.",
|
|
}),
|
|
},
|
|
expectedOutput: expectedSchedulePlan({
|
|
subaction: "propose",
|
|
shouldAct: true,
|
|
}),
|
|
},
|
|
{
|
|
input: {
|
|
user: schedulingPlannerInput({
|
|
currentMessage: "Accept the 3pm slot Dana proposed.",
|
|
}),
|
|
},
|
|
expectedOutput: expectedSchedulePlan({
|
|
subaction: "respond",
|
|
shouldAct: true,
|
|
}),
|
|
},
|
|
{
|
|
input: {
|
|
user: schedulingPlannerInput({
|
|
currentMessage:
|
|
"Lock in the Tuesday 10am option for the design sync.",
|
|
}),
|
|
},
|
|
expectedOutput: expectedSchedulePlan({
|
|
subaction: "finalize",
|
|
shouldAct: true,
|
|
}),
|
|
},
|
|
{
|
|
input: {
|
|
user: schedulingPlannerInput({
|
|
currentMessage:
|
|
"Cancel the scheduling negotiation for the offsite planning call.",
|
|
}),
|
|
},
|
|
expectedOutput: expectedSchedulePlan({
|
|
subaction: "cancel",
|
|
shouldAct: true,
|
|
}),
|
|
},
|
|
{
|
|
input: {
|
|
user: schedulingPlannerInput({
|
|
currentMessage: "Which scheduling negotiations are still open?",
|
|
}),
|
|
},
|
|
expectedOutput: expectedSchedulePlan({
|
|
subaction: "list_active",
|
|
shouldAct: true,
|
|
}),
|
|
},
|
|
{
|
|
input: {
|
|
user: schedulingPlannerInput({
|
|
currentMessage:
|
|
"Show me the proposed times for the Q3 roadmap meeting.",
|
|
}),
|
|
},
|
|
expectedOutput: expectedSchedulePlan({
|
|
subaction: "list_proposals",
|
|
shouldAct: true,
|
|
}),
|
|
},
|
|
{
|
|
input: {
|
|
user: schedulingPlannerInput({
|
|
currentMessage:
|
|
"Trouve un créneau avec Camille la semaine prochaine pour caler une réunion d'une heure.",
|
|
}),
|
|
},
|
|
expectedOutput: expectedSchedulePlan({
|
|
subaction: "start",
|
|
shouldAct: true,
|
|
}),
|
|
},
|
|
{
|
|
input: {
|
|
user: schedulingPlannerInput({
|
|
currentMessage:
|
|
"Acepto la propuesta de las 4 de la tarde de Marco.",
|
|
}),
|
|
},
|
|
expectedOutput: expectedSchedulePlan({
|
|
subaction: "respond",
|
|
shouldAct: true,
|
|
}),
|
|
},
|
|
{
|
|
input: {
|
|
user: schedulingPlannerInput({
|
|
currentMessage: "What's on my calendar today?",
|
|
}),
|
|
},
|
|
expectedOutput: expectedSchedulePlan({
|
|
subaction: null,
|
|
shouldAct: false,
|
|
}),
|
|
},
|
|
{
|
|
input: {
|
|
user: schedulingPlannerInput({
|
|
currentMessage: "Can you help me with scheduling stuff?",
|
|
}),
|
|
},
|
|
expectedOutput: expectedSchedulePlan({
|
|
subaction: null,
|
|
shouldAct: false,
|
|
}),
|
|
},
|
|
],
|
|
},
|
|
// inbox_triage is the live cross-channel classifier in plugin-inbox. Keep
|
|
// the seed rows aligned with INBOX_TRIAGE_INSTRUCTIONS so GEPA optimizes the
|
|
// production classifier, not the old PA Gmail planner.
|
|
inbox_triage: {
|
|
task: "inbox_triage",
|
|
baseline: INBOX_TRIAGE_INSTRUCTIONS,
|
|
dataset: [
|
|
{
|
|
input: {
|
|
user: inboxClassificationInput({
|
|
senderName: "CFO",
|
|
text: "Wire cutoff is in 20 minutes and we need your approval on the settlement transfer.",
|
|
}),
|
|
},
|
|
expectedOutput: expectedInboxClassification({
|
|
category: "urgent",
|
|
urgency: "high",
|
|
}),
|
|
},
|
|
{
|
|
input: {
|
|
user: inboxClassificationInput({
|
|
senderName: "Mia",
|
|
text: "Can you confirm whether 2pm works and send the deck before the client call?",
|
|
}),
|
|
},
|
|
expectedOutput: expectedInboxClassification({
|
|
category: "needs_reply",
|
|
urgency: "medium",
|
|
}),
|
|
},
|
|
{
|
|
input: {
|
|
user: inboxClassificationInput({
|
|
senderName: "Northstar Vendor",
|
|
text: "Your renewal invoice is attached for records. No action is needed unless details changed.",
|
|
}),
|
|
},
|
|
expectedOutput: expectedInboxClassification({
|
|
category: "info",
|
|
urgency: "low",
|
|
}),
|
|
},
|
|
{
|
|
input: {
|
|
user: inboxClassificationInput({
|
|
senderName: "Airline",
|
|
text: "Boarding pass for Denver is ready. Gate B12. Boarding starts at 4:35pm.",
|
|
}),
|
|
},
|
|
expectedOutput: expectedInboxClassification({
|
|
category: "notify",
|
|
urgency: "medium",
|
|
}),
|
|
},
|
|
{
|
|
input: {
|
|
user: inboxClassificationInput({
|
|
senderName: "Marketing Newsletter",
|
|
text: "This week's roundup: five productivity tips and our latest product launch.",
|
|
}),
|
|
},
|
|
expectedOutput: expectedInboxClassification({
|
|
category: "ignore",
|
|
urgency: "low",
|
|
}),
|
|
},
|
|
{
|
|
input: {
|
|
user: inboxClassificationInput({
|
|
senderName: "Ana",
|
|
text: "¿Puedes revisar la factura de renovación hoy y decirme si la aprobamos?",
|
|
}),
|
|
},
|
|
expectedOutput: expectedInboxClassification({
|
|
category: "needs_reply",
|
|
urgency: "medium",
|
|
}),
|
|
},
|
|
{
|
|
input: {
|
|
user: inboxClassificationInput({
|
|
senderName: "Security",
|
|
text: "Unusual login detected from a new device. Confirm whether this was you.",
|
|
}),
|
|
},
|
|
expectedOutput: expectedInboxClassification({
|
|
category: "urgent",
|
|
urgency: "high",
|
|
}),
|
|
},
|
|
{
|
|
input: {
|
|
user: inboxClassificationInput({
|
|
senderName: "Calendar Bot",
|
|
text: "Room booking accepted for tomorrow's staff meeting.",
|
|
}),
|
|
},
|
|
expectedOutput: expectedInboxClassification({
|
|
category: "info",
|
|
urgency: "low",
|
|
}),
|
|
},
|
|
],
|
|
},
|
|
// health_checkin is the live HEALTH action planner
|
|
// (resolveHealthPlanWithLlm → HEALTH_PLAN_INSTRUCTIONS). It returns a JSON
|
|
// object with subaction / metric / days / shouldAct. Rows cover every
|
|
// subaction, the by_metric enum, day-window inference, the vague guard
|
|
// (shouldAct=false), and multilingual phrasing — the same discipline as
|
|
// schedule_plan so the structured-field scorer is discriminative.
|
|
health_checkin: {
|
|
task: "health_checkin",
|
|
baseline: HEALTH_PLAN_INSTRUCTIONS,
|
|
dataset: [
|
|
{
|
|
input: {
|
|
user: healthPlannerInput({
|
|
currentMessage: "How am I doing health-wise today?",
|
|
}),
|
|
},
|
|
expectedOutput: expectedHealthPlan({
|
|
subaction: "today",
|
|
metric: null,
|
|
days: null,
|
|
shouldAct: true,
|
|
}),
|
|
},
|
|
{
|
|
input: {
|
|
user: healthPlannerInput({
|
|
currentMessage: "Show me my activity trend over the last week.",
|
|
}),
|
|
},
|
|
expectedOutput: expectedHealthPlan({
|
|
subaction: "trend",
|
|
metric: null,
|
|
days: 7,
|
|
shouldAct: true,
|
|
}),
|
|
},
|
|
{
|
|
input: {
|
|
user: healthPlannerInput({
|
|
currentMessage: "How many steps did I take today?",
|
|
}),
|
|
},
|
|
expectedOutput: expectedHealthPlan({
|
|
subaction: "by_metric",
|
|
metric: "steps",
|
|
days: null,
|
|
shouldAct: true,
|
|
}),
|
|
},
|
|
{
|
|
input: {
|
|
user: healthPlannerInput({
|
|
currentMessage: "What's my resting heart rate lately?",
|
|
}),
|
|
},
|
|
expectedOutput: expectedHealthPlan({
|
|
subaction: "by_metric",
|
|
metric: "heart_rate",
|
|
days: null,
|
|
shouldAct: true,
|
|
}),
|
|
},
|
|
{
|
|
input: {
|
|
user: healthPlannerInput({
|
|
currentMessage: "How much did I sleep last night?",
|
|
}),
|
|
},
|
|
expectedOutput: expectedHealthPlan({
|
|
subaction: "by_metric",
|
|
metric: "sleep_hours",
|
|
days: null,
|
|
shouldAct: true,
|
|
}),
|
|
},
|
|
{
|
|
input: {
|
|
user: healthPlannerInput({
|
|
currentMessage: "Is my health tracker actually connected?",
|
|
}),
|
|
},
|
|
expectedOutput: expectedHealthPlan({
|
|
subaction: "status",
|
|
metric: null,
|
|
days: null,
|
|
shouldAct: true,
|
|
}),
|
|
},
|
|
{
|
|
input: {
|
|
user: healthPlannerInput({
|
|
currentMessage: "Give me my calorie burn for the past 30 days.",
|
|
}),
|
|
},
|
|
expectedOutput: expectedHealthPlan({
|
|
subaction: "trend",
|
|
metric: "calories",
|
|
days: 30,
|
|
shouldAct: true,
|
|
}),
|
|
},
|
|
{
|
|
input: {
|
|
user: healthPlannerInput({
|
|
currentMessage: "¿Cuántos pasos di hoy?",
|
|
}),
|
|
},
|
|
expectedOutput: expectedHealthPlan({
|
|
subaction: "by_metric",
|
|
metric: "steps",
|
|
days: null,
|
|
shouldAct: true,
|
|
}),
|
|
},
|
|
{
|
|
input: {
|
|
user: healthPlannerInput({
|
|
currentMessage: "Comment va ma santé aujourd'hui ?",
|
|
}),
|
|
},
|
|
expectedOutput: expectedHealthPlan({
|
|
subaction: "today",
|
|
metric: null,
|
|
days: null,
|
|
shouldAct: true,
|
|
}),
|
|
},
|
|
{
|
|
input: {
|
|
user: healthPlannerInput({
|
|
currentMessage: "Can you help me with health stuff?",
|
|
}),
|
|
},
|
|
expectedOutput: expectedHealthPlan({
|
|
subaction: null,
|
|
metric: null,
|
|
days: null,
|
|
shouldAct: false,
|
|
}),
|
|
},
|
|
],
|
|
},
|
|
// reminder_dispatch is the live reminder-nudge writer
|
|
// (buildReminderDispatchPrompt → REMINDER_DISPATCH_INSTRUCTIONS). Output is
|
|
// one or two sentences of natural prose, so rows are judge-graded rubrics:
|
|
// constraint compliance (no "Reminder" prefix, no ISO timestamps, no
|
|
// markdown/emoji, length) plus content grounding (mentions the reminder,
|
|
// tone matches lifecycle/urgency, language matches the conversation).
|
|
reminder_dispatch: {
|
|
task: "reminder_dispatch",
|
|
baseline: REMINDER_DISPATCH_INSTRUCTIONS,
|
|
dataset: [
|
|
{
|
|
input: {
|
|
user: reminderDispatchInput({
|
|
title: "Take out the trash",
|
|
due: "6/24/2026, 7:00:00 PM",
|
|
channel: "in_app",
|
|
urgency: "medium",
|
|
lifecycle: "plan",
|
|
}),
|
|
},
|
|
expectedOutput: judgeExpectation(
|
|
"Trash pickup is tomorrow morning — tonight's the night to get the bins out.",
|
|
[
|
|
"Mentions taking out the trash (or the bins).",
|
|
"Does not begin with the word 'Reminder' or 'Follow-up'.",
|
|
"Contains no ISO-8601 timestamp (like 2026-06-24T19:00:00).",
|
|
"Is one or two sentences long.",
|
|
"Contains no markdown formatting, bullets, quotes, labels, or emoji.",
|
|
],
|
|
),
|
|
},
|
|
{
|
|
input: {
|
|
user: reminderDispatchInput({
|
|
title: "Submit quarterly tax payment",
|
|
due: "6/24/2026, 5:00:00 PM",
|
|
channel: "email",
|
|
urgency: "critical",
|
|
lifecycle: "escalation",
|
|
}),
|
|
},
|
|
expectedOutput: judgeExpectation(
|
|
"The quarterly tax payment is due by 5 PM today and it still isn't in — this one really can't slip.",
|
|
[
|
|
"Mentions the quarterly tax payment.",
|
|
"Sounds firmer than a first gentle nudge while still reading as a human message, not a system log.",
|
|
"Does not begin with the word 'Reminder' or 'Follow-up'.",
|
|
"Is one or two sentences long.",
|
|
"Contains no markdown formatting, bullets, quotes, labels, or emoji.",
|
|
],
|
|
),
|
|
},
|
|
{
|
|
input: {
|
|
user: reminderDispatchInput({
|
|
title: "Take your medication",
|
|
due: "6/24/2026, 9:00:00 AM",
|
|
channel: "push",
|
|
urgency: "high",
|
|
lifecycle: "plan",
|
|
nearbyReminderTitles: ["Water the plants", "Call the pharmacy"],
|
|
}),
|
|
},
|
|
expectedOutput: judgeExpectation(
|
|
"Time for your morning medication — and while you're up, the plants and the pharmacy call are queued too.",
|
|
[
|
|
"Mentions taking the medication.",
|
|
"If it references the other reminders (plants, pharmacy), it does so briefly in the same flowing sentence rather than as a list.",
|
|
"Does not begin with the word 'Reminder' or 'Follow-up'.",
|
|
"Is one or two sentences long.",
|
|
"Contains no markdown formatting, bullets, quotes, labels, or emoji.",
|
|
],
|
|
),
|
|
},
|
|
{
|
|
input: {
|
|
user: reminderDispatchInput({
|
|
title: "Daily standup",
|
|
due: "6/24/2026, 9:15:00 AM",
|
|
channel: "in_app",
|
|
urgency: "medium",
|
|
lifecycle: "plan",
|
|
voice: [
|
|
"Name: Milady",
|
|
"Bio: playful, warm, concise personal assistant who keeps the owner on track without nagging.",
|
|
].join("\n"),
|
|
recentConversation: [
|
|
"Owner: morning! coffee first, then work",
|
|
"Milady: deal — coffee is a load-bearing beverage.",
|
|
],
|
|
}),
|
|
},
|
|
expectedOutput: judgeExpectation(
|
|
"Coffee's earned — standup is at 9:15, so bring the mug along.",
|
|
[
|
|
"Mentions the standup.",
|
|
"Reads warm or lightly playful, consistent with the character voice, not corporate or robotic.",
|
|
"Does not begin with the word 'Reminder' or 'Follow-up'.",
|
|
"Is one or two sentences long.",
|
|
"Contains no markdown formatting, bullets, quotes, labels, or emoji.",
|
|
],
|
|
),
|
|
},
|
|
{
|
|
input: {
|
|
user: reminderDispatchInput({
|
|
title: "Sacar la basura",
|
|
due: "6/24/2026, 8:00:00 PM",
|
|
channel: "in_app",
|
|
urgency: "medium",
|
|
lifecycle: "plan",
|
|
recentConversation: [
|
|
"Owner: recuérdame sacar la basura esta noche",
|
|
"Assistant: claro, te aviso a las ocho.",
|
|
],
|
|
}),
|
|
},
|
|
expectedOutput: judgeExpectation(
|
|
"Ya son las ocho — toca sacar la basura antes de que se te pase.",
|
|
[
|
|
"Is written in Spanish, matching the conversation.",
|
|
"Mentions taking out the trash (la basura).",
|
|
"Does not begin with the word 'Reminder', 'Recordatorio', or 'Follow-up'.",
|
|
"Is one or two sentences long.",
|
|
"Contains no markdown formatting, bullets, quotes, labels, or emoji.",
|
|
],
|
|
),
|
|
},
|
|
{
|
|
input: {
|
|
user: reminderDispatchInput({
|
|
title: "Appeler maman",
|
|
due: "6/24/2026, 6:30:00 PM",
|
|
channel: "sms",
|
|
urgency: "low",
|
|
lifecycle: "plan",
|
|
recentConversation: [
|
|
"Owner: rappelle-moi d'appeler maman ce soir",
|
|
"Assistant: c'est noté, je te fais signe vers 18h30.",
|
|
],
|
|
}),
|
|
},
|
|
expectedOutput: judgeExpectation(
|
|
"Il est 18h30 — c'est le bon moment pour appeler ta maman.",
|
|
[
|
|
"Is written in French, matching the conversation.",
|
|
"Mentions calling mom (appeler maman).",
|
|
"Does not begin with the word 'Reminder', 'Rappel', or 'Follow-up'.",
|
|
"Is one or two sentences long.",
|
|
"Contains no markdown formatting, bullets, quotes, labels, or emoji.",
|
|
],
|
|
),
|
|
},
|
|
{
|
|
input: {
|
|
user: reminderDispatchInput({
|
|
title: "Gym session",
|
|
due: "6/24/2026, 6:00:00 PM",
|
|
channel: "push",
|
|
urgency: "medium",
|
|
lifecycle: "plan",
|
|
recentConversation: [
|
|
"Owner: slammed with the release deadline today",
|
|
"Assistant: understood — I'll keep interruptions light.",
|
|
],
|
|
}),
|
|
},
|
|
expectedOutput: judgeExpectation(
|
|
"Deadline or not, your gym slot is here — even a short session counts.",
|
|
[
|
|
"Mentions the gym or workout.",
|
|
"Fits the busy-day context naturally without guilt-tripping or lecturing.",
|
|
"Does not begin with the word 'Reminder' or 'Follow-up'.",
|
|
"Is one or two sentences long.",
|
|
"Contains no markdown formatting, bullets, quotes, labels, or emoji.",
|
|
],
|
|
),
|
|
},
|
|
{
|
|
input: {
|
|
user: reminderDispatchInput({
|
|
title: "Submit expense report",
|
|
due: "6/23/2026, 5:00:00 PM",
|
|
channel: "in_app",
|
|
urgency: "high",
|
|
lifecycle: "escalation",
|
|
recentConversation: [
|
|
"Assistant: your expense report is due by end of day.",
|
|
"Assistant: nudging again — the expense report is still open.",
|
|
],
|
|
}),
|
|
},
|
|
expectedOutput: judgeExpectation(
|
|
"That expense report is now overdue and finance is waiting on it — please get it in today.",
|
|
[
|
|
"Mentions the expense report.",
|
|
"Sounds firmer than the earlier nudges shown in the conversation while staying human.",
|
|
"Does not use all-caps words or exclamation-heavy shouting.",
|
|
"Is one or two sentences long.",
|
|
"Contains no markdown formatting, bullets, quotes, labels, or emoji.",
|
|
],
|
|
),
|
|
},
|
|
],
|
|
},
|
|
// morning_brief is the live briefing-narrative composer
|
|
// (buildNarrativePrompt → BRIEF_NARRATIVE_INSTRUCTIONS). Rows are
|
|
// judge-graded: prioritization (schedule-changing / reply-needed first),
|
|
// per-domain coverage without empty-domain filler, 2-5 sentence length, and
|
|
// strict grounding in the JSON data payload.
|
|
morning_brief: {
|
|
task: "morning_brief",
|
|
baseline: BRIEF_NARRATIVE_INSTRUCTIONS,
|
|
dataset: [
|
|
{
|
|
input: {
|
|
user: briefNarrativeInput({
|
|
kind: "morning",
|
|
period: "today",
|
|
sections: {
|
|
calendar: [
|
|
{
|
|
id: "cal-1",
|
|
title: "Dentist appointment (moved to 3:00 PM)",
|
|
startAt: "2026-06-24T15:00:00-07:00",
|
|
endAt: "2026-06-24T15:30:00-07:00",
|
|
},
|
|
{
|
|
id: "cal-2",
|
|
title: "Team standup",
|
|
startAt: "2026-06-24T09:15:00-07:00",
|
|
endAt: "2026-06-24T09:30:00-07:00",
|
|
},
|
|
],
|
|
inbox: [
|
|
{
|
|
id: "msg-1",
|
|
channel: "email",
|
|
senderName: "Marta (landlord)",
|
|
snippet: "Need your decision on the lease renewal by Friday.",
|
|
urgency: "high",
|
|
classification: "needs_reply",
|
|
},
|
|
],
|
|
life: [
|
|
{
|
|
id: "life-1",
|
|
kind: "todo",
|
|
title: "Submit insurance forms",
|
|
dueAt: "2026-06-24T17:00:00-07:00",
|
|
},
|
|
],
|
|
},
|
|
}),
|
|
},
|
|
expectedOutput: judgeExpectation(
|
|
"Your landlord needs a lease-renewal decision by Friday, and today's dentist appointment has moved to 3 PM. Standup runs at 9:15 as usual. The insurance forms are also due by end of day.",
|
|
[
|
|
"Opens with a reply-needed or schedule-changing item (the lease-renewal reply or the moved dentist appointment), not the routine standup.",
|
|
"Mentions the calendar, inbox, and life items — each domain at least once.",
|
|
"Does not mention money, finances, or subscriptions (that domain is absent from the data).",
|
|
"Is a narrative paragraph of 2 to 5 sentences, not a bullet list.",
|
|
"States only facts present in the data (no invented times, senders, or tasks).",
|
|
],
|
|
),
|
|
},
|
|
{
|
|
input: {
|
|
user: briefNarrativeInput({
|
|
kind: "morning",
|
|
period: "today",
|
|
sections: {
|
|
calendar: [
|
|
{
|
|
id: "cal-1",
|
|
title: "Design review",
|
|
startAt: "2026-06-24T11:00:00-07:00",
|
|
endAt: "2026-06-24T12:00:00-07:00",
|
|
},
|
|
{
|
|
id: "cal-2",
|
|
title: "Lunch with Sam",
|
|
startAt: "2026-06-24T12:30:00-07:00",
|
|
endAt: "2026-06-24T13:30:00-07:00",
|
|
},
|
|
],
|
|
inbox: [],
|
|
life: [],
|
|
},
|
|
}),
|
|
},
|
|
expectedOutput: judgeExpectation(
|
|
"A light day: the design review runs 11 to noon, then you're at lunch with Sam at 12:30.",
|
|
[
|
|
"Mentions both calendar items (design review and lunch with Sam).",
|
|
"Does not say 'nothing to report' about the empty inbox or life domains, and does not dwell on them.",
|
|
"Is a narrative paragraph of 2 to 5 sentences.",
|
|
"States only facts present in the data.",
|
|
],
|
|
),
|
|
},
|
|
{
|
|
input: {
|
|
user: briefNarrativeInput({
|
|
kind: "evening",
|
|
period: "today",
|
|
sections: {
|
|
calendar: [
|
|
{
|
|
id: "cal-1",
|
|
title: "Dinner with Alex",
|
|
startAt: "2026-06-24T19:00:00-07:00",
|
|
endAt: "2026-06-24T21:00:00-07:00",
|
|
location: "Nopa",
|
|
},
|
|
],
|
|
inbox: [],
|
|
life: [
|
|
{
|
|
id: "life-1",
|
|
kind: "reminder",
|
|
title: "Take evening medication",
|
|
dueAt: "2026-06-24T22:00:00-07:00",
|
|
},
|
|
],
|
|
},
|
|
}),
|
|
},
|
|
expectedOutput: judgeExpectation(
|
|
"Tonight you have dinner with Alex at Nopa from 7 to 9, and your evening medication is set for 10 PM.",
|
|
[
|
|
"Mentions the dinner with Alex and the evening medication.",
|
|
"Does not mention the inbox (it is empty) or invent messages.",
|
|
"Is a narrative paragraph of 2 to 5 sentences.",
|
|
"States only facts present in the data.",
|
|
],
|
|
),
|
|
},
|
|
{
|
|
input: {
|
|
user: briefNarrativeInput({
|
|
kind: "weekly",
|
|
period: "this_week",
|
|
sections: {
|
|
calendar: [
|
|
{
|
|
id: "cal-1",
|
|
title: "Quarterly planning offsite",
|
|
startAt: "2026-06-25T09:00:00-07:00",
|
|
endAt: "2026-06-25T17:00:00-07:00",
|
|
},
|
|
],
|
|
inbox: [
|
|
{
|
|
id: "msg-1",
|
|
channel: "email",
|
|
senderName: "Ravi (accountant)",
|
|
snippet: "Please confirm the Q2 estimated payment figure.",
|
|
urgency: "high",
|
|
classification: "needs_reply",
|
|
},
|
|
],
|
|
life: [],
|
|
money: [
|
|
{
|
|
id: "money-1",
|
|
merchant: "Netflix",
|
|
amountUsd: 15.49,
|
|
cadence: "monthly",
|
|
nextChargeAt: "2026-06-26T00:00:00-07:00",
|
|
},
|
|
{
|
|
id: "money-2",
|
|
merchant: "City Gym",
|
|
amountUsd: 45,
|
|
cadence: "monthly",
|
|
nextChargeAt: "2026-06-28T00:00:00-07:00",
|
|
},
|
|
],
|
|
},
|
|
}),
|
|
},
|
|
expectedOutput: judgeExpectation(
|
|
"Ravi needs the Q2 estimated payment figure confirmed, so that reply comes first. Thursday is fully blocked by the quarterly planning offsite. On the money side, Netflix ($15.49) renews Friday and City Gym ($45) on Sunday.",
|
|
[
|
|
"Opens with the reply-needed accountant item rather than the offsite or the subscriptions.",
|
|
"Mentions the offsite and at least one of the upcoming charges (Netflix or City Gym).",
|
|
"Mentions the money domain since it is non-empty.",
|
|
"Is a narrative paragraph of 2 to 5 sentences.",
|
|
"States only facts present in the data (no invented amounts or dates).",
|
|
],
|
|
),
|
|
},
|
|
{
|
|
input: {
|
|
user: briefNarrativeInput({
|
|
kind: "morning",
|
|
period: "today",
|
|
sections: { calendar: [], inbox: [], life: [] },
|
|
}),
|
|
},
|
|
expectedOutput: judgeExpectation(
|
|
"Nothing is scheduled today and the inbox is quiet — a clear day to spend however you like.",
|
|
[
|
|
"States that the day is clear or light.",
|
|
"Invents no specific events, messages, tasks, or amounts.",
|
|
"Is at most two sentences.",
|
|
],
|
|
),
|
|
},
|
|
{
|
|
input: {
|
|
user: briefNarrativeInput({
|
|
kind: "morning",
|
|
period: "today",
|
|
sections: {
|
|
calendar: [
|
|
{
|
|
id: "cal-1",
|
|
title: "Sprint review",
|
|
startAt: "2026-06-24T10:00:00-07:00",
|
|
endAt: "2026-06-24T11:00:00-07:00",
|
|
},
|
|
{
|
|
id: "cal-2",
|
|
title: "Vendor demo",
|
|
startAt: "2026-06-24T14:00:00-07:00",
|
|
endAt: "2026-06-24T15:00:00-07:00",
|
|
},
|
|
{
|
|
id: "cal-3",
|
|
title: "Architecture sync",
|
|
startAt: "2026-06-24T14:00:00-07:00",
|
|
endAt: "2026-06-24T14:45:00-07:00",
|
|
},
|
|
{
|
|
id: "cal-4",
|
|
title: "Coffee with Jordan",
|
|
startAt: "2026-06-24T16:00:00-07:00",
|
|
endAt: "2026-06-24T16:30:00-07:00",
|
|
},
|
|
{
|
|
id: "cal-5",
|
|
title: "Evening yoga",
|
|
startAt: "2026-06-24T18:00:00-07:00",
|
|
endAt: "2026-06-24T19:00:00-07:00",
|
|
},
|
|
],
|
|
inbox: [],
|
|
life: [],
|
|
},
|
|
}),
|
|
},
|
|
expectedOutput: judgeExpectation(
|
|
"Heads-up: the vendor demo and the architecture sync are both at 2 PM, so one needs to move. Otherwise the day runs from sprint review at 10 through coffee with Jordan at 4 and yoga at 6.",
|
|
[
|
|
"Flags the 2 PM overlap between the vendor demo and the architecture sync before the routine items.",
|
|
"Summarizes the day in flowing prose rather than enumerating all five events as a list.",
|
|
"Is a narrative paragraph of 2 to 5 sentences.",
|
|
"States only facts present in the data.",
|
|
],
|
|
),
|
|
},
|
|
{
|
|
input: {
|
|
user: briefNarrativeInput({
|
|
kind: "morning",
|
|
period: "today",
|
|
sections: {
|
|
calendar: [],
|
|
inbox: [
|
|
{
|
|
id: "msg-1",
|
|
channel: "email",
|
|
senderName: "Chen (counsel)",
|
|
snippet:
|
|
"Contract signature deadline is 5 PM today — please review clause 7 first.",
|
|
urgency: "high",
|
|
classification: "needs_reply",
|
|
},
|
|
{
|
|
id: "msg-2",
|
|
channel: "slack",
|
|
senderName: "Design team",
|
|
snippet: "New mockups posted for the settings page.",
|
|
urgency: "low",
|
|
classification: "info",
|
|
},
|
|
{
|
|
id: "msg-3",
|
|
channel: "email",
|
|
senderName: "Newsletter",
|
|
snippet: "This week in tech...",
|
|
urgency: "low",
|
|
classification: "ignore",
|
|
},
|
|
],
|
|
life: [],
|
|
},
|
|
}),
|
|
},
|
|
expectedOutput: judgeExpectation(
|
|
"Chen needs the contract signed by 5 PM today, with clause 7 reviewed first — that's the priority. The rest of the inbox is light: new settings mockups from the design team and a newsletter you can skip.",
|
|
[
|
|
"Leads with the contract-signature deadline from counsel.",
|
|
"Does not give the low-urgency newsletter or mockups more weight than the legal deadline.",
|
|
"Is a narrative paragraph of 2 to 5 sentences.",
|
|
"States only facts present in the data.",
|
|
],
|
|
),
|
|
},
|
|
{
|
|
input: {
|
|
user: briefNarrativeInput({
|
|
kind: "morning",
|
|
period: "today",
|
|
sections: {
|
|
calendar: [
|
|
{
|
|
id: "cal-1",
|
|
title: "Product sync",
|
|
startAt: "2026-06-24T13:00:00-07:00",
|
|
endAt: "2026-06-24T13:30:00-07:00",
|
|
},
|
|
],
|
|
inbox: [],
|
|
life: [
|
|
{
|
|
id: "life-1",
|
|
kind: "todo",
|
|
title: "Renew passport",
|
|
dueAt: "2026-06-23T17:00:00-07:00",
|
|
},
|
|
{
|
|
id: "life-2",
|
|
kind: "goal",
|
|
title: "Read 12 books this year",
|
|
dueAt: null,
|
|
},
|
|
],
|
|
},
|
|
}),
|
|
},
|
|
expectedOutput: judgeExpectation(
|
|
"The passport renewal slipped past yesterday's deadline, so that's the first thing to clear. Your only meeting is the product sync at 1 PM, and the reading goal keeps ticking in the background.",
|
|
[
|
|
"Mentions the overdue passport renewal with priority over the routine items.",
|
|
"Mentions the product sync.",
|
|
"Does not mention money or finances (that domain is absent).",
|
|
"Is a narrative paragraph of 2 to 5 sentences.",
|
|
"States only facts present in the data.",
|
|
],
|
|
),
|
|
},
|
|
],
|
|
},
|
|
// meeting_prep is the same live composer (buildNarrativePrompt) resolved
|
|
// with MEETING_PREP_INSTRUCTIONS when the owner asks for meeting prep. Rows
|
|
// are judge-graded: surfacing missing agenda/location/dial-in/prep-doc,
|
|
// decision owners, blockers, and likely follow-ups — while staying compact
|
|
// and grounded in the data payload.
|
|
meeting_prep: {
|
|
task: "meeting_prep",
|
|
baseline: MEETING_PREP_INSTRUCTIONS,
|
|
dataset: [
|
|
{
|
|
input: {
|
|
user: briefNarrativeInput({
|
|
kind: "morning",
|
|
period: "tomorrow",
|
|
sections: {
|
|
calendar: [
|
|
{
|
|
id: "cal-1",
|
|
title: "Board meeting",
|
|
startAt: "2026-06-25T10:00:00-07:00",
|
|
endAt: "2026-06-25T12:00:00-07:00",
|
|
},
|
|
],
|
|
inbox: [],
|
|
life: [],
|
|
},
|
|
}),
|
|
},
|
|
expectedOutput: judgeExpectation(
|
|
"Tomorrow's board meeting (10-12) has no agenda circulated and no location or dial-in on the invite — both need chasing today. No prep document is linked either, so ask the chief of staff what pre-read the board expects.",
|
|
[
|
|
"Flags that no agenda exists for the board meeting.",
|
|
"Flags the missing location or dial-in.",
|
|
"Is compact: at most 5 sentences or an equally short structure.",
|
|
"Invents no attendees, documents, or facts not present in the data.",
|
|
],
|
|
),
|
|
},
|
|
{
|
|
input: {
|
|
user: briefNarrativeInput({
|
|
kind: "morning",
|
|
period: "today",
|
|
sections: {
|
|
calendar: [
|
|
{
|
|
id: "cal-1",
|
|
title: "Client call — Acme renewal",
|
|
startAt: "2026-06-24T14:00:00-07:00",
|
|
endAt: "2026-06-24T15:00:00-07:00",
|
|
location: "Zoom (link on invite)",
|
|
},
|
|
],
|
|
inbox: [
|
|
{
|
|
id: "msg-1",
|
|
channel: "email",
|
|
senderName: "Dana (Acme)",
|
|
snippet:
|
|
"Agenda attached: pricing tier change and the renewal timeline.",
|
|
urgency: "medium",
|
|
classification: "needs_reply",
|
|
},
|
|
],
|
|
life: [],
|
|
},
|
|
}),
|
|
},
|
|
expectedOutput: judgeExpectation(
|
|
"The 2 PM Acme renewal call has an agenda from Dana — pricing tier change and renewal timeline — and the Zoom link is on the invite. What's missing is your own prep document: pull the current contract terms and a pricing comparison before the call, and Dana's email still needs a reply.",
|
|
[
|
|
"Acknowledges the agenda from Dana (pricing and renewal timeline).",
|
|
"Flags that no prep document or pre-read of the owner's own exists.",
|
|
"Is compact: at most 5 sentences or an equally short structure.",
|
|
"Invents no facts not present in the data.",
|
|
],
|
|
),
|
|
},
|
|
{
|
|
input: {
|
|
user: briefNarrativeInput({
|
|
kind: "morning",
|
|
period: "today",
|
|
sections: {
|
|
calendar: [
|
|
{
|
|
id: "cal-1",
|
|
title: "1:1 with Priya",
|
|
startAt: "2026-06-24T15:00:00-07:00",
|
|
endAt: "2026-06-24T15:30:00-07:00",
|
|
location: "Small conference room",
|
|
},
|
|
],
|
|
inbox: [],
|
|
life: [
|
|
{
|
|
id: "life-1",
|
|
kind: "todo",
|
|
title: "Decide on Priya's promotion case",
|
|
dueAt: "2026-06-24T15:00:00-07:00",
|
|
},
|
|
],
|
|
},
|
|
}),
|
|
},
|
|
expectedOutput: judgeExpectation(
|
|
"Your 3 PM 1:1 with Priya has one real decision attached: her promotion case, which is due by the meeting and sits with you. Come in with a yes/no and the reasoning — there's no agenda beyond that on file.",
|
|
[
|
|
"Surfaces the promotion decision as the key decision point for the 1:1.",
|
|
"Makes clear the decision owner is the owner (it is their todo, due at meeting time).",
|
|
"Is compact: at most 5 sentences or an equally short structure.",
|
|
"Invents no facts not present in the data.",
|
|
],
|
|
),
|
|
},
|
|
{
|
|
input: {
|
|
user: briefNarrativeInput({
|
|
kind: "morning",
|
|
period: "today",
|
|
sections: {
|
|
calendar: [
|
|
{
|
|
id: "cal-1",
|
|
title: "Interview — backend candidate (panel 1)",
|
|
startAt: "2026-06-24T11:00:00-07:00",
|
|
endAt: "2026-06-24T11:45:00-07:00",
|
|
location: "Meet link on invite",
|
|
},
|
|
{
|
|
id: "cal-2",
|
|
title: "Interview — backend candidate (panel 2)",
|
|
startAt: "2026-06-24T11:45:00-07:00",
|
|
endAt: "2026-06-24T12:30:00-07:00",
|
|
},
|
|
],
|
|
inbox: [],
|
|
life: [],
|
|
},
|
|
}),
|
|
},
|
|
expectedOutput: judgeExpectation(
|
|
"Two back-to-back backend interviews from 11:00 to 12:30 with zero buffer between panels. Panel 1 has its Meet link, but panel 2 has no location or dial-in on the invite — chase that now so the candidate isn't left waiting.",
|
|
[
|
|
"Flags the missing dial-in or location for the second panel specifically.",
|
|
"Notes the back-to-back timing (no buffer between the two panels).",
|
|
"Is compact: at most 5 sentences or an equally short structure.",
|
|
"Invents no facts not present in the data.",
|
|
],
|
|
),
|
|
},
|
|
{
|
|
input: {
|
|
user: briefNarrativeInput({
|
|
kind: "morning",
|
|
period: "today",
|
|
sections: {
|
|
calendar: [
|
|
{
|
|
id: "cal-1",
|
|
title: "Roadmap review",
|
|
startAt: "2026-06-24T10:00:00-07:00",
|
|
endAt: "2026-06-24T11:00:00-07:00",
|
|
location: "Room 4B",
|
|
},
|
|
],
|
|
inbox: [
|
|
{
|
|
id: "msg-1",
|
|
channel: "email",
|
|
senderName: "Lee (PM)",
|
|
snippet:
|
|
"Agenda and prep doc attached for the roadmap review; decisions needed on Q3 scope.",
|
|
urgency: "medium",
|
|
classification: "info",
|
|
},
|
|
],
|
|
life: [],
|
|
},
|
|
}),
|
|
},
|
|
expectedOutput: judgeExpectation(
|
|
"The 10 AM roadmap review in Room 4B is well covered: Lee sent the agenda and prep doc, and the decision on the table is Q3 scope. Read the prep doc before 10 — otherwise nothing is missing.",
|
|
[
|
|
"States that prep is largely complete (agenda, prep doc, and location all exist).",
|
|
"Does not invent missing items or manufacture gaps.",
|
|
"Mentions the Q3 scope decision.",
|
|
"Is compact: at most 5 sentences or an equally short structure.",
|
|
],
|
|
),
|
|
},
|
|
{
|
|
input: {
|
|
user: briefNarrativeInput({
|
|
kind: "morning",
|
|
period: "today",
|
|
sections: {
|
|
calendar: [
|
|
{
|
|
id: "cal-1",
|
|
title: "Vendor negotiation — DataCo",
|
|
startAt: "2026-06-24T13:00:00-07:00",
|
|
endAt: "2026-06-24T14:00:00-07:00",
|
|
location: "Zoom (link on invite)",
|
|
},
|
|
],
|
|
inbox: [
|
|
{
|
|
id: "msg-1",
|
|
channel: "email",
|
|
senderName: "Sam (legal)",
|
|
snippet:
|
|
"DataCo contract is blocked on legal review of the data-processing addendum.",
|
|
urgency: "high",
|
|
classification: "needs_reply",
|
|
},
|
|
],
|
|
life: [],
|
|
},
|
|
}),
|
|
},
|
|
expectedOutput: judgeExpectation(
|
|
"Before the 1 PM DataCo negotiation, know that the contract is blocked on legal's review of the data-processing addendum — Sam's email is waiting on you. Don't commit to signature timing until legal clears it; that's the likely follow-up out of this call.",
|
|
[
|
|
"Surfaces the legal-review blocker on the DataCo contract.",
|
|
"Connects the blocker to the negotiation (e.g. don't commit until legal clears, or reply to Sam first).",
|
|
"Is compact: at most 5 sentences or an equally short structure.",
|
|
"Invents no facts not present in the data.",
|
|
],
|
|
),
|
|
},
|
|
{
|
|
input: {
|
|
user: briefNarrativeInput({
|
|
kind: "morning",
|
|
period: "today",
|
|
sections: {
|
|
calendar: [
|
|
{
|
|
id: "cal-1",
|
|
title: "Exec presentation — Q3 plan",
|
|
startAt: "2026-06-24T16:00:00-07:00",
|
|
endAt: "2026-06-24T17:00:00-07:00",
|
|
location: "Boardroom",
|
|
},
|
|
],
|
|
inbox: [],
|
|
life: [
|
|
{
|
|
id: "life-1",
|
|
kind: "todo",
|
|
title: "Finish Q3 slides",
|
|
dueAt: "2026-06-24T15:00:00-07:00",
|
|
},
|
|
],
|
|
},
|
|
}),
|
|
},
|
|
expectedOutput: judgeExpectation(
|
|
"The Q3 exec presentation is at 4 in the boardroom and the slides aren't finished — that todo is due at 3, which leaves you one hour of buffer. Block time this morning to close them out; the deck is the only open prep item.",
|
|
[
|
|
"Flags the unfinished Q3 slides as the prep gap before the presentation.",
|
|
"Notes the timing relationship (slides due before the 4 PM meeting).",
|
|
"Is compact: at most 5 sentences or an equally short structure.",
|
|
"Invents no facts not present in the data.",
|
|
],
|
|
),
|
|
},
|
|
{
|
|
input: {
|
|
user: briefNarrativeInput({
|
|
kind: "morning",
|
|
period: "today",
|
|
sections: {
|
|
calendar: [
|
|
{
|
|
id: "cal-1",
|
|
title: "Offsite planning call",
|
|
startAt: "2026-06-24T09:30:00-07:00",
|
|
endAt: "2026-06-24T10:00:00-07:00",
|
|
location: "Phone",
|
|
},
|
|
],
|
|
inbox: [],
|
|
life: [],
|
|
money: [
|
|
{
|
|
id: "money-1",
|
|
merchant: "Sunset Lodge (venue deposit)",
|
|
amountUsd: 500,
|
|
cadence: "irregular",
|
|
nextChargeAt: "2026-06-26T00:00:00-07:00",
|
|
},
|
|
],
|
|
},
|
|
}),
|
|
},
|
|
expectedOutput: judgeExpectation(
|
|
"The 9:30 offsite planning call has one money item attached: the $500 Sunset Lodge venue deposit charges Friday, so confirm the venue decision on this call or pause the charge. No agenda is on file beyond that.",
|
|
[
|
|
"Surfaces the $500 venue deposit charging Friday as a decision or follow-up tied to the call.",
|
|
"Is compact: at most 5 sentences or an equally short structure.",
|
|
"Invents no facts not present in the data.",
|
|
],
|
|
),
|
|
},
|
|
],
|
|
},
|
|
// screentime_recap is the live screen-time recap policy
|
|
// (buildScreenTimeRecapRules → SCREENTIME_RECAP_INSTRUCTIONS). The reply is
|
|
// a JSON envelope { recap, topApps, suggestion } whose prose fields carry
|
|
// the value, so rows are judge-graded: envelope validity, topApps
|
|
// correctness against the data, change-vs-prior emphasis, and the
|
|
// at-most-one-grounded-suggestion rule.
|
|
screentime_recap: {
|
|
task: "screentime_recap",
|
|
baseline: SCREENTIME_RECAP_INSTRUCTIONS,
|
|
dataset: [
|
|
{
|
|
input: {
|
|
user: screentimeRecapInput({
|
|
request: "Give me my screen-time recap for today.",
|
|
thisPeriodLabel: "today",
|
|
thisPeriod: [
|
|
{ app: "VS Code", minutes: 180 },
|
|
{ app: "Instagram", minutes: 95 },
|
|
{ app: "Safari", minutes: 62 },
|
|
],
|
|
priorPeriodLabel: "yesterday",
|
|
priorPeriod: [
|
|
{ app: "VS Code", minutes: 175 },
|
|
{ app: "Instagram", minutes: 55 },
|
|
{ app: "Safari", minutes: 60 },
|
|
],
|
|
}),
|
|
},
|
|
expectedOutput: judgeExpectation(
|
|
'{"recap":"Coding time held steady at 3 hours, but Instagram jumped from 55 to 95 minutes — the biggest shift today. Safari stayed flat around an hour.","topApps":[{"app":"VS Code","minutes":180},{"app":"Instagram","minutes":95}],"suggestion":"Instagram grew 40 minutes day-over-day; a 60-minute daily cap would return that time without touching your focus apps."}',
|
|
[
|
|
"Is a valid JSON object with exactly the keys recap, topApps, and suggestion.",
|
|
"topApps lists VS Code (180 minutes) and Instagram (95 minutes) with the correct minute values.",
|
|
"The recap highlights the Instagram increase (+40 minutes vs yesterday) as the largest change, not just raw totals.",
|
|
"Proposes exactly one suggestion, and it is tied to the Instagram usage pattern.",
|
|
"The tone is factual and non-clinical (no moralizing about screen addiction).",
|
|
],
|
|
),
|
|
},
|
|
{
|
|
input: {
|
|
user: screentimeRecapInput({
|
|
request: "How was my screen time today?",
|
|
thisPeriodLabel: "today",
|
|
thisPeriod: [
|
|
{ app: "YouTube", minutes: 45 },
|
|
{ app: "Messages", minutes: 30 },
|
|
{ app: "TikTok", minutes: 20 },
|
|
],
|
|
priorPeriodLabel: "yesterday",
|
|
priorPeriod: [
|
|
{ app: "TikTok", minutes: 140 },
|
|
{ app: "YouTube", minutes: 44 },
|
|
{ app: "Messages", minutes: 28 },
|
|
],
|
|
}),
|
|
},
|
|
expectedOutput: judgeExpectation(
|
|
'{"recap":"TikTok collapsed from 140 minutes to 20 — by far the day\'s biggest change. YouTube and Messages held steady around 45 and 30 minutes.","topApps":[{"app":"YouTube","minutes":45},{"app":"Messages","minutes":30}],"suggestion":"Whatever replaced the two hours of TikTok worked; keeping its notifications off would help the drop stick."}',
|
|
[
|
|
"Is a valid JSON object with exactly the keys recap, topApps, and suggestion.",
|
|
"The recap highlights the TikTok drop (140 to 20 minutes) as the largest change.",
|
|
"topApps reflects today's actual top apps by minutes (YouTube 45, Messages 30).",
|
|
"Proposes at most one suggestion, tied to a pattern actually in the data.",
|
|
"The tone is factual and non-clinical.",
|
|
],
|
|
),
|
|
},
|
|
{
|
|
input: {
|
|
user: screentimeRecapInput({
|
|
request: "Recap my browsing time today.",
|
|
thisPeriodLabel: "today",
|
|
thisPeriod: [
|
|
{ app: "youtube.com", minutes: 110 },
|
|
{ app: "github.com", minutes: 95 },
|
|
{ app: "docs.google.com", minutes: 40 },
|
|
],
|
|
priorPeriodLabel: "yesterday",
|
|
priorPeriod: [
|
|
{ app: "github.com", minutes: 100 },
|
|
{ app: "youtube.com", minutes: 30 },
|
|
{ app: "docs.google.com", minutes: 35 },
|
|
],
|
|
}),
|
|
},
|
|
expectedOutput: judgeExpectation(
|
|
'{"recap":"youtube.com nearly quadrupled, from 30 minutes yesterday to 110 today, overtaking github.com (95m) as your top site. Docs held steady around 40 minutes.","topApps":[{"app":"youtube.com","minutes":110},{"app":"github.com","minutes":95}],"suggestion":"The YouTube jump is the one pattern worth a nudge — a site limit during work hours would cap the drift without touching GitHub or Docs."}',
|
|
[
|
|
"Is a valid JSON object with exactly the keys recap, topApps, and suggestion.",
|
|
"The recap highlights the youtube.com increase (30 to 110 minutes) as the largest change.",
|
|
"topApps lists youtube.com (110) and github.com (95) with correct minutes.",
|
|
"Proposes exactly one suggestion, tied to the youtube.com pattern.",
|
|
"The tone is factual and non-clinical.",
|
|
],
|
|
),
|
|
},
|
|
{
|
|
input: {
|
|
user: screentimeRecapInput({
|
|
request: "Screen-time recap, please.",
|
|
thisPeriodLabel: "today",
|
|
thisPeriod: [
|
|
{ app: "Slack", minutes: 60 },
|
|
{ app: "Chrome", minutes: 55 },
|
|
{ app: "Terminal", minutes: 50 },
|
|
],
|
|
priorPeriodLabel: "yesterday",
|
|
priorPeriod: [
|
|
{ app: "Slack", minutes: 58 },
|
|
{ app: "Chrome", minutes: 57 },
|
|
{ app: "Terminal", minutes: 49 },
|
|
],
|
|
}),
|
|
},
|
|
expectedOutput: judgeExpectation(
|
|
'{"recap":"Usage was essentially flat versus yesterday: Slack, Chrome, and Terminal all moved by two minutes or less, holding a balanced 50-60 minutes each.","topApps":[{"app":"Slack","minutes":60},{"app":"Chrome","minutes":55}],"suggestion":"Nothing needs changing today — the split is stable and balanced."}',
|
|
[
|
|
"Is a valid JSON object with exactly the keys recap, topApps, and suggestion.",
|
|
"The recap says usage was roughly stable and does not fabricate a large change.",
|
|
"topApps lists Slack (60) and Chrome (55) with correct minutes.",
|
|
"Does not propose more than one change, and any suggestion is modest and grounded in the stable data.",
|
|
"The tone is factual and non-clinical.",
|
|
],
|
|
),
|
|
},
|
|
{
|
|
input: {
|
|
user: screentimeRecapInput({
|
|
request: "How did I do on screen time today?",
|
|
thisPeriodLabel: "today",
|
|
thisPeriod: [
|
|
{ app: "Kindle", minutes: 80 },
|
|
{ app: "Instagram", minutes: 70 },
|
|
{ app: "Mail", minutes: 25 },
|
|
],
|
|
priorPeriodLabel: "yesterday",
|
|
priorPeriod: [
|
|
{ app: "Kindle", minutes: 75 },
|
|
{ app: "Instagram", minutes: 65 },
|
|
{ app: "Mail", minutes: 30 },
|
|
],
|
|
usageNotes: ["Instagram usage clustered between 23:00 and 01:00."],
|
|
}),
|
|
},
|
|
expectedOutput: judgeExpectation(
|
|
'{"recap":"Totals were close to yesterday — Kindle led at 80 minutes, Instagram at 70 — but nearly all of the Instagram time landed between 11 PM and 1 AM.","topApps":[{"app":"Kindle","minutes":80},{"app":"Instagram","minutes":70}],"suggestion":"The late-night Instagram window is the one pattern to adjust: a wind-down block after 11 PM would move that hour toward sleep or reading."}',
|
|
[
|
|
"Is a valid JSON object with exactly the keys recap, topApps, and suggestion.",
|
|
"The suggestion addresses the late-night (23:00-01:00) Instagram window from the usage notes.",
|
|
"topApps lists Kindle (80) and Instagram (70) with correct minutes.",
|
|
"Proposes exactly one suggestion.",
|
|
"The tone is factual and non-clinical.",
|
|
],
|
|
),
|
|
},
|
|
{
|
|
input: {
|
|
user: screentimeRecapInput({
|
|
request: "Weekend screen-time recap?",
|
|
thisPeriodLabel: "Saturday",
|
|
thisPeriod: [
|
|
{ app: "Steam", minutes: 240 },
|
|
{ app: "Discord", minutes: 85 },
|
|
{ app: "Safari", minutes: 40 },
|
|
],
|
|
priorPeriodLabel: "last Saturday",
|
|
priorPeriod: [
|
|
{ app: "Steam", minutes: 30 },
|
|
{ app: "Discord", minutes: 80 },
|
|
{ app: "Safari", minutes: 45 },
|
|
],
|
|
}),
|
|
},
|
|
expectedOutput: judgeExpectation(
|
|
'{"recap":"Steam exploded from 30 minutes last Saturday to 4 hours today — the week\'s standout change. Discord and Safari stayed where they usually are.","topApps":[{"app":"Steam","minutes":240},{"app":"Discord","minutes":85}],"suggestion":"If the marathon session wasn\'t planned, a two-hour gaming block next weekend keeps Saturdays fun without eating the whole afternoon."}',
|
|
[
|
|
"Is a valid JSON object with exactly the keys recap, topApps, and suggestion.",
|
|
"The recap highlights the Steam spike (30 to 240 minutes) versus last Saturday.",
|
|
"topApps lists Steam (240) and Discord (85) with correct minutes.",
|
|
"Proposes exactly one suggestion, tied to the Steam pattern.",
|
|
"The tone is factual and non-clinical (weekend leisure is not treated as a failing).",
|
|
],
|
|
),
|
|
},
|
|
{
|
|
input: {
|
|
user: screentimeRecapInput({
|
|
request: "Give me a screen-time recap.",
|
|
thisPeriodLabel: "today",
|
|
thisPeriod: [
|
|
{ app: "Notes", minutes: 50 },
|
|
{ app: "Maps", minutes: 35 },
|
|
{ app: "Camera", minutes: 20 },
|
|
],
|
|
}),
|
|
},
|
|
expectedOutput: judgeExpectation(
|
|
'{"recap":"First tracked day: Notes led with 50 minutes, followed by Maps at 35 and Camera at 20 — about 1 hour 45 total.","topApps":[{"app":"Notes","minutes":50},{"app":"Maps","minutes":35}],"suggestion":"No baseline exists yet, so keep tracking for a few days before changing anything."}',
|
|
[
|
|
"Is a valid JSON object with exactly the keys recap, topApps, and suggestion.",
|
|
"Does not invent a prior-period comparison (the data says no prior period was recorded).",
|
|
"topApps lists Notes (50) and Maps (35) with correct minutes.",
|
|
"Any suggestion acknowledges the missing baseline rather than prescribing a change from invented trends.",
|
|
"The tone is factual and non-clinical.",
|
|
],
|
|
),
|
|
},
|
|
{
|
|
input: {
|
|
user: screentimeRecapInput({
|
|
request: "Recap today's screen time for me.",
|
|
thisPeriodLabel: "today",
|
|
thisPeriod: [
|
|
{ app: "VS Code", minutes: 300 },
|
|
{ app: "Slack", minutes: 45 },
|
|
{ app: "Spotify", minutes: 40 },
|
|
],
|
|
priorPeriodLabel: "yesterday",
|
|
priorPeriod: [
|
|
{ app: "VS Code", minutes: 120 },
|
|
{ app: "Slack", minutes: 90 },
|
|
{ app: "Spotify", minutes: 38 },
|
|
],
|
|
}),
|
|
},
|
|
expectedOutput: judgeExpectation(
|
|
'{"recap":"A deep-focus day: VS Code went from 2 hours yesterday to 5 today, while Slack fell by half to 45 minutes. Spotify hummed along unchanged.","topApps":[{"app":"VS Code","minutes":300},{"app":"Slack","minutes":45}],"suggestion":"Five straight coding hours with Slack halved suggests the focus setup worked — worth repeating, with a couple of stretch breaks folded in."}',
|
|
[
|
|
"Is a valid JSON object with exactly the keys recap, topApps, and suggestion.",
|
|
"The recap highlights both the VS Code increase (120 to 300) and the Slack halving (90 to 45).",
|
|
"topApps lists VS Code (300) and Slack (45) with correct minutes.",
|
|
"Proposes at most one suggestion, grounded in the focus pattern actually present.",
|
|
"The tone is factual and non-clinical.",
|
|
],
|
|
),
|
|
},
|
|
],
|
|
},
|
|
};
|
|
|
|
export function parseBoundedIntegerArg(
|
|
name: string,
|
|
value: string | undefined,
|
|
defaults: { defaultValue: number; min: number; max: number },
|
|
): number {
|
|
const raw = value?.trim();
|
|
if (!raw) {
|
|
return defaults.defaultValue;
|
|
}
|
|
if (!/^\d+$/.test(raw)) {
|
|
throw new Error(
|
|
`--${name} must be an integer between ${defaults.min} and ${defaults.max}`,
|
|
);
|
|
}
|
|
const parsed = Number(raw);
|
|
if (
|
|
!Number.isSafeInteger(parsed) ||
|
|
parsed < defaults.min ||
|
|
parsed > defaults.max
|
|
) {
|
|
throw new Error(
|
|
`--${name} must be an integer between ${defaults.min} and ${defaults.max}`,
|
|
);
|
|
}
|
|
return parsed;
|
|
}
|
|
|
|
function resolveTrainingProvider(): string | undefined {
|
|
return (
|
|
process.env.TRAIN_MODEL_PROVIDER?.trim() ??
|
|
process.env.TRAINING_PROVIDER?.trim()
|
|
)?.toLowerCase();
|
|
}
|
|
|
|
// Adapts the CLI EvalModelClient to the useModel-shaped adapter the
|
|
// optimizer consumes (same contract as getTrainingUseModelAdapter).
|
|
function cliUseModelAdapter(
|
|
client: EvalModelClient,
|
|
): (input: {
|
|
prompt: string;
|
|
temperature?: number;
|
|
maxTokens?: number;
|
|
}) => Promise<string> {
|
|
return async (input) => {
|
|
const result = await client({
|
|
prompt: input.prompt,
|
|
temperature: input.temperature,
|
|
maxTokens: input.maxTokens,
|
|
});
|
|
return result.text;
|
|
};
|
|
}
|
|
|
|
function resolveTrainingModelLabel(): string {
|
|
return (
|
|
process.env.TRAIN_MODEL?.trim() ??
|
|
process.env.TRAINING_MODEL?.trim() ??
|
|
process.env.TRAIN_MODEL_NAME?.trim() ??
|
|
process.env.CEREBRAS_MODEL?.trim() ??
|
|
"gemma-4-31b"
|
|
);
|
|
}
|
|
|
|
function resolveStateDir(value: string | undefined): string | undefined {
|
|
const trimmed = value?.trim();
|
|
return trimmed ? resolve(process.cwd(), trimmed) : undefined;
|
|
}
|
|
|
|
function validateOptimizedPromptForTask(
|
|
task: OptimizedPromptTask,
|
|
prompt: string,
|
|
): string[] {
|
|
const requiredFragmentsByTask: Partial<
|
|
Record<OptimizedPromptTask, string[]>
|
|
> = {
|
|
calendar_extract: [
|
|
"subaction",
|
|
"shouldAct",
|
|
"queries",
|
|
"title",
|
|
"timeMin",
|
|
"timeMax",
|
|
"feed",
|
|
"next_event",
|
|
"search_events",
|
|
"create_event",
|
|
"update_event",
|
|
"delete_event",
|
|
"trip_window",
|
|
],
|
|
inbox_triage: [
|
|
"ignore",
|
|
"info",
|
|
"notify",
|
|
"needs_reply",
|
|
"urgent",
|
|
"urgency",
|
|
"confidence",
|
|
"reasoning",
|
|
"suggestedResponse",
|
|
],
|
|
schedule_plan: [
|
|
"subaction",
|
|
"shouldAct",
|
|
"response",
|
|
"start",
|
|
"propose",
|
|
"respond",
|
|
"finalize",
|
|
"cancel",
|
|
"list_active",
|
|
"list_proposals",
|
|
],
|
|
health_checkin: [
|
|
"subaction",
|
|
"metric",
|
|
"days",
|
|
"shouldAct",
|
|
"today",
|
|
"trend",
|
|
"by_metric",
|
|
"status",
|
|
"steps",
|
|
"heart_rate",
|
|
"sleep_hours",
|
|
],
|
|
// Prose/NL tasks: fragments are the load-bearing domain nouns (and, for
|
|
// screentime_recap, the JSON envelope keys) that any valid rewrite of the
|
|
// instructions must still carry.
|
|
reminder_dispatch: ["reminder"],
|
|
meeting_prep: ["agenda"],
|
|
morning_brief: ["schedule"],
|
|
screentime_recap: ["recap", "topApps", "suggestion"],
|
|
};
|
|
const requiredFragments = requiredFragmentsByTask[task] ?? [];
|
|
const normalized = prompt.toLowerCase();
|
|
return requiredFragments
|
|
.filter((fragment) => !normalized.includes(fragment.toLowerCase()))
|
|
.map((fragment) => `optimized prompt is missing "${fragment}"`);
|
|
}
|
|
|
|
export function validatePersistableResult(
|
|
seed: SeedTask,
|
|
result: OptimizerResult,
|
|
): string[] {
|
|
const reasons: string[] = [];
|
|
if (!Number.isFinite(result.baseline) || !Number.isFinite(result.score)) {
|
|
reasons.push("optimizer returned a non-finite score");
|
|
}
|
|
if (result.score - result.baseline < MIN_PERSIST_DELTA) {
|
|
reasons.push(
|
|
`optimized score must beat baseline by at least ${MIN_PERSIST_DELTA.toFixed(4)} (baseline=${result.baseline.toFixed(3)}, optimized=${result.score.toFixed(3)})`,
|
|
);
|
|
}
|
|
const prompt = result.optimizedPrompt.trim();
|
|
if (prompt.length === 0) {
|
|
reasons.push("optimized prompt is empty");
|
|
}
|
|
reasons.push(...validateOptimizedPromptForTask(seed.task, prompt));
|
|
return reasons;
|
|
}
|
|
|
|
export async function main(argv = process.argv.slice(2)): Promise<number> {
|
|
const { values } = parseArgs({
|
|
args: argv,
|
|
options: {
|
|
task: { type: "string" },
|
|
apply: { type: "boolean" },
|
|
generations: { type: "string" },
|
|
population: { type: "string" },
|
|
"state-dir": { type: "string" },
|
|
},
|
|
allowPositionals: false,
|
|
});
|
|
|
|
const taskName = (values.task ?? "calendar_extract").trim();
|
|
const seed = SEED_TASKS[taskName];
|
|
if (!seed) {
|
|
process.stderr.write(
|
|
`[gepa-seed] unknown --task "${taskName}". Available: ${Object.keys(SEED_TASKS).join(", ")}\n`,
|
|
);
|
|
return 1;
|
|
}
|
|
|
|
const resolvedStateDir = resolveStateDir(values["state-dir"]);
|
|
if (resolvedStateDir) {
|
|
process.env.TRAINING_STATE_DIR = resolvedStateDir;
|
|
process.env.ELIZA_STATE_DIR = resolvedStateDir;
|
|
}
|
|
|
|
const generations = parseBoundedIntegerArg(
|
|
"generations",
|
|
values.generations,
|
|
{
|
|
defaultValue: DEFAULT_GENERATIONS,
|
|
min: 1,
|
|
max: MAX_GENERATIONS,
|
|
},
|
|
);
|
|
const population = parseBoundedIntegerArg("population", values.population, {
|
|
defaultValue: DEFAULT_POPULATION,
|
|
min: 2,
|
|
max: MAX_POPULATION,
|
|
});
|
|
|
|
const provider = resolveTrainingProvider();
|
|
if (
|
|
provider !== "cerebras" &&
|
|
provider !== "anthropic" &&
|
|
provider !== "cli"
|
|
) {
|
|
process.stderr.write(
|
|
"[gepa-seed] TRAIN_MODEL_PROVIDER=cerebras|anthropic|cli (or TRAINING_PROVIDER=...) is required.\n",
|
|
);
|
|
return 1;
|
|
}
|
|
|
|
const useModel =
|
|
provider === "cli"
|
|
? cliUseModelAdapter(getCliModelClient())
|
|
: getTrainingUseModelAdapter();
|
|
const adapter = createRuntimeAdapter(useModel);
|
|
const isJudgeTask = LIFEOPS_JUDGE_TASKS.has(seed.task);
|
|
// Prose/NL tasks are graded by a live judge against per-example rubrics;
|
|
// the structured planners keep the deterministic field-match scorer.
|
|
const compare = isJudgeTask
|
|
? createLifeOpsJudgeCompare(
|
|
seed.task,
|
|
provider === "cli" ? getCliModelClient() : getEvalModelClient(),
|
|
)
|
|
: (actual: string, expected: string) =>
|
|
scoreLifeOpsTask(seed.task, actual, expected);
|
|
const scorer = createPromptScorer(adapter, {
|
|
maxTokens: isJudgeTask ? 512 : 256,
|
|
compare,
|
|
});
|
|
const modelLabel =
|
|
provider === "cli" ? resolveCliModel() : resolveTrainingModelLabel();
|
|
|
|
process.stdout.write(
|
|
`[gepa-seed] task=${seed.task} dataset=${seed.dataset.length} ` +
|
|
`model=${modelLabel} (${provider}) scorer=${isJudgeTask ? "judge-rubric" : "field-match"} ` +
|
|
`generations=${generations} population=${population}\n`,
|
|
);
|
|
|
|
const result = await runGepa({
|
|
baselinePrompt: seed.baseline,
|
|
dataset: seed.dataset,
|
|
scorer,
|
|
llm: adapter,
|
|
options: {
|
|
generations,
|
|
population,
|
|
reflectionBatchSize: 2,
|
|
maxTokens: 768,
|
|
reflectionMaxTokens: 384,
|
|
},
|
|
});
|
|
|
|
process.stdout.write(
|
|
`\n[gepa-seed] RESULT task=${seed.task} ` +
|
|
`baseline=${result.baseline.toFixed(3)} optimized=${result.score.toFixed(3)} ` +
|
|
`delta=${(result.score - result.baseline).toFixed(3)}\n`,
|
|
);
|
|
process.stdout.write(`\n[gepa-seed] baseline prompt:\n${seed.baseline}\n`);
|
|
process.stdout.write(
|
|
`\n[gepa-seed] optimized prompt:\n${result.optimizedPrompt}\n`,
|
|
);
|
|
|
|
if (!values.apply) {
|
|
process.stdout.write(
|
|
"\n[gepa-seed] dry run - pass --apply to persist to the optimized-prompt store.\n",
|
|
);
|
|
return 0;
|
|
}
|
|
|
|
const persistBlockers = validatePersistableResult(seed, result);
|
|
if (persistBlockers.length > 0) {
|
|
process.stderr.write(
|
|
`\n[gepa-seed] refusing to persist ${seed.task} artifact:\n` +
|
|
persistBlockers.map((reason) => `- ${reason}`).join("\n") +
|
|
"\n",
|
|
);
|
|
return 1;
|
|
}
|
|
|
|
const artifact: OptimizedPromptArtifact = {
|
|
task: seed.task,
|
|
optimizer: "gepa",
|
|
baseline: seed.baseline,
|
|
prompt: result.optimizedPrompt,
|
|
score: result.score,
|
|
baselineScore: result.baseline,
|
|
datasetId: `seed:${seed.task}`,
|
|
datasetSize: seed.dataset.length,
|
|
generatedAt: new Date().toISOString(),
|
|
lineage: result.lineage.map((entry) => ({
|
|
round: entry.round,
|
|
variant: entry.variant,
|
|
score: entry.score,
|
|
notes: entry.notes,
|
|
})),
|
|
};
|
|
|
|
const service = new OptimizedPromptService();
|
|
if (resolvedStateDir) {
|
|
service.setStoreRoot(join(resolvedStateDir, "optimized-prompts"));
|
|
}
|
|
const path = await service.setPrompt(seed.task, artifact);
|
|
process.stdout.write(
|
|
`\n[gepa-seed] persisted optimized artifact -> ${path}\n`,
|
|
);
|
|
return 0;
|
|
}
|
|
|
|
const entrypointUrl = process.argv[1]
|
|
? pathToFileURL(resolve(process.argv[1])).href
|
|
: "";
|
|
|
|
if (import.meta.url === entrypointUrl) {
|
|
main()
|
|
.then((code) => {
|
|
process.exitCode = code;
|
|
})
|
|
.catch((err) => {
|
|
process.stderr.write(
|
|
`[gepa-seed] failed: ${err instanceof Error ? err.message : String(err)}\n`,
|
|
);
|
|
process.exitCode = 1;
|
|
});
|
|
}
|