openmerit 0.1.11 → 0.2.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/CHANGELOG.md +15 -0
- package/README.md +20 -56
- package/dist/pi/scout-extension.mjs +190 -0
- package/dist/pi/src/index.d.ts +6 -32
- package/dist/pi/src/index.js +49 -787
- package/dist/terminal/public/app.js +37 -37
- package/dist/terminal/public/styles.css +0 -125
- package/dist/terminal/scout-update.json +1 -1
- package/dist/terminal/src/cli.mjs +2 -0
- package/dist/terminal/src/install.mjs +103 -166
- package/dist/terminal/src/scout-credentials.d.ts +12 -0
- package/dist/terminal/src/scout-credentials.js +41 -0
- package/dist/terminal/src/scout-credentials.ts +44 -0
- package/dist/terminal/src/scout-instructions.d.ts +5 -0
- package/dist/terminal/src/scout-instructions.js +116 -0
- package/dist/terminal/src/scout-instructions.ts +119 -0
- package/dist/terminal/src/scout-pi.d.ts +44 -0
- package/dist/terminal/src/scout-pi.js +84 -0
- package/dist/terminal/src/scout-pi.ts +76 -0
- package/dist/terminal/src/scout.d.ts +77 -0
- package/dist/terminal/src/scout.js +181 -0
- package/dist/terminal/src/scout.ts +164 -0
- package/dist/terminal/src/server.mjs +1 -1
- package/dist/terminal/src/telemetry.mjs +2 -2
- package/docs/commands.md +3 -77
- package/docs/getting-started.md +20 -45
- package/docs/index.md +9 -14
- package/docs/inference-terminal.md +2 -63
- package/docs/pi-extension.md +32 -28
- package/docs/roadmap.md +6 -18
- package/docs/scout.md +39 -255
- package/docs/security.md +8 -14
- package/docs/testing.md +8 -34
- package/docs/troubleshooting.md +23 -28
- package/package.json +6 -27
- package/dist/core/src/index.d.ts +0 -101
- package/dist/core/src/index.js +0 -1649
- package/dist/core/src/store.d.ts +0 -35
- package/dist/core/src/store.js +0 -102
- package/dist/pi/src/scheduler.d.ts +0 -11
- package/dist/pi/src/scheduler.js +0 -137
- package/dist/pi/src/wakeup.d.ts +0 -2
- package/dist/pi/src/wakeup.js +0 -108
- package/dist/protocol/src/index.d.ts +0 -484
- package/dist/protocol/src/index.js +0 -47
- package/dist/protocol/src/schemas.d.ts +0 -576
- package/dist/protocol/src/schemas.js +0 -280
- package/dist/terminal/public/brands/vllm.ico +0 -0
- package/dist/terminal/public/flow.css +0 -1
- package/dist/terminal/public/flow.js +0 -770
- package/dist/terminal/src/demo-policy.mjs +0 -205
- package/dist/terminal/src/enterprise-sample.mjs +0 -151
- package/docs/adapter-guide.md +0 -189
- package/docs/architecture.md +0 -59
- package/docs/automation.md +0 -74
- package/docs/budgets.md +0 -37
- package/docs/demo-backfill.md +0 -31
- package/docs/demo-fieldkit.md +0 -49
- package/docs/demo-placement.md +0 -32
- package/docs/demo-spam.md +0 -17
- package/docs/demo-support.md +0 -44
- package/docs/demo.md +0 -74
- package/docs/first-trial.md +0 -60
- package/docs/interactive-demo.md +0 -118
- package/docs/lifecycle.md +0 -30
- package/docs/metrics-and-evidence.md +0 -48
- package/docs/operations.md +0 -38
- package/docs/pareto-spec.md +0 -76
- package/docs/saga/a-steadier-compass.md +0 -25
- package/docs/saga/defining-a-task-for-ai.md +0 -154
- package/docs/saga/the-benchmark-is-not-the-job.md +0 -33
- package/docs/ux-reference.md +0 -32
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// `openmerit scout`: a conversation with Scout, the agent behind openmerit.ai/scout,
|
|
2
|
+
// for coding agents without the MCP server and for people at a terminal. It calls
|
|
3
|
+
// the same scout_models MCP tool with this device's key, so every client gets the
|
|
4
|
+
// same clarifying questions and per-task shortlists.
|
|
5
|
+
import { readFile } from 'node:fs/promises';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { credentialsPath, readCredentials } from "./scout-credentials.js";
|
|
8
|
+
export const policyFile = '.openmerit/scout.json';
|
|
9
|
+
export const components = ['input', 'work', 'output', 'criteria', 'constraints'];
|
|
10
|
+
export const help = `OpenMerit · Scout
|
|
11
|
+
|
|
12
|
+
openmerit scout "<the work models need to do>" [--all-endpoints] [--json]
|
|
13
|
+
openmerit scout --conversation <id> [--brief <component>=<text> ...] [--skip] ["<follow-up>"] [--json]
|
|
14
|
+
|
|
15
|
+
Asks Scout, the agent behind openmerit.ai/scout. Scout may first return a task
|
|
16
|
+
brief with clarifying questions: answer them with --brief (components: input,
|
|
17
|
+
work, output, criteria, constraints) in the same conversation, or --skip to let
|
|
18
|
+
Scout assume. It then splits the work into tasks and recommends up to five
|
|
19
|
+
models for each. A turn usually takes one to three minutes.
|
|
20
|
+
|
|
21
|
+
A new conversation is limited to the endpoint IDs in ${policyFile}
|
|
22
|
+
(set with openmerit project). --all-endpoints searches the whole catalogue and is
|
|
23
|
+
refused under the existing-only policy. Uses the device key that
|
|
24
|
+
"openmerit install" saved for Claude Code or Pi, this project's first. Never include secrets, source files or customer records.
|
|
25
|
+
`;
|
|
26
|
+
const isObject = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
|
|
27
|
+
async function readJSON(path) {
|
|
28
|
+
try {
|
|
29
|
+
const value = JSON.parse(await readFile(path, 'utf8'));
|
|
30
|
+
return isObject(value) ? value : null;
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
if (error.code === 'ENOENT')
|
|
34
|
+
return null;
|
|
35
|
+
throw new Error(`Could not read ${path}.`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// The device key an installation saved: this project's installation first, then the user's.
|
|
39
|
+
function deviceKey(root) {
|
|
40
|
+
for (const scope of ['project', 'user'])
|
|
41
|
+
for (const client of ['claude-code', 'pi']) {
|
|
42
|
+
const credentials = readCredentials(credentialsPath(client, scope, root));
|
|
43
|
+
if (credentials)
|
|
44
|
+
return credentials;
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
// One turn through the scout_models MCP tool. Errors are written for the person or agent running it.
|
|
49
|
+
export async function askScout(root, request, signal) {
|
|
50
|
+
const credentials = deviceKey(root);
|
|
51
|
+
if (!credentials)
|
|
52
|
+
throw new Error('This device is not signed in to OpenMerit. Run: npx -y openmerit@latest install');
|
|
53
|
+
const server = credentials.server;
|
|
54
|
+
let response;
|
|
55
|
+
try {
|
|
56
|
+
response = await fetch(`${server}/scout/mcp`, {
|
|
57
|
+
method: 'POST', redirect: 'error',
|
|
58
|
+
// A turn can take minutes; Scout keeps working if the connection drops.
|
|
59
|
+
signal: AbortSignal.any([AbortSignal.timeout(10 * 60_000), ...(signal ? [signal] : [])]),
|
|
60
|
+
headers: { ...credentials.headers, 'Content-Type': 'application/json', Authorization: `Bearer ${credentials.key}`, 'X-Scout-Client': 'cli' },
|
|
61
|
+
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/call', params: { name: 'scout_models', arguments: request } }),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
const id = request.conversationId;
|
|
66
|
+
throw new Error(error.name === 'TimeoutError' && id ? `Scout is still working. Read the outcome with: openmerit scout --conversation ${id}` : `Could not reach ${server}. Check your connection and retry.`);
|
|
67
|
+
}
|
|
68
|
+
if (response.status === 401)
|
|
69
|
+
throw new Error('The device key was rejected. Run: npx -y openmerit@latest login, then install again.');
|
|
70
|
+
const body = await response.json().catch(() => null);
|
|
71
|
+
const result = isObject(body) && isObject(body.result) ? body.result : null;
|
|
72
|
+
if (!result)
|
|
73
|
+
throw new Error(`Scout returned HTTP ${response.status}${isObject(body) && isObject(body.error) ? `: ${String(body.error.message)}` : ''}.`);
|
|
74
|
+
const text = Array.isArray(result.content) && isObject(result.content[0]) ? String(result.content[0].text) : '';
|
|
75
|
+
if (result.isError && !isObject(result.structuredContent))
|
|
76
|
+
throw new Error(text || 'Scout could not finish this turn.');
|
|
77
|
+
return result.structuredContent;
|
|
78
|
+
}
|
|
79
|
+
// A turn under the project's access policy. A new conversation is limited to the saved
|
|
80
|
+
// inventory; the whole catalogue needs an explicit choice the policy permits.
|
|
81
|
+
export async function scoutTurn(root, request, signal) {
|
|
82
|
+
const { allEndpoints, ...turn } = request;
|
|
83
|
+
if (turn.conversationId) {
|
|
84
|
+
if (allEndpoints)
|
|
85
|
+
throw new Error('--all-endpoints applies when a conversation starts. Start a new conversation instead.');
|
|
86
|
+
return askScout(root, turn, signal);
|
|
87
|
+
}
|
|
88
|
+
const policy = await readJSON(join(root, policyFile));
|
|
89
|
+
if (!policy) {
|
|
90
|
+
if (!allEndpoints)
|
|
91
|
+
throw new Error(`${policyFile} is missing. Ask which endpoints this project can use and save them with "npx -y openmerit@latest project --policy existing-only|existing-first --endpoint-id <id> ...", or pass --all-endpoints to search the whole catalogue.`);
|
|
92
|
+
return askScout(root, turn, signal);
|
|
93
|
+
}
|
|
94
|
+
const ids = Array.isArray(policy.endpointIds) && policy.endpointIds.every(id => typeof id === 'string') ? policy.endpointIds : null;
|
|
95
|
+
if (!ids || !['existing-only', 'existing-first'].includes(String(policy.policy)))
|
|
96
|
+
throw new Error(`${policyFile} is not a valid Scout policy. Save it again with "openmerit project".`);
|
|
97
|
+
if (allEndpoints && policy.policy === 'existing-only')
|
|
98
|
+
throw new Error('This project uses the existing-only policy, so Scout cannot search the whole catalogue.');
|
|
99
|
+
return askScout(root, allEndpoints ? turn : { ...turn, endpointIds: ids }, signal);
|
|
100
|
+
}
|
|
101
|
+
const money = (usd) => `$${usd < 0.1 ? usd.toFixed(3) : usd.toFixed(2)}`;
|
|
102
|
+
const rankings = { quality: 'Highest quality estimate first.', latency: 'Fastest first.', recency: 'Newest first.' };
|
|
103
|
+
export function renderScout(result) {
|
|
104
|
+
const lines = [];
|
|
105
|
+
if (result.status === 'needs_answers') {
|
|
106
|
+
lines.push('Scout needs more detail. Its task brief:', '');
|
|
107
|
+
for (const c of components) {
|
|
108
|
+
const part = result.brief?.[c];
|
|
109
|
+
lines.push(` ${c.padEnd(12)}${part?.text || '(missing)'}${part ? ` [${part.source}]` : ''}`);
|
|
110
|
+
}
|
|
111
|
+
lines.push('', 'Questions:', ...(result.questions ?? []).map(q => ` ${q.component}: ${q.question}`), '', `Answer: openmerit scout --conversation ${result.conversationId} --brief <component>="<answer>" ...`, `Or skip: openmerit scout --conversation ${result.conversationId} --skip`);
|
|
112
|
+
}
|
|
113
|
+
else if (result.status === 'recommended') {
|
|
114
|
+
const tasks = result.tasks ?? [];
|
|
115
|
+
tasks.forEach((task, i) => {
|
|
116
|
+
if (tasks.length > 1)
|
|
117
|
+
lines.push(`${i + 1}. ${task.title}`, ` ${task.work}`);
|
|
118
|
+
// As on the page, the default cheapest-first order is unlabelled; another priority is named with the value it sorts by.
|
|
119
|
+
const priority = task.priority ?? 'cost';
|
|
120
|
+
if (priority !== 'cost')
|
|
121
|
+
lines.push(` ${rankings[priority]}`);
|
|
122
|
+
for (const m of task.models) {
|
|
123
|
+
const price = m.cost ? `${money(m.cost.inputPerMillion)} in / ${money(m.cost.outputPerMillion)} out per 1M via ${m.cost.route} (${m.cost.endpointId})` : 'no priced route';
|
|
124
|
+
const metric = priority === 'latency' ? ` · ${m.latency ? `${m.latency.seconds.toFixed(1)}s per task` : 'time unknown'}`
|
|
125
|
+
: priority === 'recency' ? ` · ${m.recency ? `released ${m.recency.released}` : 'release date unknown'}` : '';
|
|
126
|
+
lines.push(` ${m.rank}. ${m.name}${metric} · quality ${m.quality.score}/100 · ${price}`, ` ${m.rationale}`);
|
|
127
|
+
}
|
|
128
|
+
lines.push(` ${task.summary}`, '');
|
|
129
|
+
});
|
|
130
|
+
if (result.assumptions?.length)
|
|
131
|
+
lines.push('Assumptions:', ...result.assumptions.map(a => ` - ${a}`), '');
|
|
132
|
+
lines.push(`Refine: openmerit scout --conversation ${result.conversationId} "<follow-up>"`);
|
|
133
|
+
lines.push('Quality is Scout\'s estimate from public benchmarks, not measured accuracy for your application.');
|
|
134
|
+
}
|
|
135
|
+
else if (result.status === 'working')
|
|
136
|
+
lines.push(`Scout is still working. Read the outcome with: openmerit scout --conversation ${result.conversationId}`);
|
|
137
|
+
else
|
|
138
|
+
lines.push(result.text ?? result.error ?? 'Scout replied without an answer.');
|
|
139
|
+
return lines.join('\n');
|
|
140
|
+
}
|
|
141
|
+
export async function main(args, { root = process.cwd(), print = console.log } = {}) {
|
|
142
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
143
|
+
print(help);
|
|
144
|
+
return 0;
|
|
145
|
+
}
|
|
146
|
+
const request = {};
|
|
147
|
+
const words = [];
|
|
148
|
+
let json = false;
|
|
149
|
+
for (let i = 0; i < args.length; i++) {
|
|
150
|
+
const arg = args[i] ?? '';
|
|
151
|
+
const value = () => { const v = args[++i]; if (v === undefined || v.startsWith('--'))
|
|
152
|
+
throw new Error(`${arg} needs a value.`); return v; };
|
|
153
|
+
if (arg === '--conversation')
|
|
154
|
+
request.conversationId = value();
|
|
155
|
+
else if (arg === '--brief') {
|
|
156
|
+
const [component, ...text] = value().split('=');
|
|
157
|
+
if (!components.includes(component ?? '') || !text.length)
|
|
158
|
+
throw new Error(`--brief takes <component>=<text>; components: ${components.join(', ')}.`);
|
|
159
|
+
request.brief = { ...request.brief, [component]: text.join('=') };
|
|
160
|
+
}
|
|
161
|
+
else if (arg === '--skip')
|
|
162
|
+
request.skip = true;
|
|
163
|
+
else if (arg === '--all-endpoints')
|
|
164
|
+
request.allEndpoints = true;
|
|
165
|
+
else if (arg === '--json')
|
|
166
|
+
json = true;
|
|
167
|
+
else if (arg.startsWith('-'))
|
|
168
|
+
throw new Error(`Unknown option for scout: ${arg}`);
|
|
169
|
+
else
|
|
170
|
+
words.push(arg);
|
|
171
|
+
}
|
|
172
|
+
if (words.length)
|
|
173
|
+
request.prompt = words.join(' ');
|
|
174
|
+
if (!request.prompt && !request.conversationId) {
|
|
175
|
+
print(help);
|
|
176
|
+
return 1;
|
|
177
|
+
}
|
|
178
|
+
const result = await scoutTurn(root, request);
|
|
179
|
+
print(json ? JSON.stringify(result, null, 2) : renderScout(result));
|
|
180
|
+
return result.status === 'error' ? 1 : 0;
|
|
181
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// `openmerit scout`: a conversation with Scout, the agent behind openmerit.ai/scout,
|
|
2
|
+
// for coding agents without the MCP server and for people at a terminal. It calls
|
|
3
|
+
// the same scout_models MCP tool with this device's key, so every client gets the
|
|
4
|
+
// same clarifying questions and per-task shortlists.
|
|
5
|
+
import { readFile } from 'node:fs/promises';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { credentialsPath, readCredentials, type DeviceCredentials } from './scout-credentials.ts';
|
|
8
|
+
|
|
9
|
+
export const policyFile = '.openmerit/scout.json';
|
|
10
|
+
export const components = ['input', 'work', 'output', 'criteria', 'constraints'] as const;
|
|
11
|
+
|
|
12
|
+
export const help = `OpenMerit · Scout
|
|
13
|
+
|
|
14
|
+
openmerit scout "<the work models need to do>" [--all-endpoints] [--json]
|
|
15
|
+
openmerit scout --conversation <id> [--brief <component>=<text> ...] [--skip] ["<follow-up>"] [--json]
|
|
16
|
+
|
|
17
|
+
Asks Scout, the agent behind openmerit.ai/scout. Scout may first return a task
|
|
18
|
+
brief with clarifying questions: answer them with --brief (components: input,
|
|
19
|
+
work, output, criteria, constraints) in the same conversation, or --skip to let
|
|
20
|
+
Scout assume. It then splits the work into tasks and recommends up to five
|
|
21
|
+
models for each. A turn usually takes one to three minutes.
|
|
22
|
+
|
|
23
|
+
A new conversation is limited to the endpoint IDs in ${policyFile}
|
|
24
|
+
(set with openmerit project). --all-endpoints searches the whole catalogue and is
|
|
25
|
+
refused under the existing-only policy. Uses the device key that
|
|
26
|
+
"openmerit install" saved for Claude Code or Pi, this project's first. Never include secrets, source files or customer records.
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
type Json = Record<string, unknown>;
|
|
30
|
+
export interface ScoutRequest { prompt?: string; conversationId?: string; brief?: Partial<Record<typeof components[number], string>>; skip?: boolean; endpointIds?: string[] }
|
|
31
|
+
export interface ScoutModel {
|
|
32
|
+
rank: number; id: string; name: string; provider: string; rationale: string; watchOut: string | null;
|
|
33
|
+
quality: { score: number; evidence: Array<{ name: string; rank: number; of: number }> };
|
|
34
|
+
cost: { usdPer1kTasks: number; inputPerMillion: number; outputPerMillion: number; route: string; endpointId: string; providerModelId: string } | null;
|
|
35
|
+
latency?: { seconds: number } | null; recency?: { released: string } | null;
|
|
36
|
+
}
|
|
37
|
+
// How a task's models are ordered. Cost (cheapest first among models that reach the quality bar) is the default.
|
|
38
|
+
type Priority = 'cost' | 'quality' | 'latency' | 'recency';
|
|
39
|
+
export interface ScoutResult {
|
|
40
|
+
conversationId: string; status: 'needs_answers' | 'recommended' | 'working' | 'message' | 'error'; next?: string;
|
|
41
|
+
brief?: Record<string, { text: string; source: string }>; questions?: Array<{ component: string; question: string }>;
|
|
42
|
+
assumptions?: string[]; tasks?: Array<{ title: string; work: string; summary: string; priority?: Priority; models: ScoutModel[] }>;
|
|
43
|
+
text?: string; error?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const isObject = (v: unknown): v is Json => !!v && typeof v === 'object' && !Array.isArray(v);
|
|
47
|
+
async function readJSON(path: string): Promise<Json | null> {
|
|
48
|
+
try { const value: unknown = JSON.parse(await readFile(path, 'utf8')); return isObject(value) ? value : null; }
|
|
49
|
+
catch (error) { if ((error as NodeJS.ErrnoException).code === 'ENOENT') return null; throw new Error(`Could not read ${path}.`); }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// The device key an installation saved: this project's installation first, then the user's.
|
|
53
|
+
function deviceKey(root: string): DeviceCredentials | null {
|
|
54
|
+
for (const scope of ['project', 'user'] as const) for (const client of ['claude-code', 'pi'] as const) {
|
|
55
|
+
const credentials = readCredentials(credentialsPath(client, scope, root));
|
|
56
|
+
if (credentials) return credentials;
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// One turn through the scout_models MCP tool. Errors are written for the person or agent running it.
|
|
62
|
+
export async function askScout(root: string, request: ScoutRequest, signal?: AbortSignal): Promise<ScoutResult> {
|
|
63
|
+
const credentials = deviceKey(root);
|
|
64
|
+
if (!credentials) throw new Error('This device is not signed in to OpenMerit. Run: npx -y openmerit@latest install');
|
|
65
|
+
const server = credentials.server;
|
|
66
|
+
let response: Response;
|
|
67
|
+
try {
|
|
68
|
+
response = await fetch(`${server}/scout/mcp`, {
|
|
69
|
+
method: 'POST', redirect: 'error',
|
|
70
|
+
// A turn can take minutes; Scout keeps working if the connection drops.
|
|
71
|
+
signal: AbortSignal.any([AbortSignal.timeout(10 * 60_000), ...(signal ? [signal] : [])]),
|
|
72
|
+
headers: { ...credentials.headers, 'Content-Type': 'application/json', Authorization: `Bearer ${credentials.key}`, 'X-Scout-Client': 'cli' },
|
|
73
|
+
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/call', params: { name: 'scout_models', arguments: request } }),
|
|
74
|
+
});
|
|
75
|
+
} catch (error) {
|
|
76
|
+
const id = request.conversationId;
|
|
77
|
+
throw new Error((error as Error).name === 'TimeoutError' && id ? `Scout is still working. Read the outcome with: openmerit scout --conversation ${id}` : `Could not reach ${server}. Check your connection and retry.`);
|
|
78
|
+
}
|
|
79
|
+
if (response.status === 401) throw new Error('The device key was rejected. Run: npx -y openmerit@latest login, then install again.');
|
|
80
|
+
const body: unknown = await response.json().catch(() => null);
|
|
81
|
+
const result = isObject(body) && isObject(body.result) ? body.result : null;
|
|
82
|
+
if (!result) throw new Error(`Scout returned HTTP ${response.status}${isObject(body) && isObject(body.error) ? `: ${String(body.error.message)}` : ''}.`);
|
|
83
|
+
const text = Array.isArray(result.content) && isObject(result.content[0]) ? String(result.content[0].text) : '';
|
|
84
|
+
if (result.isError && !isObject(result.structuredContent)) throw new Error(text || 'Scout could not finish this turn.');
|
|
85
|
+
return result.structuredContent as unknown as ScoutResult;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// A turn under the project's access policy. A new conversation is limited to the saved
|
|
89
|
+
// inventory; the whole catalogue needs an explicit choice the policy permits.
|
|
90
|
+
export async function scoutTurn(root: string, request: Omit<ScoutRequest, 'endpointIds'> & { allEndpoints?: boolean }, signal?: AbortSignal): Promise<ScoutResult> {
|
|
91
|
+
const { allEndpoints, ...turn } = request;
|
|
92
|
+
if (turn.conversationId) {
|
|
93
|
+
if (allEndpoints) throw new Error('--all-endpoints applies when a conversation starts. Start a new conversation instead.');
|
|
94
|
+
return askScout(root, turn, signal);
|
|
95
|
+
}
|
|
96
|
+
const policy = await readJSON(join(root, policyFile));
|
|
97
|
+
if (!policy) {
|
|
98
|
+
if (!allEndpoints) throw new Error(`${policyFile} is missing. Ask which endpoints this project can use and save them with "npx -y openmerit@latest project --policy existing-only|existing-first --endpoint-id <id> ...", or pass --all-endpoints to search the whole catalogue.`);
|
|
99
|
+
return askScout(root, turn, signal);
|
|
100
|
+
}
|
|
101
|
+
const ids = Array.isArray(policy.endpointIds) && policy.endpointIds.every(id => typeof id === 'string') ? policy.endpointIds as string[] : null;
|
|
102
|
+
if (!ids || !['existing-only', 'existing-first'].includes(String(policy.policy))) throw new Error(`${policyFile} is not a valid Scout policy. Save it again with "openmerit project".`);
|
|
103
|
+
if (allEndpoints && policy.policy === 'existing-only') throw new Error('This project uses the existing-only policy, so Scout cannot search the whole catalogue.');
|
|
104
|
+
return askScout(root, allEndpoints ? turn : { ...turn, endpointIds: ids }, signal);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const money = (usd: number) => `$${usd < 0.1 ? usd.toFixed(3) : usd.toFixed(2)}`;
|
|
108
|
+
const rankings: Record<Exclude<Priority, 'cost'>, string> = { quality: 'Highest quality estimate first.', latency: 'Fastest first.', recency: 'Newest first.' };
|
|
109
|
+
export function renderScout(result: ScoutResult): string {
|
|
110
|
+
const lines: string[] = [];
|
|
111
|
+
if (result.status === 'needs_answers') {
|
|
112
|
+
lines.push('Scout needs more detail. Its task brief:', '');
|
|
113
|
+
for (const c of components) { const part = result.brief?.[c]; lines.push(` ${c.padEnd(12)}${part?.text || '(missing)'}${part ? ` [${part.source}]` : ''}`); }
|
|
114
|
+
lines.push('', 'Questions:', ...(result.questions ?? []).map(q => ` ${q.component}: ${q.question}`), '',
|
|
115
|
+
`Answer: openmerit scout --conversation ${result.conversationId} --brief <component>="<answer>" ...`,
|
|
116
|
+
`Or skip: openmerit scout --conversation ${result.conversationId} --skip`);
|
|
117
|
+
} else if (result.status === 'recommended') {
|
|
118
|
+
const tasks = result.tasks ?? [];
|
|
119
|
+
tasks.forEach((task, i) => {
|
|
120
|
+
if (tasks.length > 1) lines.push(`${i + 1}. ${task.title}`, ` ${task.work}`);
|
|
121
|
+
// As on the page, the default cheapest-first order is unlabelled; another priority is named with the value it sorts by.
|
|
122
|
+
const priority = task.priority ?? 'cost';
|
|
123
|
+
if (priority !== 'cost') lines.push(` ${rankings[priority]}`);
|
|
124
|
+
for (const m of task.models) {
|
|
125
|
+
const price = m.cost ? `${money(m.cost.inputPerMillion)} in / ${money(m.cost.outputPerMillion)} out per 1M via ${m.cost.route} (${m.cost.endpointId})` : 'no priced route';
|
|
126
|
+
const metric = priority === 'latency' ? ` · ${m.latency ? `${m.latency.seconds.toFixed(1)}s per task` : 'time unknown'}`
|
|
127
|
+
: priority === 'recency' ? ` · ${m.recency ? `released ${m.recency.released}` : 'release date unknown'}` : '';
|
|
128
|
+
lines.push(` ${m.rank}. ${m.name}${metric} · quality ${m.quality.score}/100 · ${price}`, ` ${m.rationale}`);
|
|
129
|
+
}
|
|
130
|
+
lines.push(` ${task.summary}`, '');
|
|
131
|
+
});
|
|
132
|
+
if (result.assumptions?.length) lines.push('Assumptions:', ...result.assumptions.map(a => ` - ${a}`), '');
|
|
133
|
+
lines.push(`Refine: openmerit scout --conversation ${result.conversationId} "<follow-up>"`);
|
|
134
|
+
lines.push('Quality is Scout\'s estimate from public benchmarks, not measured accuracy for your application.');
|
|
135
|
+
} else if (result.status === 'working') lines.push(`Scout is still working. Read the outcome with: openmerit scout --conversation ${result.conversationId}`);
|
|
136
|
+
else lines.push(result.text ?? result.error ?? 'Scout replied without an answer.');
|
|
137
|
+
return lines.join('\n');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export async function main(args: string[], { root = process.cwd(), print = console.log } = {}): Promise<number> {
|
|
141
|
+
if (args.includes('--help') || args.includes('-h')) { print(help); return 0; }
|
|
142
|
+
const request: Parameters<typeof scoutTurn>[1] = {};
|
|
143
|
+
const words: string[] = [];
|
|
144
|
+
let json = false;
|
|
145
|
+
for (let i = 0; i < args.length; i++) {
|
|
146
|
+
const arg = args[i] ?? '';
|
|
147
|
+
const value = () => { const v = args[++i]; if (v === undefined || v.startsWith('--')) throw new Error(`${arg} needs a value.`); return v; };
|
|
148
|
+
if (arg === '--conversation') request.conversationId = value();
|
|
149
|
+
else if (arg === '--brief') {
|
|
150
|
+
const [component, ...text] = value().split('=');
|
|
151
|
+
if (!(components as readonly string[]).includes(component ?? '') || !text.length) throw new Error(`--brief takes <component>=<text>; components: ${components.join(', ')}.`);
|
|
152
|
+
request.brief = { ...request.brief, [component as typeof components[number]]: text.join('=') };
|
|
153
|
+
} else if (arg === '--skip') request.skip = true;
|
|
154
|
+
else if (arg === '--all-endpoints') request.allEndpoints = true;
|
|
155
|
+
else if (arg === '--json') json = true;
|
|
156
|
+
else if (arg.startsWith('-')) throw new Error(`Unknown option for scout: ${arg}`);
|
|
157
|
+
else words.push(arg);
|
|
158
|
+
}
|
|
159
|
+
if (words.length) request.prompt = words.join(' ');
|
|
160
|
+
if (!request.prompt && !request.conversationId) { print(help); return 1; }
|
|
161
|
+
const result = await scoutTurn(root, request);
|
|
162
|
+
print(json ? JSON.stringify(result, null, 2) : renderScout(result));
|
|
163
|
+
return result.status === 'error' ? 1 : 0;
|
|
164
|
+
}
|
|
@@ -6,7 +6,7 @@ import { createCollector, loadConfig } from './collect.mjs';
|
|
|
6
6
|
import { sampleSnapshot } from './sample.mjs';
|
|
7
7
|
import { telemetryEnabled, terminalProperties, sendTerminalEvent, errorProperties } from './telemetry.mjs';
|
|
8
8
|
|
|
9
|
-
const assets = new Map([['/', ['index.html', 'text/html']], ['/app.js', ['app.js', 'text/javascript']], ['/
|
|
9
|
+
const assets = new Map([['/', ['index.html', 'text/html']], ['/app.js', ['app.js', 'text/javascript']], ['/styles.css', ['styles.css', 'text/css']], ['/favicon.svg', ['favicon.svg', 'image/svg+xml']]]);
|
|
10
10
|
for (const file of ['baseten.png', 'groq.ico', 'cerebras.png', 'qwen.png', 'meta.ico', 'nomic.png', 'baai.png', 'ollama.png', 'lm-studio.png', 'vllm.png', 'anthropic.png', 'google.png', 'deepseek.ico', 'cohere.png', 'openai.png', 'mistral.png', 'openrouter.png', 'huggingface.svg']) assets.set(`/brands/${file}`, [`brands/${file}`, file.endsWith('.svg') ? 'image/svg+xml' : file.endsWith('.ico') ? 'image/x-icon' : 'image/png']);
|
|
11
11
|
|
|
12
12
|
export async function startTerminal({ root = process.cwd(), port = 4310, interval = 15, demo = false, collect: customCollect, config, fallbackPort = false } = {}) {
|
|
@@ -38,7 +38,7 @@ export const setupUUID = value => typeof value === 'string' && /^[0-9a-f]{8}-[0-
|
|
|
38
38
|
export function setupHeaders(ids = {}, client = 'cli') {
|
|
39
39
|
return {
|
|
40
40
|
'X-Scout-Client': client,
|
|
41
|
-
'X-Scout-Agent': 'claude-code',
|
|
41
|
+
'X-Scout-Agent': ids.agent === 'pi' ? 'pi' : 'claude-code',
|
|
42
42
|
...(clientVersion ? { 'X-Scout-Client-Version': clientVersion } : {}),
|
|
43
43
|
'X-Scout-Analytics-Content': 'off',
|
|
44
44
|
...(client === 'cli' ? { 'X-Scout-Analytics': telemetryEnabled(true) ? 'on' : 'off' } : !telemetryEnabled(true) ? { 'X-Scout-Analytics': 'off' } : {}),
|
|
@@ -54,7 +54,7 @@ export function installationProperties(input) {
|
|
|
54
54
|
stage: ['started', 'preflight', 'login', 'browser_open', 'configuration', 'verification', 'status', 'remove', 'completed'],
|
|
55
55
|
outcome: ['started', 'ok', 'error', 'pending', 'denied', 'conflict', 'not_installed'],
|
|
56
56
|
command: ['install', 'status', 'remove', 'login', 'logout', 'project', 'doctor', 'unknown'],
|
|
57
|
-
agent: ['claude-code'], scope: ['user', 'project'],
|
|
57
|
+
agent: ['claude-code', 'pi'], scope: ['user', 'project'],
|
|
58
58
|
rule_status: ['matches', 'missing', 'modified_or_unowned'],
|
|
59
59
|
skill_status: ['matches', 'missing', 'modified_or_unowned'],
|
|
60
60
|
server_status: ['matches', 'missing', 'modified_or_unowned'],
|
package/docs/commands.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Commands
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Pi adapter — npm 0.1.12 and later:** Pi 1.5.0 uses only native Scout MCP and shared guidance. Run `npx -y openmerit@latest install --client pi [--scope user|project]` and approve in the browser. Shell `login`, `logout`, `status`, `remove`, and `doctor` accept the same target flags. Updates require explicit reinstall; no Pi update hooks or scheduler are installed. See [Pi setup](pi-extension.md).
|
|
4
|
+
|
|
5
|
+
The standalone `openmerit dash` and `openmerit eval` commands are unchanged. The Pi 0.87 `/openmerit` slash commands were removed with that adapter and have no replacement.
|
|
4
6
|
|
|
5
7
|
Stable npm `openmerit@0.1.7` provides `openmerit eval init`, `doctor`, `plan`, `run`, `watch` and `report`. See [agent evaluations](agent-evaluations.md) for scope selection, repeated task checks and budgets. These are shell commands, separate from Pi commands. The same release includes [Scout setup commands](scout.md#commands), beginning with `npx -y openmerit@latest install --client claude-code`.
|
|
6
8
|
|
|
@@ -11,79 +13,3 @@ Since `0.1.7-preview.2`, `openmerit eval plan [--scope existing|all] [--budget <
|
|
|
11
13
|
Install with `npm install -g openmerit` to use `openmerit dash [path]` for a passive local view of models, connections, usage, and compute. From this checkout, use `npm run dash -- [path]`. It is independent of Pi setup and does not run model comparisons or inference.
|
|
12
14
|
|
|
13
15
|
See the [Inference Terminal guide](inference-terminal.md) for local installation, automatic discovery, `--no-discovery`, `--json`, `--demo`, collection intervals, and data limitations. Discovery is part of `dash` and is scoped to application evidence. Coding-harness histories are not collected.
|
|
14
|
-
|
|
15
|
-
## Inspect status
|
|
16
|
-
|
|
17
|
-
```text
|
|
18
|
-
/openmerit
|
|
19
|
-
/openmerit status
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
Both show the task profile, metric readiness, evaluation status, active work, next assessment, lifecycle stage, incumbent candidate, candidate and assessment counts, challenger spend, proposed candidate, and pause flag. A readiness label is based on the harness’s structured reports; inspect the evidence for the underlying measurements.
|
|
23
|
-
|
|
24
|
-
## Start or recover setup
|
|
25
|
-
|
|
26
|
-
```text
|
|
27
|
-
/openmerit setup
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
Requests setup explicitly or reopens setup to correct stale target/incumbent state. Accepted replacement setup clears prior candidate, assessment, frontier, and baseline handoffs while retaining the audit log.
|
|
31
|
-
|
|
32
|
-
Request setup explicitly only when recovering, rerunning, or configuring a custom integration. An unconfigured project starts setup when bounded source detection finds an application LLM call at session start or after a settled Pi build turn. OpenMerit refuses another intent while work is active.
|
|
33
|
-
|
|
34
|
-
## Cancel interrupted work
|
|
35
|
-
|
|
36
|
-
```text
|
|
37
|
-
/openmerit cancel
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
Record the active intent as cancelled after an interrupted or abandoned harness run. This clears durable active-work state without claiming success and releases any interactive request that was queued behind setup.
|
|
41
|
-
|
|
42
|
-
## Request an assessment
|
|
43
|
-
|
|
44
|
-
```text
|
|
45
|
-
/openmerit assess
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
Ask OpenMerit core to check stored baseline metric windows now. It reports missing or immature required metrics without starting Pi, or starts candidate discovery when they pass. It does not create missing measurements or override sample requirements.
|
|
49
|
-
|
|
50
|
-
## Request a frontier
|
|
51
|
-
|
|
52
|
-
```text
|
|
53
|
-
/openmerit frontier
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
Ask the harness to calculate a model comparison now. OpenMerit validates the returned frontier against the confirmed profile and supplied assessments. The command does not itself authorize a model change.
|
|
57
|
-
|
|
58
|
-
## Approve a proposal
|
|
59
|
-
|
|
60
|
-
```text
|
|
61
|
-
/openmerit approve
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
Approve the current proposal after its frontier result has passed validation. If there is no proposal in the expected lifecycle stage, the command reports that no verified proposal is awaiting approval. See [comparisons and swaps](lifecycle.md) for automatic-swap policy.
|
|
65
|
-
|
|
66
|
-
## Pause and resume
|
|
67
|
-
|
|
68
|
-
```text
|
|
69
|
-
/openmerit pause
|
|
70
|
-
/openmerit resume
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
Pause is persisted in `.openmerit/state.json`, blocks automatic due decisions and lifecycle follow-ups, and unloads the project's managed macOS LaunchAgent. Incoming signals can still be recorded. Resume clears pause and restores scheduling only when the confirmed policy authorizes it. Neither command cancels an already-active intent; explicit commands remain available. The standalone Inference Terminal is independent and keeps collecting until its CLI process exits.
|
|
74
|
-
|
|
75
|
-
## Locate logs and evidence
|
|
76
|
-
|
|
77
|
-
```text
|
|
78
|
-
/openmerit logs
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
Show the local audit, exporter-error, evidence-manifest, and state paths. See [project files and logs](operations.md) for their contents.
|
|
82
|
-
|
|
83
|
-
## Check the installation
|
|
84
|
-
|
|
85
|
-
```text
|
|
86
|
-
/openmerit doctor
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
Show adapter diagnostics, UI availability, and scheduling support. This checks extension wiring; it does not prove provider authentication, successful evaluations, or a completed model swap.
|
package/docs/getting-started.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Getting started
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
OpenMerit provides a passive Inference Terminal, Scout model-selection guidance, and a separate [agent-evaluation CLI](agent-evaluations.md). Pi Scout setup requires OpenMerit **0.1.12** or newer and Pi **1.5.0**. Package publication and website deployment are independent releases.
|
|
4
4
|
|
|
5
5
|
## Open your inference environment
|
|
6
6
|
|
|
@@ -12,65 +12,40 @@ cd /path/to/your/application
|
|
|
12
12
|
openmerit dash
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
The Inference Terminal
|
|
15
|
+
The Inference Terminal opens a local, read-only view of the selected application's services, models, recorded requests, and linked compute. It does not require Pi or a provider API key, make inference calls, or change model assignments. Supported code/configuration declarations identify inventory; request history requires existing supported logs or exports. See [the terminal guide](inference-terminal.md) for collection, privacy, configuration, and diagnostics.
|
|
16
16
|
|
|
17
|
-
To inspect fictional data
|
|
17
|
+
To inspect fictional data, run `openmerit dash --demo` or choose the sample from the environment selector. Sample data never enters the real snapshot or project files.
|
|
18
18
|
|
|
19
|
-
##
|
|
19
|
+
## Connect Scout to a coding agent
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
Install the extension in Pi:
|
|
24
|
-
|
|
25
|
-
```sh
|
|
26
|
-
pi install npm:openmerit
|
|
27
|
-
pi list
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
Run `/openmerit doctor` inside Pi to confirm that the extension is available.
|
|
31
|
-
|
|
32
|
-
## Install from source
|
|
21
|
+
Claude Code setup is described in [the Scout guide](scout.md#connect-a-coding-agent). For the new Pi integration, use Pi **1.5.0**, Node.js 22.19 or newer, and npm. Run the intended command in your shell:
|
|
33
22
|
|
|
34
23
|
```sh
|
|
35
|
-
|
|
36
|
-
cd openmerit
|
|
37
|
-
npm ci
|
|
24
|
+
npx -y openmerit@latest install --client pi --scope user
|
|
38
25
|
```
|
|
39
26
|
|
|
40
|
-
|
|
27
|
+
Approve the matching device code in the browser, then start a **new Pi session**. Only approve a request you initiated. User scope is the default and applies across projects. For one project, run this from that project's root instead:
|
|
41
28
|
|
|
42
29
|
```sh
|
|
43
|
-
|
|
30
|
+
npx -y openmerit@latest install --client pi --scope project
|
|
44
31
|
```
|
|
45
32
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
## Establish a product profile
|
|
49
|
-
|
|
50
|
-
Open the product repository in interactive Pi with the extension loaded. If the project has no OpenMerit state, the Pi adapter performs a bounded source preflight. It also scans after a settled Pi build turn, so an application created from an initially empty directory can enter setup without a slash command. A repository without such a target remains idle; use `/openmerit setup` only to recover or configure a custom integration explicitly.
|
|
33
|
+
Each client/scope installation has its own private Scout key; project keys are stored outside the repository. Do not install overlapping Pi user/project adapters or a duplicate `openmerit` MCP entry. The installer reports conflicts rather than overwriting edited or unowned files.
|
|
51
34
|
|
|
52
|
-
|
|
35
|
+
The adapter connects Scout through Pi's native MCP and supplies the shared Scout rule and skill. Pi owns tool discovery, tool execution, model choice, permissions, and sessions. Scout recommends application-model candidates; it does not execute those candidates or change Pi's own model. There are no `/openmerit` lifecycle commands, automatic evaluation setup, scheduler, or LaunchAgent in this adapter.
|
|
53
36
|
|
|
54
|
-
|
|
37
|
+
## Check, update, or remove Pi setup
|
|
55
38
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
## Collect the baseline
|
|
63
|
-
|
|
64
|
-
After setup, instrument the confirmed application target and use the product normally. Report application-task completions and cumulative metric windows through Pi's typed signal tool, including the application model where applicable, sample counts, and durable evidence. Pi's own model, tokens, cost, and latency are orchestration telemetry and do not satisfy product metrics. OpenMerit core checks the stored baseline windows at the approved cadence without calling Pi; missing evidence stays insufficient.
|
|
65
|
-
|
|
66
|
-
## Find the frontier
|
|
67
|
-
|
|
68
|
-
When the baseline meets every required metric threshold, OpenMerit issues the next approved nudges: discover candidates, run controlled challenger trials, and calculate the Pareto frontier. Pi performs that work. OpenMerit checks the returned result against the [Pareto specification](pareto-spec.md).
|
|
39
|
+
```sh
|
|
40
|
+
npx -y openmerit@latest status --client pi --scope user --check
|
|
41
|
+
npx -y openmerit@latest doctor --client pi --scope user
|
|
42
|
+
npx -y openmerit@latest remove --client pi --scope user
|
|
43
|
+
```
|
|
69
44
|
|
|
70
|
-
|
|
45
|
+
Use `--scope project` from the original project root for a project installation. `login` and `logout` accept the same `--client` and `--scope` flags. These commands target that installation, not another client's connection. Updating Pi setup requires explicitly rerunning `install` and starting a new session; no Pi auto-update hooks are installed. Installing or updating the npm package alone does not reconfigure Pi.
|
|
71
46
|
|
|
72
|
-
|
|
47
|
+
See [the Pi extension guide](pi-extension.md) for installed paths, migration from Pi 0.87, and SDK embedding. Use [troubleshooting](troubleshooting.md) for setup failures.
|
|
73
48
|
|
|
74
|
-
|
|
49
|
+
## Evaluate application changes
|
|
75
50
|
|
|
76
|
-
|
|
51
|
+
The standalone `openmerit eval` CLI is unchanged by the new Pi adapter. It evaluates Claude Code, Codex and OpenCode through explicit task, scope, budget and grader configuration; see [agent evaluations](agent-evaluations.md).
|
package/docs/index.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# OpenMerit documentation
|
|
2
2
|
|
|
3
|
-
These guides
|
|
3
|
+
These guides distinguish current source work from published behavior. OpenMerit **0.1.12** adds native Scout support for Pi **1.5.0**, replacing the old Pi 0.87 evaluation extension. The Inference Terminal and standalone agent-evaluation CLI are separate products and are unchanged by that replacement. The website, Scout service and npm package are deployed independently; dated receipts describe the versions actually tested.
|
|
4
4
|
|
|
5
5
|
## See your inference environment
|
|
6
6
|
|
|
@@ -14,27 +14,22 @@ openmerit dash
|
|
|
14
14
|
|
|
15
15
|
The terminal shows application services, model declarations, recorded requests, and linked compute. It reads supported local evidence, needs no Pi installation or provider API key, and makes no inference calls or model changes. Configuration can identify declared services; actual activity requires existing supported logs or exports.
|
|
16
16
|
|
|
17
|
-
Start with [installation and setup](getting-started.md) or the [Inference Terminal guide](inference-terminal.md).
|
|
17
|
+
Start with [installation and setup](getting-started.md) or the [Inference Terminal guide](inference-terminal.md).
|
|
18
18
|
|
|
19
19
|
## Discover model candidates
|
|
20
20
|
|
|
21
21
|
For a shortlist before building an integration, the separate [Scout beta](scout.md) accepts a task description and returns up to five evidence-backed model candidates. It is available at [openmerit.ai/scout/](https://openmerit.ai/scout/), with a prompt-only JSON API and MCP tool for coding agents.
|
|
22
22
|
|
|
23
|
-
##
|
|
23
|
+
## Connect Scout to Pi
|
|
24
24
|
|
|
25
|
-
The
|
|
25
|
+
The thin Pi 1.5.0 adapter uses native MCP and shared Scout guidance. Pi owns tools, models, permissions and sessions; the adapter has no `/openmerit` lifecycle commands, evaluation scheduling, LaunchAgent or automatic updates.
|
|
26
26
|
|
|
27
|
-
Follow [
|
|
27
|
+
Follow [setup](getting-started.md#connect-scout-to-a-coding-agent) and [the Pi guide](pi-extension.md), including browser approval, per-client/scope credentials, explicit reinstall updates and SDK requirements. Registry setup requires OpenMerit 0.1.12 or newer.
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
- [Comparisons and swaps](lifecycle.md): baseline, challenger trials, approval, verification, and rollback.
|
|
31
|
-
- [Budgets and permissions](budgets.md): challenger accounting and provider-spend limits.
|
|
32
|
-
- [Automatic checks](automation.md): durable signals, cadence, and authorized macOS scheduling.
|
|
29
|
+
## Evaluate application changes
|
|
33
30
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
[Commands](commands.md) and [project files and logs](operations.md) explain status, pause, diagnostics, and recovery. [Troubleshooting](troubleshooting.md), [security and data](security.md), and [current limitations](roadmap.md) describe the boundaries to check before relying on automation.
|
|
31
|
+
Use [the standalone evaluation CLI](agent-evaluations.md) to compare CLI revisions across Claude Code, Codex and OpenCode with explicit tasks, model scope, budgets and graders.
|
|
37
32
|
|
|
38
|
-
##
|
|
33
|
+
## Operate and troubleshoot
|
|
39
34
|
|
|
40
|
-
|
|
35
|
+
[Commands](commands.md) lists the terminal, Scout setup and evaluation commands. [Troubleshooting](troubleshooting.md), [security and data](security.md), and [current limitations](roadmap.md) explain setup failures and product boundaries. [Testing and validation](testing.md) distinguishes automated coverage from product evidence.
|