@coder/aegis 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -238,14 +238,7 @@ var ExperimentBuilder = class {
238
238
  strict(this.baseRepos.has(task.seed.base_repo), `task ${task.id}: base repo ${task.seed.base_repo} was not declared`);
239
239
  validateTaskGraders(task, graders);
240
240
  }
241
- const subject = {
242
- harness: this.config.subject.harness,
243
- harness_version: this.config.subject.harnessVersion,
244
- control_transport: this.config.subject.controlTransport,
245
- model: this.config.subject.model,
246
- allowed_tools: [...this.config.subject.allowedTools],
247
- env: sortedStringRecord(this.config.subject.env ?? {})
248
- };
241
+ const subjectFields = this.config.subject !== void 0 ? { subject: experimentSubjectDoc(this.config.subject) } : { subjects: codeUnitSortedSubjects(this.config.subjects ?? {}) };
249
242
  const budget = {
250
243
  max_usd: this.config.budget.maxUsd,
251
244
  confirm_required: this.config.budget.confirmRequired,
@@ -262,7 +255,7 @@ var ExperimentBuilder = class {
262
255
  backend: this.config.backend,
263
256
  ...backendOptions === void 0 ? {} : { backend_options: backendOptions },
264
257
  hypothesis: this.config.hypothesis,
265
- subject,
258
+ ...subjectFields,
266
259
  suite: suiteID,
267
260
  grader_registry: ["graders/common.yaml"],
268
261
  suites: suiteDocs,
@@ -595,12 +588,23 @@ function assertExperimentConfig(id, config) {
595
588
  }
596
589
  }
597
590
  assertNonEmpty(`experiment ${id}.hypothesis`, config.hypothesis);
598
- strict(config.subject && typeof config.subject === "object", `experiment ${id}: subject is required`);
599
- assertNonEmpty(`experiment ${id}.subject.harness`, config.subject.harness);
600
- assertNonEmpty(`experiment ${id}.subject.harnessVersion`, config.subject.harnessVersion);
601
- assertNonEmpty(`experiment ${id}.subject.controlTransport`, config.subject.controlTransport);
602
- assertNonEmpty(`experiment ${id}.subject.model`, config.subject.model);
603
- assertStringArray(`experiment ${id}.subject.allowedTools`, config.subject.allowedTools);
591
+ strict(config.subject === void 0 !== (config.subjects === void 0), `experiment ${id}: exactly one of subject or subjects must be set`);
592
+ if (config.subject !== void 0) {
593
+ strict(config.subject && typeof config.subject === "object", `experiment ${id}: subject is required`);
594
+ assertExperimentSubjectInput(`experiment ${id}.subject`, config.subject);
595
+ } else {
596
+ strict(config.subjects !== null && typeof config.subjects === "object" && !Array.isArray(config.subjects), `experiment ${id}.subjects must be an object`);
597
+ const subjectIDs = Object.keys(config.subjects ?? {});
598
+ strict(subjectIDs.length > 0, `experiment ${id}.subjects must declare at least one subject`);
599
+ for (const subjectID of subjectIDs) {
600
+ assertSafeID("subject", subjectID);
601
+ strict(!/^[0-9]+$/.test(subjectID), `experiment ${id}.subjects id ${JSON.stringify(subjectID)} must not be purely numeric: JSON object key order for integer-like keys diverges between emitters`);
602
+ strict(!subjectID.includes("__"), `experiment ${id}.subjects id ${JSON.stringify(subjectID)} must not contain "__": trial ids join task, subject, variant, and repeat segments with it`);
603
+ const subject = (config.subjects ?? {})[subjectID];
604
+ strict(subject && typeof subject === "object", `experiment ${id}.subjects[${JSON.stringify(subjectID)}] must be an object`);
605
+ assertExperimentSubjectInput(`experiment ${id}.subjects[${JSON.stringify(subjectID)}]`, subject);
606
+ }
607
+ }
604
608
  strict(Number.isInteger(config.repeats) && config.repeats > 0, `experiment ${id}: repeats must be a positive integer`);
605
609
  strict(config.budget && typeof config.budget === "object", `experiment ${id}: budget is required`);
606
610
  assertFiniteNumber(`experiment ${id}.budget.maxUsd`, config.budget.maxUsd);
@@ -613,6 +617,13 @@ function assertExperimentConfig(id, config) {
613
617
  assertNonEmpty(`experiment ${id}.scheduling.order`, config.scheduling.order);
614
618
  assertNonEmpty(`experiment ${id}.scheduling.retry`, config.scheduling.retry);
615
619
  }
620
+ function assertExperimentSubjectInput(context, subject) {
621
+ assertNonEmpty(`${context}.harness`, subject.harness);
622
+ assertNonEmpty(`${context}.harnessVersion`, subject.harnessVersion);
623
+ assertNonEmpty(`${context}.controlTransport`, subject.controlTransport);
624
+ assertNonEmpty(`${context}.model`, subject.model);
625
+ assertStringArray(`${context}.allowedTools`, subject.allowedTools);
626
+ }
616
627
  function assertGraderRef(context, grader) {
617
628
  strict(grader && typeof grader === "object", `${context}: grader ref must be an object`);
618
629
  strict(grader.kind === "aegis.grader-ref", `${context}: expected an Aegis grader helper result`);
@@ -825,6 +836,20 @@ function normalizePrompt(input) {
825
836
  strict(content.length > 0, "prompt must not be blank");
826
837
  return `${content}\n`;
827
838
  }
839
+ function experimentSubjectDoc(subject) {
840
+ return {
841
+ harness: subject.harness,
842
+ harness_version: subject.harnessVersion,
843
+ control_transport: subject.controlTransport,
844
+ model: subject.model,
845
+ allowed_tools: [...subject.allowedTools],
846
+ env: sortedStringRecord(subject.env ?? {})
847
+ };
848
+ }
849
+ function codeUnitSortedSubjects(input) {
850
+ const entries = Object.entries(input).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
851
+ return Object.fromEntries(entries.map(([id, subject]) => [id, experimentSubjectDoc(subject)]));
852
+ }
828
853
  function sortedStringRecord(input) {
829
854
  const entries = Object.entries(input).sort(([a], [b]) => a.localeCompare(b));
830
855
  for (const [key, value] of entries) {
@@ -10,7 +10,10 @@ export interface ExperimentConfig {
10
10
  backend: string;
11
11
  backendOptions?: BackendOptionsInput;
12
12
  hypothesis: string;
13
- subject: ExperimentSubjectInput;
13
+ /** Exactly one of subject or subjects must be set. */
14
+ subject?: ExperimentSubjectInput;
15
+ /** Multi-subject axis keyed by repo-safe subject id. */
16
+ subjects?: Record<string, ExperimentSubjectInput>;
14
17
  repeats: number;
15
18
  budget: BudgetInput;
16
19
  primaryMetric: string;
@@ -4,7 +4,8 @@ export interface ExperimentDoc {
4
4
  backend: string;
5
5
  backend_options?: BackendOptionsDoc;
6
6
  hypothesis: string;
7
- subject: ExperimentSubjectDoc;
7
+ subject?: ExperimentSubjectDoc;
8
+ subjects?: Record<string, ExperimentSubjectDoc>;
8
9
  suite: string;
9
10
  grader_registry: string[];
10
11
  suites: Record<string, SuiteDoc>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coder/aegis",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "aegis CLI and TypeScript eval-authoring facade (Go binary with embedded TS eval compiler and web UI, plus the @coder/aegis import surface)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -23,10 +23,10 @@
23
23
  "url": "git+https://github.com/coder/aegis.git"
24
24
  },
25
25
  "optionalDependencies": {
26
- "@coder/aegis-linux-x64": "0.2.0",
27
- "@coder/aegis-linux-arm64": "0.2.0",
28
- "@coder/aegis-darwin-x64": "0.2.0",
29
- "@coder/aegis-darwin-arm64": "0.2.0",
30
- "@coder/aegis-win32-x64": "0.2.0"
26
+ "@coder/aegis-linux-x64": "0.3.0",
27
+ "@coder/aegis-linux-arm64": "0.3.0",
28
+ "@coder/aegis-darwin-x64": "0.3.0",
29
+ "@coder/aegis-darwin-arm64": "0.3.0",
30
+ "@coder/aegis-win32-x64": "0.3.0"
31
31
  }
32
32
  }