@tangle-network/agent-bench 0.7.1 → 0.8.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.
Files changed (131) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/HARNESS.md +1 -1
  3. package/dist/adapters.js +2 -2
  4. package/dist/benchmarks/appworld.d.ts +61 -2
  5. package/dist/benchmarks/appworld.js +111 -40
  6. package/dist/benchmarks/appworld.js.map +1 -1
  7. package/dist/benchmarks/cadbench.js +1 -1
  8. package/dist/benchmarks/cadgenbench.js +1 -1
  9. package/dist/benchmarks/finresearchbench.js +16 -22
  10. package/dist/benchmarks/finresearchbench.js.map +1 -1
  11. package/dist/benchmarks/finsearchcomp.js +16 -22
  12. package/dist/benchmarks/finsearchcomp.js.map +1 -1
  13. package/dist/benchmarks/frames.js +18 -20
  14. package/dist/benchmarks/frames.js.map +1 -1
  15. package/dist/benchmarks/simpleqa.js +15 -19
  16. package/dist/benchmarks/simpleqa.js.map +1 -1
  17. package/dist/benchmarks/trata-hedge.js +20 -23
  18. package/dist/benchmarks/trata-hedge.js.map +1 -1
  19. package/dist/{cadbench-BrpwOU6A.js → cadbench-BLSyxR1N.js} +21 -20
  20. package/dist/cadbench-BLSyxR1N.js.map +1 -0
  21. package/dist/{cadgenbench-DF7hYHdl.js → cadgenbench-x2OFkf8y.js} +2 -2
  22. package/dist/cadgenbench-x2OFkf8y.js.map +1 -0
  23. package/dist/index.d.ts +0 -1
  24. package/dist/index.js +10 -9
  25. package/dist/index.js.map +1 -1
  26. package/dist/router-turn-C2wMiDoo.js +31 -0
  27. package/dist/router-turn-C2wMiDoo.js.map +1 -0
  28. package/package.json +7 -7
  29. package/scripts/appworld_driver.py +1 -170
  30. package/scripts/run-package-tests.mjs +56 -36
  31. package/scripts/run-package-tests.test.mjs +33 -0
  32. package/scripts/trata-hedge/README.md +3 -3
  33. package/scripts/trata-hedge/run.sh +6 -2
  34. package/src/aec-gate.mts +30 -9
  35. package/src/agent-graphs-gen2.mts +523 -0
  36. package/src/agent-graphs-gen3.mts +660 -0
  37. package/src/agent-graphs-improve/offline-seams.mts +128 -0
  38. package/src/agent-graphs-improve.mts +747 -0
  39. package/src/atom-humaneval.mts +76 -55
  40. package/src/atom-mcp-e2e.mts +27 -13
  41. package/src/benchmarks/appworld.test.mts +119 -1
  42. package/src/benchmarks/appworld.ts +156 -33
  43. package/src/benchmarks/cadbench.ts +21 -8
  44. package/src/benchmarks/finresearchbench.ts +20 -16
  45. package/src/benchmarks/finsearchcomp.ts +20 -20
  46. package/src/benchmarks/frames.ts +19 -18
  47. package/src/benchmarks/simpleqa.ts +19 -17
  48. package/src/benchmarks/trata-hedge.ts +26 -18
  49. package/src/browser/ui-reviewer.ts +1 -1
  50. package/src/clbench-context-gate.mts +45 -9
  51. package/src/cloud-loop.mts +24 -6
  52. package/src/commit0-env-run.mts +6 -3
  53. package/src/commit0-env.ts +2 -3
  54. package/src/commit0-gate.mts +53 -24
  55. package/src/corpus.ts +3 -3
  56. package/src/david-attribution.mts +28 -9
  57. package/src/david-goliath.mts +31 -12
  58. package/src/egress-probe.mts +3 -3
  59. package/src/examples/lean-proof-gate.mts +10 -3
  60. package/src/examples/math-demo.mts +10 -3
  61. package/src/examples/strategy-demo.mts +10 -4
  62. package/src/fleet.mts +30 -8
  63. package/src/gate-cli.mts +9 -4
  64. package/src/gate.test.mts +7 -2
  65. package/src/gate.ts +23 -75
  66. package/src/generate-eval/certify.ts +13 -5
  67. package/src/hev-eval.mts +39 -10
  68. package/src/hev-improve.mts +28 -16
  69. package/src/hev-structural.mts +28 -20
  70. package/src/humaneval-gate.mts +24 -8
  71. package/src/humaneval-object-ablation.mts +52 -14
  72. package/src/humaneval-repair-gate.mts +53 -14
  73. package/src/mbpp-structural.mts +28 -20
  74. package/src/mcp-mount-probe.mts +23 -11
  75. package/src/official-optimizer-config.mts +61 -12
  76. package/src/official-optimizer-config.test.mts +72 -3
  77. package/src/quant-arena/profiles/quant-researcher.profile.json +6 -1
  78. package/src/quant-arena/quant-loop.mts +88 -61
  79. package/src/research-shot.ts +21 -11
  80. package/src/resolve-client.ts +6 -8
  81. package/src/rollout-ledger/backfill-swe-arena.test.mts +7 -2
  82. package/src/router-executor.ts +1 -1
  83. package/src/router-turn.ts +135 -0
  84. package/src/run-benchmarks.ts +7 -2
  85. package/src/sandbox-run.ts +23 -41
  86. package/src/search-bench/bridge.ts +66 -8
  87. package/src/search-bench/parametric-check.mts +19 -4
  88. package/src/search-bench/profiles.ts +24 -9
  89. package/src/search-bench/run.mts +3 -4
  90. package/src/smoke-structural-rollout.mts +21 -5
  91. package/src/supervisor-arena.mts +28 -20
  92. package/src/swe-arena/arms.ts +73 -15
  93. package/src/swe-arena/capacity.ts +44 -41
  94. package/src/swe-arena/diagnosis-ensemble.ts +51 -48
  95. package/src/swe-arena/execution.test.mts +12 -12
  96. package/src/swe-arena/gepa-seat.mts +12 -5
  97. package/src/swe-arena/gepa-seat.test.mts +45 -33
  98. package/src/swe-arena/outer-loop.mts +94 -70
  99. package/src/swe-arena/premeasured-from-cells.mts +16 -1
  100. package/src/swe-arena/premeasured-from-cells.test.mts +21 -1
  101. package/src/swe-arena/profiles/deepseek-author.profile.json +12 -0
  102. package/src/swe-arena/profiles/default-author.profile.json +9 -1
  103. package/src/swe-arena/proposer-fanout.mts +52 -99
  104. package/src/swe-arena/proposer-fanout.test.mts +92 -57
  105. package/src/swe-arena/proposer-provenance.mts +1 -1
  106. package/src/swe-code-improve.mts +30 -52
  107. package/src/swe-emit-patch.mts +26 -5
  108. package/src/swe-improve.mts +27 -8
  109. package/src/swe-jail.test.ts +128 -0
  110. package/src/swe-jail.ts +106 -22
  111. package/src/swe-local-proof.mts +27 -5
  112. package/src/swe-repro-calibrate.mts +5 -0
  113. package/src/swe-self-improve.mts +45 -10
  114. package/src/swe-stream.mts +68 -6
  115. package/src/swe-structural.mts +46 -7
  116. package/src/tb-container-executor.mts +38 -6
  117. package/src/tb-container-executor.test.mts +11 -3
  118. package/src/tb-supervisor-sidecar.mts +11 -27
  119. package/src/terminal-compare.ts +38 -18
  120. package/src/trata-gate.mts +26 -26
  121. package/src/trata-gepa.mts +25 -16
  122. package/src/trata-hedge-solve.mts +80 -0
  123. package/src/worker-blender.ts +21 -11
  124. package/src/worker-browser.ts +18 -5
  125. package/src/worker-build123d.ts +17 -8
  126. package/src/worker-cad.ts +31 -12
  127. package/src/worker.ts +1 -1
  128. package/tb_agents/opencode_router_agent.py +84 -42
  129. package/dist/cadbench-BrpwOU6A.js.map +0 -1
  130. package/dist/cadgenbench-DF7hYHdl.js.map +0 -1
  131. package/scripts/trata-hedge/solve.py +0 -83
@@ -20,9 +20,20 @@
20
20
  * tsx src/humaneval-repair-gate.mts
21
21
  */
22
22
  import { type HumanEvalTask, basePrompt, extractCode, loadHumanEval, runChecker } from './benchmarks/humaneval'
23
- import { type RouterConfig, type ToolSpec, routerChatWithUsage, routerToolLoop } from '@tangle-network/agent-runtime/kernel'
23
+ import {
24
+ collectAgentTurn,
25
+ createExecutor,
26
+ streamAgentTurn,
27
+ type ToolSpec,
28
+ } from '@tangle-network/agent-runtime/kernel'
24
29
  import { verifierGroundedSelect } from './selector'
25
30
  import { type PairedLift, pairedLift, pool } from './stats.mts'
31
+ import {
32
+ benchRouterProfile,
33
+ type BenchRouterTarget,
34
+ runBenchRouterTurn,
35
+ withBenchProfile,
36
+ } from './router-turn'
26
37
 
27
38
  function must(name: string): string {
28
39
  const v = process.env[name]
@@ -51,14 +62,23 @@ const repairSystem = [
51
62
  ].join(' ')
52
63
 
53
64
  /** repair@K: one worker, up to K inference turns, steering on real test failures. */
54
- async function repairAttempt(cfg: RouterConfig, task: HumanEvalTask, k: number): Promise<number> {
65
+ async function repairAttempt(cfg: BenchRouterTarget, task: HumanEvalTask, k: number): Promise<number> {
55
66
  let lastTested = ''
56
- const r = await routerToolLoop(
57
- cfg,
58
- repairSystem,
59
- basePrompt(task),
60
- [runTestsTool],
61
- async (name, args) => {
67
+ const profile = {
68
+ ...withBenchProfile(cfg.profile, {
69
+ name: 'humaneval-repair-worker',
70
+ systemPrompt: repairSystem,
71
+ maxTurns: k,
72
+ temperature: 0.3,
73
+ }),
74
+ tools: { run_tests: true },
75
+ }
76
+ const factory = createExecutor({
77
+ backend: 'router-tools',
78
+ routerBaseUrl: cfg.routerBaseUrl,
79
+ routerKey: cfg.routerKey,
80
+ tools: [runTestsTool],
81
+ executeToolCall: async (name, args) => {
62
82
  if (name !== 'run_tests') return `error: unknown tool ${name}`
63
83
  const code = extractCode(String(args.code ?? ''))
64
84
  lastTested = code
@@ -67,22 +87,37 @@ async function repairAttempt(cfg: RouterConfig, task: HumanEvalTask, k: number):
67
87
  ? 'ALL TESTS PASSED. Reply with the final function now; do not call run_tests again.'
68
88
  : `TESTS FAILED:\n${res.detail ?? 'no output'}\n\nFix the function and call run_tests again.`
69
89
  },
70
- { maxTurns: k, temperature: 0.3 },
90
+ })
91
+ const r = await collectAgentTurn(
92
+ streamAgentTurn({ kind: 'executor', factory, profile }, basePrompt(task)),
71
93
  )
94
+ if (r.status !== 'completed') {
95
+ throw new Error(r.error?.message ?? `repair turn ended with status ${r.status}`)
96
+ }
72
97
  // Judge the model's final answer; fall back to the last code it tested (it may
73
98
  // report "done" without re-pasting the passing function).
74
- const finalCode = extractCode(r.final) || lastTested
99
+ const finalCode = extractCode(r.finalText) || lastTested
75
100
  if (!finalCode) return 0
76
101
  return (await runChecker(task, finalCode)).pass
77
102
  }
78
103
 
79
104
  /** blind@K: K independent completions, verifier-grounded pick (the resample control). */
80
- async function blindAttempts(cfg: RouterConfig, task: HumanEvalTask, k: number): Promise<number[]> {
105
+ async function blindAttempts(cfg: BenchRouterTarget, task: HumanEvalTask, k: number): Promise<number[]> {
81
106
  const base = basePrompt(task)
82
107
  const passes: number[] = []
83
108
  for (let i = 0; i < k; i += 1) {
84
- const res = await routerChatWithUsage(cfg, [{ role: 'user', content: base }], { temperature: 0.8 })
85
- passes.push((await runChecker(task, extractCode(res.content))).pass)
109
+ const res = await runBenchRouterTurn(
110
+ {
111
+ routerBaseUrl: cfg.routerBaseUrl,
112
+ routerKey: cfg.routerKey,
113
+ profile: withBenchProfile(cfg.profile, {
114
+ name: 'humaneval-blind-worker',
115
+ temperature: 0.8,
116
+ }),
117
+ },
118
+ base,
119
+ )
120
+ passes.push((await runChecker(task, extractCode(res.finalText))).pass)
86
121
  }
87
122
  return passes
88
123
  }
@@ -95,7 +130,11 @@ async function main(): Promise<void> {
95
130
  const k = Number(process.env.K ?? 3)
96
131
  const offset = Number(process.env.OFFSET ?? 82)
97
132
  const model = process.env.WORKER_MODEL ?? 'deepseek-v4-flash'
98
- const cfg: RouterConfig = { routerBaseUrl: process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1', routerKey: must('TANGLE_API_KEY'), model }
133
+ const cfg: BenchRouterTarget = {
134
+ routerBaseUrl: process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1',
135
+ routerKey: must('TANGLE_API_KEY'),
136
+ profile: benchRouterProfile('humaneval-worker', model),
137
+ }
99
138
  const concurrency = Number(process.env.CONCURRENCY ?? 6)
100
139
  if (k < 2) throw new Error('K must be >= 2 (repair needs at least write + one fix)')
101
140
 
@@ -32,6 +32,7 @@ import { tmpdir } from 'node:os'
32
32
  import { join, resolve } from 'node:path'
33
33
  import { fileURLToPath } from 'node:url'
34
34
  import { extractCode } from './benchmarks/humaneval'
35
+ import { runBenchRouterTurn } from './router-turn'
35
36
  import { type PairedLift, pairedLift, pool } from './stats.mts'
36
37
 
37
38
  const dockerImage = 'python:3.12-slim'
@@ -326,33 +327,40 @@ async function complete(cfg: ClientCfg, messages: Array<{ role: string; content:
326
327
  let lastErr = ''
327
328
  for (let attempt = 1; attempt <= 4; attempt += 1) {
328
329
  if (attempt > 1) await new Promise((r) => setTimeout(r, 2000 * 2 ** attempt))
329
- const ctl = new AbortController()
330
- const timer = setTimeout(() => ctl.abort(), 240_000)
331
330
  try {
332
- const res = await fetch(`${cfg.base}/chat/completions`, {
333
- method: 'POST',
334
- headers: { Authorization: `Bearer ${cfg.key}`, 'Content-Type': 'application/json' },
335
- body: JSON.stringify({ model: cfg.model, max_tokens: cfg.maxTokens, temperature: cfg.temperature, messages }),
336
- signal: ctl.signal,
337
- })
338
- if (!res.ok) {
339
- lastErr = `HTTP ${res.status}: ${(await res.text()).slice(0, 200)}`
340
- continue
341
- }
342
- const d = (await res.json()) as {
343
- choices?: Array<{ message?: { content?: string } }>
344
- usage?: { prompt_tokens?: number; completion_tokens?: number }
345
- }
346
- const content = d.choices?.[0]?.message?.content ?? ''
331
+ const system = messages.find((message) => message.role === 'system')?.content
332
+ const result = await runBenchRouterTurn(
333
+ {
334
+ routerBaseUrl: cfg.base,
335
+ routerKey: cfg.key,
336
+ profile: {
337
+ name: 'mbpp-structural-worker',
338
+ harness: 'cli-base',
339
+ model: {
340
+ provider: 'tangle-router',
341
+ default: cfg.model,
342
+ metadata: { temperature: cfg.temperature, maxTokens: cfg.maxTokens },
343
+ },
344
+ ...(system ? { prompt: { systemPrompt: system } } : {}),
345
+ },
346
+ timeoutMs: Number(process.env.LLM_TIMEOUT_MS ?? 240_000),
347
+ },
348
+ { messages: messages.filter((message) => message.role !== 'system') },
349
+ )
350
+ if (result.usage.tokensKnown === false) throw new Error('provider omitted token usage')
351
+ const content = result.finalText
347
352
  if (content.trim() === '') {
348
353
  lastErr = 'empty content'
349
354
  continue
350
355
  }
351
- return { content, attempts: attempt, tokensIn: d.usage?.prompt_tokens ?? 0, tokensOut: d.usage?.completion_tokens ?? 0 }
356
+ return {
357
+ content,
358
+ attempts: attempt,
359
+ tokensIn: result.usage.input,
360
+ tokensOut: result.usage.output,
361
+ }
352
362
  } catch (e) {
353
363
  lastErr = e instanceof Error ? e.message : String(e)
354
- } finally {
355
- clearTimeout(timer)
356
364
  }
357
365
  }
358
366
  throw new Error(`completion failed after retries: ${lastErr}`)
@@ -14,6 +14,8 @@ import {
14
14
  type Agent,
15
15
  type AgentProfile,
16
16
  type AgentSpec,
17
+ collectAgentTurn,
18
+ createExecutor,
17
19
  createExecutorRegistry,
18
20
  createSupervisor,
19
21
  type Executor,
@@ -21,6 +23,7 @@ import {
21
23
  InMemoryResultBlobStore,
22
24
  InMemorySpawnJournal,
23
25
  type Scope,
26
+ streamAgentTurn,
24
27
  type UsageEvent,
25
28
  } from '../../src/runtime/index'
26
29
  import { serveCoordinationMcp } from '../../src/runtime/supervise/coordination-mcp'
@@ -53,18 +56,27 @@ function deliveringLeaf(name: string, out: unknown): Agent<unknown, unknown> {
53
56
  }
54
57
 
55
58
  async function bridgeChat(messages: Array<{ role: string; content: string }>, mcpUrl: string): Promise<string> {
56
- const r = await fetch(`${BRIDGE.replace(/\/$/, '')}/chat/completions`, {
57
- method: 'POST',
58
- headers: { authorization: `Bearer ${BEARER}`, 'content-type': 'application/json' },
59
- body: JSON.stringify({
60
- model: MODEL,
61
- messages,
62
- mcp: { mcpServers: { coordination: { type: 'http', url: mcpUrl } } },
63
- }),
59
+ if (!BEARER) throw new Error('TANGLE_API_KEY is required')
60
+ const profile: AgentProfile = {
61
+ name: 'mcp-mount-probe-supervisor',
62
+ model: { default: MODEL },
63
+ mcp: { coordination: { transport: 'http', url: mcpUrl } },
64
+ }
65
+ const factory = createExecutor({
66
+ backend: 'bridge',
67
+ bridgeUrl: BRIDGE.replace(/\/v1\/?$/u, ''),
68
+ bridgeBearer: BEARER,
64
69
  })
65
- if (!r.ok) return `(bridge HTTP ${r.status}: ${(await r.text()).slice(0, 200)})`
66
- const j = (await r.json()) as { choices?: Array<{ message?: { content?: string } }> }
67
- return j.choices?.[0]?.message?.content ?? ''
70
+ const turn = await collectAgentTurn(
71
+ streamAgentTurn(
72
+ { kind: 'executor', factory, profile },
73
+ messages.map((message) => message.content).join('\n\n'),
74
+ ),
75
+ )
76
+ if (turn.status !== 'completed') {
77
+ throw new Error(turn.error?.message ?? `bridge turn ended with ${turn.status}`)
78
+ }
79
+ return turn.finalText
68
80
  }
69
81
 
70
82
  async function main(): Promise<void> {
@@ -1,3 +1,15 @@
1
+ import type {
2
+ OpenAICompatibleOptimizerModel,
3
+ OptimizerModelBudget,
4
+ } from '@tangle-network/agent-eval/campaign'
5
+ import {
6
+ type AgentProfile,
7
+ canonicalAgentProfileDigest,
8
+ canonicalCandidateDigest,
9
+ } from '@tangle-network/agent-interface'
10
+ import { profileOptimizerModelCall } from '../../src/runtime/profile-chat-client'
11
+ import type { RouterSeam } from '../../src/runtime/supervise/runtime'
12
+
1
13
  function requiredNonNegativeNumber(
2
14
  env: NodeJS.ProcessEnv,
3
15
  name: string,
@@ -57,22 +69,59 @@ export function officialOptimizerModel(options: {
57
69
  maxCostUsd: number
58
70
  maxOutputTokensPerRequest: number
59
71
  envPrefix?: string
60
- }) {
72
+ provider?: string
73
+ temperature?: number
74
+ reasoningEffort?: NonNullable<AgentProfile['model']>['reasoningEffort']
75
+ callRef?: string
76
+ complete?: RouterSeam['complete']
77
+ }): OpenAICompatibleOptimizerModel {
61
78
  const { env } = options
62
79
  const envPrefix = options.envPrefix ?? 'REFLECT'
80
+ const budget: OptimizerModelBudget = {
81
+ maxCostUsd: options.maxCostUsd,
82
+ maxRequests: positiveInteger(env, `${envPrefix}_MAX_REQUESTS`, 100),
83
+ maxRequestBytes: positiveInteger(env, `${envPrefix}_MAX_REQUEST_BYTES`, 2_000_000),
84
+ maxResponseBytes: positiveInteger(env, `${envPrefix}_MAX_RESPONSE_BYTES`, 2_000_000),
85
+ maxOutputTokensPerRequest: options.maxOutputTokensPerRequest,
86
+ requestTimeoutMs: positiveInteger(env, `${envPrefix}_REQUEST_TIMEOUT_MS`, 300_000),
87
+ pricing: requiredTokenPricing(env, envPrefix),
88
+ }
89
+ const profile: AgentProfile = {
90
+ name: 'official-optimizer-model',
91
+ harness: 'cli-base',
92
+ model: {
93
+ provider: options.provider ?? new URL(options.baseUrl).hostname,
94
+ default: options.model,
95
+ ...(options.reasoningEffort ? { reasoningEffort: options.reasoningEffort } : {}),
96
+ metadata: {
97
+ maxTokens: options.maxOutputTokensPerRequest,
98
+ ...(options.temperature !== undefined ? { temperature: options.temperature } : {}),
99
+ },
100
+ },
101
+ }
102
+ const profileDigest = canonicalAgentProfileDigest(profile)
103
+ const executor: RouterSeam & { backend: 'router' } = {
104
+ backend: 'router',
105
+ routerBaseUrl: options.baseUrl,
106
+ routerKey: options.apiKey,
107
+ ...(options.complete ? { complete: options.complete } : {}),
108
+ }
109
+ const call = profileOptimizerModelCall({
110
+ profile,
111
+ context: 'official optimizer model',
112
+ executor,
113
+ pricing: budget.pricing,
114
+ })
63
115
  return {
64
116
  model: options.model,
65
- baseUrl: options.baseUrl,
66
- apiKey: options.apiKey,
67
- budget: {
68
- maxCostUsd: options.maxCostUsd,
69
- maxRequests: positiveInteger(env, `${envPrefix}_MAX_REQUESTS`, 100),
70
- maxRequestBytes: positiveInteger(env, `${envPrefix}_MAX_REQUEST_BYTES`, 2_000_000),
71
- maxResponseBytes: positiveInteger(env, `${envPrefix}_MAX_RESPONSE_BYTES`, 2_000_000),
72
- maxOutputTokensPerRequest: options.maxOutputTokensPerRequest,
73
- requestTimeoutMs: positiveInteger(env, `${envPrefix}_REQUEST_TIMEOUT_MS`, 300_000),
74
- pricing: requiredTokenPricing(env, envPrefix),
75
- },
117
+ callRef:
118
+ options.callRef ??
119
+ `agent-runtime:${canonicalCandidateDigest({
120
+ profileDigest,
121
+ endpoint: new URL(options.baseUrl).origin,
122
+ })}`,
123
+ call,
124
+ budget,
76
125
  }
77
126
  }
78
127
 
@@ -30,10 +30,8 @@ describe('official optimizer configuration', () => {
30
30
  maxOutputTokensPerRequest: 4000,
31
31
  })
32
32
 
33
- expect(model).toEqual({
33
+ expect(model).toMatchObject({
34
34
  model: 'test-model',
35
- baseUrl: 'http://127.0.0.1:8080/v1',
36
- apiKey: 'test-key',
37
35
  budget: {
38
36
  maxCostUsd: 2,
39
37
  maxRequests: 7,
@@ -49,6 +47,77 @@ describe('official optimizer configuration', () => {
49
47
  },
50
48
  },
51
49
  })
50
+ expect(model.call).toEqual(expect.any(Function))
51
+ expect(model.callRef).toMatch(/^agent-runtime:sha256:/)
52
+ expect(model).not.toHaveProperty('baseUrl')
53
+ expect(model).not.toHaveProperty('apiKey')
54
+ })
55
+
56
+ it('executes the published optimizer callback through the exact Runtime profile', async () => {
57
+ const requests: unknown[] = []
58
+ const model = officialOptimizerModel({
59
+ env: pricingEnv,
60
+ envPrefix: 'OPT',
61
+ model: 'test-model',
62
+ baseUrl: 'http://127.0.0.1:8080/v1',
63
+ apiKey: 'test-key',
64
+ maxCostUsd: 2,
65
+ maxOutputTokensPerRequest: 4000,
66
+ complete: async (request) => {
67
+ requests.push(request)
68
+ return {
69
+ model: 'test-model',
70
+ choices: [{ message: { content: 'ok' }, finish_reason: 'stop' }],
71
+ usage: {
72
+ prompt_tokens: 3,
73
+ completion_tokens: 2,
74
+ cost: 0.001,
75
+ prompt_tokens_details: { cached_tokens: 1 },
76
+ completion_tokens_details: { reasoning_tokens: 1 },
77
+ },
78
+ }
79
+ },
80
+ })
81
+
82
+ const result = await model.call({
83
+ callId: 'optimizer-call-1',
84
+ request: {
85
+ model: 'test-model',
86
+ messages: [{ role: 'user', content: 'hi' }],
87
+ maxTokens: 4000,
88
+ },
89
+ endpointFormat: 'chat-completions',
90
+ signal: new AbortController().signal,
91
+ })
92
+
93
+ expect(result.succeeded).toBe(true)
94
+ if (!result.succeeded) throw new Error(result.error)
95
+ expect(result.response).toMatchObject({
96
+ model: 'test-model',
97
+ content: 'ok',
98
+ usage: { promptTokens: 3, completionTokens: 2, totalTokens: 5 },
99
+ })
100
+ expect(result.receipt).toMatchObject({
101
+ model: 'test-model',
102
+ inputTokens: 2,
103
+ cachedTokens: 1,
104
+ outputTokens: 2,
105
+ reasoningTokens: 1,
106
+ actualCostUsd: 0.001,
107
+ })
108
+ expect(result.execution).toMatchObject({
109
+ kind: 'agent-runtime-profile-model-call',
110
+ executed: true,
111
+ succeeded: true,
112
+ model: 'test-model',
113
+ callId: 'optimizer-call-1',
114
+ endpointFormat: 'chat-completions',
115
+ })
116
+ expect(requests).toHaveLength(1)
117
+ expect(requests[0]).toMatchObject({
118
+ model: 'test-model',
119
+ max_tokens: 4000,
120
+ })
52
121
  })
53
122
 
54
123
  it('requires every token price instead of inventing cost data', () => {
@@ -1,6 +1,11 @@
1
1
  {
2
2
  "name": "quant-researcher",
3
- "description": "Strategy-author lens that speaks quant language: regimes, turnover, capacity, transaction costs. Same claude harness as default-author; only the system prompt differs, so any performance delta is attributable to the lens.",
3
+ "description": "Pi strategy-author lens that speaks quant language: regimes, turnover, capacity, transaction costs. The model and harness match the default author; only the system prompt differs.",
4
+ "harness": "pi",
5
+ "model": {
6
+ "provider": "tangle-router",
7
+ "default": "glm-5.2"
8
+ },
4
9
  "prompt": {
5
10
  "systemPrompt": "You are a systematic-trading researcher at a mid-frequency equity desk. You think in regimes (trend vs chop, calm vs stress), you treat turnover as a cost center (every rebalance pays spread + impact), you size positions for capacity, and you are allergic to backtest overfitting — you prefer one robust effect over five fitted parameters. When you write a strategy you state, in comments, the economic rationale the rule is harvesting and why it should survive out-of-sample."
6
11
  }