@parcha/agentrun-pi 0.1.0-beta.2 → 0.1.0-beta.21

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 (114) hide show
  1. package/README.md +123 -32
  2. package/dist/cli.js +15 -4
  3. package/dist/demo-cli.d.ts +2 -0
  4. package/dist/demo-cli.js +25 -0
  5. package/dist/demo.js +1 -0
  6. package/dist/durable/decision-recovery.d.ts +23 -0
  7. package/dist/durable/decision-recovery.js +57 -0
  8. package/dist/durable/decisions.d.ts +113 -0
  9. package/dist/durable/decisions.js +71 -0
  10. package/dist/durable/index.d.ts +20 -0
  11. package/dist/durable/index.js +14 -0
  12. package/dist/durable/node-runner.d.ts +106 -0
  13. package/dist/durable/node-runner.js +253 -0
  14. package/dist/durable/node.d.ts +88 -0
  15. package/dist/durable/node.js +98 -0
  16. package/dist/durable/pi-versions.d.ts +34 -0
  17. package/dist/durable/pi-versions.js +66 -0
  18. package/dist/durable/readers.d.ts +28 -0
  19. package/dist/durable/readers.js +103 -0
  20. package/dist/durable/receipts.d.ts +54 -0
  21. package/dist/durable/receipts.js +92 -0
  22. package/dist/durable/record-file.d.ts +10 -0
  23. package/dist/durable/record-file.js +56 -0
  24. package/dist/durable/record-tool.d.ts +76 -0
  25. package/dist/durable/record-tool.js +123 -0
  26. package/dist/durable/record.d.ts +157 -0
  27. package/dist/durable/record.js +402 -0
  28. package/dist/durable/recording.d.ts +90 -0
  29. package/dist/durable/recording.js +292 -0
  30. package/dist/durable/store.d.ts +23 -0
  31. package/dist/durable/store.js +242 -0
  32. package/dist/durable/task.d.ts +133 -0
  33. package/dist/durable/task.js +411 -0
  34. package/dist/durable/workflow-tool.d.ts +32 -0
  35. package/dist/durable/workflow-tool.js +188 -0
  36. package/dist/extension-service.js +63 -22
  37. package/dist/extension.d.ts +2 -0
  38. package/dist/extension.js +463 -80
  39. package/dist/host-addendum.d.ts +4 -0
  40. package/dist/host-addendum.js +20 -0
  41. package/dist/host-session.js +5 -2
  42. package/dist/index.d.ts +1 -2
  43. package/dist/index.js +1 -2
  44. package/dist/pi-host.d.ts +7 -0
  45. package/dist/pi-host.js +22 -0
  46. package/dist/presentation.js +2 -2
  47. package/dist/run-error.d.ts +6 -0
  48. package/dist/run-error.js +13 -0
  49. package/dist/runner.d.ts +1 -6
  50. package/dist/runner.js +7 -13
  51. package/dist/signin/index.d.ts +9 -0
  52. package/dist/signin/index.js +7 -0
  53. package/dist/signin/limit.d.ts +10 -0
  54. package/dist/signin/limit.js +23 -0
  55. package/dist/signin/resolve.d.ts +47 -0
  56. package/dist/signin/resolve.js +163 -0
  57. package/dist/signin/signin.d.ts +44 -0
  58. package/dist/signin/signin.js +126 -0
  59. package/dist/signin/spec.d.ts +116 -0
  60. package/dist/signin/spec.js +181 -0
  61. package/dist/signin/store.d.ts +40 -0
  62. package/dist/signin/store.js +206 -0
  63. package/dist/skills/author/SKILL.md +138 -0
  64. package/{skills → dist/skills}/author/references/jev-decisions.md +27 -14
  65. package/dist/skills/author/references/language.md +134 -0
  66. package/dist/skills/author/references/sdk-authoring.md +11 -0
  67. package/{skills → dist/skills}/author/references/workflow-format.md +7 -15
  68. package/dist/sop.d.ts +12 -0
  69. package/dist/sop.js +29 -0
  70. package/dist/thinking.d.ts +3 -0
  71. package/dist/thinking.js +7 -0
  72. package/dist/tool-context.d.ts +21 -0
  73. package/dist/tool-context.js +13 -0
  74. package/dist/triage-demo.d.ts +62 -0
  75. package/dist/triage-demo.js +153 -0
  76. package/dist/types.d.ts +7 -0
  77. package/dist/workflow-inspector.d.ts +47 -0
  78. package/dist/workflow-inspector.js +235 -0
  79. package/dist/workflow-session.d.ts +23 -0
  80. package/dist/workflow-session.js +168 -0
  81. package/dist/workflow-store.d.ts +36 -0
  82. package/dist/workflow-store.js +255 -0
  83. package/dist/workflow-tool.d.ts +81 -0
  84. package/dist/workflow-tool.js +94 -0
  85. package/dist/workflow-view.d.ts +64 -0
  86. package/dist/workflow-view.js +273 -0
  87. package/dist/workflows.d.ts +1 -0
  88. package/dist/workflows.js +3 -0
  89. package/package.json +57 -11
  90. package/dist/admission.d.ts +0 -6
  91. package/dist/admission.js +0 -51
  92. package/dist/author.d.ts +0 -21
  93. package/dist/author.js +0 -72
  94. package/dist/benchmark-contract.d.ts +0 -88
  95. package/dist/benchmark-contract.js +0 -50
  96. package/dist/benchmark-freeze.d.ts +0 -9
  97. package/dist/benchmark-freeze.js +0 -9
  98. package/dist/benchmark.d.ts +0 -127
  99. package/dist/benchmark.js +0 -260
  100. package/dist/comparison.d.ts +0 -69
  101. package/dist/comparison.js +0 -229
  102. package/dist/skill-bundle.d.ts +0 -5
  103. package/dist/skill-bundle.js +0 -15
  104. package/skills/author/SKILL.md +0 -102
  105. package/skills/author/references/sdk-authoring.md +0 -16
  106. /package/{skills → dist/skills}/author/examples/evidence-gate.input.json +0 -0
  107. /package/{skills → dist/skills}/author/examples/evidence-gate.json +0 -0
  108. /package/{skills → dist/skills}/author/examples/read-file.input.json +0 -0
  109. /package/{skills → dist/skills}/author/examples/read-file.json +0 -0
  110. /package/{skills → dist/skills}/author/examples/read-source-decision.input.json +0 -0
  111. /package/{skills → dist/skills}/author/examples/read-source-decision.json +0 -0
  112. /package/{skills → dist/skills}/author/examples/release-note.txt +0 -0
  113. /package/{skills → dist/skills}/author/examples/review-release-notes.input.json +0 -0
  114. /package/{skills → dist/skills}/author/examples/review-release-notes.json +0 -0
@@ -1,50 +0,0 @@
1
- import { createHash } from 'node:crypto';
2
- import { readFileSync } from 'node:fs';
3
- import { Compile } from 'typebox/compile';
4
- const cases = [
5
- { id: 'docs-editing', split: 'development', question: 'Can contributors edit our docs in the browser?', topics: [{ id: 'editing', question: 'Did contributors edit a page in the browser during our trial?' }], sources: [{ id: 'd1', text: 'All three contributors edited a page in the browser during the trial.' }, { id: 'd2', text: 'The documentation product has a new logo.' }], expected: [{ topicId: 'editing', status: 'supported', sourceIds: ['d1'] }] },
6
- { id: 'migration', split: 'development', question: 'What changes with a repository-based wiki?', topics: [{ id: 'review', question: 'How are changes reviewed?' }, { id: 'editing', question: 'Could non-developers edit documentation?' }, { id: 'search', question: 'What did the search trial find?' }], sources: [{ id: 'm1', text: 'Documentation changes require an assigned reviewer to approve the pull request.' }, { id: 'm2', text: 'Non-developers edited in the browser but two needed help with merge conflicts.' }, { id: 'm3', text: 'Search found exact words but missed synonyms during our trial.' }, { id: 'm4', text: 'A revolutionary documentation experience.' }], expected: [{ topicId: 'review', status: 'supported', sourceIds: ['m1'] }, { topicId: 'editing', status: 'supported', sourceIds: ['m2'] }, { topicId: 'search', status: 'supported', sourceIds: ['m3'] }] },
7
- { id: 'missing-conflicting', split: 'development', question: 'Can we rely on restricted editing and synonym search?', topics: [{ id: 'permissions', question: 'Who can edit restricted pages?' }, { id: 'search', question: 'Did synonym search work in this trial?' }], sources: [{ id: 'c1', text: 'Restricted-page permissions: details pending.' }, { id: 'c2', text: 'The trial report says all ten synonym searches found their target pages.' }, { id: 'c3', text: 'An audit of the same trial says none of those ten searches found their target pages.' }], expected: [{ topicId: 'permissions', status: 'insufficient', sourceIds: [] }, { topicId: 'search', status: 'conflicting', sourceIds: ['c2', 'c3'] }] },
8
- { id: 'backup', split: 'heldout', question: 'Did the restore drill recover the archive?', topics: [{ id: 'restore', question: 'Was the archive recovered during the drill?' }], sources: [{ id: 'h1', text: 'The drill restored the archive and every file matched its recorded checksum.' }, { id: 'h2', text: 'Backups that give you peace of mind.' }], expected: [{ topicId: 'restore', status: 'supported', sourceIds: ['h1'] }] },
9
- { id: 'queue-adoption', split: 'heldout', question: 'What should we know before adopting this task queue?', topics: [{ id: 'retries', question: 'What happens to jobs after worker failure?' }, { id: 'ordering', question: 'Is task order guaranteed?' }, { id: 'visibility', question: 'Can operators inspect stuck jobs?' }], sources: [{ id: 'q1', text: 'In the failure test, an unfinished job returned to the queue when its lease expired.' }, { id: 'q2', text: 'Tasks from different workers completed out of submission order in the trial.' }, { id: 'q3', text: 'Operators used the dashboard to inspect job age and the last worker error.' }, { id: 'q4', text: 'Supercharge your asynchronous future.' }], expected: [{ topicId: 'retries', status: 'supported', sourceIds: ['q1'] }, { topicId: 'ordering', status: 'supported', sourceIds: ['q2'] }, { topicId: 'visibility', status: 'supported', sourceIds: ['q3'] }] },
10
- { id: 'cache-evidence', split: 'heldout', question: 'What do the cache trials tell us about deletion and encryption?', topics: [{ id: 'deletion', question: 'Did deleted entries disappear immediately in the trial?' }, { id: 'encryption', question: 'Which encryption protects cached entries?' }], sources: [{ id: 'k1', text: 'The trial log says every deleted entry disappeared immediately.' }, { id: 'k2', text: 'A second observer of that same trial recorded deleted entries remaining readable for a full minute.' }, { id: 'k3', text: 'Encryption: documentation forthcoming.' }], expected: [{ topicId: 'deletion', status: 'conflicting', sourceIds: ['k1', 'k2'] }, { topicId: 'encryption', status: 'insufficient', sourceIds: [] }] },
11
- ];
12
- export const benchmarkHash = (value) => createHash('sha256').update(JSON.stringify(value)).digest('hex');
13
- export const benchmarkInstructions = 'Answer using only the fictional source corpus. Produce exactly one finding for each supplied topic. Status supported means concrete evidence establishes a finding, including a negative or qualified answer. Conflicting means incompatible accounts. Insufficient means no concrete information answers the question. Preserve conditions and contrary evidence; cite every material source by ID. Do not invent facts or citations. Submit the final structured report.';
14
- const str = { type: 'string', minLength: 1 };
15
- export const benchmarkReportSchema = { type: 'object', additionalProperties: false, required: ['findings'], properties: { findings: { type: 'array', minItems: 1, items: { type: 'object', additionalProperties: false, required: ['topicId', 'status', 'summary', 'sourceIds'], properties: { topicId: str, status: { enum: ['supported', 'conflicting', 'insufficient'] }, summary: str, sourceIds: { type: 'array', uniqueItems: true, items: str } } } } } };
16
- export const benchmarkEvidenceSchema = { type: 'object', additionalProperties: false, required: ['relevant'], properties: { relevant: { type: 'boolean', description: 'Does the passage contain a concrete fact, condition, limitation or contrary evidence bearing on the question?', criteria: { true: 'Concrete evidence relevant to the question, including qualified or contradictory answers.', false: 'Only topic mentions, marketing promises, missing details, or unrelated information.' } } } };
17
- const validator = Compile(benchmarkReportSchema);
18
- export function benchmarkCases(split) {
19
- if (split !== 'development' && split !== 'heldout')
20
- throw new Error('Choose development or heldout explicitly');
21
- return structuredClone(cases.filter(row => row.split === split));
22
- }
23
- /** Frozen independent checks. Never return these results to an authoring agent. */
24
- export function evaluatePiBenchmark(caseId, output) {
25
- const item = cases.find(row => row.id === caseId);
26
- if (!item)
27
- throw new Error('Unknown benchmark case');
28
- if (!validator.Check(output))
29
- return { passed: false, issues: ['schema'] };
30
- const findings = output.findings;
31
- const issues = [];
32
- if (findings.length !== item.expected.length || new Set(findings.map(row => row.topicId)).size !== findings.length)
33
- issues.push('coverage');
34
- for (const expected of item.expected) {
35
- const actual = findings.find(row => row.topicId === expected.topicId);
36
- if (actual?.status !== expected.status)
37
- issues.push(`${expected.topicId}:status`);
38
- if (!actual || JSON.stringify([...actual.sourceIds].sort()) !== JSON.stringify([...expected.sourceIds].sort()))
39
- issues.push(`${expected.topicId}:citations`);
40
- }
41
- return { passed: issues.length === 0, issues };
42
- }
43
- export function benchmarkContract() {
44
- return {
45
- version: 1, fixturesSha256: benchmarkHash(cases),
46
- verifierSha256: createHash('sha256').update(readFileSync(new URL('./benchmark-contract.js', import.meta.url))).digest('hex'),
47
- outputSchemaSha256: benchmarkHash(benchmarkReportSchema),
48
- instructionsSha256: benchmarkHash(benchmarkInstructions),
49
- };
50
- }
@@ -1,9 +0,0 @@
1
- /** Frozen before any live sample. Changing these pins requires a new benchmark
2
- * version and a fresh baseline; never update them to improve a skill's score. */
3
- export declare const frozenBenchmarkContract: {
4
- readonly version: 1;
5
- readonly fixturesSha256: "1a8f9ff022d5cdaf2455e327e59ae4593aeffcc8ae4c510a870b36ea0d9e7de2";
6
- readonly verifierSha256: "f21d0c8b790186f571003907a5a6c7a6360423d77d1125c4398994cb1ae11f9a";
7
- readonly outputSchemaSha256: "38a0d7ccd8590ad8a486a5b7b7edd36c0b8bd32e642ead5345a3f93d12f3e574";
8
- readonly instructionsSha256: "d8d3fc6721ed99bb163ff3dbd93dffc15a1a92ab400f11f781bb25e9f8a61535";
9
- };
@@ -1,9 +0,0 @@
1
- /** Frozen before any live sample. Changing these pins requires a new benchmark
2
- * version and a fresh baseline; never update them to improve a skill's score. */
3
- export const frozenBenchmarkContract = {
4
- version: 1,
5
- fixturesSha256: '1a8f9ff022d5cdaf2455e327e59ae4593aeffcc8ae4c510a870b36ea0d9e7de2',
6
- verifierSha256: 'f21d0c8b790186f571003907a5a6c7a6360423d77d1125c4398994cb1ae11f9a',
7
- outputSchemaSha256: '38a0d7ccd8590ad8a486a5b7b7edd36c0b8bd32e642ead5345a3f93d12f3e574',
8
- instructionsSha256: 'd8d3fc6721ed99bb163ff3dbd93dffc15a1a92ab400f11f781bb25e9f8a61535',
9
- };
@@ -1,127 +0,0 @@
1
- import { type WorkflowDeps } from '@parcha/agentrun-dsl';
2
- import type { PiHostContext } from './types.js';
3
- import { evaluatePiBenchmark, type BenchmarkSplit } from './benchmark-contract.js';
4
- export interface PiBenchmarkBudgets {
5
- timeoutMs: number;
6
- maxModelRequests: number;
7
- maxToolCalls: number;
8
- maxTurns: number;
9
- maxSubmissions: number;
10
- }
11
- export interface PiSkillBenchmarkOptions {
12
- host: PiHostContext;
13
- runJudge: NonNullable<WorkflowDeps['runJudge']>;
14
- /** Exact native skill plus its loaded references, supplied by the caller. No hidden authoring contract is added. */
15
- skillText: string;
16
- split: BenchmarkSplit;
17
- mode: 'live' | 'scripted-transport';
18
- signal?: AbortSignal;
19
- budgets?: Partial<PiBenchmarkBudgets>;
20
- onProgress?: (event: {
21
- caseId: string;
22
- arm: 'plain-pi' | 'skill-dsl';
23
- stage: 'arm' | 'outer' | 'workflow';
24
- event: 'start' | 'end';
25
- status?: string;
26
- }) => unknown;
27
- }
28
- type Metrics = {
29
- pi: number;
30
- jev: number;
31
- tools: number;
32
- submissions: number;
33
- tokens: number;
34
- usageSamples: number;
35
- knownCostUsd: number;
36
- costSamples: number;
37
- };
38
- type Arm = 'plain-pi' | 'skill-dsl';
39
- export interface PiBenchmarkArm {
40
- arm: Arm;
41
- status: 'passed' | 'rejected' | 'failed' | 'cancelled';
42
- elapsedMs: number;
43
- total: Metrics;
44
- stages: {
45
- outer: Metrics;
46
- workflow: Metrics;
47
- };
48
- costComplete: boolean;
49
- usageComplete: boolean;
50
- candidates: {
51
- digest?: string;
52
- accepted: boolean;
53
- workflow?: unknown;
54
- }[];
55
- workflowRuns: {
56
- status: string;
57
- digest: string;
58
- output?: unknown;
59
- }[];
60
- output?: unknown;
61
- evaluation?: ReturnType<typeof evaluatePiBenchmark>;
62
- failure?: string;
63
- }
64
- /** End-to-end comparison: DSL authoring, repair, execution and delivery all count.
65
- * Uses the selected host model, never a model selector or alternate provider. */
66
- export declare function runPiSkillBenchmark(options: PiSkillBenchmarkOptions): Promise<{
67
- mode: "live" | "scripted-transport";
68
- split: BenchmarkSplit;
69
- status: "complete" | "cancelled";
70
- passed: boolean;
71
- contract: {
72
- version: number;
73
- fixturesSha256: string;
74
- verifierSha256: string;
75
- outputSchemaSha256: string;
76
- instructionsSha256: string;
77
- };
78
- skillSha256: string;
79
- budgets: {
80
- timeoutMs: number;
81
- maxModelRequests: number;
82
- maxToolCalls: number;
83
- maxTurns: number;
84
- maxSubmissions: number;
85
- };
86
- runtime: {
87
- model: string;
88
- provider: string;
89
- api: string;
90
- thinking: import("./types.js").PiThinkingLevel;
91
- };
92
- runs: {
93
- caseId: string;
94
- inputSha256: string;
95
- order: Arm[];
96
- arms: PiBenchmarkArm[];
97
- }[];
98
- limitations: string[];
99
- }>;
100
- export type PiSkillBenchmarkReport = Awaited<ReturnType<typeof runPiSkillBenchmark>>;
101
- /** A development comparison receipt, not automatic promotion. Heldout is a final
102
- * confirmation set, never the optimization target for this helper. */
103
- export declare function comparePiSkillBenchmarks(baseline: PiSkillBenchmarkReport, candidate: PiSkillBenchmarkReport): {
104
- mode: "live" | "scripted-transport";
105
- baselineSkillSha256: string;
106
- candidateSkillSha256: string;
107
- regressions: string[];
108
- previous: {
109
- passed: number;
110
- requests: number;
111
- tokens: number;
112
- usageComplete: boolean;
113
- knownCostUsd: number;
114
- costComplete: boolean;
115
- };
116
- current: {
117
- passed: number;
118
- requests: number;
119
- tokens: number;
120
- usageComplete: boolean;
121
- knownCostUsd: number;
122
- costComplete: boolean;
123
- };
124
- improved: boolean;
125
- eligibleForLiveHeldout: boolean;
126
- };
127
- export {};
package/dist/benchmark.js DELETED
@@ -1,260 +0,0 @@
1
- import { isDeepStrictEqual } from 'node:util';
2
- import { frozenBenchmarkContract } from './benchmark-freeze.js';
3
- import { Compile } from 'typebox/compile';
4
- import { formatWorkflowTree, runWorkflowSlice } from '@parcha/agentrun-dsl';
5
- import { createPiHostSessionFactory } from './host-session.js';
6
- import { createPiRunner, PiRunError } from './runner.js';
7
- import { WorkflowExtensionService, ExtensionServiceError } from './extension-service.js';
8
- import { benchmarkCases, benchmarkContract, benchmarkHash, benchmarkInstructions, benchmarkReportSchema, benchmarkEvidenceSchema, evaluatePiBenchmark } from './benchmark-contract.js';
9
- const emptyMetrics = () => ({ pi: 0, jev: 0, tools: 0, submissions: 0, tokens: 0, usageSamples: 0, knownCostUsd: 0, costSamples: 0 });
10
- const ceilings = { timeoutMs: 600_000, maxModelRequests: 24, maxToolCalls: 48, maxTurns: 24, maxSubmissions: 4 };
11
- class BenchmarkStop extends Error {
12
- code;
13
- constructor(code) {
14
- super(code);
15
- this.code = code;
16
- }
17
- }
18
- const textResult = (value) => ({ content: [{ type: 'text', text: JSON.stringify(value) }], details: value });
19
- const selectionWorkflow = { v: 2, name: 'benchmark-evidence', schemas: { Evidence: benchmarkEvidenceSchema, Result: { type: 'object' } }, output: { schemaId: 'Result', path: 'evidence' }, root: { node: 'sift', label: 'screen', itemsPath: 'sources', state: { question: '{question}' }, out: 'Evidence', as: 'evidence', keep: { path: 'relevant', gte: 0.8 } } };
20
- /** End-to-end comparison: DSL authoring, repair, execution and delivery all count.
21
- * Uses the selected host model, never a model selector or alternate provider. */
22
- export async function runPiSkillBenchmark(options) {
23
- if (options.mode !== 'live' && options.mode !== 'scripted-transport')
24
- throw new Error('Choose live or scripted-transport explicitly');
25
- if (typeof options.skillText !== 'string' || !options.skillText.trim() || Buffer.byteLength(options.skillText) > 128 * 1024)
26
- throw new Error('Provide the exact native skill bundle, at most 128 KiB');
27
- if (typeof options.runJudge !== 'function')
28
- throw new Error('Both arms require the same Jev capability');
29
- const suite = benchmarkCases(options.split);
30
- const contract = benchmarkContract();
31
- if (!isDeepStrictEqual(contract, frozenBenchmarkContract))
32
- throw new Error('Frozen benchmark contract changed; establish a new version and baseline');
33
- const skillText = options.skillText;
34
- const budgets = { ...ceilings, ...options.budgets };
35
- for (const key of Object.keys(ceilings))
36
- if (!Number.isSafeInteger(budgets[key]) || budgets[key] < 1 || budgets[key] > ceilings[key])
37
- throw new Error(`Invalid benchmark budget: ${key}`);
38
- const captured = { ...options.host };
39
- createPiHostSessionFactory(captured);
40
- const model = captured.model;
41
- const runs = [];
42
- for (const [index, item] of suite.entries()) {
43
- if (options.signal?.aborted)
44
- break;
45
- const order = index % 2 ? ['skill-dsl', 'plain-pi'] : ['plain-pi', 'skill-dsl'];
46
- const arms = [];
47
- for (const arm of order) {
48
- if (options.signal?.aborted)
49
- break;
50
- arms.push(await runArm(item, arm));
51
- }
52
- runs.push({ caseId: item.id, inputSha256: benchmarkHash({ question: item.question, topics: item.topics, sources: item.sources }), order: arms.map(row => row.arm), arms });
53
- }
54
- if (!isDeepStrictEqual(contract, benchmarkContract()))
55
- throw new Error('Benchmark verifier changed during execution');
56
- return {
57
- mode: options.mode, split: options.split, status: options.signal?.aborted ? 'cancelled' : 'complete',
58
- passed: runs.length === suite.length && runs.every(row => row.arms.length === 2 && row.arms.every(arm => arm.status === 'passed')),
59
- contract, skillSha256: benchmarkHash(skillText), budgets,
60
- runtime: { model: model.id, provider: model.provider, api: model.api, thinking: captured.thinkingLevel ?? 'low' }, runs,
61
- limitations: ['Three fictional cases per split are not a statistical or real-world benchmark.', 'Gold checks cover statuses and citation sets; summary entailment remains unscored.', 'All outer-agent authoring, repair, final delivery, child execution and failed attempts count; initial package/skill development does not.', 'Request caps count adapter invocations, not provider-internal HTTP retries. There is no token or spending cap.', 'Known cost uses returned provider/SDK accounting only; missing samples are unknown, including failed requests.', 'Host caches are not reset; execution order alternates. Heldout cases must not be used to tune the skill.', 'The DSL arm uses the native bridge and service with a constrained benchmark tool surface; this is not a test of interactive skill discovery.'],
62
- };
63
- async function runArm(item, arm) {
64
- const started = performance.now();
65
- const notify = (stage, event, status) => {
66
- try {
67
- void Promise.resolve(options.onProgress?.({ caseId: item.id, arm, stage, event, ...(status ? { status } : {}) })).catch(() => { });
68
- }
69
- catch { /* UI hooks cannot change the measurement. */ }
70
- };
71
- notify('arm', 'start');
72
- notify('outer', 'start');
73
- const controller = new AbortController();
74
- const signal = controller.signal;
75
- const stop = (code) => { if (!signal.aborted)
76
- controller.abort(new BenchmarkStop(code)); };
77
- const externalAbort = () => stop('cancelled');
78
- options.signal?.addEventListener('abort', externalAbort, { once: true });
79
- if (options.signal?.aborted)
80
- externalAbort();
81
- const timer = setTimeout(() => stop('deadline'), budgets.timeoutMs);
82
- const interrupted = new Promise((_resolve, reject) => {
83
- signal.addEventListener('abort', () => reject(signal.reason), { once: true });
84
- if (signal.aborted)
85
- reject(signal.reason);
86
- });
87
- void interrupted.catch(() => { });
88
- const bounded = (promise) => Promise.race([promise, interrupted]);
89
- let stage = 'outer';
90
- const stages = { outer: emptyMetrics(), workflow: emptyMetrics() };
91
- const candidates = [], workflowRuns = [];
92
- const total = () => Object.fromEntries(Object.keys(stages.outer).map(key => [key, stages.outer[key] + stages.workflow[key]]));
93
- const admit = (kind) => {
94
- signal.throwIfAborted();
95
- const counts = total();
96
- if (kind === 'tools' ? counts.tools >= budgets.maxToolCalls : counts.pi + counts.jev >= budgets.maxModelRequests)
97
- stop(kind === 'tools' ? 'tool_limit' : 'model_limit');
98
- signal.throwIfAborted();
99
- stages[stage][kind]++;
100
- };
101
- const usage = (bucket, tokens, cost) => {
102
- if (typeof tokens === 'number' && Number.isFinite(tokens) && tokens >= 0) {
103
- bucket.tokens += tokens;
104
- bucket.usageSamples++;
105
- }
106
- if (typeof cost === 'number' && Number.isFinite(cost) && cost >= 0) {
107
- bucket.knownCostUsd += cost;
108
- bucket.costSamples++;
109
- }
110
- };
111
- const host = { ...captured, modelRegistry: {
112
- getAll: () => captured.modelRegistry.getAll(),
113
- streamSimple(selected, transcript, opts) {
114
- admit('pi');
115
- const bucket = stages[stage];
116
- const stream = captured.modelRegistry.streamSimple(selected, transcript, opts);
117
- void stream.result().then(result => {
118
- const value = result;
119
- usage(bucket, value.usage?.totalTokens, value.usage?.cost?.total);
120
- }).catch(() => { });
121
- return stream;
122
- },
123
- } };
124
- const runJudge = async (request) => {
125
- request.signal?.throwIfAborted();
126
- admit('jev');
127
- const bucket = stages[stage];
128
- const result = await bounded(options.runJudge({ ...request, signal: request.signal ? AbortSignal.any([signal, request.signal]) : signal }));
129
- usage(bucket, result.usage ? result.usage.input_tokens + result.usage.output_tokens : undefined, result.cost_usd);
130
- return result;
131
- };
132
- const domainTools = [
133
- { name: 'read_sources', label: 'Read sources', description: 'Return {sources:[{id,text}]} for this fictional task. Direct workflow calls return this same structured value. This is not web search.', parameters: { type: 'object', properties: {}, additionalProperties: false }, async execute() { return textResult({ sources: structuredClone(item.sources) }); } },
134
- { name: 'screen_evidence', label: 'Screen evidence', description: 'Jev System One decisions select concrete evidence including limitations and contradictions at threshold 0.8.', parameters: { type: 'object', additionalProperties: false, required: ['topicId', 'sourceIds'], properties: { topicId: { type: 'string' }, sourceIds: { type: 'array', uniqueItems: true, items: { type: 'string' } } } },
135
- async execute(_id, value, toolSignal) {
136
- const args = value;
137
- const topic = item.topics.find(row => row.id === args.topicId);
138
- if (!topic || args.sourceIds.some(id => !item.sources.some(source => source.id === id)))
139
- throw new Error('Unknown topic or source');
140
- const result = await bounded(runWorkflowSlice(selectionWorkflow, { question: topic.question, sources: item.sources.filter(source => args.sourceIds.includes(source.id)) }, { from: 'screen' }, { runJudge: request => runJudge({ ...request, signal: toolSignal }) }));
141
- if (result.status !== 'complete')
142
- throw new Error('Screening did not complete');
143
- return textResult(result.state.evidence);
144
- } },
145
- ];
146
- const service = new WorkflowExtensionService({ allowedTools: domainTools.map(tool => tool.name), limits: { deadlineMs: budgets.timeoutMs } });
147
- const input = { question: item.question, topics: structuredClone(item.topics) };
148
- let lastOutput;
149
- let workflowInput = input;
150
- try {
151
- const factory = createPiHostSessionFactory(host);
152
- const makeRunner = (tools) => createPiRunner({ model, thinkingLevel: captured.thinkingLevel, cwd: captured.cwd, signal, tools,
153
- maxTurns: budgets.maxTurns, maxSubmissions: budgets.maxSubmissions, timeoutMs: budgets.timeoutMs,
154
- sessionFactory: opts => factory({ ...opts, customTools: opts.customTools.map(tool => ({ ...tool, async execute(...args) {
155
- args[2]?.throwIfAborted();
156
- admit('tools');
157
- if (tool.name === 'submit')
158
- stages[stage].submissions++;
159
- const validator = Compile(tool.parameters);
160
- if (!validator.Check(args[1]))
161
- throw new Error('Tool input does not match its schema');
162
- return tool.execute(...args);
163
- } })) }),
164
- });
165
- const child = makeRunner(domainTools);
166
- const agentrun = { name: 'agentrun', label: 'AgentRun workflow', description: 'Describe available tools, inspect a workflow, then run the inspected workflow. Never run an inline candidate. No shell, code, file access or model selection.', parameters: { type: 'object', additionalProperties: false, required: ['action'], properties: { action: { enum: ['describe', 'inspect', 'run'] }, workflow: {}, input: { type: 'object' } } }, async execute(_id, value) {
167
- const args = value;
168
- if (args.action === 'inspect' && args.input !== undefined)
169
- workflowInput = args.input;
170
- if (args.action === 'describe')
171
- return textResult({ tools: domainTools.map(tool => ({ name: tool.name, description: tool.description, parameters: tool.parameters })), skill: true, pi: true, jev: true, running: false, workflow: !!service.inspect().current });
172
- if (args.action === 'inspect') {
173
- try {
174
- if (args.workflow === undefined) {
175
- const current = service.inspect().current;
176
- if (!current)
177
- throw new Error('Inspect a workflow first');
178
- return textResult({ digest: current.digest, tree: formatWorkflowTree(current.inspection) });
179
- }
180
- const prepared = service.prepare(args.workflow);
181
- candidates.push({ digest: prepared.digest, accepted: true, workflow: prepared.workflow });
182
- return textResult({ digest: prepared.digest, tree: formatWorkflowTree(prepared.inspection) });
183
- }
184
- catch (error) {
185
- candidates.push({ accepted: false, workflow: structuredClone(args.workflow) });
186
- throw error;
187
- }
188
- }
189
- if (args.action !== 'run' || args.workflow !== undefined)
190
- throw new Error('Inspect the workflow first; run without a workflow argument');
191
- notify('outer', 'end');
192
- stage = 'workflow';
193
- notify('workflow', 'start');
194
- try {
195
- const report = await bounded(service.run(args.input ?? workflowInput, { signal, deps: {
196
- // A candidate cannot change the captured host thinking setting.
197
- runNode: request => child({ ...request, thinking: undefined }), runJudge,
198
- runEffect: async (request) => {
199
- request.signal.throwIfAborted();
200
- admit('tools');
201
- const tool = domainTools.find(tool => tool.name === request.node.tool);
202
- if (!tool || !Compile(tool.parameters).Check(request.input))
203
- throw new Error('Unsupported tool input');
204
- return (await bounded(tool.execute('benchmark-effect', request.input, request.signal, undefined, host))).details;
205
- },
206
- } }));
207
- workflowRuns.push({ status: report.status, digest: report.digest, ...(report.output === undefined ? {} : { output: report.output }) });
208
- lastOutput = report.status === 'complete' ? report.output : undefined;
209
- return textResult(report);
210
- }
211
- finally {
212
- notify('workflow', 'end');
213
- stage = 'outer';
214
- notify('outer', 'start');
215
- }
216
- } };
217
- const outer = makeRunner(arm === 'skill-dsl' ? [...domainTools, agentrun] : domainTools);
218
- const output = await bounded(outer({ kind: 'agent', label: 'benchmark-outer',
219
- system: [benchmarkInstructions, ...(arm === 'skill-dsl' ? [skillText, 'Use agentrun to inspect and execute your workflow. Deliver its completed output unchanged with submit. The benchmark forbids code, shell, files and changing models. Available task tools and their contracts are returned by agentrun describe.'] : [])],
220
- user: JSON.stringify(input), schema: benchmarkReportSchema }));
221
- signal.throwIfAborted();
222
- if (arm === 'skill-dsl' && (lastOutput === undefined || !isDeepStrictEqual(lastOutput, output)))
223
- return finish({ status: 'rejected', output, failure: 'workflow_delivery_missing_or_changed' });
224
- const evaluation = evaluatePiBenchmark(item.id, output);
225
- return finish({ status: evaluation.passed ? 'passed' : 'rejected', output, evaluation });
226
- }
227
- catch (error) {
228
- return finish({ status: options.signal?.aborted ? 'cancelled' : 'failed', failure: signal.reason instanceof BenchmarkStop ? signal.reason.code : error instanceof PiRunError ? `pi_${error.reason}` : error instanceof ExtensionServiceError ? error.code : 'execution_failed' });
229
- }
230
- finally {
231
- clearTimeout(timer);
232
- options.signal?.removeEventListener('abort', externalAbort);
233
- stop('arm_finished');
234
- void service.dispose();
235
- }
236
- function finish(result) {
237
- const counts = total();
238
- notify('outer', 'end', result.status);
239
- notify('arm', 'end', result.status);
240
- return structuredClone({ arm, ...result, elapsedMs: Math.round(performance.now() - started), total: counts, stages, candidates, workflowRuns, costComplete: counts.costSamples === counts.pi + counts.jev, usageComplete: counts.usageSamples === counts.pi + counts.jev });
241
- }
242
- }
243
- }
244
- /** A development comparison receipt, not automatic promotion. Heldout is a final
245
- * confirmation set, never the optimization target for this helper. */
246
- export function comparePiSkillBenchmarks(baseline, candidate) {
247
- if (baseline.split !== 'development' || candidate.split !== 'development')
248
- throw new Error('Hillclimb comparisons use development only');
249
- if (baseline.status !== 'complete' || candidate.status !== 'complete' || baseline.mode !== candidate.mode || !isDeepStrictEqual(baseline.contract, candidate.contract) || !isDeepStrictEqual(baseline.budgets, candidate.budgets) || !isDeepStrictEqual(baseline.runtime, candidate.runtime))
250
- throw new Error('Incomparable benchmark receipts');
251
- const select = (report) => report.runs.map(run => ({ id: run.caseId, input: run.inputSha256, arm: run.arms.find(arm => arm.arm === 'skill-dsl') }));
252
- const before = select(baseline), after = select(candidate);
253
- if (before.length !== 3 || after.length !== 3 || !isDeepStrictEqual(before.map(row => [row.id, row.input]), after.map(row => [row.id, row.input])) || [...before, ...after].some(row => !row.arm))
254
- throw new Error('Incomplete benchmark receipts');
255
- const regressions = before.filter((row, i) => row.arm.status === 'passed' && after[i].arm.status !== 'passed').map(row => row.id);
256
- const sums = (rows) => ({ passed: rows.filter(row => row.arm.status === 'passed').length, requests: rows.reduce((sum, row) => sum + row.arm.total.pi + row.arm.total.jev, 0), tokens: rows.reduce((sum, row) => sum + row.arm.total.tokens, 0), usageComplete: rows.every(row => row.arm.usageComplete), knownCostUsd: rows.reduce((sum, row) => sum + row.arm.total.knownCostUsd, 0), costComplete: rows.every(row => row.arm.costComplete) });
257
- const previous = sums(before), current = sums(after);
258
- const improved = !regressions.length && (current.passed > previous.passed || current.passed === previous.passed && current.passed === 3 && previous.usageComplete && current.usageComplete && current.tokens < previous.tokens);
259
- return { mode: candidate.mode, baselineSkillSha256: baseline.skillSha256, candidateSkillSha256: candidate.skillSha256, regressions, previous, current, improved, eligibleForLiveHeldout: candidate.mode === 'live' && improved && current.passed === 3 };
260
- }
@@ -1,69 +0,0 @@
1
- import { type WorkflowDeps } from '@parcha/agentrun-dsl';
2
- import type { PiHostContext } from './types.js';
3
- export declare function evaluatePiComparison(caseId: string, output: unknown): {
4
- passed: boolean;
5
- issues: string[];
6
- };
7
- export interface PiComparisonBudgets {
8
- timeoutMs: number;
9
- maxModelRequests: number;
10
- maxToolCalls: number;
11
- maxTurns: number;
12
- maxSubmissions: number;
13
- }
14
- export interface PiComparisonOptions {
15
- host: PiHostContext;
16
- runJudge: NonNullable<WorkflowDeps['runJudge']>;
17
- signal?: AbortSignal;
18
- mode: 'live' | 'scripted-transport';
19
- budgets?: Partial<PiComparisonBudgets>;
20
- }
21
- export interface PiComparisonArm {
22
- arm: 'plain-pi' | 'workflow';
23
- status: 'passed' | 'rejected' | 'failed' | 'cancelled';
24
- elapsedMs: number;
25
- requests: {
26
- pi: number;
27
- jev: number;
28
- total: number;
29
- };
30
- toolCalls: number;
31
- submissions: number;
32
- tokens: {
33
- pi: number;
34
- jev: number;
35
- piUsageSamples: number;
36
- jevUsageSamples: number;
37
- };
38
- output?: unknown;
39
- evaluation?: ReturnType<typeof evaluatePiComparison>;
40
- failure?: string;
41
- }
42
- export declare function runPiComparison(options: PiComparisonOptions): Promise<{
43
- mode: "live" | "scripted-transport";
44
- status: "complete" | "cancelled";
45
- passed: boolean;
46
- runtime: {
47
- provider: string;
48
- model: string;
49
- api: string;
50
- thinking: import("./types.js").PiThinkingLevel;
51
- };
52
- budgets: {
53
- timeoutMs: number;
54
- maxModelRequests: number;
55
- maxToolCalls: number;
56
- maxTurns: number;
57
- maxSubmissions: number;
58
- };
59
- workflowSha256: string;
60
- suiteSha256: string;
61
- runs: {
62
- caseId: string;
63
- inputSha256: string;
64
- expectedSha256: string;
65
- executionOrder: string[];
66
- arms: PiComparisonArm[];
67
- }[];
68
- limits: string[];
69
- }>;