openmerit 0.1.10 → 0.1.12

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.
Files changed (45) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +20 -53
  3. package/dist/pi/scout-extension.mjs +177 -0
  4. package/dist/pi/src/index.d.ts +6 -32
  5. package/dist/pi/src/index.js +49 -787
  6. package/dist/terminal/scout-update.json +1 -1
  7. package/dist/terminal/src/install.mjs +102 -166
  8. package/dist/terminal/src/scout-credentials.d.ts +12 -0
  9. package/dist/terminal/src/scout-credentials.js +41 -0
  10. package/dist/terminal/src/scout-credentials.ts +44 -0
  11. package/dist/terminal/src/scout-instructions.d.ts +5 -0
  12. package/dist/terminal/src/scout-instructions.js +103 -0
  13. package/dist/terminal/src/scout-instructions.ts +106 -0
  14. package/dist/terminal/src/scout-pi.d.ts +44 -0
  15. package/dist/terminal/src/scout-pi.js +84 -0
  16. package/dist/terminal/src/scout-pi.ts +76 -0
  17. package/dist/terminal/src/telemetry.mjs +2 -2
  18. package/dist/terminal/src/updates.js +2 -1
  19. package/dist/terminal/src/updates.ts +2 -1
  20. package/docs/adapter-guide.md +2 -0
  21. package/docs/architecture.md +7 -5
  22. package/docs/automation.md +2 -0
  23. package/docs/budgets.md +2 -0
  24. package/docs/commands.md +3 -1
  25. package/docs/demo.md +1 -1
  26. package/docs/first-trial.md +2 -0
  27. package/docs/getting-started.md +19 -44
  28. package/docs/index.md +10 -6
  29. package/docs/inference-terminal.md +1 -1
  30. package/docs/lifecycle.md +2 -0
  31. package/docs/operations.md +2 -0
  32. package/docs/pi-extension.md +32 -28
  33. package/docs/roadmap.md +6 -5
  34. package/docs/scout-data-policy.md +22 -64
  35. package/docs/scout.md +137 -19
  36. package/docs/security.md +8 -2
  37. package/docs/testing.md +2 -0
  38. package/docs/troubleshooting.md +23 -28
  39. package/docs/ux-reference.md +1 -1
  40. package/package.json +11 -7
  41. package/dist/pi/src/scheduler.d.ts +0 -11
  42. package/dist/pi/src/scheduler.js +0 -137
  43. package/dist/pi/src/wakeup.d.ts +0 -2
  44. package/dist/pi/src/wakeup.js +0 -108
  45. package/docs/memo.md +0 -126
@@ -1,6 +1,6 @@
1
1
  # Pi UX behavioral reference
2
2
 
3
- This document records user-visible behaviors from the earlier OpenMerit implementation. It is not an implementation specification; the current implementation carries forward only the behaviors listed below, rather than its source code, storage schema, or internal contracts.
3
+ This document is a **historical evaluation-adapter reference**, not the current Pi interface specification. The unreleased Pi 1.5.0 native Scout replacement does not carry forward the `/openmerit` commands, lifecycle, pause controls, or scheduling described here. See [the current Pi guide](pi-extension.md). The list below preserves earlier UX ideas for custom evaluation harnesses only.
4
4
 
5
5
  ## Behaviors worth preserving
6
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openmerit",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "Evidence-driven model decisions for coding harnesses.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -61,7 +61,8 @@
61
61
  "access": "public"
62
62
  },
63
63
  "workspaces": [
64
- "packages/*"
64
+ "packages/*",
65
+ "scout/agent"
65
66
  ],
66
67
  "engines": {
67
68
  "node": ">=22.19.0"
@@ -69,7 +70,7 @@
69
70
  "scripts": {
70
71
  "check": "npm run check --workspaces --if-present",
71
72
  "test": "npm run test --workspaces --if-present",
72
- "typecheck": "tsc -p tsconfig.json && tsc -p tsconfig.scout.json",
73
+ "typecheck": "tsc -p tsconfig.json && tsc -p tsconfig.scout.json && tsc -p scout/agent/tsconfig.json",
73
74
  "check:typescript": "node scripts/check-typescript.ts",
74
75
  "types:workers": "node scripts/worker-types.ts",
75
76
  "check:worker-types": "node scripts/worker-types.ts --check",
@@ -81,7 +82,8 @@
81
82
  "dash": "node packages/terminal/src/cli.mjs dash",
82
83
  "build:docs": "node scripts/build-docs.mjs",
83
84
  "check:docs": "npm run build:docs && node --test scripts/docs.test.mjs",
84
- "check:demo": "node --test demo/test/*.test.mjs",
85
+ "memo": "node demo/memo/preview.ts",
86
+ "check:demo": "npm ci --ignore-scripts --prefix demo && npm run build:docs && tsc -p tsconfig.memo.json && node --test demo/test/*.test.mjs demo/test/*.test.ts",
85
87
  "prepare": "npm run build",
86
88
  "verify": "npm test && npm run check && npm run typecheck && npm run check:typescript && npm run build && npm run check:demo && npm run check:scout && npm run check:docs && npm run build:scout && git diff --check",
87
89
  "prepublishOnly": "npm run verify",
@@ -89,11 +91,10 @@
89
91
  "deploy:dash-preview": "node scripts/deploy.ts terminal-preview",
90
92
  "scout": "node scout/dev.mjs",
91
93
  "scout:refresh": "node scout/refresh.mjs",
92
- "check:scout": "node --test scout/test/*.test.mjs scout/test/*.test.ts && node --check scout/worker.mjs && node --check site/scout/scout.js",
94
+ "check:scout": "node --test scout/test/*.test.mjs scout/test/*.test.ts && node --check scout/worker.mjs",
93
95
  "build:scout": "node scripts/build-scout.mjs"
94
96
  },
95
97
  "dependencies": {
96
- "@earendil-works/pi-ai": "0.87.0",
97
98
  "@iarna/toml": "2.2.5",
98
99
  "@lezer/javascript": "1.5.6",
99
100
  "@lezer/python": "1.1.19",
@@ -102,7 +103,7 @@
102
103
  "yaml": "2.9.1"
103
104
  },
104
105
  "peerDependencies": {
105
- "@earendil-works/pi-coding-agent": ">=0.87.0 <0.88.0"
106
+ "@earendil-works/pi-coding-agent": "1.5.0"
106
107
  },
107
108
  "peerDependenciesMeta": {
108
109
  "@earendil-works/pi-coding-agent": {
@@ -110,9 +111,11 @@
110
111
  }
111
112
  },
112
113
  "devDependencies": {
114
+ "@earendil-works/pi-coding-agent": "1.5.0",
113
115
  "@clerk/backend": "3.26.1",
114
116
  "@clerk/shared": "4.41.0",
115
117
  "@resvg/resvg-js": "2.6.2",
118
+ "@types/markdown-it": "14.1.2",
116
119
  "@types/node": "^22.18.6",
117
120
  "@xyflow/react": "12.12.0",
118
121
  "elkjs": "0.12.0",
@@ -120,6 +123,7 @@
120
123
  "fflate": "0.8.2",
121
124
  "liveline": "0.0.7",
122
125
  "markdown-it": "15.0.2",
126
+ "miniflare": "5.20260921.0-alpha",
123
127
  "minisearch": "7.2.0",
124
128
  "morphdom": "2.7.8",
125
129
  "react": "19.3.0",
@@ -1,11 +0,0 @@
1
- export interface PiWakeupOptions {
2
- readonly projectRoot: string;
3
- readonly extensionPath: string;
4
- readonly provider: string;
5
- readonly modelId: string;
6
- }
7
- export declare function piWakeupLabel(projectRoot: string): string;
8
- export declare function piWakeupPlistPath(projectRoot: string): string;
9
- export declare function renderPiWakeupPlist(options: PiWakeupOptions, piPath: string, runnerPath: string): string;
10
- export declare function provisionPiWakeup(options: PiWakeupOptions): Promise<string>;
11
- export declare function removePiWakeup(projectRoot: string): Promise<boolean>;
@@ -1,137 +0,0 @@
1
- import { execFile } from "node:child_process";
2
- import { createHash } from "node:crypto";
3
- import { constants } from "node:fs";
4
- import { access, mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
5
- import { homedir } from "node:os";
6
- import { basename, dirname, extname, join, resolve } from "node:path";
7
- import { promisify } from "node:util";
8
- const execFileAsync = promisify(execFile);
9
- const POLL_SECONDS = 300;
10
- function projectHash(projectRoot) {
11
- return createHash("sha256").update(resolve(projectRoot)).digest("hex").slice(0, 16);
12
- }
13
- export function piWakeupLabel(projectRoot) {
14
- return `com.openmerit.pi.${projectHash(projectRoot)}`;
15
- }
16
- export function piWakeupPlistPath(projectRoot) {
17
- return join(homedir(), "Library", "LaunchAgents", `${piWakeupLabel(projectRoot)}.plist`);
18
- }
19
- function managedMarker(projectRoot) {
20
- return `<!-- openmerit-managed:${projectHash(projectRoot)} -->`;
21
- }
22
- function xml(value) {
23
- return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;")
24
- .replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
25
- }
26
- async function executable(name) {
27
- for (const directory of (process.env.PATH ?? "").split(":")) {
28
- if (!directory)
29
- continue;
30
- const path = join(directory, name);
31
- try {
32
- await access(path, constants.X_OK);
33
- return path;
34
- }
35
- catch { /* try the next PATH entry */ }
36
- }
37
- throw new Error(`${name} is not on PATH`);
38
- }
39
- export function renderPiWakeupPlist(options, piPath, runnerPath) {
40
- const args = [process.execPath, runnerPath, resolve(options.projectRoot), piPath,
41
- resolve(options.extensionPath), options.provider, options.modelId];
42
- const argumentsXml = args.map((argument) => ` <string>${xml(argument)}</string>`).join("\n");
43
- const path = [dirname(process.execPath), dirname(piPath), "/usr/bin", "/bin", "/usr/sbin", "/sbin"].join(":");
44
- return `<?xml version="1.0" encoding="UTF-8"?>
45
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
46
- ${managedMarker(options.projectRoot)}
47
- <plist version="1.0">
48
- <dict>
49
- <key>Label</key><string>${xml(piWakeupLabel(options.projectRoot))}</string>
50
- <key>ProgramArguments</key><array>
51
- ${argumentsXml}
52
- </array>
53
- <key>WorkingDirectory</key><string>${xml(resolve(options.projectRoot))}</string>
54
- <key>EnvironmentVariables</key><dict>
55
- <key>PATH</key><string>${xml(path)}</string>
56
- </dict>
57
- <key>RunAtLoad</key><true/>
58
- <key>StartInterval</key><integer>${POLL_SECONDS}</integer>
59
- </dict>
60
- </plist>
61
- `;
62
- }
63
- async function loaded(label) {
64
- try {
65
- await execFileAsync("launchctl", ["print", `gui/${process.getuid?.()}/${label}`]);
66
- return true;
67
- }
68
- catch {
69
- return false;
70
- }
71
- }
72
- export async function provisionPiWakeup(options) {
73
- if (process.platform !== "darwin" || process.getuid === undefined) {
74
- throw new Error("Persistent Pi wakeups require a macOS user LaunchAgent.");
75
- }
76
- if (!options.provider || !options.modelId)
77
- throw new Error("Select an authenticated Pi model before scheduling.");
78
- const piPath = await executable("pi");
79
- const auth = await execFileAsync(piPath, ["auth", "check", "--model", `${options.provider}/${options.modelId}`], {
80
- env: {
81
- HOME: homedir(),
82
- PATH: [dirname(process.execPath), dirname(piPath), "/usr/bin", "/bin", "/usr/sbin", "/sbin"].join(":"),
83
- },
84
- });
85
- if (auth.stdout.trim() !== "ready") {
86
- throw new Error(`Pi authentication is not ready for ${options.provider}/${options.modelId} in the background environment.`);
87
- }
88
- const extensionPath = resolve(options.extensionPath);
89
- const runnerPath = join(dirname(extensionPath), `wakeup${extname(extensionPath)}`);
90
- await access(runnerPath, constants.R_OK);
91
- const path = piWakeupPlistPath(options.projectRoot);
92
- const content = renderPiWakeupPlist({ ...options, extensionPath }, piPath, runnerPath);
93
- let prior;
94
- try {
95
- prior = await readFile(path, "utf8");
96
- }
97
- catch (error) {
98
- if (error.code !== "ENOENT")
99
- throw error;
100
- }
101
- if (prior && !prior.includes(managedMarker(options.projectRoot))) {
102
- throw new Error(`Refusing to replace unmanaged LaunchAgent ${basename(path)}.`);
103
- }
104
- const label = piWakeupLabel(options.projectRoot);
105
- if (prior === content && await loaded(label))
106
- return path;
107
- if (await loaded(label))
108
- await execFileAsync("launchctl", ["bootout", `gui/${process.getuid()}/${label}`]);
109
- await mkdir(dirname(path), { recursive: true });
110
- const temporary = `${path}.${process.pid}.tmp`;
111
- await writeFile(temporary, content, { mode: 0o600 });
112
- await rename(temporary, path);
113
- await execFileAsync("launchctl", ["bootstrap", `gui/${process.getuid()}`, path]);
114
- return path;
115
- }
116
- export async function removePiWakeup(projectRoot) {
117
- if (process.platform !== "darwin" || process.getuid === undefined)
118
- return false;
119
- const path = piWakeupPlistPath(projectRoot);
120
- let content;
121
- try {
122
- content = await readFile(path, "utf8");
123
- }
124
- catch (error) {
125
- if (error.code === "ENOENT")
126
- return false;
127
- throw error;
128
- }
129
- if (!content.includes(managedMarker(projectRoot))) {
130
- throw new Error(`Refusing to remove unmanaged LaunchAgent ${basename(path)}.`);
131
- }
132
- const label = piWakeupLabel(projectRoot);
133
- if (await loaded(label))
134
- await execFileAsync("launchctl", ["bootout", `gui/${process.getuid()}/${label}`]);
135
- await unlink(path);
136
- return true;
137
- }
@@ -1,2 +0,0 @@
1
- import type { OpenMeritProjectState } from "../../protocol/src/index.ts";
2
- export declare function isPiWakeupDue(state: OpenMeritProjectState, now?: string): boolean;
@@ -1,108 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- import { writeFile } from "node:fs/promises";
3
- import { join, resolve } from "node:path";
4
- import { fileURLToPath } from "node:url";
5
- import { buildAutomationPlan, OpenMeritCoordinator, ProjectStore } from "../../core/src/index.js";
6
- const wakeupDescriptor = {
7
- id: "pi-launchagent", name: "Pi LaunchAgent", version: "0.87", protocolVersions: ["0.1"],
8
- capabilities: [], executionModes: ["non_interactive"],
9
- structuredOutput: { schemaDialect: "https://json-schema.org/draft/2020-12/schema", presentation: "adapter_validation", enforcement: "adapter" },
10
- automation: {
11
- supportedSignals: ["product_task_completed", "metric_window_available", "scheduled_tick", "model_catalog_changed", "verification_window_completed"],
12
- persistentScheduling: true, backgroundExecution: true, wakeupProvisioning: "infrastructure_change",
13
- },
14
- };
15
- export function isPiWakeupDue(state, now = new Date().toISOString()) {
16
- const execution = state.cycle.policy?.checkPolicy.execution;
17
- if (execution?.mode !== "persistent" || !execution.infrastructureChangesAllowed || state.activeIntent || state.automation.paused)
18
- return false;
19
- if (state.automation.pendingIntentKind)
20
- return true;
21
- const at = buildAutomationPlan(state, wakeupDescriptor).nextWakeupAt;
22
- return at !== undefined && Date.parse(at) <= Date.parse(now);
23
- }
24
- async function run() {
25
- const [projectRoot, piPath, extensionPath, provider, modelId] = process.argv.slice(2);
26
- if (!projectRoot || !piPath || !extensionPath || !provider || !modelId)
27
- process.exitCode = 2;
28
- if (process.exitCode)
29
- return;
30
- const store = new ProjectStore(projectRoot);
31
- const state = await store.readState();
32
- if (!state || !isPiWakeupDue(state))
33
- return;
34
- const startedAt = new Date().toISOString();
35
- const coordinator = new OpenMeritCoordinator(store, {
36
- descriptor: wakeupDescriptor,
37
- async dispatch() { return { accepted: false, reason: "The wakeup runner does not dispatch harness work directly." }; },
38
- });
39
- const check = state.automation.pendingIntentKind === "run_assessment"
40
- ? await coordinator.assessBaseline(startedAt)
41
- : state.automation.pendingIntentKind
42
- ? undefined
43
- : await coordinator.recordAutomationSignal({
44
- protocolVersion: "0.1", id: `wakeup-${crypto.randomUUID()}`,
45
- type: "scheduled_tick", occurredAt: startedAt, targetId: state.cycle.targetId,
46
- });
47
- if (check && check.decision.action !== "issue_intent") {
48
- await writeFile(join(store.directory, "scheduler-status.json"), JSON.stringify({
49
- schemaVersion: 1, startedAt, completedAt: new Date().toISOString(), exitCode: 0,
50
- decision: check.decision.reason,
51
- }, null, 2) + "\n", { mode: 0o600 });
52
- return;
53
- }
54
- const configuredTimeout = Number(process.env.OPENMERIT_WAKEUP_TIMEOUT_MS);
55
- const runLimitMs = Number.isFinite(configuredTimeout) && configuredTimeout >= 5_000
56
- ? Math.min(configuredTimeout, 15 * 60_000)
57
- : 15 * 60_000;
58
- const deadline = Date.now() + runLimitMs;
59
- const invokePi = async (prompt) => {
60
- const child = spawn(piPath, [
61
- "--no-extensions", "--extension", extensionPath, "--provider", provider, "--model", modelId,
62
- ...(process.env.OPENMERIT_WAKEUP_DEBUG === "1" ? ["--mode", "json"] : []),
63
- "--no-session", "--print", prompt,
64
- ], { cwd: projectRoot, stdio: process.env.OPENMERIT_WAKEUP_DEBUG === "1" ? "inherit" : "ignore", env: process.env });
65
- return new Promise((resolve, reject) => {
66
- let timedOut = false;
67
- const timeout = setTimeout(() => {
68
- timedOut = true;
69
- child.kill("SIGTERM");
70
- setTimeout(() => child.kill("SIGKILL"), 5_000).unref();
71
- }, Math.max(1, deadline - Date.now()));
72
- child.once("error", (error) => { clearTimeout(timeout); reject(error); });
73
- child.once("exit", (code) => { clearTimeout(timeout); resolve(timedOut ? 124 : code ?? 1); });
74
- });
75
- };
76
- let exitCode = await invokePi("Run the due OpenMerit scheduled check for this configured project. " +
77
- "On startup, OpenMerit may create an active intent in .openmerit/state.json. Read that intent and .openmerit/config.json, " +
78
- "perform only its authorized work, and call openmerit_complete_intent exactly once with verified evidence. " +
79
- "For baseline assessment, if no application metric windows exist, report insufficiency promptly; do not wait for new data or invent samples. " +
80
- "If evidence is unavailable, report failure honestly. Never change Pi's model or an unauthorized application route.");
81
- const issued = await store.readState();
82
- if (exitCode === 0 && issued?.activeIntent && !state.activeIntent && Date.now() < deadline) {
83
- const intent = issued.activeIntent;
84
- exitCode = await invokePi(`Complete the already active OpenMerit ${intent.kind} intent ${intent.id}. ` +
85
- "Read .openmerit/config.json and the project evidence. Follow the requested outcome and constraints below. " +
86
- "Use the registered openmerit_complete_intent tool exactly once, with verified evidence; if evidence is unavailable, report failure honestly. " +
87
- "Never change Pi's model or the application model unless this intent explicitly authorizes that target-bound change.\n\n" +
88
- JSON.stringify(intent));
89
- if ((await store.readState())?.activeIntent?.id === intent.id)
90
- exitCode = 1;
91
- }
92
- const finalState = await store.readState();
93
- if (finalState?.activeIntent && exitCode === 0)
94
- exitCode = 1;
95
- if (finalState?.automation.pendingIntentKind && exitCode === 0)
96
- exitCode = 1;
97
- await writeFile(join(store.directory, "scheduler-status.json"), JSON.stringify({
98
- schemaVersion: 1, startedAt, completedAt: new Date().toISOString(), exitCode,
99
- ...(finalState?.activeIntent ? { activeIntentId: finalState.activeIntent.id } : {}),
100
- }, null, 2) + "\n", { mode: 0o600 });
101
- process.exitCode = exitCode;
102
- }
103
- if (process.argv[1] && fileURLToPath(import.meta.url) === resolve(process.argv[1])) {
104
- run().catch((error) => {
105
- process.stderr.write(`OpenMerit Pi wakeup failed: ${error instanceof Error ? error.message : String(error)}\n`);
106
- process.exitCode = 1;
107
- });
108
- }
package/docs/memo.md DELETED
@@ -1,126 +0,0 @@
1
- # Optimum Inference For All
2
-
3
- *Lazim*
4
-
5
- How can we accelerate the distribution of intelligence?
6
-
7
- Building and deploying more models and improving the compute landscape is where most of the energy being spent right now. I have another, less explored direction in my mind, which I call, the optimum inference problem.
8
-
9
- I see a substantial part of what limits autonomous systems is how the models are selected and assessed throughout their lifecycle. It's a harness-level infrastructure concern.
10
-
11
- #### Does this need to get solved?
12
-
13
- In companies that I had the privilege of working with, I have built or maintained or used a rudimentary version of this. When budget constraints strangled the roadmap, teams scrambled their way out by building something enough to justify why they need to pay y amount of money to solve x task. This was never a maintained system nor it was ever prioritised because it was seen as an optional optimisation issue and overlooked the power it had over the dissemination of intelligence inside the org and the products they ship.
14
-
15
- If it was ever solved, it was always at a task level and if it had room and engineers to pressure, maybe at the agent level but rarely. even rarer at the harness level. As the system develops, its inference choices should develop with it. I don't think every team should have to build and maintain that process and stack themselves. We are building OpenMerit to solve this. In the Mission of optimum inference for all.
16
-
17
- #### What optimum means
18
-
19
- Optimum depends on the work. We need to know what counts as success, how reliably it must be delivered and the customer's constraints on cost, latency and operation.
20
-
21
- A cheaper model that needs repeated attempts may make the completed job more expensive. A stronger model may add cost without improving the result. We need to account for the whole workflow, including anything a person has to fix.
22
-
23
- The range of models matters here. There are frontier models from leading labs, general-purpose open-weight models, fine-tuned and distilled variants on Hugging Face, and specialists for coding, retrieval, vision and speech. Jev, by TypeSafe, is designed for fast structured decisions. Its recent release shows there is demand for a different breed of system-one models. Smaller models can run task-specific programs optimised through tools such as DSPy. An agent may have a place for several of these. We want OpenMerit to assess all of them on the work they can actually do, to allocate as it required.
24
-
25
- The right deployment could use a direct API from the lab, a router, an inference cloud or a customer-operated model. It could combine them. A company using only commercial APIs should get a complete product. So should one operating entirely within its own environment.
26
-
27
- Hardware is equally part of the decision. For customer-operated deployments, we need to account for available compute and memory, concurrent workloads and the cost of loading or swapping models. With managed APIs, we work with measurable outcomes and the constraints the provider exposes.
28
-
29
- Local models, open weights and fine-tunes extend the possibilities. We have no preference for moving customers towards them. If the industry the customer operates in demands sovereignty then it should be able to, without sacrificing the intelligence spread.
30
-
31
- Better allocation also matters beyond the bill. Where a smaller configuration meets the requirements and the freed capacity can be reused, the same resources can support more work. That is part of what I mean by distributing intelligence.
32
-
33
- #### What we are building
34
-
35
- There's no end-to-end product or platform clarity at the moment, but on a broader spectrum, you could place OpenMerit as infrastructure for assessing, benchmarking, selecting, assigning and maintaining all model choices within your harness.
36
-
37
- It operates at task, agent and harness levels. An assignment should suit the individual task while accounting for the agent's overall result. Across a harness, those choices have to work within shared budgets and capacity.
38
-
39
- To get there, OpenMerit first needs to establish which options the customer can use. It should then coordinate comparisons on representative work using their success criteria. Production evidence tells us how the current configuration behaves. It does not establish how an untested alternative would perform.
40
-
41
- After deployment, OpenMerit should check whether the expected improvement happened and decide where further investigation is worthwhile. Keeping the current assignment is a valid decision.
42
-
43
- The interface should be agent-native. A harness should be able to request a comparison and retrieve an assignment with its evidence programmatically. Within the customer's permissions, OpenMerit should coordinate validated changes through the existing infrastructure, then retain or reverse them based on the results.
44
-
45
- We will remain harness-agnostic. The customer's harness owns execution and coordination. Its providers and serving systems run inference. We integrate with its observability and evaluation tools. We will not build those products ourselves as much as we can, and production traffic will not have to pass through us to fulfil the task, but sure can control the traffic based on the evidence.
46
-
47
- #### A continuing research problem
48
-
49
- Most of the components are still very nascent and will increasingly need newer ways of operating as models become increasingly intelligent and the compute becomes increasingly efficient. Hence we see OpenMerit as a research lab as much as a product company.
50
-
51
- To zoom out, we see optimum inference as a long-term research and deployment effort. There is no final configuration we can find, hand over and consider the problem solved.
52
-
53
- We need better methods for deciding how much evidence an assignment requires and when that evidence needs updating. Testing every option is expensive. Testing too little can leave us maintaining a poor decision. OpenMerit should spend its testing budget where additional evidence is likely to be useful.
54
-
55
- We also need to understand how individual choices affect the complete outcome. A model that performs well on one step may leave the next step with harder work. Several assignments that look sensible separately may compete for the same compute.
56
-
57
- These are some of the questions we will have to study in production. Deployment will expose cases our experiments missed and show us which improvements were worth pursuing. That experience should inform the next investigation and the methods we build.
58
-
59
- OpenMerit should support this work throughout a system's creation, iteration and ongoing operation.
60
-
61
- #### Where we start
62
-
63
- Our first customers are engineering and AI teams running repeatable production workflows with measurable outcomes and meaningful inference costs or capacity constraints.
64
-
65
- One way to think about it goes like this. A developer or their coding agent connects one workflow. OpenMerit establishes a baseline, compares eligible alternatives and returns a decision they can act on. They keep using it because maintaining that assignment is useful, then connect more work.
66
-
67
- The first line of product combines L1, organisation-wide understanding, with L2, repeatable workflows. Individual workflows provide the evidence for a wider view of which models are assigned where, why those choices were made and which ones deserve reassessment and swapping.
68
-
69
- L3, adaptive agents, is the longer-term research direction. Agents may revise their plans or create subagents while working. We want to support inference decisions during that execution without taking over the harness's responsibility.
70
-
71
- L2 should support a business on its own. It also gives us production experience for the harder questions ahead.
72
-
73
- #### A separate company, not a feature
74
-
75
- We want OpenMerit to follow the customer's work across whichever models and infrastructure are appropriate.
76
-
77
- A customer should be able to change providers, introduce a fine-tune or replace a harness without rebuilding how they make model decisions. Some evidence will need to be revalidated. The history of what they tried and why they changed should remain useful.
78
-
79
- That continuing responsibility is substantial enough to build a company around. We intend to earn it through the quality of our decisions and the maintenance we remove from the customer's team.
80
-
81
- We are thinking of building an open-core, with hosted and customer-operated options, and charge for continuous assessment and decision management. We will not mark up inference.
82
-
83
- #### We need to prove
84
-
85
- Our evaluations have to reflect the customer's actual work. We should begin with representative examples and usable success criteria, and limit autonomous changes to decisions the evidence supports. Where the criteria are inadequate, we need to identify that before automating the wrong objective.
86
-
87
- Our own costs belong in the calculation. Comparisons consume resources. Switching can introduce failures and maintenance. OpenMerit needs to recognise when an apparent improvement is too small to pursue.
88
-
89
- We also need dependable integrations. We should start with a narrow set and make them work well before extending our coverage. Supporting different infrastructure does not require supporting everything immediately.
90
-
91
- The standard is a measurable improvement after accounting for testing, switching and running OpenMerit. Depending on the workload, that means better outcomes within the same budget, lower total cost at the required quality or more completed work with available capacity.
92
-
93
- #### What is already here in some shape or form
94
-
95
- There are useful tools here, including ones that already make model decisions. OpenRouter's Auto Router selects models for incoming requests. Portkey's gateway supports conditional routing, fallbacks and controlled testing of new models in production. Not Diamond lets teams train routers on their own data and evaluation criteria, with quality, cost and latency preferences.
96
-
97
- Langfuse helps teams compare changes through evaluation scores, cost, latency and individual failures. Braintrust supports experiments and continuous production evaluation, including bringing production examples back into testing. These tools already help teams investigate and improve their systems.
98
-
99
- Many of these capabilities overlap with parts of what we want to explore. They are also components we expect to work with.
100
-
101
- We consider optimum inference far from solved. A system needs to determine when the evidence behind an assignment is no longer enough, which alternatives deserve testing and whether a change will improve the completed work after its costs are included. It needs to maintain that process across changes to its tasks, models and infrastructure.
102
-
103
- OpenMerit exists to take on that continuing responsibility. The tools for an investigation should be available to the harness, along with the means to act on the result. The customer should not inherit a permanent engineering project to keep it all working.
104
-
105
- #### Path ahead
106
-
107
- We want teams to know when the strongest model is worth paying for, when an inexpensive alternative is sufficient and when a different deployment would let them do more.
108
-
109
- We want those decisions to become part of what a system can maintain for itself. Its engineers should be able to define the work and its constraints without inheriting a permanent job of reconsidering every model assignment.
110
-
111
- How much more useful work can we make possible with the intelligence and compute available to us?
112
-
113
- ---
114
-
115
- **Additional Reading**
116
-
117
- 1. [**"The Shift from Models to Compound AI Systems"**](https://bair.berkeley.edu/blog/2024/02/18/compound-ai-systems/) — Matei Zaharia et al., BAIR. (February 18, 2024)
118
- 2. [**"DSPy: Program, Don't Prompt"**](https://dspy.ai/current/) — DSPy / Stanford NLP.
119
- 3. [**"Optimas: Optimizing Compound AI Systems with Globally Aligned Local Rewards"**](https://optimas.stanford.edu/) — Shirley Wu et al., arXiv. (February 9, 2026)
120
- 4. [**"AI Agents That Matter"**](https://arxiv.org/abs/2407.01502) — Sayash Kapoor, Benedikt Stroebl, Zachary S. Siegel, Nitya Nadgir, and Arvind Narayanan, arXiv. (July 1, 2024)
121
- 5. [**"Training a Custom Router"**](https://docs.notdiamond.ai/docs/router-training-quickstart) — Not Diamond.
122
- 6. [**"Auto Router — Intelligent Model Selection"**](https://openrouter.ai/docs/guides/routing/routers/auto-router) — OpenRouter.
123
- 7. [**"AI Gateway"**](https://portkey.ai/docs/product/ai-gateway) — Portkey.
124
- 8. [**"Compare Experiments"**](https://langfuse.com/docs/evaluation/experiments/compare-experiments) — Langfuse.
125
- 9. [**"Evaluate Systematically"**](https://www.braintrust.dev/docs/evaluate) — Braintrust.
126
- 10. [**"Introducing System One Models & Jev"**](https://typesafe.ai/blog/introducing-system-one-models-and-jev) — Diogo Almeida, TypeSafe AI. (September 15, 2026)