@openlines/opl-pi-sht 0.1.15 → 0.1.17

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/README.md CHANGED
@@ -11,8 +11,8 @@ A portable collection of various Pi coding agent extensions. Repository director
11
11
  Install a versioned release from npm or GitHub. Both ship identical content; pick one source per machine, because Pi treats the npm and Git entries as separate packages and installing both loads every extension twice.
12
12
 
13
13
  ```bash
14
- pi install npm:@openlines/opl-pi-sht@0.1.15
15
- pi install git:github.com/linellazatin/opl-pi-sht@v0.1.15
14
+ pi install npm:@openlines/opl-pi-sht@0.1.17
15
+ pi install git:github.com/linellazatin/opl-pi-sht@v0.1.17
16
16
  ```
17
17
 
18
18
  > Omitting the version on the npm source tracks the latest published release; Git refs stay pinned, so move them with `pi install ...@v<new>`.
@@ -34,7 +34,7 @@ npx playwright install chromium
34
34
  {
35
35
  "packages": [
36
36
  {
37
- "source": "npm:@openlines/opl-pi-sht@0.1.15",
37
+ "source": "npm:@openlines/opl-pi-sht@0.1.17",
38
38
  "extensions": ["extensions/opl-init/index.ts", "extensions/opl-todo/index.ts"]
39
39
  }
40
40
  ]
@@ -200,7 +200,7 @@ npx playwright install chromium
200
200
 
201
201
  A Pi package (npm or Git) still needs the one-time `npx playwright install chromium` command shown above.
202
202
 
203
- `opl-simplebench` writes a full JSON benchmark artifact in Pi's current working directory by default. `--test-all` additionally writes `research.md` and `page.html` beside `result.json` in a result bundle. Copy `configs/opl-simplebench.json.sample` to `~/.pi/agent/configs/opl-simplebench.json` to configure DDGS/SearXNG research and optional llama-server/llamagputop metadata endpoints. Use `/simplebench --no-artifact` or `simplebench({ no_artifact: true })` when responses must not be written to disk. Provider credentials remain outside tracked configuration; configure them through Pi provider settings, environment variables, or Pi authentication.
203
+ `opl-simplebench` writes a full JSON benchmark artifact in Pi's current working directory by default. `--test-all` additionally writes `research.md` and `page.html` beside `result.json` in a result bundle. `/simplebench --sequence[=<name>]` runs a named templated multi-run protocol (for example a model warm-up curve) defined in the `runSequence.sequences` block, with per-iteration tags, optional llama-server/llamagputop metrics, and a configurable pause between iterations; block-level `llamaMetrics`/`pauseMs` are defaults a profile can override. Copy `configs/opl-simplebench.json.sample` to `~/.pi/agent/configs/opl-simplebench.json` to configure DDGS/SearXNG research, optional llama-server/llamagputop metadata endpoints, and the run sequence. Use `/simplebench --no-artifact` or `simplebench({ no_artifact: true })` when responses must not be written to disk. Provider credentials remain outside tracked configuration; configure them through Pi provider settings, environment variables, or Pi authentication.
204
204
 
205
205
  ## Tests
206
206
 
@@ -208,4 +208,4 @@ A Pi package (npm or Git) still needs the one-time `npx playwright install chrom
208
208
  npm test
209
209
  ```
210
210
 
211
- Run one extension suite with `npm run test:opl-<name>` for `footer`, `init`, `input`, `modes`, `questionnaire`, `todo`, `webaccess`, `simplebench`, or `ctxtrim`. Every helper, functional, and selected-entrypoint smoke check uses Bun's named-test reporter; output includes per-test status, timings, and pass/fail totals. Functional tests cover deterministic helpers where practical; smoke tests bundle entrypoints and parse config. They do not test live TUI behavior, provider credentials, network access, or PDF extraction.
211
+ Run one extension suite with `npm run test:opl-<name>` for `browser`, `footer`, `init`, `input`, `modes`, `questionnaire`, `todo`, `webaccess`, `simplebench`, or `ctxtrim`. Every helper, functional, and selected-entrypoint smoke check uses Bun's named-test reporter; output includes per-test status, timings, and pass/fail totals. Functional tests cover deterministic helpers where practical; smoke tests bundle entrypoints and parse config. They do not test live TUI behavior, provider credentials, network access, or PDF extraction.
@@ -1,8 +1,33 @@
1
1
  {
2
- "_comment": "Copy to ~/.pi/agent/configs/opl-simplebench.json. Endpoints are optional and only used by their corresponding Simplebench flags.",
2
+ "_comment": "Copy to ~/.pi/agent/configs/opl-simplebench.json. Endpoints are optional and only used by their corresponding Simplebench flags. runSequence powers /simplebench --sequence[=<name>]: each profile has a single-word name and an iterations list of flag strings; llamaMetrics appends --llama-server --llamagputop to every entry and pauseMs sleeps between iterations. Block-level llamaMetrics/pauseMs are the defaults; a profile may override either. A bare --sequence runs the only defined profile and otherwise lists the names. The legacy flat runSequence.sequence array is still accepted as an anonymous profile.",
3
3
  "researchSearchProvider": "ddgs",
4
4
  "researchSearchUrl": "http://127.0.0.1:8000",
5
5
  "researchMaxResults": 5,
6
6
  "llamaServerUrl": "http://127.0.0.1:8080/v1",
7
- "llamagputopUrl": "http://127.0.0.1:4321/stats"
7
+ "llamagputopUrl": "http://127.0.0.1:4321/stats",
8
+ "runSequence": {
9
+ "enabled": false,
10
+ "llamaMetrics": true,
11
+ "pauseMs": 5000,
12
+ "sequences": [
13
+ {
14
+ "name": "warmup",
15
+ "iterations": [
16
+ "--coding-lite --tag=coldest",
17
+ "--3ptest --tag=colder",
18
+ "--coding-lite --tag=semiwarm",
19
+ "--test-all --research-live --tag=warm"
20
+ ]
21
+ },
22
+ {
23
+ "name": "coding-only",
24
+ "llamaMetrics": false,
25
+ "pauseMs": 3000,
26
+ "iterations": [
27
+ "--coding-lite --tag=coldest",
28
+ "--coding-lite --tag=warmer"
29
+ ]
30
+ }
31
+ ]
32
+ }
8
33
  }
@@ -8,12 +8,14 @@ Auditable Pi model benchmark for closed-answer contracts, JSON instruction follo
8
8
  /simplebench
9
9
  /simplebench <model>
10
10
  /simplebench <model> [--no-artifact] [--thinking-max]
11
+ /simplebench <model> --3ptest
11
12
  /simplebench <model> --coding-lite
12
13
  /simplebench <model> --test-all
13
14
  /simplebench <model> --research-live
14
15
  /simplebench <model> --llama-server
15
16
  /simplebench <model> --llamagputop
16
17
  /simplebench <model> --tag=<word>
18
+ /simplebench <model> --sequence[=<name>]
17
19
  /simplebench --all [--no-artifact]
18
20
  /simplebench --all --test-all
19
21
  /simplebench --help
@@ -39,6 +41,8 @@ simplebench({ model: "global.openai.gpt-5.6-terra", test_all: true })
39
41
  - Twenty fixed closed-answer contracts, one strict JSON instruction test, and one chained tool-call test.
40
42
  - Six opt-in execution-backed coding tasks in disposable directories (`--coding-lite`).
41
43
  - `--test-all` runs baseline, coding-lite, and deterministic grounded research; `--research-live` adds the configured live research-artifact integration smoke test. `--all` still selects every Ollama model.
44
+ - `--3ptest` is the explicit form of the default baseline suite (reasoning, instruction following, tool calls). It changes nothing about the run; artifacts already name the baseline suite `3ptest`.
45
+ - `--sequence[=<name>]` runs a named templated multi-iteration profile stored in `opl-simplebench.json` (`runSequence.sequences`): sequential iterations, per-entry flags and `--tag`, `llamaMetrics` and `pauseMs` cooldown with block-level defaults a profile can override. See "Run a templated sequence" below.
42
46
  - `--llama-server` (`llama_server: true`) captures configured direct llama-server `/props` and `/metrics` without changing inference routing, so it works with a LiteLLM proxy.
43
47
  - `--llamagputop` (`llamagputop: true`) captures configured llama.cpp `/stats` metadata independently of the inference provider; its served model ID is authoritative.
44
48
  - `--tag=<word>` (`tag: "<word>"`) labels a run: stored as `benchmark.tag` and prefixed onto the artifact file or bundle name, for example `simplebench-coldrun-coding-lite-<model>-...`. The tag must be a single word (letters, digits, dot, dash, underscore).
@@ -80,6 +84,51 @@ Use this in sensitive environments. The terminal summary still reports category
80
84
 
81
85
  `--all` is intentionally limited to Ollama discovery. It writes one artifact per model unless `--no-artifact` is set.
82
86
 
87
+ ### Run a templated sequence
88
+
89
+ ```text
90
+ /simplebench --sequence=<name>
91
+ ```
92
+
93
+ Repeating a warm-up protocol (first-run-after-boot coding check, then baseline, then the full suite) means re-typing the same flags every session. `--sequence` reads a named `runSequence` profile from `~/.pi/agent/configs/opl-simplebench.json` and runs each entry as its own iteration against the current model (or the model passed with `--sequence`):
94
+
95
+ ```json
96
+ {
97
+ "runSequence": {
98
+ "enabled": true,
99
+ "llamaMetrics": true,
100
+ "pauseMs": 5000,
101
+ "sequences": [
102
+ {
103
+ "name": "warmup",
104
+ "iterations": [
105
+ "--coding-lite --tag=coldest",
106
+ "--3ptest --tag=colder",
107
+ "--coding-lite --tag=semiwarm",
108
+ "--test-all --research-live --tag=warm"
109
+ ]
110
+ },
111
+ {
112
+ "name": "coding-only",
113
+ "llamaMetrics": false,
114
+ "pauseMs": 3000,
115
+ "iterations": ["--coding-lite --tag=coldest", "--coding-lite --tag=warmer"]
116
+ }
117
+ ]
118
+ }
119
+ }
120
+ ```
121
+
122
+ - `enabled` gates the flag: `/simplebench --sequence` errors when it is missing or `false`. A bare `/simplebench` is unaffected.
123
+ - Profile names are single words (letters, digits, dot, dash, underscore) and must be unique. `/simplebench --sequence` without a name runs the only defined profile; with several profiles it errors and lists the available names.
124
+ - Block-level `llamaMetrics` and `pauseMs` are defaults; a profile may override either, so a coding-only curve can skip server stats or use a different cooldown.
125
+ - Each `iterations` entry is the same flag string you would type after `/simplebench`, including its own `--tag`.
126
+ - `llamaMetrics: true` appends `--llama-server --llamagputop` to every entry, so local server stats are captured per iteration without repeating the flags.
127
+ - `pauseMs` sleeps between iterations (never after the last one), including after a failed iteration, so thermal/KV-cache cooldown stays consistent across the warm-up curve.
128
+ - Iterations run sequentially and each writes its normal artifact/bundle; a failed iteration is reported and the sequence continues. A final summary line counts completed iterations.
129
+ - Entries containing `--all` or `--sequence` are rejected before anything runs, as are invalid configurations (no profiles, empty `iterations`, negative `pauseMs`) and invalid per-entry tags. The outer `--tag` is ignored; each entry carries its own.
130
+ - The legacy flat `runSequence.sequence` array still works as an anonymous profile, so existing configs need no change. Selecting it by name is not possible; migrate to `sequences` when you need more than one protocol.
131
+
83
132
  ### Grounded research in `--test-all`
84
133
 
85
134
  `--test-all` runs a deterministic agent workflow after coding-lite. `web_search` returns three benchmark-local source cards. The model must write every required finding as a Markdown bullet with its inline source ID (`[S1]`, `[S2]`) and list each cited source under `## Sources` as a Markdown link, for example `- [S1](https://research.fixture/S1)`. The verifier checks each exact claim-to-source relationship, source-list URL, tool trace, and the responsive minimalist `page.html` contract. It invokes no LLM judge, external search service, embedding model, or entailment model.
@@ -94,7 +143,7 @@ When coding-lite is enabled, each coding task is recorded as a `kind: "coding"`
94
143
 
95
144
  ### Configured metadata (`--llama-server`, `--llamagputop`)
96
145
 
97
- Copy `configs/opl-simplebench.json.sample` to `~/.pi/agent/configs/opl-simplebench.json`. It uses camelCase fields: `llamaServerUrl`, `llamagputopUrl`, `researchSearchProvider`, `researchSearchUrl`, and `researchMaxResults`. `--llama-server` captures `GET /props` and `/metrics` from configured `llamaServerUrl`; inference remains on the configured provider route, including LiteLLM. `--llamagputop` captures configured `llamagputopUrl` stats.
146
+ Copy `configs/opl-simplebench.json.sample` to `~/.pi/agent/configs/opl-simplebench.json`. It uses camelCase fields: `llamaServerUrl`, `llamagputopUrl`, `researchSearchProvider`, `researchSearchUrl`, `researchMaxResults`, and `runSequence` (see "Run a templated sequence"). `--llama-server` captures `GET /props` and `/metrics` from configured `llamaServerUrl`; inference remains on the configured provider route, including LiteLLM. `--llamagputop` captures configured `llamagputopUrl` stats.
98
147
 
99
148
  `serverStats` is omitted entirely for runs without the flag. Its shape:
100
149
 
@@ -138,6 +138,7 @@ function makeOpenAiChatFn(baseUrl: string, apiKey?: string, thinkingMax = false)
138
138
 
139
139
  const controller = new AbortController();
140
140
  const timeoutId = setTimeout(() => controller.abort(), CONFIG.TOOL_TEST_TIMEOUT_MS);
141
+ const startedAt = new Date().toISOString();
141
142
  const start = Date.now();
142
143
  try {
143
144
  const res = await fetch(`${baseUrl}/chat/completions`, {
@@ -163,6 +164,8 @@ function makeOpenAiChatFn(baseUrl: string, apiKey?: string, thinkingMax = false)
163
164
  toolCalls: toolCalls && toolCalls.length > 0 ? toolCalls : undefined,
164
165
  elapsedMs,
165
166
  raw: parsed,
167
+ startedAt,
168
+ finishedAt: new Date().toISOString(),
166
169
  };
167
170
  } catch (e: any) {
168
171
  clearTimeout(timeoutId);
@@ -265,6 +268,7 @@ function makeBedrockChatFn(providerInfo: { region?: string }): ChatFn {
265
268
 
266
269
  const controller = new AbortController();
267
270
  const timeoutId = setTimeout(() => controller.abort(), CONFIG.TOOL_TEST_TIMEOUT_MS);
271
+ const startedAt = new Date().toISOString();
268
272
  const start = Date.now();
269
273
  try {
270
274
  const res = await fetch(`https://${host}${urlPath}`, {
@@ -284,7 +288,7 @@ function makeBedrockChatFn(providerInfo: { region?: string }): ChatFn {
284
288
  const toolCalls = blocks.filter((b: any) => b.toolUse).map((b: any) => ({
285
289
  function: { name: b.toolUse.name, arguments: b.toolUse.input ?? {} },
286
290
  }));
287
- return { content, toolCalls: toolCalls.length > 0 ? toolCalls : undefined, elapsedMs, raw: parsed };
291
+ return { content, toolCalls: toolCalls.length > 0 ? toolCalls : undefined, elapsedMs, raw: parsed, startedAt, finishedAt: new Date().toISOString() };
288
292
  } catch (e: any) {
289
293
  clearTimeout(timeoutId);
290
294
  if (e.name === "AbortError") throw new Error(`Bedrock API timed out after ${msHuman(CONFIG.TOOL_TEST_TIMEOUT_MS)}`);
@@ -344,6 +348,7 @@ function makeOllamaToolChatFn(): ChatFn {
344
348
 
345
349
  const controller = new AbortController();
346
350
  const timeoutId = setTimeout(() => controller.abort(), CONFIG.TOOL_TEST_TIMEOUT_MS);
351
+ const startedAt = new Date().toISOString();
347
352
  const start = Date.now();
348
353
  try {
349
354
  const res = await fetch(`${ollamaBase()}/api/chat`, {
@@ -370,6 +375,8 @@ function makeOllamaToolChatFn(): ChatFn {
370
375
  toolCalls: toolCalls && toolCalls.length > 0 ? toolCalls : undefined,
371
376
  elapsedMs,
372
377
  raw: parsed,
378
+ startedAt,
379
+ finishedAt: new Date().toISOString(),
373
380
  };
374
381
  } catch (e: any) {
375
382
  clearTimeout(timeoutId);
@@ -1,7 +1,7 @@
1
1
  import type { ExtensionAPI, AgentToolResult } from "@earendil-works/pi-coding-agent";
2
2
  import { debugLog } from "./util/debug";
3
3
  import { detectProvider } from "./util/providers";
4
- import { TOOL_SUPPORT_CACHE_PATH } from "./util/config";
4
+ import { readTestConfig, TOOL_SUPPORT_CACHE_PATH, type ModelTestUserConfig, type RunSequenceProfile } from "./util/config";
5
5
  import type { SimplebenchOptions } from "./types";
6
6
  import { createBenchmark } from "./benchmark";
7
7
 
@@ -13,7 +13,50 @@ export function parseCommandArgs(args: string): SimplebenchOptions {
13
13
  tag = tagToken.slice("--tag=".length);
14
14
  if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(tag)) throw new Error(`--tag must be a single word (letters, digits, dot, dash, underscore): got "${tag}"`);
15
15
  }
16
- return { model: tokens.find(token => !token.startsWith("--")), allModels: tokens.includes("--all"), writeArtifact: !tokens.includes("--no-artifact"), thinkingMax: tokens.includes("--thinking-max"), codingLite: tokens.includes("--coding-lite"), testAll: tokens.includes("--test-all"), researchLive: tokens.includes("--research-live"), llamaServer: tokens.includes("--llama-server"), llamagputop: tokens.includes("--llamagputop"), ...(tag ? { tag } : {}) };
16
+ const seqToken = tokens.find(token => token === "--sequence" || token.startsWith("--sequence="));
17
+ let sequence: boolean | string | undefined;
18
+ if (seqToken) {
19
+ if (seqToken === "--sequence") sequence = true;
20
+ else {
21
+ sequence = seqToken.slice("--sequence=".length);
22
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(sequence)) throw new Error(`--sequence= must name a single word (letters, digits, dot, dash, underscore): got "${seqToken.slice("--sequence=".length)}"`);
23
+ }
24
+ }
25
+ return { model: tokens.find(token => !token.startsWith("--")), allModels: tokens.includes("--all"), writeArtifact: !tokens.includes("--no-artifact"), thinkingMax: tokens.includes("--thinking-max"), codingLite: tokens.includes("--coding-lite"), testAll: tokens.includes("--test-all"), researchLive: tokens.includes("--research-live"), llamaServer: tokens.includes("--llama-server"), llamagputop: tokens.includes("--llamagputop"), ...(tag ? { tag } : {}), ...(tokens.includes("--3ptest") ? { threePTest: true } : {}), ...(sequence ? { sequence } : {}) };
26
+ }
27
+
28
+ export interface ResolvedRunSequence { profile: string; runs: Array<{ entry: string; options: SimplebenchOptions }>; pauseMs: number; llamaMetrics: boolean }
29
+
30
+ const PROFILE_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
31
+
32
+ /** Expand one configured runSequence profile into per-iteration benchmark options. Throws on disabled/invalid configuration. */
33
+ export function resolveRunSequence(userConfig: ModelTestUserConfig, name?: string): ResolvedRunSequence {
34
+ const rs = userConfig.runSequence;
35
+ if (!rs?.enabled) throw new Error("runSequence is not enabled in opl-simplebench.json");
36
+ const profiles: RunSequenceProfile[] = [];
37
+ if (Array.isArray(rs.sequence) && rs.sequence.length > 0) profiles.push({ name: "", iterations: rs.sequence });
38
+ for (const profile of rs.sequences ?? []) {
39
+ if (typeof profile?.name !== "string" || !PROFILE_NAME_RE.test(profile.name)) throw new Error(`runSequence profile name must be a single word (letters, digits, dot, dash, underscore): got ${JSON.stringify(profile?.name)}`);
40
+ if (!Array.isArray(profile.iterations) || profile.iterations.length === 0) throw new Error(`runSequence profile "${profile.name}" needs a non-empty iterations array`);
41
+ if (profiles.some(known => known.name === profile.name)) throw new Error(`duplicate runSequence profile name: "${profile.name}"`);
42
+ profiles.push(profile);
43
+ }
44
+ const label = (p: RunSequenceProfile) => p.name || "(legacy)";
45
+ if (profiles.length === 0) throw new Error("runSequence needs a non-empty sequence array or sequences list");
46
+ let chosen: RunSequenceProfile | undefined;
47
+ if (name) chosen = profiles.find(p => p.name === name);
48
+ else if (profiles.length === 1) chosen = profiles[0];
49
+ if (!chosen) throw new Error(name ? `unknown runSequence profile "${name}"; available: ${profiles.map(label).join(", ")}` : `--sequence needs a profile name; available: ${profiles.map(label).join(", ")}`);
50
+ const pauseMs = chosen.pauseMs ?? rs.pauseMs ?? 0;
51
+ if (typeof pauseMs !== "number" || !Number.isFinite(pauseMs) || pauseMs < 0) throw new Error(`runSequence.pauseMs must be a non-negative number: got ${JSON.stringify(chosen.pauseMs ?? rs.pauseMs)}`);
52
+ const llamaMetrics = (chosen.llamaMetrics ?? rs.llamaMetrics) === true;
53
+ const runs = chosen.iterations.map((entry: string) => {
54
+ const tokens = String(entry).trim().split(/\s+/);
55
+ if (tokens.includes("--all")) throw new Error(`runSequence entry cannot contain --all: "${entry}"`);
56
+ if (tokens.some(token => token === "--sequence" || token.startsWith("--sequence="))) throw new Error(`runSequence entry cannot contain --sequence: "${entry}"`);
57
+ return { entry: String(entry), options: parseCommandArgs(`${entry}${llamaMetrics ? " --llama-server --llamagputop" : ""}`) };
58
+ });
59
+ return { profile: chosen.name, runs, pauseMs, llamaMetrics };
17
60
  }
18
61
 
19
62
  export default function (pi: ExtensionAPI) {
@@ -22,7 +65,7 @@ export default function (pi: ExtensionAPI) {
22
65
 
23
66
  pi.registerCommand("simplebench", {
24
67
  description: "Benchmark a model with auditable closed-answer, instruction, and tool-use tests.",
25
- detailedHelp: "\n\n🔍 Simplebench Extension\n\nThis extension tests AI models across multiple dimensions:\n• Closed-answer contract: 20 deterministic final-line answers (logic, math, spatial, commonsense)\n• Tool Usage: Ability to use available tools effectively\n• Instruction Following: How well the model follows complex JSON instructions\n• Coding Lite: Six isolated, execution-backed coding tasks\n\n📋 Usage Examples:\n /simplebench # Test current model\n /simplebench <model> # Test a specific model\n /simplebench --all # Test all Ollama models\n /simplebench <model> --coding-lite # Run only coding tasks\n /simplebench <model> --test-all # Run baseline, coding, and grounded research\n /simplebench <model> --research-live # Add live search integration smoke test\n /simplebench --all --test-all # Run complete suite for every Ollama model\n /simplebench <model> --thinking-max # Request max reasoning\n /simplebench <model> --llama-server # Capture configured /props and /metrics\n /simplebench <model> --llamagputop # Capture configured llama.cpp stats\n /simplebench <model> --tag=coldrun # Label the run: benchmark.tag plus artifact filename prefix\n /simplebench --help # Show this help\n /simplebench --clear-cache # Clear tool support cache\n\nCoding tasks run in disposable directories and never access the user repository.\n",
68
+ detailedHelp: "\n\n🔍 Simplebench Extension\n\nThis extension tests AI models across multiple dimensions:\n• Closed-answer contract: 20 deterministic final-line answers (logic, math, spatial, commonsense)\n• Tool Usage: Ability to use available tools effectively\n• Instruction Following: How well the model follows complex JSON instructions\n• Coding Lite: Six isolated, execution-backed coding tasks\n\n📋 Usage Examples:\n /simplebench # Test current model\n /simplebench <model> # Test a specific model\n /simplebench --all # Test all Ollama models\n /simplebench <model> --3ptest # Run the default 3ptest baseline explicitly\n /simplebench <model> --coding-lite # Run only coding tasks\n /simplebench <model> --test-all # Run baseline, coding, and grounded research\n /simplebench <model> --research-live # Add live search integration smoke test\n /simplebench --all --test-all # Run complete suite for every Ollama model\n /simplebench <model> --sequence[=<name>] # Run a configured runSequence profile\n /simplebench <model> --thinking-max # Request max reasoning\n /simplebench <model> --llama-server # Capture configured /props and /metrics\n /simplebench <model> --llamagputop # Capture configured llama.cpp stats\n /simplebench <model> --tag=coldrun # Label the run: benchmark.tag plus artifact filename prefix\n /simplebench --help # Show this help\n /simplebench --clear-cache # Clear tool support cache\n\nCoding tasks run in disposable directories and never access the user repository.\n",
26
69
  getArgumentCompletions: async (prefix) => {
27
70
  try {
28
71
  const models = await getOllamaModels();
@@ -54,6 +97,8 @@ pi.registerCommand("simplebench", {
54
97
  " /simplebench [model] --test-all - Run baseline, coding, and deterministic grounded research\n" +
55
98
  " /simplebench [model] --research-live - Add live-search integration smoke test\n" +
56
99
  " /simplebench [model] --tag=<word> - Label the run (single word; added to benchmark.tag and the artifact name)\n" +
100
+ " /simplebench [model] --3ptest - Run the default baseline suite explicitly\n" +
101
+ " /simplebench [model] --sequence[=<name>] - Run a templated multi-iteration sequence profile from opl-simplebench.json (runSequence.sequences; bare --sequence needs exactly one profile; per-entry --tag, llamaMetrics and pauseMs supported, profile values override the block ones; the outer --tag is ignored)\n" +
57
102
  " /simplebench --all --test-all - Run complete suite for all Ollama models\n" +
58
103
  " /simplebench --clear-cache - Clear tool support cache\n",
59
104
  "info"
@@ -76,6 +121,47 @@ pi.registerCommand("simplebench", {
76
121
  return;
77
122
  }
78
123
 
124
+ if (parsedArgs.sequence) {
125
+ const requested = typeof parsedArgs.sequence === "string" ? parsedArgs.sequence : undefined;
126
+ let resolved: ResolvedRunSequence;
127
+ try {
128
+ resolved = resolveRunSequence(readTestConfig(), requested);
129
+ } catch (e: any) {
130
+ ctx.ui.notify(e?.message || String(e), "error");
131
+ return;
132
+ }
133
+ const sequenceModel = parsedArgs.model || ctx.model?.id;
134
+ if (!sequenceModel) {
135
+ ctx.ui.notify("No model specified and no model currently selected", "error");
136
+ return;
137
+ }
138
+ ctx.ui.notify(`Running ${resolved.runs.length}-iteration sequence${resolved.profile ? ` "${resolved.profile}"` : ""} (llamaMetrics ${resolved.llamaMetrics ? "on" : "off"}, pause ${resolved.pauseMs / 1000}s): ${resolved.runs.map(r => r.entry).join(" | ")}...`, "info");
139
+ let completed = 0;
140
+ for (let i = 0; i < resolved.runs.length; i++) {
141
+ if (i > 0 && resolved.pauseMs > 0) {
142
+ ctx.ui.notify(`Cooling down ${resolved.pauseMs / 1000}s before iteration ${i + 1}/${resolved.runs.length}...`, "info");
143
+ await new Promise(resolve => setTimeout(resolve, resolved.pauseMs));
144
+ }
145
+ const { entry, options } = resolved.runs[i];
146
+ const model = options.model || sequenceModel;
147
+ ctx.ui.notify(`Iteration ${i + 1}/${resolved.runs.length} (${entry}) on ${model}...`, "info");
148
+ try {
149
+ const report = await testModel(model, ctx, { ...options, model });
150
+ completed += 1;
151
+ pi.sendMessage({
152
+ customType: "simplebench-report",
153
+ content: report,
154
+ display: { type: "content", content: report },
155
+ details: { model, timestamp: new Date().toISOString() },
156
+ });
157
+ } catch (e: any) {
158
+ ctx.ui.notify(`Iteration ${i + 1} (${entry}) failed: ${e.message}`, "error");
159
+ }
160
+ }
161
+ ctx.ui.notify(`Sequence done: ${completed}/${resolved.runs.length} iterations completed`, "info");
162
+ return;
163
+ }
164
+
79
165
  if (parsedArgs.allModels) {
80
166
  const providerInfo = detectProvider(ctx);
81
167
  if (providerInfo.kind !== "ollama") {
@@ -1,6 +1,6 @@
1
1
  import type { ServerStats } from "./llama-server";
2
2
 
3
- export interface SimplebenchOptions { model?: string; allModels: boolean; writeArtifact: boolean; thinkingMax?: boolean; codingLite?: boolean; testAll?: boolean; researchLive?: boolean; llamaServer?: boolean; llamagputop?: boolean; tag?: string; }
3
+ export interface SimplebenchOptions { model?: string; allModels: boolean; writeArtifact: boolean; thinkingMax?: boolean; codingLite?: boolean; testAll?: boolean; researchLive?: boolean; llamaServer?: boolean; llamagputop?: boolean; tag?: string; threePTest?: boolean; sequence?: boolean | string; }
4
4
  export interface RequestMetrics { requestCount: number; retryCount: number; wallTimeMs: number; timeToAnswerMs: number; timeToFirstTokenMs: number | null; startedAt: string | null; finishedAt: string | null; inputTokens: number | null; outputTokens: number | null; totalTokens: number | null; outputTokensPerSecond: number | null; toolCalls: Array<{ name: string; arguments?: unknown }>; }
5
5
  export interface TestRecord { id: string; kind: "reasoning" | "instructions" | "tools" | "coding" | "research-grounded" | "research-live"; category?: string; prompt: string; response: string | null; expectedAnswer?: string | string[]; extractedAnswer?: string; score: string; passed: boolean; error: string | null; metrics: RequestMetrics; coding?: { publicPassed: boolean; hiddenPassed: boolean; verifiedAfterEdit: boolean; unrelatedFiles: string[]; toolCalls: number; turns: number; efficiency?: "STRONG" | "MODERATE" | "WEAK" | "FAIL"; }; research?: { toolCalls: string[]; files: string[]; }; }
6
6
  export interface RunArtifact { schemaVersion: 1; benchmark: { name: "opl-simplebench"; suite?: "baseline" | "coding-lite" | "test-all"; tag?: string; model: string; provider: string; providerKind: string; thinking: { requested: "default" | "max"; effective: "provider-default" | "openai-reasoning-effort" | "pi-bedrock-reasoning"; level: "max" | null; modelMetadataSource: "active-context" | "scoped-model" | null; }; startedAt: string; finishedAt: string; wallTimeMs: number; artifactEnabled: boolean; }; tests: TestRecord[]; summary: Record<string, unknown> & { serverStats?: ServerStats }; }
@@ -79,6 +79,26 @@ export interface ModelTestUserConfig {
79
79
  researchMaxResults?: number;
80
80
  llamaServerUrl?: string;
81
81
  llamagputopUrl?: string;
82
+ runSequence?: RunSequenceConfig;
83
+ }
84
+
85
+ /** Templated multi-run benchmark sequence used by `/simplebench --sequence`. */
86
+ export interface RunSequenceConfig {
87
+ enabled?: boolean;
88
+ llamaMetrics?: boolean;
89
+ pauseMs?: number;
90
+ /** Legacy anonymous sequence; still resolved when no `sequences` profile matches. */
91
+ sequence?: string[];
92
+ /** Named sequences selectable with `/simplebench --sequence=<name>`. */
93
+ sequences?: RunSequenceProfile[];
94
+ }
95
+
96
+ /** One named sequence profile. `llamaMetrics` and `pauseMs` override the block-level values. */
97
+ export interface RunSequenceProfile {
98
+ name: string;
99
+ iterations: string[];
100
+ llamaMetrics?: boolean;
101
+ pauseMs?: number;
82
102
  }
83
103
 
84
104
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openlines/opl-pi-sht",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "Pi coding-agent extension kit: token-efficient context, safe modes, fingerprinted /init, model benchmarking, web access, and browser automation.",
5
5
  "repository": {
6
6
  "type": "git",