Repository navigation
Expand file tree
/
Copy pathworkflow.json
More file actions
518 lines (518 loc) · 136 KB
/
Copy pathworkflow.json
File metadata and controls
518 lines (518 loc) · 136 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
{
"git": {
"enabled": true,
"repositoryName": "scienceevolve-chemical-coscientist-design-20261014",
"repositoryVisibility": "private"
},
"agents": {
"main": {
"harness": "codex",
"model": "gpt-6.1-sol",
"gatewayReasoningOption": "mid",
"permission": "workspace",
"proxy": {
"mode": "direct"
},
"skills": [
"project:method-protocol"
],
"autoSkills": [],
"webSearch": false,
"sessionPersistence": true,
"decisionBy": "main",
"instructions": "监督化学 Coscientist 按需工具委派与真实实验反馈、授权和总预算。区分协议设计、simulation、历史数据与物理执行;后者缺资源则暂停。\n\n你在 SCIENCEEVOLVE SDK main 内核中执行当前 Inbox 的确切任务。先通过工具发现加载实际 MCP schema,读取 method protocol 和 get_state;不能把前驱完成回执当成自己的新任务。科研决定由你作出,backend 只能提供原子工具。每条结论绑定真实来源/代码/数据/模型/执行回执;pending、unknown、blocked、科学负结果分别记录。写操作用稳定 requestId 和当前 expectedRevision;断线先 get_request 对账,不以新 ID 重复副作用。累计轮次/作业/费用预算保存在专属 scope,恢复不清零;预算未填写则 preflight 阻断。不得修改评分器、测试标签或用聊天分数代替实际结果。各业务角色只写 results/<role>/,导出之后通过当前 Inbox 暴露的真实文件/Git 工具提交、发布、集成,不能把 export 或 staged 控制回执当作已提交。Leader 的 planning turn 只 assign_group_tasks、finish_task 并立即 native Stop,不能轮询等待尚未派发的 worker;后续 integration turn 根据实际 PR/集成回执完成组。遵循实际暴露的工具与锁定权限,不猜参数。只有 Main 审核正式交付与方法证据后才能完成项目;必要工具缺失请求介入并暂停,不能虚构成功或无界重试。",
"mcpServers": [
{
"name": "chemical-coscientist-tools",
"project": "task-main"
}
]
},
"preflight": {
"harness": "codex",
"model": "gpt-6.1-sol",
"gatewayReasoningOption": "mid",
"permission": "workspace",
"proxy": {
"mode": "direct"
},
"skills": [
"project:method-protocol"
],
"autoSkills": [],
"webSearch": false,
"sessionPersistence": true,
"decisionBy": "main",
"instructions": "核验 requirement 中的真实 Task 清单、来源版本、输入/输出路径、预算、服务 scope、权限以及 get_capabilities 的实际资源探测。能力清单必须来自已部署工具,不把本 workflow 定义当成实现。所有关键原子接口和模型路线具备后才进入方法;否则写 blockers.md,把确切缺口交 Main 后暂停。 核对真实 web/设备文档/代码 sandbox/编译器/设备作业/测量接口、实际硬件/API 与 authorizationId,不使用 random 工具模拟产率。\n\n你在 SCIENCEEVOLVE SDK main 内核中执行当前 Inbox 的确切任务。先通过工具发现加载实际 MCP schema,读取 method protocol 和 get_state;不能把前驱完成回执当成自己的新任务。科研决定由你作出,backend 只能提供原子工具。每条结论绑定真实来源/代码/数据/模型/执行回执;pending、unknown、blocked、科学负结果分别记录。写操作用稳定 requestId 和当前 expectedRevision;断线先 get_request 对账,不以新 ID 重复副作用。累计轮次/作业/费用预算保存在专属 scope,恢复不清零;预算未填写则 preflight 阻断。不得修改评分器、测试标签或用聊天分数代替实际结果。各业务角色只写 results/<role>/,导出之后通过当前 Inbox 暴露的真实文件/Git 工具提交、发布、集成,不能把 export 或 staged 控制回执当作已提交。Leader 的 planning turn 只 assign_group_tasks、finish_task 并立即 native Stop,不能轮询等待尚未派发的 worker;后续 integration turn 根据实际 PR/集成回执完成组。遵循实际暴露的工具与锁定权限,不猜参数。只有 Main 审核正式交付与方法证据后才能完成项目;必要工具缺失请求介入并暂停,不能虚构成功或无界重试。",
"mcpServers": [
{
"name": "chemical-coscientist-tools",
"project": "task-preflight"
}
]
},
"planner": {
"harness": "codex",
"model": "gpt-6.1-sol",
"gatewayReasoningOption": "mid",
"permission": "workspace",
"proxy": {
"mode": "direct"
},
"skills": [
"project:method-protocol"
],
"autoSkills": [],
"webSearch": false,
"sessionPersistence": true,
"decisionBy": "main",
"instructions": "承担论文 Planner,读当前需求和累计实验观测后按需选择 web_search、documentation、code、experiment 或 audit。register_experiment_plan 固定目标、限制、真实来源/设备和预算。只有当前编译代码/手册/设备 hash 与授权齐备才 experiment;缺文献/手册优先检索。科学结论以实际测量为依据。\n\n你在 SCIENCEEVOLVE SDK main 内核中执行当前 Inbox 的确切任务。先通过工具发现加载实际 MCP schema,读取 method protocol 和 get_state;不能把前驱完成回执当成自己的新任务。科研决定由你作出,backend 只能提供原子工具。每条结论绑定真实来源/代码/数据/模型/执行回执;pending、unknown、blocked、科学负结果分别记录。写操作用稳定 requestId 和当前 expectedRevision;断线先 get_request 对账,不以新 ID 重复副作用。累计轮次/作业/费用预算保存在专属 scope,恢复不清零;预算未填写则 preflight 阻断。不得修改评分器、测试标签或用聊天分数代替实际结果。各业务角色只写 results/<role>/,导出之后通过当前 Inbox 暴露的真实文件/Git 工具提交、发布、集成,不能把 export 或 staged 控制回执当作已提交。Leader 的 planning turn 只 assign_group_tasks、finish_task 并立即 native Stop,不能轮询等待尚未派发的 worker;后续 integration turn 根据实际 PR/集成回执完成组。遵循实际暴露的工具与锁定权限,不猜参数。只有 Main 审核正式交付与方法证据后才能完成项目;必要工具缺失请求介入并暂停,不能虚构成功或无界重试。",
"mcpServers": [
{
"name": "chemical-coscientist-tools",
"project": "task-planner"
}
]
},
"web_search": {
"harness": "codex",
"model": "gpt-6.1-sol",
"gatewayReasoningOption": "mid",
"permission": "workspace",
"proxy": {
"mode": "direct"
},
"skills": [
"project:method-protocol"
],
"autoSkills": [],
"webSearch": false,
"sessionPersistence": true,
"decisionBy": "main",
"instructions": "针对 Planner 的确切化学问题检索公开真实主来源,读取全文/证据,search_sources 保存 URL/来源跨度/不确定性与矛盾。返回 Planner 的答案和未解决项;不推测实际设备支持。\n\n你在 SCIENCEEVOLVE SDK main 内核中执行当前 Inbox 的确切任务。先通过工具发现加载实际 MCP schema,读取 method protocol 和 get_state;不能把前驱完成回执当成自己的新任务。科研决定由你作出,backend 只能提供原子工具。每条结论绑定真实来源/代码/数据/模型/执行回执;pending、unknown、blocked、科学负结果分别记录。写操作用稳定 requestId 和当前 expectedRevision;断线先 get_request 对账,不以新 ID 重复副作用。累计轮次/作业/费用预算保存在专属 scope,恢复不清零;预算未填写则 preflight 阻断。不得修改评分器、测试标签或用聊天分数代替实际结果。各业务角色只写 results/<role>/,导出之后通过当前 Inbox 暴露的真实文件/Git 工具提交、发布、集成,不能把 export 或 staged 控制回执当作已提交。Leader 的 planning turn 只 assign_group_tasks、finish_task 并立即 native Stop,不能轮询等待尚未派发的 worker;后续 integration turn 根据实际 PR/集成回执完成组。遵循实际暴露的工具与锁定权限,不猜参数。只有 Main 审核正式交付与方法证据后才能完成项目;必要工具缺失请求介入并暂停,不能虚构成功或无界重试。",
"mcpServers": [
{
"name": "chemical-coscientist-tools",
"project": "task-web_search"
}
]
},
"documentation": {
"harness": "codex",
"model": "gpt-6.1-sol",
"gatewayReasoningOption": "mid",
"permission": "workspace",
"proxy": {
"mode": "direct"
},
"skills": [
"project:method-protocol"
],
"autoSkills": [],
"webSearch": false,
"sessionPersistence": true,
"decisionBy": "main",
"instructions": "search_device_docs 核对具体硬件、firmware/API 和动作限制,读完整相关指令及错误信息;给 Planner 可执行接口和证据 hash。与设备不匹配或文档不可达返回明确阻断。\n\n你在 SCIENCEEVOLVE SDK main 内核中执行当前 Inbox 的确切任务。先通过工具发现加载实际 MCP schema,读取 method protocol 和 get_state;不能把前驱完成回执当成自己的新任务。科研决定由你作出,backend 只能提供原子工具。每条结论绑定真实来源/代码/数据/模型/执行回执;pending、unknown、blocked、科学负结果分别记录。写操作用稳定 requestId 和当前 expectedRevision;断线先 get_request 对账,不以新 ID 重复副作用。累计轮次/作业/费用预算保存在专属 scope,恢复不清零;预算未填写则 preflight 阻断。不得修改评分器、测试标签或用聊天分数代替实际结果。各业务角色只写 results/<role>/,导出之后通过当前 Inbox 暴露的真实文件/Git 工具提交、发布、集成,不能把 export 或 staged 控制回执当作已提交。Leader 的 planning turn 只 assign_group_tasks、finish_task 并立即 native Stop,不能轮询等待尚未派发的 worker;后续 integration turn 根据实际 PR/集成回执完成组。遵循实际暴露的工具与锁定权限,不猜参数。只有 Main 审核正式交付与方法证据后才能完成项目;必要工具缺失请求介入并暂停,不能虚构成功或无界重试。",
"mcpServers": [
{
"name": "chemical-coscientist-tools",
"project": "task-documentation"
}
]
},
"code": {
"harness": "codex",
"model": "gpt-6.1-sol",
"gatewayReasoningOption": "mid",
"permission": "workspace",
"proxy": {
"mode": "direct"
},
"skills": [
"project:method-protocol"
],
"autoSkills": [],
"webSearch": false,
"sessionPersistence": true,
"decisionBy": "main",
"instructions": "按当前 plan 与实际手册编写协议/实验代码,用 run_code 做有界的纯计算/数据检查,用 compile_protocol 针对真实设备/API 校验。错误返回 Planner 具体诊断;只有实际编译 receipt 与精确 source/manual/device hash 可用于后续提交。此角色不提交物理实验。\n\n你在 SCIENCEEVOLVE SDK main 内核中执行当前 Inbox 的确切任务。先通过工具发现加载实际 MCP schema,读取 method protocol 和 get_state;不能把前驱完成回执当成自己的新任务。科研决定由你作出,backend 只能提供原子工具。每条结论绑定真实来源/代码/数据/模型/执行回执;pending、unknown、blocked、科学负结果分别记录。写操作用稳定 requestId 和当前 expectedRevision;断线先 get_request 对账,不以新 ID 重复副作用。累计轮次/作业/费用预算保存在专属 scope,恢复不清零;预算未填写则 preflight 阻断。不得修改评分器、测试标签或用聊天分数代替实际结果。各业务角色只写 results/<role>/,导出之后通过当前 Inbox 暴露的真实文件/Git 工具提交、发布、集成,不能把 export 或 staged 控制回执当作已提交。Leader 的 planning turn 只 assign_group_tasks、finish_task 并立即 native Stop,不能轮询等待尚未派发的 worker;后续 integration turn 根据实际 PR/集成回执完成组。遵循实际暴露的工具与锁定权限,不猜参数。只有 Main 审核正式交付与方法证据后才能完成项目;必要工具缺失请求介入并暂停,不能虚构成功或无界重试。",
"mcpServers": [
{
"name": "chemical-coscientist-tools",
"project": "task-code"
}
]
},
"experiment": {
"harness": "codex",
"model": "gpt-6.1-sol",
"gatewayReasoningOption": "mid",
"permission": "workspace",
"proxy": {
"mode": "direct"
},
"skills": [
"project:method-protocol"
],
"autoSkills": [],
"webSearch": false,
"sessionPersistence": true,
"decisionBy": "main",
"instructions": "核对 authorizationId、精确 compiledArtifact/device/plan 和最新状态,submit_experiment 提交真实物理 job。get_job 读 actual status/原始测量/样品/错误;unknown 先 get_request 对账,不能换 ID 或声称取消。record_observation 保留真实 raw receipt 与解释后交 optimization。\n\n你在 SCIENCEEVOLVE SDK main 内核中执行当前 Inbox 的确切任务。先通过工具发现加载实际 MCP schema,读取 method protocol 和 get_state;不能把前驱完成回执当成自己的新任务。科研决定由你作出,backend 只能提供原子工具。每条结论绑定真实来源/代码/数据/模型/执行回执;pending、unknown、blocked、科学负结果分别记录。写操作用稳定 requestId 和当前 expectedRevision;断线先 get_request 对账,不以新 ID 重复副作用。累计轮次/作业/费用预算保存在专属 scope,恢复不清零;预算未填写则 preflight 阻断。不得修改评分器、测试标签或用聊天分数代替实际结果。各业务角色只写 results/<role>/,导出之后通过当前 Inbox 暴露的真实文件/Git 工具提交、发布、集成,不能把 export 或 staged 控制回执当作已提交。Leader 的 planning turn 只 assign_group_tasks、finish_task 并立即 native Stop,不能轮询等待尚未派发的 worker;后续 integration turn 根据实际 PR/集成回执完成组。遵循实际暴露的工具与锁定权限,不猜参数。只有 Main 审核正式交付与方法证据后才能完成项目;必要工具缺失请求介入并暂停,不能虚构成功或无界重试。",
"mcpServers": [
{
"name": "chemical-coscientist-tools",
"project": "task-experiment"
}
]
},
"optimization": {
"harness": "codex",
"model": "gpt-6.1-sol",
"gatewayReasoningOption": "mid",
"permission": "workspace",
"proxy": {
"mode": "direct"
},
"skills": [
"project:method-protocol"
],
"autoSkills": [],
"webSearch": false,
"sessionPersistence": true,
"decisionBy": "main",
"instructions": "evaluate_objective 从真实观测/对照计算目标与不确定性;比较先前失败/有效测量,提出下一实验的可检验改动、信息增益和预算成本,返回 Planner。算法来自已核验实验任务,不能把模型打分当产率;负结果保留。\n\n你在 SCIENCEEVOLVE SDK main 内核中执行当前 Inbox 的确切任务。先通过工具发现加载实际 MCP schema,读取 method protocol 和 get_state;不能把前驱完成回执当成自己的新任务。科研决定由你作出,backend 只能提供原子工具。每条结论绑定真实来源/代码/数据/模型/执行回执;pending、unknown、blocked、科学负结果分别记录。写操作用稳定 requestId 和当前 expectedRevision;断线先 get_request 对账,不以新 ID 重复副作用。累计轮次/作业/费用预算保存在专属 scope,恢复不清零;预算未填写则 preflight 阻断。不得修改评分器、测试标签或用聊天分数代替实际结果。各业务角色只写 results/<role>/,导出之后通过当前 Inbox 暴露的真实文件/Git 工具提交、发布、集成,不能把 export 或 staged 控制回执当作已提交。Leader 的 planning turn 只 assign_group_tasks、finish_task 并立即 native Stop,不能轮询等待尚未派发的 worker;后续 integration turn 根据实际 PR/集成回执完成组。遵循实际暴露的工具与锁定权限,不猜参数。只有 Main 审核正式交付与方法证据后才能完成项目;必要工具缺失请求介入并暂停,不能虚构成功或无界重试。",
"mcpServers": [
{
"name": "chemical-coscientist-tools",
"project": "task-optimization"
}
]
},
"audit": {
"harness": "codex",
"model": "gpt-6.1-sol",
"gatewayReasoningOption": "mid",
"permission": "workspace",
"proxy": {
"mode": "direct"
},
"skills": [
"project:method-protocol"
],
"autoSkills": [],
"webSearch": false,
"sessionPersistence": true,
"decisionBy": "main",
"instructions": "独立核对原始证据、所有累计预算、失败/unknown 的去向、每项方法门槛与最终 Git/原目录交付。生成 report.md、acceptance.json 和 evidence-index.json,包含 methodAccepted、executedSteps、missingSteps、sourceHashes、actualRunId、scopeId 与限制。前面的研究角色不能替自己盖章;methodAccepted=true 仅限全合同成立。科学负结果可如实报告,缺执行不得伪装为负结果完成。 核对 Planner 按需委派、真实设备与文档匹配、source/compile/物理 job/raw measurements 绑定和结果驱动优化。历史分析/模拟可以出报告,但 methodAccepted=false。\n\n你在 SCIENCEEVOLVE SDK main 内核中执行当前 Inbox 的确切任务。先通过工具发现加载实际 MCP schema,读取 method protocol 和 get_state;不能把前驱完成回执当成自己的新任务。科研决定由你作出,backend 只能提供原子工具。每条结论绑定真实来源/代码/数据/模型/执行回执;pending、unknown、blocked、科学负结果分别记录。写操作用稳定 requestId 和当前 expectedRevision;断线先 get_request 对账,不以新 ID 重复副作用。累计轮次/作业/费用预算保存在专属 scope,恢复不清零;预算未填写则 preflight 阻断。不得修改评分器、测试标签或用聊天分数代替实际结果。各业务角色只写 results/<role>/,导出之后通过当前 Inbox 暴露的真实文件/Git 工具提交、发布、集成,不能把 export 或 staged 控制回执当作已提交。Leader 的 planning turn 只 assign_group_tasks、finish_task 并立即 native Stop,不能轮询等待尚未派发的 worker;后续 integration turn 根据实际 PR/集成回执完成组。遵循实际暴露的工具与锁定权限,不猜参数。只有 Main 审核正式交付与方法证据后才能完成项目;必要工具缺失请求介入并暂停,不能虚构成功或无界重试。",
"mcpServers": [
{
"name": "chemical-coscientist-tools",
"project": "task-audit"
}
]
}
},
"skillDefinitions": {
"method-protocol": {
"files": {
"SKILL.md": "---\nname: method-protocol\ndescription: chemical-coscientist 方法状态、输入输出、循环与验收协议\n---\n先阅读 references/method.md 与 references/backend.md。按当前任务核验真实证据,缺少必需资源时返回阻断。\n",
"references/method.md": "# 化学 Coscientist 方法协议\n来源:https://www.nature.com/articles/s41586-023-06792-0 与固定 gomesgroup/coscientist 417e82b 支持材料。公开 simple_implementation 只有简单表达式、随机数/停止工具,不是论文完整系统。不能把 Google co-scientist 假设系统混进这个方法。\n\nPlanner 按当前问题选择 web search、设备文档、code 或 experiment;这些工具不是强制顺序全跑,docs/web/code 可再次检索后返回 Planner。Planner 保存研究目标、科学假设、设备/样品/API 与预算,分解具体任务。工具角色完成后必须把实际证据交回 Planner,失败影响下一动作。\n\nCode 根据真实设备手册与 plan 编写完整可执行协议,用原子 sandbox 做必要数值/数据处理,用 compile_protocol 检查设备/API/代码 hash;编译/模拟成功不宣称执行。只有匹配当前计划和设备、具备真实 authorizationId 才由 experiment 提交物理作业。记录设备确认、job/样品 chain、原始测量/失败、代码与手册版本;queued/running/unknown 不计实验结果,断线先对账。\n\noptimization 从原始观测和目标/噪声/对照出发优化下一实验,保留失败并将原因/结果返回 Planner。优化算法和停止条件来自具体实验问题/固定支持材料,不能把随机数或模型自评分当收益。预算是累计实际实验次数/时间/费用,不由 weak route 次数隐含决定。支持历史数据/simulation 的独立评估模式,但它们只验证分析工具,不计湿实验闭环。Git 管理代码和报告,不撤销物理操作。\n\n审计须关联 search/docs/code/device/actual measurement 的完整证据链、真实结果驱动的后续实验、预算/停止和科学限制;Planner 自己写完方案不算任务完成。没有设备服务时 workflow 前置阻断;服务 backend 不得整套调用原 Coscientist。",
"references/backend.md": "# 新增工作流的原子工具合同\n\n2026-10-14。此接口是本次设计选择,不是上游原有 API。`task-bridge.cjs` 是传输适配器,不能提供训练器、设备、知识图谱或科研算法本身;相应服务尚未实现/接入。所有新 workflow 内嵌该代码及逐角色 `tools.json`,可单独复制 JSON 校验。SDK 提供 MCP 协议库;外部依赖由服务部署方负责。\n\n## 部署与请求\n\n每个方法配置其 `*_BACKEND_URL`、可选 `*_BACKEND_TOKEN` 和全新的 `*_SCOPE_ID` 环境变量。workflow 的 `envFrom` 映射到桥接器的 `SCIENCEEVOLVE_TASK_BACKEND_URL/TOKEN/SCOPE_ID`。URL 是明确的 HTTPS 或本机 HTTP POST 端点;不保存凭据,不自动重定向。backend 在远端按 token/scope/actor 校验权限,不能把调用方传来的 actor 当身份认证。\n\n请求格式:\n\n```json\n{\"schemaVersion\":1,\"method\":\"internagent-1-5\",\"scopeId\":\"unique-run-scope\",\"actor\":\"verification\",\"operation\":\"get_state\",\"requestId\":\"unique-read-id\",\"arguments\":{}}\n```\n\n响应必须回显 schemaVersion、method、scopeId、operation、requestId,并返回 `status: ok|blocked|failed|unknown`、`data` 和 `evidence` 数组。每个证据包含 `id/uri/kind/sha256`,SHA-256 是真实字节摘要。成功写入必须有非空的持久回执。`read_artifact` 返回 `data.text/data.sha256`,桥接器核验实际 UTF-8 字节;其他证据的科学内容仍需审核,不由传输层判定正确。成功提交作业表示已入队,只有后续 `get_job` 的真实终态与测量能证明执行。\n\n## 一致性与恢复\n\n- 状态保存在 backend 的专属 scope,不能放在 SDK 会刷新的 Skill/MCP 执行副本。各 worktree 只导出自己的结果目录。\n- 写操作要求稳定 `requestId`;状态变更还要求 `expectedRevision`。backend 在事务中实现幂等、CAS 和追加式事件账本。参数相同的重放返回原回执;同 ID 不同参数拒绝。\n- 30 秒传输超时或异常响应后,写操作结果为 unknown。用 `get_request(originalRequestId)` 对账,不换 ID 重复训练、部署或物理作业。查询本身不触发新作业。\n- backend 返回类型化的能力清单、原子接口版本、软件/模型/数据/评分器摘要、资源探测和预算。能力缺失返回 blocked,不提供模拟回执。\n- 服务只能提供检索、图/记忆存取、数值选择/回传、具体环境步、编译、实验作业、训练作业和权重部署等原子操作。禁止暴露 `run_internagent/run_raven_host/run_coscientist` 等完整自治系统入口。研究规划、候选内容、优化方向和终止裁决由 SDK Agent 作出。\n\n## 各方法的附加合同\n\n### InternAgent 1.5\n\n能力清单至少包括真实文献检索/全文、图+向量检索、FlowGraph CAS、可执行 baseline、实验评分器、图增强 Monte Carlo 的数值选择/回传、SPM/TEM/SKM 以及有效 embedding。任务记忆、在线保存、长期 IdeaGraph/PromptEvolver 与 DR 的 SPM 需区分。`select_search_context` 返回选择参数、visit/value、父/祖先/跨支引用、运算符和租约,Agent 产生候选;`record_evaluation` 根据真实 job 回执回传分数。不得把最新 MLEvolve 整体启动当成这些能力。\n\nFlowGraph 节点含 search/solve/answer 类型、任务、状态、结果 context;边含 requires/provides/constrains 语义、来源与修订。`claim_flow_node` 事务中检查全部依赖、节点未重复执行、分支身份与 epoch。当前 SDK 用两个 worker 在批次边界调度,新增动态节点只能由 Planner 后续批次处理:这保持数据依赖,但不是原方法即时按 ready-set 推进,报告须扣分。\n\n### Biomni-R0\n\n`read_training_recipe` 必须给出已溯源的原方法 recipe 及待核对项、基础权重/环境/任务版本、真实 reward 定义、数据划分、训练/评估参数。无法核对原训练策略时,不能填写自选 GRPO/PPO 或随意 reward 当作原方法。此设计没有固化未经证实的 SFT/RL 阶段;服务按核实 recipe 执行原子训练作业。训练前核验 reward 与 split,held-out 标注不进入训练上下文。\n\n`collect_trajectories` 是固定 policy 与真实 E1 工具环境的任务执行;轨迹保留 observation/action/tool result/terminal outcome/policy hash,不包含自改研究规划。`score_trajectories` 由独立评分器产出奖励。`submit_training` 必须返回 job、输入轨迹/奖励/recipe 摘要;终态返回真实 checkpoint。部署和评估均绑定 checkpoint 字节摘要,明确环境与旧权重回退。使用发布成品 R0 只能作为比较基线,不能替代训练。\n\n### Chemical Coscientist\n\n检索/设备文档工具返回真实 URL、版本、内容摘要及可读取证据;文档必须与实际设备/API 匹配。`compile_protocol` 校验输入代码和设备能力,返回源码摘要、编译结果与设备绑定。`submit_experiment` 需要真实 `authorizationId` 和相同代码/设备摘要;设备服务在提交端核验授权、参数范围与资源,workflow prompt 不代替设备约束。\n\n`get_job` 返回真实物理作业状态、测量/错误/样品链;断线未知作业先对账。优化读取真实结果、噪声/失败和预算后决定下一实验。Git 只管理代码/报告,不能撤销湿实验。simulation/historical-data 是独立标记的模式,不计为湿实验复现。\n\n### Raven Host\n\n新模板只提供 SDK 内可执行的固定双 specialist 批次及边界判定骨架。backend 仅记录图、节点、instance、记忆与原子领域工具,不可承担 Host 规划或另建运行时来声称 SDK 原生支持完整 Raven。严格默认 Host 需求在 preflight 返回 blocked:当前合同缺任意 AND ready-set、在线通知/裁决、在途取消与图替换。\n\njudge 回执区分 incomplete、判定不可用、参数错误;未通过节点输出不能提供给依赖节点。最多两次 continuation 是节点总预算,不因新批次/重启归零。固定两个 SDK 会话仅提供有限 instance 复用;跨任意图实例、逐节点 Skills/MCP、stint 自动推进仍是待实现接口。这个模板不能把所有 specialist 强制串行,也不能把批次重跑写成原语义的在线 continue/replan。\n\n## 运行前应做的验证\n\n先离线校验 JSON 和实际 Skill/MCP 暂存,再对部署后的原子服务验证资源/权限/幂等/CAS/unknown 对账,最后才运行真实方法。JSON 校验、桥接 localhost 合同或接口清单不等于方法/论文验收。每次新运行记录唯一 SDK run ID 与 backend scope,保留负面结果、源字节摘要、累计预算和完整事件链。\n"
}
}
},
"mcpDefinitions": {
"task-main": {
"command": "node",
"entrypoint": "task-bridge.cjs",
"args": [],
"envFrom": {
"SCIENCEEVOLVE_TASK_BACKEND_URL": "SCIENCEEVOLVE_COSCIENTIST_BACKEND_URL",
"SCIENCEEVOLVE_TASK_BACKEND_TOKEN": "SCIENCEEVOLVE_COSCIENTIST_BACKEND_TOKEN",
"SCIENCEEVOLVE_TASK_SCOPE_ID": "SCIENCEEVOLVE_COSCIENTIST_SCOPE_ID"
},
"files": {
"task-bridge.cjs": "'use strict';\n// Transport only. Scientific decisions stay in SCIENCEEVOLVE agents; backends expose atomic tools.\nconst { Server } = require('@modelcontextprotocol/sdk/server/index.js');\nconst { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');\nconst { ListToolsRequestSchema, CallToolRequestSchema } = require('@modelcontextprotocol/sdk/types.js');\nconst { randomUUID, createHash } = require('node:crypto');\nconst spec = require('./tools.json');\n\nfunction check(schema, value, at = 'arguments') {\n if (schema.enum && !schema.enum.includes(value)) throw new Error(`${at}: unsupported enum`);\n if (schema.type === 'object') {\n if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`${at}: object required`);\n for (const key of schema.required || []) if (!(key in value)) throw new Error(`${at}.${key}: required`);\n for (const [key, item] of Object.entries(value)) {\n if (schema.properties?.[key]) check(schema.properties[key], item, `${at}.${key}`);\n else if (schema.additionalProperties === false) throw new Error(`${at}.${key}: unknown field`);\n }\n } else if (schema.type === 'array') {\n if (!Array.isArray(value)) throw new Error(`${at}: array required`);\n if (schema.minItems !== undefined && value.length < schema.minItems) throw new Error(`${at}: too few items`);\n if (schema.maxItems !== undefined && value.length > schema.maxItems) throw new Error(`${at}: too many items`);\n for (const item of value) check(schema.items, item, `${at}[]`);\n } else if (schema.type === 'string') {\n if (typeof value !== 'string' || value.length < (schema.minLength ?? 0) || value.length > (schema.maxLength ?? Infinity)) throw new Error(`${at}: invalid string`);\n if (schema.pattern && !new RegExp(schema.pattern).test(value)) throw new Error(`${at}: invalid pattern`);\n } else if (schema.type === 'integer') {\n if (!Number.isSafeInteger(value) || value < (schema.minimum ?? -Infinity) || value > (schema.maximum ?? Infinity)) throw new Error(`${at}: invalid integer`);\n } else if (schema.type === 'boolean') {\n if (typeof value !== 'boolean') throw new Error(`${at}: boolean required`);\n }\n}\n\nasync function dispatch(tool, args) {\n check(tool.inputSchema, args);\n const raw = process.env.SCIENCEEVOLVE_TASK_BACKEND_URL;\n const scopeId = process.env.SCIENCEEVOLVE_TASK_SCOPE_ID;\n if (!raw || !scopeId) return { isError: true, content: [{ type: 'text', text: JSON.stringify({ status: 'blocked', reason: 'Configure the method-specific backend URL and a fresh scope ID; no backend is bundled.' }) }] };\n const endpoint = new URL(raw);\n if (!(endpoint.protocol === 'https:' || (endpoint.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(endpoint.hostname))) || endpoint.username || endpoint.password || endpoint.search || endpoint.hash) throw new Error('Use an explicit HTTPS or loopback HTTP endpoint without inline credentials');\n const requestId = tool.mode === 'write' ? args.requestId : randomUUID();\n if (tool.mode === 'write' && !requestId) throw new Error('Mutation requires a stable requestId');\n let response;\n try {\n response = await fetch(endpoint, {\n method: 'POST', redirect: 'error', signal: AbortSignal.timeout(30000),\n headers: { 'content-type': 'application/json', ...(process.env.SCIENCEEVOLVE_TASK_BACKEND_TOKEN ? { authorization: `Bearer ${process.env.SCIENCEEVOLVE_TASK_BACKEND_TOKEN}` } : {}) },\n body: JSON.stringify({ schemaVersion: 1, method: spec.method, scopeId, actor: spec.actor, operation: tool.name, requestId, arguments: args })\n });\n } catch {\n // Never retry a possibly accepted training/physical job automatically.\n return { isError: true, content: [{ type: 'text', text: JSON.stringify({ status: tool.mode === 'write' ? 'outcome_unknown' : 'unavailable', requestId, operation: tool.name, reason: 'Transport failed; reconcile this exact requestId using get_request. Do not create a replacement job.' }) }] };\n }\n if (!response.ok) return { isError: true, content: [{ type: 'text', text: JSON.stringify({ status: tool.mode === 'write' ? 'outcome_unknown' : 'unavailable', requestId, operation: tool.name, httpStatus: response.status, reason: 'Reconcile before replaying any mutation.' }) }] };\n const body = await response.text();\n if (Buffer.byteLength(body) > 2 * 1024 * 1024) throw new Error('Backend response too large; use paged evidence');\n const result = JSON.parse(body);\n if (result.schemaVersion !== 1 || result.method !== spec.method || result.scopeId !== scopeId || result.operation !== tool.name || result.requestId !== requestId) throw new Error(`Mismatched backend receipt; mutation outcome remains unknown: ${requestId}`);\n if (!['ok', 'blocked', 'failed', 'unknown'].includes(result.status)) throw new Error(`Invalid receipt status: ${requestId}`);\n if (!Array.isArray(result.evidence)) throw new Error(`Receipt requires an evidence array: ${requestId}`);\n for (const evidence of result.evidence) {\n if (!evidence || typeof evidence.id !== 'string' || typeof evidence.uri !== 'string' || typeof evidence.kind !== 'string' || !/^[a-f0-9]{64}$/.test(evidence.sha256)) throw new Error(`Malformed evidence reference: ${requestId}`);\n }\n if (tool.mode === 'write' && result.status === 'ok' && result.evidence.length === 0) throw new Error(`Successful mutation requires a durable receipt: ${requestId}`);\n if (tool.name === 'read_artifact' && result.status === 'ok') {\n if (typeof result.data?.text !== 'string' || createHash('sha256').update(result.data.text, 'utf8').digest('hex') !== result.data.sha256) throw new Error('Artifact bytes do not match the claimed SHA-256');\n }\n // Evidence references and an HTTP OK are not a scientific pass. Agents audit actual bytes/results.\n return { isError: result.status !== 'ok', content: [{ type: 'text', text: JSON.stringify(result) }] };\n}\n\nconst server = new Server({ name: `${spec.method}-${spec.actor}-atomic-tools`, version: '1.0.0' }, { capabilities: { tools: {} } });\nserver.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: spec.tools.map(({ mode, ...tool }) => tool) }));\nserver.setRequestHandler(CallToolRequestSchema, async request => {\n const tool = spec.tools.find(item => item.name === request.params.name);\n if (!tool) throw new Error('Tool is not assigned to this role');\n try { return await dispatch(tool, request.params.arguments || {}); }\n catch (error) { return { isError: true, content: [{ type: 'text', text: error.message }] }; }\n});\nserver.connect(new StdioServerTransport());\nprocess.stdin.on('end', () => { server.close().finally(() => process.exit()); });\n\n",
"tools.json": "{\n \"method\": \"chemical-coscientist\",\n \"actor\": \"main\",\n \"tools\": [\n {\n \"name\": \"get_capabilities\",\n \"description\": \"Read actual method/tool versions, resources, scope budget, provenance and capability limitations. A design declaration is not a resource probe.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [],\n \"properties\": {}\n }\n },\n {\n \"name\": \"get_state\",\n \"description\": \"Read durable current scope revision, budgets, jobs, graph/memory/weight versions and accepted evidence. Do not infer current tasks from an old predecessor handoff.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [],\n \"properties\": {}\n }\n },\n {\n \"name\": \"get_request\",\n \"description\": \"Reconcile the original durable request without submitting another job; preserve unknown until authoritative evidence exists.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"originalRequestId\"\n ],\n \"properties\": {\n \"originalRequestId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n }\n }\n }\n },\n {\n \"name\": \"get_job\",\n \"description\": \"Read actual job status, input/output hashes, error or measurement receipts. Accepted/queued is not completed.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"jobId\"\n ],\n \"properties\": {\n \"jobId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n }\n }\n }\n },\n {\n \"name\": \"read_artifact\",\n \"description\": \"Read a small retained UTF-8 artifact and verify its SHA-256 bytes; paginate large evidence at the backend.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"artifactId\"\n ],\n \"properties\": {\n \"artifactId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n }\n }\n }\n }\n ]\n}\n"
}
},
"task-preflight": {
"command": "node",
"entrypoint": "task-bridge.cjs",
"args": [],
"envFrom": {
"SCIENCEEVOLVE_TASK_BACKEND_URL": "SCIENCEEVOLVE_COSCIENTIST_BACKEND_URL",
"SCIENCEEVOLVE_TASK_BACKEND_TOKEN": "SCIENCEEVOLVE_COSCIENTIST_BACKEND_TOKEN",
"SCIENCEEVOLVE_TASK_SCOPE_ID": "SCIENCEEVOLVE_COSCIENTIST_SCOPE_ID"
},
"files": {
"task-bridge.cjs": "'use strict';\n// Transport only. Scientific decisions stay in SCIENCEEVOLVE agents; backends expose atomic tools.\nconst { Server } = require('@modelcontextprotocol/sdk/server/index.js');\nconst { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');\nconst { ListToolsRequestSchema, CallToolRequestSchema } = require('@modelcontextprotocol/sdk/types.js');\nconst { randomUUID, createHash } = require('node:crypto');\nconst spec = require('./tools.json');\n\nfunction check(schema, value, at = 'arguments') {\n if (schema.enum && !schema.enum.includes(value)) throw new Error(`${at}: unsupported enum`);\n if (schema.type === 'object') {\n if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`${at}: object required`);\n for (const key of schema.required || []) if (!(key in value)) throw new Error(`${at}.${key}: required`);\n for (const [key, item] of Object.entries(value)) {\n if (schema.properties?.[key]) check(schema.properties[key], item, `${at}.${key}`);\n else if (schema.additionalProperties === false) throw new Error(`${at}.${key}: unknown field`);\n }\n } else if (schema.type === 'array') {\n if (!Array.isArray(value)) throw new Error(`${at}: array required`);\n if (schema.minItems !== undefined && value.length < schema.minItems) throw new Error(`${at}: too few items`);\n if (schema.maxItems !== undefined && value.length > schema.maxItems) throw new Error(`${at}: too many items`);\n for (const item of value) check(schema.items, item, `${at}[]`);\n } else if (schema.type === 'string') {\n if (typeof value !== 'string' || value.length < (schema.minLength ?? 0) || value.length > (schema.maxLength ?? Infinity)) throw new Error(`${at}: invalid string`);\n if (schema.pattern && !new RegExp(schema.pattern).test(value)) throw new Error(`${at}: invalid pattern`);\n } else if (schema.type === 'integer') {\n if (!Number.isSafeInteger(value) || value < (schema.minimum ?? -Infinity) || value > (schema.maximum ?? Infinity)) throw new Error(`${at}: invalid integer`);\n } else if (schema.type === 'boolean') {\n if (typeof value !== 'boolean') throw new Error(`${at}: boolean required`);\n }\n}\n\nasync function dispatch(tool, args) {\n check(tool.inputSchema, args);\n const raw = process.env.SCIENCEEVOLVE_TASK_BACKEND_URL;\n const scopeId = process.env.SCIENCEEVOLVE_TASK_SCOPE_ID;\n if (!raw || !scopeId) return { isError: true, content: [{ type: 'text', text: JSON.stringify({ status: 'blocked', reason: 'Configure the method-specific backend URL and a fresh scope ID; no backend is bundled.' }) }] };\n const endpoint = new URL(raw);\n if (!(endpoint.protocol === 'https:' || (endpoint.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(endpoint.hostname))) || endpoint.username || endpoint.password || endpoint.search || endpoint.hash) throw new Error('Use an explicit HTTPS or loopback HTTP endpoint without inline credentials');\n const requestId = tool.mode === 'write' ? args.requestId : randomUUID();\n if (tool.mode === 'write' && !requestId) throw new Error('Mutation requires a stable requestId');\n let response;\n try {\n response = await fetch(endpoint, {\n method: 'POST', redirect: 'error', signal: AbortSignal.timeout(30000),\n headers: { 'content-type': 'application/json', ...(process.env.SCIENCEEVOLVE_TASK_BACKEND_TOKEN ? { authorization: `Bearer ${process.env.SCIENCEEVOLVE_TASK_BACKEND_TOKEN}` } : {}) },\n body: JSON.stringify({ schemaVersion: 1, method: spec.method, scopeId, actor: spec.actor, operation: tool.name, requestId, arguments: args })\n });\n } catch {\n // Never retry a possibly accepted training/physical job automatically.\n return { isError: true, content: [{ type: 'text', text: JSON.stringify({ status: tool.mode === 'write' ? 'outcome_unknown' : 'unavailable', requestId, operation: tool.name, reason: 'Transport failed; reconcile this exact requestId using get_request. Do not create a replacement job.' }) }] };\n }\n if (!response.ok) return { isError: true, content: [{ type: 'text', text: JSON.stringify({ status: tool.mode === 'write' ? 'outcome_unknown' : 'unavailable', requestId, operation: tool.name, httpStatus: response.status, reason: 'Reconcile before replaying any mutation.' }) }] };\n const body = await response.text();\n if (Buffer.byteLength(body) > 2 * 1024 * 1024) throw new Error('Backend response too large; use paged evidence');\n const result = JSON.parse(body);\n if (result.schemaVersion !== 1 || result.method !== spec.method || result.scopeId !== scopeId || result.operation !== tool.name || result.requestId !== requestId) throw new Error(`Mismatched backend receipt; mutation outcome remains unknown: ${requestId}`);\n if (!['ok', 'blocked', 'failed', 'unknown'].includes(result.status)) throw new Error(`Invalid receipt status: ${requestId}`);\n if (!Array.isArray(result.evidence)) throw new Error(`Receipt requires an evidence array: ${requestId}`);\n for (const evidence of result.evidence) {\n if (!evidence || typeof evidence.id !== 'string' || typeof evidence.uri !== 'string' || typeof evidence.kind !== 'string' || !/^[a-f0-9]{64}$/.test(evidence.sha256)) throw new Error(`Malformed evidence reference: ${requestId}`);\n }\n if (tool.mode === 'write' && result.status === 'ok' && result.evidence.length === 0) throw new Error(`Successful mutation requires a durable receipt: ${requestId}`);\n if (tool.name === 'read_artifact' && result.status === 'ok') {\n if (typeof result.data?.text !== 'string' || createHash('sha256').update(result.data.text, 'utf8').digest('hex') !== result.data.sha256) throw new Error('Artifact bytes do not match the claimed SHA-256');\n }\n // Evidence references and an HTTP OK are not a scientific pass. Agents audit actual bytes/results.\n return { isError: result.status !== 'ok', content: [{ type: 'text', text: JSON.stringify(result) }] };\n}\n\nconst server = new Server({ name: `${spec.method}-${spec.actor}-atomic-tools`, version: '1.0.0' }, { capabilities: { tools: {} } });\nserver.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: spec.tools.map(({ mode, ...tool }) => tool) }));\nserver.setRequestHandler(CallToolRequestSchema, async request => {\n const tool = spec.tools.find(item => item.name === request.params.name);\n if (!tool) throw new Error('Tool is not assigned to this role');\n try { return await dispatch(tool, request.params.arguments || {}); }\n catch (error) { return { isError: true, content: [{ type: 'text', text: error.message }] }; }\n});\nserver.connect(new StdioServerTransport());\nprocess.stdin.on('end', () => { server.close().finally(() => process.exit()); });\n\n",
"tools.json": "{\n \"method\": \"chemical-coscientist\",\n \"actor\": \"preflight\",\n \"tools\": [\n {\n \"name\": \"get_capabilities\",\n \"description\": \"Read actual method/tool versions, resources, scope budget, provenance and capability limitations. A design declaration is not a resource probe.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [],\n \"properties\": {}\n }\n },\n {\n \"name\": \"get_state\",\n \"description\": \"Read durable current scope revision, budgets, jobs, graph/memory/weight versions and accepted evidence. Do not infer current tasks from an old predecessor handoff.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [],\n \"properties\": {}\n }\n },\n {\n \"name\": \"get_request\",\n \"description\": \"Reconcile the original durable request without submitting another job; preserve unknown until authoritative evidence exists.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"originalRequestId\"\n ],\n \"properties\": {\n \"originalRequestId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n }\n }\n }\n },\n {\n \"name\": \"get_job\",\n \"description\": \"Read actual job status, input/output hashes, error or measurement receipts. Accepted/queued is not completed.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"jobId\"\n ],\n \"properties\": {\n \"jobId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n }\n }\n }\n },\n {\n \"name\": \"read_artifact\",\n \"description\": \"Read a small retained UTF-8 artifact and verify its SHA-256 bytes; paginate large evidence at the backend.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"artifactId\"\n ],\n \"properties\": {\n \"artifactId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n }\n }\n }\n },\n {\n \"name\": \"search_device_docs\",\n \"description\": \"Retrieve actual device/API manuals matching installed hardware/firmware versions, with source spans and content hashes.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"deviceId\",\n \"query\"\n ],\n \"properties\": {\n \"deviceId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n },\n \"query\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n }\n }\n }\n }\n ]\n}\n"
}
},
"task-planner": {
"command": "node",
"entrypoint": "task-bridge.cjs",
"args": [],
"envFrom": {
"SCIENCEEVOLVE_TASK_BACKEND_URL": "SCIENCEEVOLVE_COSCIENTIST_BACKEND_URL",
"SCIENCEEVOLVE_TASK_BACKEND_TOKEN": "SCIENCEEVOLVE_COSCIENTIST_BACKEND_TOKEN",
"SCIENCEEVOLVE_TASK_SCOPE_ID": "SCIENCEEVOLVE_COSCIENTIST_SCOPE_ID"
},
"files": {
"task-bridge.cjs": "'use strict';\n// Transport only. Scientific decisions stay in SCIENCEEVOLVE agents; backends expose atomic tools.\nconst { Server } = require('@modelcontextprotocol/sdk/server/index.js');\nconst { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');\nconst { ListToolsRequestSchema, CallToolRequestSchema } = require('@modelcontextprotocol/sdk/types.js');\nconst { randomUUID, createHash } = require('node:crypto');\nconst spec = require('./tools.json');\n\nfunction check(schema, value, at = 'arguments') {\n if (schema.enum && !schema.enum.includes(value)) throw new Error(`${at}: unsupported enum`);\n if (schema.type === 'object') {\n if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`${at}: object required`);\n for (const key of schema.required || []) if (!(key in value)) throw new Error(`${at}.${key}: required`);\n for (const [key, item] of Object.entries(value)) {\n if (schema.properties?.[key]) check(schema.properties[key], item, `${at}.${key}`);\n else if (schema.additionalProperties === false) throw new Error(`${at}.${key}: unknown field`);\n }\n } else if (schema.type === 'array') {\n if (!Array.isArray(value)) throw new Error(`${at}: array required`);\n if (schema.minItems !== undefined && value.length < schema.minItems) throw new Error(`${at}: too few items`);\n if (schema.maxItems !== undefined && value.length > schema.maxItems) throw new Error(`${at}: too many items`);\n for (const item of value) check(schema.items, item, `${at}[]`);\n } else if (schema.type === 'string') {\n if (typeof value !== 'string' || value.length < (schema.minLength ?? 0) || value.length > (schema.maxLength ?? Infinity)) throw new Error(`${at}: invalid string`);\n if (schema.pattern && !new RegExp(schema.pattern).test(value)) throw new Error(`${at}: invalid pattern`);\n } else if (schema.type === 'integer') {\n if (!Number.isSafeInteger(value) || value < (schema.minimum ?? -Infinity) || value > (schema.maximum ?? Infinity)) throw new Error(`${at}: invalid integer`);\n } else if (schema.type === 'boolean') {\n if (typeof value !== 'boolean') throw new Error(`${at}: boolean required`);\n }\n}\n\nasync function dispatch(tool, args) {\n check(tool.inputSchema, args);\n const raw = process.env.SCIENCEEVOLVE_TASK_BACKEND_URL;\n const scopeId = process.env.SCIENCEEVOLVE_TASK_SCOPE_ID;\n if (!raw || !scopeId) return { isError: true, content: [{ type: 'text', text: JSON.stringify({ status: 'blocked', reason: 'Configure the method-specific backend URL and a fresh scope ID; no backend is bundled.' }) }] };\n const endpoint = new URL(raw);\n if (!(endpoint.protocol === 'https:' || (endpoint.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(endpoint.hostname))) || endpoint.username || endpoint.password || endpoint.search || endpoint.hash) throw new Error('Use an explicit HTTPS or loopback HTTP endpoint without inline credentials');\n const requestId = tool.mode === 'write' ? args.requestId : randomUUID();\n if (tool.mode === 'write' && !requestId) throw new Error('Mutation requires a stable requestId');\n let response;\n try {\n response = await fetch(endpoint, {\n method: 'POST', redirect: 'error', signal: AbortSignal.timeout(30000),\n headers: { 'content-type': 'application/json', ...(process.env.SCIENCEEVOLVE_TASK_BACKEND_TOKEN ? { authorization: `Bearer ${process.env.SCIENCEEVOLVE_TASK_BACKEND_TOKEN}` } : {}) },\n body: JSON.stringify({ schemaVersion: 1, method: spec.method, scopeId, actor: spec.actor, operation: tool.name, requestId, arguments: args })\n });\n } catch {\n // Never retry a possibly accepted training/physical job automatically.\n return { isError: true, content: [{ type: 'text', text: JSON.stringify({ status: tool.mode === 'write' ? 'outcome_unknown' : 'unavailable', requestId, operation: tool.name, reason: 'Transport failed; reconcile this exact requestId using get_request. Do not create a replacement job.' }) }] };\n }\n if (!response.ok) return { isError: true, content: [{ type: 'text', text: JSON.stringify({ status: tool.mode === 'write' ? 'outcome_unknown' : 'unavailable', requestId, operation: tool.name, httpStatus: response.status, reason: 'Reconcile before replaying any mutation.' }) }] };\n const body = await response.text();\n if (Buffer.byteLength(body) > 2 * 1024 * 1024) throw new Error('Backend response too large; use paged evidence');\n const result = JSON.parse(body);\n if (result.schemaVersion !== 1 || result.method !== spec.method || result.scopeId !== scopeId || result.operation !== tool.name || result.requestId !== requestId) throw new Error(`Mismatched backend receipt; mutation outcome remains unknown: ${requestId}`);\n if (!['ok', 'blocked', 'failed', 'unknown'].includes(result.status)) throw new Error(`Invalid receipt status: ${requestId}`);\n if (!Array.isArray(result.evidence)) throw new Error(`Receipt requires an evidence array: ${requestId}`);\n for (const evidence of result.evidence) {\n if (!evidence || typeof evidence.id !== 'string' || typeof evidence.uri !== 'string' || typeof evidence.kind !== 'string' || !/^[a-f0-9]{64}$/.test(evidence.sha256)) throw new Error(`Malformed evidence reference: ${requestId}`);\n }\n if (tool.mode === 'write' && result.status === 'ok' && result.evidence.length === 0) throw new Error(`Successful mutation requires a durable receipt: ${requestId}`);\n if (tool.name === 'read_artifact' && result.status === 'ok') {\n if (typeof result.data?.text !== 'string' || createHash('sha256').update(result.data.text, 'utf8').digest('hex') !== result.data.sha256) throw new Error('Artifact bytes do not match the claimed SHA-256');\n }\n // Evidence references and an HTTP OK are not a scientific pass. Agents audit actual bytes/results.\n return { isError: result.status !== 'ok', content: [{ type: 'text', text: JSON.stringify(result) }] };\n}\n\nconst server = new Server({ name: `${spec.method}-${spec.actor}-atomic-tools`, version: '1.0.0' }, { capabilities: { tools: {} } });\nserver.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: spec.tools.map(({ mode, ...tool }) => tool) }));\nserver.setRequestHandler(CallToolRequestSchema, async request => {\n const tool = spec.tools.find(item => item.name === request.params.name);\n if (!tool) throw new Error('Tool is not assigned to this role');\n try { return await dispatch(tool, request.params.arguments || {}); }\n catch (error) { return { isError: true, content: [{ type: 'text', text: error.message }] }; }\n});\nserver.connect(new StdioServerTransport());\nprocess.stdin.on('end', () => { server.close().finally(() => process.exit()); });\n\n",
"tools.json": "{\n \"method\": \"chemical-coscientist\",\n \"actor\": \"planner\",\n \"tools\": [\n {\n \"name\": \"get_capabilities\",\n \"description\": \"Read actual method/tool versions, resources, scope budget, provenance and capability limitations. A design declaration is not a resource probe.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [],\n \"properties\": {}\n }\n },\n {\n \"name\": \"get_state\",\n \"description\": \"Read durable current scope revision, budgets, jobs, graph/memory/weight versions and accepted evidence. Do not infer current tasks from an old predecessor handoff.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [],\n \"properties\": {}\n }\n },\n {\n \"name\": \"get_request\",\n \"description\": \"Reconcile the original durable request without submitting another job; preserve unknown until authoritative evidence exists.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"originalRequestId\"\n ],\n \"properties\": {\n \"originalRequestId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n }\n }\n }\n },\n {\n \"name\": \"get_job\",\n \"description\": \"Read actual job status, input/output hashes, error or measurement receipts. Accepted/queued is not completed.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"jobId\"\n ],\n \"properties\": {\n \"jobId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n }\n }\n }\n },\n {\n \"name\": \"read_artifact\",\n \"description\": \"Read a small retained UTF-8 artifact and verify its SHA-256 bytes; paginate large evidence at the backend.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"artifactId\"\n ],\n \"properties\": {\n \"artifactId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n }\n }\n }\n },\n {\n \"name\": \"register_experiment_plan\",\n \"description\": \"CAS-register a Planner-authored testable experiment, domain constraints, uncertainties, optimization objective and cumulative budget.\",\n \"mode\": \"write\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"plan\",\n \"evidenceIds\",\n \"requestId\",\n \"expectedRevision\"\n ],\n \"properties\": {\n \"plan\": {\n \"type\": \"object\",\n \"additionalProperties\": true\n },\n \"evidenceIds\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n },\n \"minItems\": 1,\n \"maxItems\": 100\n },\n \"requestId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 128\n },\n \"expectedRevision\": {\n \"type\": \"integer\",\n \"minimum\": 0\n }\n }\n }\n },\n {\n \"name\": \"evaluate_objective\",\n \"description\": \"Compute the predeclared chemical objective and uncertainty from authentic observations and controls; retain invalid/failed outcomes.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"observationIds\",\n \"objectiveId\"\n ],\n \"properties\": {\n \"observationIds\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n },\n \"minItems\": 1,\n \"maxItems\": 100\n },\n \"objectiveId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n }\n }\n }\n }\n ]\n}\n"
}
},
"task-web_search": {
"command": "node",
"entrypoint": "task-bridge.cjs",
"args": [],
"envFrom": {
"SCIENCEEVOLVE_TASK_BACKEND_URL": "SCIENCEEVOLVE_COSCIENTIST_BACKEND_URL",
"SCIENCEEVOLVE_TASK_BACKEND_TOKEN": "SCIENCEEVOLVE_COSCIENTIST_BACKEND_TOKEN",
"SCIENCEEVOLVE_TASK_SCOPE_ID": "SCIENCEEVOLVE_COSCIENTIST_SCOPE_ID"
},
"files": {
"task-bridge.cjs": "'use strict';\n// Transport only. Scientific decisions stay in SCIENCEEVOLVE agents; backends expose atomic tools.\nconst { Server } = require('@modelcontextprotocol/sdk/server/index.js');\nconst { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');\nconst { ListToolsRequestSchema, CallToolRequestSchema } = require('@modelcontextprotocol/sdk/types.js');\nconst { randomUUID, createHash } = require('node:crypto');\nconst spec = require('./tools.json');\n\nfunction check(schema, value, at = 'arguments') {\n if (schema.enum && !schema.enum.includes(value)) throw new Error(`${at}: unsupported enum`);\n if (schema.type === 'object') {\n if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`${at}: object required`);\n for (const key of schema.required || []) if (!(key in value)) throw new Error(`${at}.${key}: required`);\n for (const [key, item] of Object.entries(value)) {\n if (schema.properties?.[key]) check(schema.properties[key], item, `${at}.${key}`);\n else if (schema.additionalProperties === false) throw new Error(`${at}.${key}: unknown field`);\n }\n } else if (schema.type === 'array') {\n if (!Array.isArray(value)) throw new Error(`${at}: array required`);\n if (schema.minItems !== undefined && value.length < schema.minItems) throw new Error(`${at}: too few items`);\n if (schema.maxItems !== undefined && value.length > schema.maxItems) throw new Error(`${at}: too many items`);\n for (const item of value) check(schema.items, item, `${at}[]`);\n } else if (schema.type === 'string') {\n if (typeof value !== 'string' || value.length < (schema.minLength ?? 0) || value.length > (schema.maxLength ?? Infinity)) throw new Error(`${at}: invalid string`);\n if (schema.pattern && !new RegExp(schema.pattern).test(value)) throw new Error(`${at}: invalid pattern`);\n } else if (schema.type === 'integer') {\n if (!Number.isSafeInteger(value) || value < (schema.minimum ?? -Infinity) || value > (schema.maximum ?? Infinity)) throw new Error(`${at}: invalid integer`);\n } else if (schema.type === 'boolean') {\n if (typeof value !== 'boolean') throw new Error(`${at}: boolean required`);\n }\n}\n\nasync function dispatch(tool, args) {\n check(tool.inputSchema, args);\n const raw = process.env.SCIENCEEVOLVE_TASK_BACKEND_URL;\n const scopeId = process.env.SCIENCEEVOLVE_TASK_SCOPE_ID;\n if (!raw || !scopeId) return { isError: true, content: [{ type: 'text', text: JSON.stringify({ status: 'blocked', reason: 'Configure the method-specific backend URL and a fresh scope ID; no backend is bundled.' }) }] };\n const endpoint = new URL(raw);\n if (!(endpoint.protocol === 'https:' || (endpoint.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(endpoint.hostname))) || endpoint.username || endpoint.password || endpoint.search || endpoint.hash) throw new Error('Use an explicit HTTPS or loopback HTTP endpoint without inline credentials');\n const requestId = tool.mode === 'write' ? args.requestId : randomUUID();\n if (tool.mode === 'write' && !requestId) throw new Error('Mutation requires a stable requestId');\n let response;\n try {\n response = await fetch(endpoint, {\n method: 'POST', redirect: 'error', signal: AbortSignal.timeout(30000),\n headers: { 'content-type': 'application/json', ...(process.env.SCIENCEEVOLVE_TASK_BACKEND_TOKEN ? { authorization: `Bearer ${process.env.SCIENCEEVOLVE_TASK_BACKEND_TOKEN}` } : {}) },\n body: JSON.stringify({ schemaVersion: 1, method: spec.method, scopeId, actor: spec.actor, operation: tool.name, requestId, arguments: args })\n });\n } catch {\n // Never retry a possibly accepted training/physical job automatically.\n return { isError: true, content: [{ type: 'text', text: JSON.stringify({ status: tool.mode === 'write' ? 'outcome_unknown' : 'unavailable', requestId, operation: tool.name, reason: 'Transport failed; reconcile this exact requestId using get_request. Do not create a replacement job.' }) }] };\n }\n if (!response.ok) return { isError: true, content: [{ type: 'text', text: JSON.stringify({ status: tool.mode === 'write' ? 'outcome_unknown' : 'unavailable', requestId, operation: tool.name, httpStatus: response.status, reason: 'Reconcile before replaying any mutation.' }) }] };\n const body = await response.text();\n if (Buffer.byteLength(body) > 2 * 1024 * 1024) throw new Error('Backend response too large; use paged evidence');\n const result = JSON.parse(body);\n if (result.schemaVersion !== 1 || result.method !== spec.method || result.scopeId !== scopeId || result.operation !== tool.name || result.requestId !== requestId) throw new Error(`Mismatched backend receipt; mutation outcome remains unknown: ${requestId}`);\n if (!['ok', 'blocked', 'failed', 'unknown'].includes(result.status)) throw new Error(`Invalid receipt status: ${requestId}`);\n if (!Array.isArray(result.evidence)) throw new Error(`Receipt requires an evidence array: ${requestId}`);\n for (const evidence of result.evidence) {\n if (!evidence || typeof evidence.id !== 'string' || typeof evidence.uri !== 'string' || typeof evidence.kind !== 'string' || !/^[a-f0-9]{64}$/.test(evidence.sha256)) throw new Error(`Malformed evidence reference: ${requestId}`);\n }\n if (tool.mode === 'write' && result.status === 'ok' && result.evidence.length === 0) throw new Error(`Successful mutation requires a durable receipt: ${requestId}`);\n if (tool.name === 'read_artifact' && result.status === 'ok') {\n if (typeof result.data?.text !== 'string' || createHash('sha256').update(result.data.text, 'utf8').digest('hex') !== result.data.sha256) throw new Error('Artifact bytes do not match the claimed SHA-256');\n }\n // Evidence references and an HTTP OK are not a scientific pass. Agents audit actual bytes/results.\n return { isError: result.status !== 'ok', content: [{ type: 'text', text: JSON.stringify(result) }] };\n}\n\nconst server = new Server({ name: `${spec.method}-${spec.actor}-atomic-tools`, version: '1.0.0' }, { capabilities: { tools: {} } });\nserver.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: spec.tools.map(({ mode, ...tool }) => tool) }));\nserver.setRequestHandler(CallToolRequestSchema, async request => {\n const tool = spec.tools.find(item => item.name === request.params.name);\n if (!tool) throw new Error('Tool is not assigned to this role');\n try { return await dispatch(tool, request.params.arguments || {}); }\n catch (error) { return { isError: true, content: [{ type: 'text', text: error.message }] }; }\n});\nserver.connect(new StdioServerTransport());\nprocess.stdin.on('end', () => { server.close().finally(() => process.exit()); });\n\n",
"tools.json": "{\n \"method\": \"chemical-coscientist\",\n \"actor\": \"web_search\",\n \"tools\": [\n {\n \"name\": \"get_capabilities\",\n \"description\": \"Read actual method/tool versions, resources, scope budget, provenance and capability limitations. A design declaration is not a resource probe.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [],\n \"properties\": {}\n }\n },\n {\n \"name\": \"get_state\",\n \"description\": \"Read durable current scope revision, budgets, jobs, graph/memory/weight versions and accepted evidence. Do not infer current tasks from an old predecessor handoff.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [],\n \"properties\": {}\n }\n },\n {\n \"name\": \"get_request\",\n \"description\": \"Reconcile the original durable request without submitting another job; preserve unknown until authoritative evidence exists.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"originalRequestId\"\n ],\n \"properties\": {\n \"originalRequestId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n }\n }\n }\n },\n {\n \"name\": \"get_job\",\n \"description\": \"Read actual job status, input/output hashes, error or measurement receipts. Accepted/queued is not completed.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"jobId\"\n ],\n \"properties\": {\n \"jobId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n }\n }\n }\n },\n {\n \"name\": \"read_artifact\",\n \"description\": \"Read a small retained UTF-8 artifact and verify its SHA-256 bytes; paginate large evidence at the backend.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"artifactId\"\n ],\n \"properties\": {\n \"artifactId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n }\n }\n }\n },\n {\n \"name\": \"search_sources\",\n \"description\": \"Search actual scholarly/web/domain sources and return source IDs, URLs, versions and full-text access references.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"query\",\n \"domains\"\n ],\n \"properties\": {\n \"query\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n },\n \"domains\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n },\n \"minItems\": 1,\n \"maxItems\": 100\n }\n }\n }\n },\n {\n \"name\": \"export_evidence\",\n \"description\": \"Create an immutable scope evidence manifest for this actor. Export receipt does not commit SDK branch files or complete a group.\",\n \"mode\": \"write\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"selection\",\n \"requestId\",\n \"expectedRevision\"\n ],\n \"properties\": {\n \"selection\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n },\n \"minItems\": 1,\n \"maxItems\": 100\n },\n \"requestId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 128\n },\n \"expectedRevision\": {\n \"type\": \"integer\",\n \"minimum\": 0\n }\n }\n }\n }\n ]\n}\n"
}
},
"task-documentation": {
"command": "node",
"entrypoint": "task-bridge.cjs",
"args": [],
"envFrom": {
"SCIENCEEVOLVE_TASK_BACKEND_URL": "SCIENCEEVOLVE_COSCIENTIST_BACKEND_URL",
"SCIENCEEVOLVE_TASK_BACKEND_TOKEN": "SCIENCEEVOLVE_COSCIENTIST_BACKEND_TOKEN",
"SCIENCEEVOLVE_TASK_SCOPE_ID": "SCIENCEEVOLVE_COSCIENTIST_SCOPE_ID"
},
"files": {
"task-bridge.cjs": "'use strict';\n// Transport only. Scientific decisions stay in SCIENCEEVOLVE agents; backends expose atomic tools.\nconst { Server } = require('@modelcontextprotocol/sdk/server/index.js');\nconst { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');\nconst { ListToolsRequestSchema, CallToolRequestSchema } = require('@modelcontextprotocol/sdk/types.js');\nconst { randomUUID, createHash } = require('node:crypto');\nconst spec = require('./tools.json');\n\nfunction check(schema, value, at = 'arguments') {\n if (schema.enum && !schema.enum.includes(value)) throw new Error(`${at}: unsupported enum`);\n if (schema.type === 'object') {\n if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`${at}: object required`);\n for (const key of schema.required || []) if (!(key in value)) throw new Error(`${at}.${key}: required`);\n for (const [key, item] of Object.entries(value)) {\n if (schema.properties?.[key]) check(schema.properties[key], item, `${at}.${key}`);\n else if (schema.additionalProperties === false) throw new Error(`${at}.${key}: unknown field`);\n }\n } else if (schema.type === 'array') {\n if (!Array.isArray(value)) throw new Error(`${at}: array required`);\n if (schema.minItems !== undefined && value.length < schema.minItems) throw new Error(`${at}: too few items`);\n if (schema.maxItems !== undefined && value.length > schema.maxItems) throw new Error(`${at}: too many items`);\n for (const item of value) check(schema.items, item, `${at}[]`);\n } else if (schema.type === 'string') {\n if (typeof value !== 'string' || value.length < (schema.minLength ?? 0) || value.length > (schema.maxLength ?? Infinity)) throw new Error(`${at}: invalid string`);\n if (schema.pattern && !new RegExp(schema.pattern).test(value)) throw new Error(`${at}: invalid pattern`);\n } else if (schema.type === 'integer') {\n if (!Number.isSafeInteger(value) || value < (schema.minimum ?? -Infinity) || value > (schema.maximum ?? Infinity)) throw new Error(`${at}: invalid integer`);\n } else if (schema.type === 'boolean') {\n if (typeof value !== 'boolean') throw new Error(`${at}: boolean required`);\n }\n}\n\nasync function dispatch(tool, args) {\n check(tool.inputSchema, args);\n const raw = process.env.SCIENCEEVOLVE_TASK_BACKEND_URL;\n const scopeId = process.env.SCIENCEEVOLVE_TASK_SCOPE_ID;\n if (!raw || !scopeId) return { isError: true, content: [{ type: 'text', text: JSON.stringify({ status: 'blocked', reason: 'Configure the method-specific backend URL and a fresh scope ID; no backend is bundled.' }) }] };\n const endpoint = new URL(raw);\n if (!(endpoint.protocol === 'https:' || (endpoint.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(endpoint.hostname))) || endpoint.username || endpoint.password || endpoint.search || endpoint.hash) throw new Error('Use an explicit HTTPS or loopback HTTP endpoint without inline credentials');\n const requestId = tool.mode === 'write' ? args.requestId : randomUUID();\n if (tool.mode === 'write' && !requestId) throw new Error('Mutation requires a stable requestId');\n let response;\n try {\n response = await fetch(endpoint, {\n method: 'POST', redirect: 'error', signal: AbortSignal.timeout(30000),\n headers: { 'content-type': 'application/json', ...(process.env.SCIENCEEVOLVE_TASK_BACKEND_TOKEN ? { authorization: `Bearer ${process.env.SCIENCEEVOLVE_TASK_BACKEND_TOKEN}` } : {}) },\n body: JSON.stringify({ schemaVersion: 1, method: spec.method, scopeId, actor: spec.actor, operation: tool.name, requestId, arguments: args })\n });\n } catch {\n // Never retry a possibly accepted training/physical job automatically.\n return { isError: true, content: [{ type: 'text', text: JSON.stringify({ status: tool.mode === 'write' ? 'outcome_unknown' : 'unavailable', requestId, operation: tool.name, reason: 'Transport failed; reconcile this exact requestId using get_request. Do not create a replacement job.' }) }] };\n }\n if (!response.ok) return { isError: true, content: [{ type: 'text', text: JSON.stringify({ status: tool.mode === 'write' ? 'outcome_unknown' : 'unavailable', requestId, operation: tool.name, httpStatus: response.status, reason: 'Reconcile before replaying any mutation.' }) }] };\n const body = await response.text();\n if (Buffer.byteLength(body) > 2 * 1024 * 1024) throw new Error('Backend response too large; use paged evidence');\n const result = JSON.parse(body);\n if (result.schemaVersion !== 1 || result.method !== spec.method || result.scopeId !== scopeId || result.operation !== tool.name || result.requestId !== requestId) throw new Error(`Mismatched backend receipt; mutation outcome remains unknown: ${requestId}`);\n if (!['ok', 'blocked', 'failed', 'unknown'].includes(result.status)) throw new Error(`Invalid receipt status: ${requestId}`);\n if (!Array.isArray(result.evidence)) throw new Error(`Receipt requires an evidence array: ${requestId}`);\n for (const evidence of result.evidence) {\n if (!evidence || typeof evidence.id !== 'string' || typeof evidence.uri !== 'string' || typeof evidence.kind !== 'string' || !/^[a-f0-9]{64}$/.test(evidence.sha256)) throw new Error(`Malformed evidence reference: ${requestId}`);\n }\n if (tool.mode === 'write' && result.status === 'ok' && result.evidence.length === 0) throw new Error(`Successful mutation requires a durable receipt: ${requestId}`);\n if (tool.name === 'read_artifact' && result.status === 'ok') {\n if (typeof result.data?.text !== 'string' || createHash('sha256').update(result.data.text, 'utf8').digest('hex') !== result.data.sha256) throw new Error('Artifact bytes do not match the claimed SHA-256');\n }\n // Evidence references and an HTTP OK are not a scientific pass. Agents audit actual bytes/results.\n return { isError: result.status !== 'ok', content: [{ type: 'text', text: JSON.stringify(result) }] };\n}\n\nconst server = new Server({ name: `${spec.method}-${spec.actor}-atomic-tools`, version: '1.0.0' }, { capabilities: { tools: {} } });\nserver.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: spec.tools.map(({ mode, ...tool }) => tool) }));\nserver.setRequestHandler(CallToolRequestSchema, async request => {\n const tool = spec.tools.find(item => item.name === request.params.name);\n if (!tool) throw new Error('Tool is not assigned to this role');\n try { return await dispatch(tool, request.params.arguments || {}); }\n catch (error) { return { isError: true, content: [{ type: 'text', text: error.message }] }; }\n});\nserver.connect(new StdioServerTransport());\nprocess.stdin.on('end', () => { server.close().finally(() => process.exit()); });\n\n",
"tools.json": "{\n \"method\": \"chemical-coscientist\",\n \"actor\": \"documentation\",\n \"tools\": [\n {\n \"name\": \"get_capabilities\",\n \"description\": \"Read actual method/tool versions, resources, scope budget, provenance and capability limitations. A design declaration is not a resource probe.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [],\n \"properties\": {}\n }\n },\n {\n \"name\": \"get_state\",\n \"description\": \"Read durable current scope revision, budgets, jobs, graph/memory/weight versions and accepted evidence. Do not infer current tasks from an old predecessor handoff.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [],\n \"properties\": {}\n }\n },\n {\n \"name\": \"get_request\",\n \"description\": \"Reconcile the original durable request without submitting another job; preserve unknown until authoritative evidence exists.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"originalRequestId\"\n ],\n \"properties\": {\n \"originalRequestId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n }\n }\n }\n },\n {\n \"name\": \"get_job\",\n \"description\": \"Read actual job status, input/output hashes, error or measurement receipts. Accepted/queued is not completed.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"jobId\"\n ],\n \"properties\": {\n \"jobId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n }\n }\n }\n },\n {\n \"name\": \"read_artifact\",\n \"description\": \"Read a small retained UTF-8 artifact and verify its SHA-256 bytes; paginate large evidence at the backend.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"artifactId\"\n ],\n \"properties\": {\n \"artifactId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n }\n }\n }\n },\n {\n \"name\": \"search_device_docs\",\n \"description\": \"Retrieve actual device/API manuals matching installed hardware/firmware versions, with source spans and content hashes.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"deviceId\",\n \"query\"\n ],\n \"properties\": {\n \"deviceId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n },\n \"query\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n }\n }\n }\n },\n {\n \"name\": \"export_evidence\",\n \"description\": \"Create an immutable scope evidence manifest for this actor. Export receipt does not commit SDK branch files or complete a group.\",\n \"mode\": \"write\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"selection\",\n \"requestId\",\n \"expectedRevision\"\n ],\n \"properties\": {\n \"selection\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n },\n \"minItems\": 1,\n \"maxItems\": 100\n },\n \"requestId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 128\n },\n \"expectedRevision\": {\n \"type\": \"integer\",\n \"minimum\": 0\n }\n }\n }\n }\n ]\n}\n"
}
},
"task-code": {
"command": "node",
"entrypoint": "task-bridge.cjs",
"args": [],
"envFrom": {
"SCIENCEEVOLVE_TASK_BACKEND_URL": "SCIENCEEVOLVE_COSCIENTIST_BACKEND_URL",
"SCIENCEEVOLVE_TASK_BACKEND_TOKEN": "SCIENCEEVOLVE_COSCIENTIST_BACKEND_TOKEN",
"SCIENCEEVOLVE_TASK_SCOPE_ID": "SCIENCEEVOLVE_COSCIENTIST_SCOPE_ID"
},
"files": {
"task-bridge.cjs": "'use strict';\n// Transport only. Scientific decisions stay in SCIENCEEVOLVE agents; backends expose atomic tools.\nconst { Server } = require('@modelcontextprotocol/sdk/server/index.js');\nconst { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');\nconst { ListToolsRequestSchema, CallToolRequestSchema } = require('@modelcontextprotocol/sdk/types.js');\nconst { randomUUID, createHash } = require('node:crypto');\nconst spec = require('./tools.json');\n\nfunction check(schema, value, at = 'arguments') {\n if (schema.enum && !schema.enum.includes(value)) throw new Error(`${at}: unsupported enum`);\n if (schema.type === 'object') {\n if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`${at}: object required`);\n for (const key of schema.required || []) if (!(key in value)) throw new Error(`${at}.${key}: required`);\n for (const [key, item] of Object.entries(value)) {\n if (schema.properties?.[key]) check(schema.properties[key], item, `${at}.${key}`);\n else if (schema.additionalProperties === false) throw new Error(`${at}.${key}: unknown field`);\n }\n } else if (schema.type === 'array') {\n if (!Array.isArray(value)) throw new Error(`${at}: array required`);\n if (schema.minItems !== undefined && value.length < schema.minItems) throw new Error(`${at}: too few items`);\n if (schema.maxItems !== undefined && value.length > schema.maxItems) throw new Error(`${at}: too many items`);\n for (const item of value) check(schema.items, item, `${at}[]`);\n } else if (schema.type === 'string') {\n if (typeof value !== 'string' || value.length < (schema.minLength ?? 0) || value.length > (schema.maxLength ?? Infinity)) throw new Error(`${at}: invalid string`);\n if (schema.pattern && !new RegExp(schema.pattern).test(value)) throw new Error(`${at}: invalid pattern`);\n } else if (schema.type === 'integer') {\n if (!Number.isSafeInteger(value) || value < (schema.minimum ?? -Infinity) || value > (schema.maximum ?? Infinity)) throw new Error(`${at}: invalid integer`);\n } else if (schema.type === 'boolean') {\n if (typeof value !== 'boolean') throw new Error(`${at}: boolean required`);\n }\n}\n\nasync function dispatch(tool, args) {\n check(tool.inputSchema, args);\n const raw = process.env.SCIENCEEVOLVE_TASK_BACKEND_URL;\n const scopeId = process.env.SCIENCEEVOLVE_TASK_SCOPE_ID;\n if (!raw || !scopeId) return { isError: true, content: [{ type: 'text', text: JSON.stringify({ status: 'blocked', reason: 'Configure the method-specific backend URL and a fresh scope ID; no backend is bundled.' }) }] };\n const endpoint = new URL(raw);\n if (!(endpoint.protocol === 'https:' || (endpoint.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(endpoint.hostname))) || endpoint.username || endpoint.password || endpoint.search || endpoint.hash) throw new Error('Use an explicit HTTPS or loopback HTTP endpoint without inline credentials');\n const requestId = tool.mode === 'write' ? args.requestId : randomUUID();\n if (tool.mode === 'write' && !requestId) throw new Error('Mutation requires a stable requestId');\n let response;\n try {\n response = await fetch(endpoint, {\n method: 'POST', redirect: 'error', signal: AbortSignal.timeout(30000),\n headers: { 'content-type': 'application/json', ...(process.env.SCIENCEEVOLVE_TASK_BACKEND_TOKEN ? { authorization: `Bearer ${process.env.SCIENCEEVOLVE_TASK_BACKEND_TOKEN}` } : {}) },\n body: JSON.stringify({ schemaVersion: 1, method: spec.method, scopeId, actor: spec.actor, operation: tool.name, requestId, arguments: args })\n });\n } catch {\n // Never retry a possibly accepted training/physical job automatically.\n return { isError: true, content: [{ type: 'text', text: JSON.stringify({ status: tool.mode === 'write' ? 'outcome_unknown' : 'unavailable', requestId, operation: tool.name, reason: 'Transport failed; reconcile this exact requestId using get_request. Do not create a replacement job.' }) }] };\n }\n if (!response.ok) return { isError: true, content: [{ type: 'text', text: JSON.stringify({ status: tool.mode === 'write' ? 'outcome_unknown' : 'unavailable', requestId, operation: tool.name, httpStatus: response.status, reason: 'Reconcile before replaying any mutation.' }) }] };\n const body = await response.text();\n if (Buffer.byteLength(body) > 2 * 1024 * 1024) throw new Error('Backend response too large; use paged evidence');\n const result = JSON.parse(body);\n if (result.schemaVersion !== 1 || result.method !== spec.method || result.scopeId !== scopeId || result.operation !== tool.name || result.requestId !== requestId) throw new Error(`Mismatched backend receipt; mutation outcome remains unknown: ${requestId}`);\n if (!['ok', 'blocked', 'failed', 'unknown'].includes(result.status)) throw new Error(`Invalid receipt status: ${requestId}`);\n if (!Array.isArray(result.evidence)) throw new Error(`Receipt requires an evidence array: ${requestId}`);\n for (const evidence of result.evidence) {\n if (!evidence || typeof evidence.id !== 'string' || typeof evidence.uri !== 'string' || typeof evidence.kind !== 'string' || !/^[a-f0-9]{64}$/.test(evidence.sha256)) throw new Error(`Malformed evidence reference: ${requestId}`);\n }\n if (tool.mode === 'write' && result.status === 'ok' && result.evidence.length === 0) throw new Error(`Successful mutation requires a durable receipt: ${requestId}`);\n if (tool.name === 'read_artifact' && result.status === 'ok') {\n if (typeof result.data?.text !== 'string' || createHash('sha256').update(result.data.text, 'utf8').digest('hex') !== result.data.sha256) throw new Error('Artifact bytes do not match the claimed SHA-256');\n }\n // Evidence references and an HTTP OK are not a scientific pass. Agents audit actual bytes/results.\n return { isError: result.status !== 'ok', content: [{ type: 'text', text: JSON.stringify(result) }] };\n}\n\nconst server = new Server({ name: `${spec.method}-${spec.actor}-atomic-tools`, version: '1.0.0' }, { capabilities: { tools: {} } });\nserver.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: spec.tools.map(({ mode, ...tool }) => tool) }));\nserver.setRequestHandler(CallToolRequestSchema, async request => {\n const tool = spec.tools.find(item => item.name === request.params.name);\n if (!tool) throw new Error('Tool is not assigned to this role');\n try { return await dispatch(tool, request.params.arguments || {}); }\n catch (error) { return { isError: true, content: [{ type: 'text', text: error.message }] }; }\n});\nserver.connect(new StdioServerTransport());\nprocess.stdin.on('end', () => { server.close().finally(() => process.exit()); });\n\n",
"tools.json": "{\n \"method\": \"chemical-coscientist\",\n \"actor\": \"code\",\n \"tools\": [\n {\n \"name\": \"get_capabilities\",\n \"description\": \"Read actual method/tool versions, resources, scope budget, provenance and capability limitations. A design declaration is not a resource probe.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [],\n \"properties\": {}\n }\n },\n {\n \"name\": \"get_state\",\n \"description\": \"Read durable current scope revision, budgets, jobs, graph/memory/weight versions and accepted evidence. Do not infer current tasks from an old predecessor handoff.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [],\n \"properties\": {}\n }\n },\n {\n \"name\": \"get_request\",\n \"description\": \"Reconcile the original durable request without submitting another job; preserve unknown until authoritative evidence exists.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"originalRequestId\"\n ],\n \"properties\": {\n \"originalRequestId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n }\n }\n }\n },\n {\n \"name\": \"get_job\",\n \"description\": \"Read actual job status, input/output hashes, error or measurement receipts. Accepted/queued is not completed.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"jobId\"\n ],\n \"properties\": {\n \"jobId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n }\n }\n }\n },\n {\n \"name\": \"read_artifact\",\n \"description\": \"Read a small retained UTF-8 artifact and verify its SHA-256 bytes; paginate large evidence at the backend.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"artifactId\"\n ],\n \"properties\": {\n \"artifactId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n }\n }\n }\n },\n {\n \"name\": \"run_code\",\n \"description\": \"Run a bounded atomic computation against authorized data in the declared sandbox; return actual exit/errors/output hashes. Never execute wet operations as local code.\",\n \"mode\": \"write\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"code\",\n \"inputArtifactIds\",\n \"requestId\",\n \"expectedRevision\"\n ],\n \"properties\": {\n \"code\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 50000\n },\n \"inputArtifactIds\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n },\n \"minItems\": 1,\n \"maxItems\": 100\n },\n \"requestId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 128\n },\n \"expectedRevision\": {\n \"type\": \"integer\",\n \"minimum\": 0\n }\n }\n }\n },\n {\n \"name\": \"compile_protocol\",\n \"description\": \"Compile/check actual code against the exact device/API capability and protocol/plan hashes; retain dry-run and diagnostics without claiming a physical run.\",\n \"mode\": \"write\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"code\",\n \"deviceId\",\n \"planId\",\n \"manualArtifactIds\",\n \"requestId\",\n \"expectedRevision\"\n ],\n \"properties\": {\n \"code\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 50000\n },\n \"deviceId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n },\n \"planId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n },\n \"manualArtifactIds\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n },\n \"minItems\": 1,\n \"maxItems\": 100\n },\n \"requestId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 128\n },\n \"expectedRevision\": {\n \"type\": \"integer\",\n \"minimum\": 0\n }\n }\n }\n },\n {\n \"name\": \"export_evidence\",\n \"description\": \"Create an immutable scope evidence manifest for this actor. Export receipt does not commit SDK branch files or complete a group.\",\n \"mode\": \"write\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"selection\",\n \"requestId\",\n \"expectedRevision\"\n ],\n \"properties\": {\n \"selection\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n },\n \"minItems\": 1,\n \"maxItems\": 100\n },\n \"requestId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 128\n },\n \"expectedRevision\": {\n \"type\": \"integer\",\n \"minimum\": 0\n }\n }\n }\n }\n ]\n}\n"
}
},
"task-experiment": {
"command": "node",
"entrypoint": "task-bridge.cjs",
"args": [],
"envFrom": {
"SCIENCEEVOLVE_TASK_BACKEND_URL": "SCIENCEEVOLVE_COSCIENTIST_BACKEND_URL",
"SCIENCEEVOLVE_TASK_BACKEND_TOKEN": "SCIENCEEVOLVE_COSCIENTIST_BACKEND_TOKEN",
"SCIENCEEVOLVE_TASK_SCOPE_ID": "SCIENCEEVOLVE_COSCIENTIST_SCOPE_ID"
},
"files": {
"task-bridge.cjs": "'use strict';\n// Transport only. Scientific decisions stay in SCIENCEEVOLVE agents; backends expose atomic tools.\nconst { Server } = require('@modelcontextprotocol/sdk/server/index.js');\nconst { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');\nconst { ListToolsRequestSchema, CallToolRequestSchema } = require('@modelcontextprotocol/sdk/types.js');\nconst { randomUUID, createHash } = require('node:crypto');\nconst spec = require('./tools.json');\n\nfunction check(schema, value, at = 'arguments') {\n if (schema.enum && !schema.enum.includes(value)) throw new Error(`${at}: unsupported enum`);\n if (schema.type === 'object') {\n if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`${at}: object required`);\n for (const key of schema.required || []) if (!(key in value)) throw new Error(`${at}.${key}: required`);\n for (const [key, item] of Object.entries(value)) {\n if (schema.properties?.[key]) check(schema.properties[key], item, `${at}.${key}`);\n else if (schema.additionalProperties === false) throw new Error(`${at}.${key}: unknown field`);\n }\n } else if (schema.type === 'array') {\n if (!Array.isArray(value)) throw new Error(`${at}: array required`);\n if (schema.minItems !== undefined && value.length < schema.minItems) throw new Error(`${at}: too few items`);\n if (schema.maxItems !== undefined && value.length > schema.maxItems) throw new Error(`${at}: too many items`);\n for (const item of value) check(schema.items, item, `${at}[]`);\n } else if (schema.type === 'string') {\n if (typeof value !== 'string' || value.length < (schema.minLength ?? 0) || value.length > (schema.maxLength ?? Infinity)) throw new Error(`${at}: invalid string`);\n if (schema.pattern && !new RegExp(schema.pattern).test(value)) throw new Error(`${at}: invalid pattern`);\n } else if (schema.type === 'integer') {\n if (!Number.isSafeInteger(value) || value < (schema.minimum ?? -Infinity) || value > (schema.maximum ?? Infinity)) throw new Error(`${at}: invalid integer`);\n } else if (schema.type === 'boolean') {\n if (typeof value !== 'boolean') throw new Error(`${at}: boolean required`);\n }\n}\n\nasync function dispatch(tool, args) {\n check(tool.inputSchema, args);\n const raw = process.env.SCIENCEEVOLVE_TASK_BACKEND_URL;\n const scopeId = process.env.SCIENCEEVOLVE_TASK_SCOPE_ID;\n if (!raw || !scopeId) return { isError: true, content: [{ type: 'text', text: JSON.stringify({ status: 'blocked', reason: 'Configure the method-specific backend URL and a fresh scope ID; no backend is bundled.' }) }] };\n const endpoint = new URL(raw);\n if (!(endpoint.protocol === 'https:' || (endpoint.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(endpoint.hostname))) || endpoint.username || endpoint.password || endpoint.search || endpoint.hash) throw new Error('Use an explicit HTTPS or loopback HTTP endpoint without inline credentials');\n const requestId = tool.mode === 'write' ? args.requestId : randomUUID();\n if (tool.mode === 'write' && !requestId) throw new Error('Mutation requires a stable requestId');\n let response;\n try {\n response = await fetch(endpoint, {\n method: 'POST', redirect: 'error', signal: AbortSignal.timeout(30000),\n headers: { 'content-type': 'application/json', ...(process.env.SCIENCEEVOLVE_TASK_BACKEND_TOKEN ? { authorization: `Bearer ${process.env.SCIENCEEVOLVE_TASK_BACKEND_TOKEN}` } : {}) },\n body: JSON.stringify({ schemaVersion: 1, method: spec.method, scopeId, actor: spec.actor, operation: tool.name, requestId, arguments: args })\n });\n } catch {\n // Never retry a possibly accepted training/physical job automatically.\n return { isError: true, content: [{ type: 'text', text: JSON.stringify({ status: tool.mode === 'write' ? 'outcome_unknown' : 'unavailable', requestId, operation: tool.name, reason: 'Transport failed; reconcile this exact requestId using get_request. Do not create a replacement job.' }) }] };\n }\n if (!response.ok) return { isError: true, content: [{ type: 'text', text: JSON.stringify({ status: tool.mode === 'write' ? 'outcome_unknown' : 'unavailable', requestId, operation: tool.name, httpStatus: response.status, reason: 'Reconcile before replaying any mutation.' }) }] };\n const body = await response.text();\n if (Buffer.byteLength(body) > 2 * 1024 * 1024) throw new Error('Backend response too large; use paged evidence');\n const result = JSON.parse(body);\n if (result.schemaVersion !== 1 || result.method !== spec.method || result.scopeId !== scopeId || result.operation !== tool.name || result.requestId !== requestId) throw new Error(`Mismatched backend receipt; mutation outcome remains unknown: ${requestId}`);\n if (!['ok', 'blocked', 'failed', 'unknown'].includes(result.status)) throw new Error(`Invalid receipt status: ${requestId}`);\n if (!Array.isArray(result.evidence)) throw new Error(`Receipt requires an evidence array: ${requestId}`);\n for (const evidence of result.evidence) {\n if (!evidence || typeof evidence.id !== 'string' || typeof evidence.uri !== 'string' || typeof evidence.kind !== 'string' || !/^[a-f0-9]{64}$/.test(evidence.sha256)) throw new Error(`Malformed evidence reference: ${requestId}`);\n }\n if (tool.mode === 'write' && result.status === 'ok' && result.evidence.length === 0) throw new Error(`Successful mutation requires a durable receipt: ${requestId}`);\n if (tool.name === 'read_artifact' && result.status === 'ok') {\n if (typeof result.data?.text !== 'string' || createHash('sha256').update(result.data.text, 'utf8').digest('hex') !== result.data.sha256) throw new Error('Artifact bytes do not match the claimed SHA-256');\n }\n // Evidence references and an HTTP OK are not a scientific pass. Agents audit actual bytes/results.\n return { isError: result.status !== 'ok', content: [{ type: 'text', text: JSON.stringify(result) }] };\n}\n\nconst server = new Server({ name: `${spec.method}-${spec.actor}-atomic-tools`, version: '1.0.0' }, { capabilities: { tools: {} } });\nserver.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: spec.tools.map(({ mode, ...tool }) => tool) }));\nserver.setRequestHandler(CallToolRequestSchema, async request => {\n const tool = spec.tools.find(item => item.name === request.params.name);\n if (!tool) throw new Error('Tool is not assigned to this role');\n try { return await dispatch(tool, request.params.arguments || {}); }\n catch (error) { return { isError: true, content: [{ type: 'text', text: error.message }] }; }\n});\nserver.connect(new StdioServerTransport());\nprocess.stdin.on('end', () => { server.close().finally(() => process.exit()); });\n\n",
"tools.json": "{\n \"method\": \"chemical-coscientist\",\n \"actor\": \"experiment\",\n \"tools\": [\n {\n \"name\": \"get_capabilities\",\n \"description\": \"Read actual method/tool versions, resources, scope budget, provenance and capability limitations. A design declaration is not a resource probe.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [],\n \"properties\": {}\n }\n },\n {\n \"name\": \"get_state\",\n \"description\": \"Read durable current scope revision, budgets, jobs, graph/memory/weight versions and accepted evidence. Do not infer current tasks from an old predecessor handoff.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [],\n \"properties\": {}\n }\n },\n {\n \"name\": \"get_request\",\n \"description\": \"Reconcile the original durable request without submitting another job; preserve unknown until authoritative evidence exists.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"originalRequestId\"\n ],\n \"properties\": {\n \"originalRequestId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n }\n }\n }\n },\n {\n \"name\": \"get_job\",\n \"description\": \"Read actual job status, input/output hashes, error or measurement receipts. Accepted/queued is not completed.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"jobId\"\n ],\n \"properties\": {\n \"jobId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n }\n }\n }\n },\n {\n \"name\": \"read_artifact\",\n \"description\": \"Read a small retained UTF-8 artifact and verify its SHA-256 bytes; paginate large evidence at the backend.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"artifactId\"\n ],\n \"properties\": {\n \"artifactId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n }\n }\n }\n },\n {\n \"name\": \"submit_experiment\",\n \"description\": \"Enqueue a physically authorized experiment with exact compiled source/device/plan and idempotent receipt. Service validates authorization; Git cannot undo physical work.\",\n \"mode\": \"write\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"compiledArtifactId\",\n \"deviceId\",\n \"planId\",\n \"authorizationId\",\n \"requestId\",\n \"expectedRevision\"\n ],\n \"properties\": {\n \"compiledArtifactId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n },\n \"deviceId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n },\n \"planId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n },\n \"authorizationId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n },\n \"requestId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 128\n },\n \"expectedRevision\": {\n \"type\": \"integer\",\n \"minimum\": 0\n }\n }\n }\n },\n {\n \"name\": \"record_observation\",\n \"description\": \"Append authentic raw measurement/error/sample provenance linked to the exact physical job; never substitute simulation or historical data.\",\n \"mode\": \"write\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"jobReceiptId\",\n \"interpretation\",\n \"requestId\",\n \"expectedRevision\"\n ],\n \"properties\": {\n \"jobReceiptId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n },\n \"interpretation\": {\n \"type\": \"object\",\n \"additionalProperties\": true\n },\n \"requestId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 128\n },\n \"expectedRevision\": {\n \"type\": \"integer\",\n \"minimum\": 0\n }\n }\n }\n },\n {\n \"name\": \"export_evidence\",\n \"description\": \"Create an immutable scope evidence manifest for this actor. Export receipt does not commit SDK branch files or complete a group.\",\n \"mode\": \"write\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"selection\",\n \"requestId\",\n \"expectedRevision\"\n ],\n \"properties\": {\n \"selection\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n },\n \"minItems\": 1,\n \"maxItems\": 100\n },\n \"requestId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 128\n },\n \"expectedRevision\": {\n \"type\": \"integer\",\n \"minimum\": 0\n }\n }\n }\n }\n ]\n}\n"
}
},
"task-optimization": {
"command": "node",
"entrypoint": "task-bridge.cjs",
"args": [],
"envFrom": {
"SCIENCEEVOLVE_TASK_BACKEND_URL": "SCIENCEEVOLVE_COSCIENTIST_BACKEND_URL",
"SCIENCEEVOLVE_TASK_BACKEND_TOKEN": "SCIENCEEVOLVE_COSCIENTIST_BACKEND_TOKEN",
"SCIENCEEVOLVE_TASK_SCOPE_ID": "SCIENCEEVOLVE_COSCIENTIST_SCOPE_ID"
},
"files": {
"task-bridge.cjs": "'use strict';\n// Transport only. Scientific decisions stay in SCIENCEEVOLVE agents; backends expose atomic tools.\nconst { Server } = require('@modelcontextprotocol/sdk/server/index.js');\nconst { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');\nconst { ListToolsRequestSchema, CallToolRequestSchema } = require('@modelcontextprotocol/sdk/types.js');\nconst { randomUUID, createHash } = require('node:crypto');\nconst spec = require('./tools.json');\n\nfunction check(schema, value, at = 'arguments') {\n if (schema.enum && !schema.enum.includes(value)) throw new Error(`${at}: unsupported enum`);\n if (schema.type === 'object') {\n if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`${at}: object required`);\n for (const key of schema.required || []) if (!(key in value)) throw new Error(`${at}.${key}: required`);\n for (const [key, item] of Object.entries(value)) {\n if (schema.properties?.[key]) check(schema.properties[key], item, `${at}.${key}`);\n else if (schema.additionalProperties === false) throw new Error(`${at}.${key}: unknown field`);\n }\n } else if (schema.type === 'array') {\n if (!Array.isArray(value)) throw new Error(`${at}: array required`);\n if (schema.minItems !== undefined && value.length < schema.minItems) throw new Error(`${at}: too few items`);\n if (schema.maxItems !== undefined && value.length > schema.maxItems) throw new Error(`${at}: too many items`);\n for (const item of value) check(schema.items, item, `${at}[]`);\n } else if (schema.type === 'string') {\n if (typeof value !== 'string' || value.length < (schema.minLength ?? 0) || value.length > (schema.maxLength ?? Infinity)) throw new Error(`${at}: invalid string`);\n if (schema.pattern && !new RegExp(schema.pattern).test(value)) throw new Error(`${at}: invalid pattern`);\n } else if (schema.type === 'integer') {\n if (!Number.isSafeInteger(value) || value < (schema.minimum ?? -Infinity) || value > (schema.maximum ?? Infinity)) throw new Error(`${at}: invalid integer`);\n } else if (schema.type === 'boolean') {\n if (typeof value !== 'boolean') throw new Error(`${at}: boolean required`);\n }\n}\n\nasync function dispatch(tool, args) {\n check(tool.inputSchema, args);\n const raw = process.env.SCIENCEEVOLVE_TASK_BACKEND_URL;\n const scopeId = process.env.SCIENCEEVOLVE_TASK_SCOPE_ID;\n if (!raw || !scopeId) return { isError: true, content: [{ type: 'text', text: JSON.stringify({ status: 'blocked', reason: 'Configure the method-specific backend URL and a fresh scope ID; no backend is bundled.' }) }] };\n const endpoint = new URL(raw);\n if (!(endpoint.protocol === 'https:' || (endpoint.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(endpoint.hostname))) || endpoint.username || endpoint.password || endpoint.search || endpoint.hash) throw new Error('Use an explicit HTTPS or loopback HTTP endpoint without inline credentials');\n const requestId = tool.mode === 'write' ? args.requestId : randomUUID();\n if (tool.mode === 'write' && !requestId) throw new Error('Mutation requires a stable requestId');\n let response;\n try {\n response = await fetch(endpoint, {\n method: 'POST', redirect: 'error', signal: AbortSignal.timeout(30000),\n headers: { 'content-type': 'application/json', ...(process.env.SCIENCEEVOLVE_TASK_BACKEND_TOKEN ? { authorization: `Bearer ${process.env.SCIENCEEVOLVE_TASK_BACKEND_TOKEN}` } : {}) },\n body: JSON.stringify({ schemaVersion: 1, method: spec.method, scopeId, actor: spec.actor, operation: tool.name, requestId, arguments: args })\n });\n } catch {\n // Never retry a possibly accepted training/physical job automatically.\n return { isError: true, content: [{ type: 'text', text: JSON.stringify({ status: tool.mode === 'write' ? 'outcome_unknown' : 'unavailable', requestId, operation: tool.name, reason: 'Transport failed; reconcile this exact requestId using get_request. Do not create a replacement job.' }) }] };\n }\n if (!response.ok) return { isError: true, content: [{ type: 'text', text: JSON.stringify({ status: tool.mode === 'write' ? 'outcome_unknown' : 'unavailable', requestId, operation: tool.name, httpStatus: response.status, reason: 'Reconcile before replaying any mutation.' }) }] };\n const body = await response.text();\n if (Buffer.byteLength(body) > 2 * 1024 * 1024) throw new Error('Backend response too large; use paged evidence');\n const result = JSON.parse(body);\n if (result.schemaVersion !== 1 || result.method !== spec.method || result.scopeId !== scopeId || result.operation !== tool.name || result.requestId !== requestId) throw new Error(`Mismatched backend receipt; mutation outcome remains unknown: ${requestId}`);\n if (!['ok', 'blocked', 'failed', 'unknown'].includes(result.status)) throw new Error(`Invalid receipt status: ${requestId}`);\n if (!Array.isArray(result.evidence)) throw new Error(`Receipt requires an evidence array: ${requestId}`);\n for (const evidence of result.evidence) {\n if (!evidence || typeof evidence.id !== 'string' || typeof evidence.uri !== 'string' || typeof evidence.kind !== 'string' || !/^[a-f0-9]{64}$/.test(evidence.sha256)) throw new Error(`Malformed evidence reference: ${requestId}`);\n }\n if (tool.mode === 'write' && result.status === 'ok' && result.evidence.length === 0) throw new Error(`Successful mutation requires a durable receipt: ${requestId}`);\n if (tool.name === 'read_artifact' && result.status === 'ok') {\n if (typeof result.data?.text !== 'string' || createHash('sha256').update(result.data.text, 'utf8').digest('hex') !== result.data.sha256) throw new Error('Artifact bytes do not match the claimed SHA-256');\n }\n // Evidence references and an HTTP OK are not a scientific pass. Agents audit actual bytes/results.\n return { isError: result.status !== 'ok', content: [{ type: 'text', text: JSON.stringify(result) }] };\n}\n\nconst server = new Server({ name: `${spec.method}-${spec.actor}-atomic-tools`, version: '1.0.0' }, { capabilities: { tools: {} } });\nserver.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: spec.tools.map(({ mode, ...tool }) => tool) }));\nserver.setRequestHandler(CallToolRequestSchema, async request => {\n const tool = spec.tools.find(item => item.name === request.params.name);\n if (!tool) throw new Error('Tool is not assigned to this role');\n try { return await dispatch(tool, request.params.arguments || {}); }\n catch (error) { return { isError: true, content: [{ type: 'text', text: error.message }] }; }\n});\nserver.connect(new StdioServerTransport());\nprocess.stdin.on('end', () => { server.close().finally(() => process.exit()); });\n\n",
"tools.json": "{\n \"method\": \"chemical-coscientist\",\n \"actor\": \"optimization\",\n \"tools\": [\n {\n \"name\": \"get_capabilities\",\n \"description\": \"Read actual method/tool versions, resources, scope budget, provenance and capability limitations. A design declaration is not a resource probe.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [],\n \"properties\": {}\n }\n },\n {\n \"name\": \"get_state\",\n \"description\": \"Read durable current scope revision, budgets, jobs, graph/memory/weight versions and accepted evidence. Do not infer current tasks from an old predecessor handoff.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [],\n \"properties\": {}\n }\n },\n {\n \"name\": \"get_request\",\n \"description\": \"Reconcile the original durable request without submitting another job; preserve unknown until authoritative evidence exists.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"originalRequestId\"\n ],\n \"properties\": {\n \"originalRequestId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n }\n }\n }\n },\n {\n \"name\": \"get_job\",\n \"description\": \"Read actual job status, input/output hashes, error or measurement receipts. Accepted/queued is not completed.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"jobId\"\n ],\n \"properties\": {\n \"jobId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n }\n }\n }\n },\n {\n \"name\": \"read_artifact\",\n \"description\": \"Read a small retained UTF-8 artifact and verify its SHA-256 bytes; paginate large evidence at the backend.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"artifactId\"\n ],\n \"properties\": {\n \"artifactId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n }\n }\n }\n },\n {\n \"name\": \"evaluate_objective\",\n \"description\": \"Compute the predeclared chemical objective and uncertainty from authentic observations and controls; retain invalid/failed outcomes.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"observationIds\",\n \"objectiveId\"\n ],\n \"properties\": {\n \"observationIds\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n },\n \"minItems\": 1,\n \"maxItems\": 100\n },\n \"objectiveId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n }\n }\n }\n },\n {\n \"name\": \"export_evidence\",\n \"description\": \"Create an immutable scope evidence manifest for this actor. Export receipt does not commit SDK branch files or complete a group.\",\n \"mode\": \"write\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"selection\",\n \"requestId\",\n \"expectedRevision\"\n ],\n \"properties\": {\n \"selection\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n },\n \"minItems\": 1,\n \"maxItems\": 100\n },\n \"requestId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 128\n },\n \"expectedRevision\": {\n \"type\": \"integer\",\n \"minimum\": 0\n }\n }\n }\n }\n ]\n}\n"
}
},
"task-audit": {
"command": "node",
"entrypoint": "task-bridge.cjs",
"args": [],
"envFrom": {
"SCIENCEEVOLVE_TASK_BACKEND_URL": "SCIENCEEVOLVE_COSCIENTIST_BACKEND_URL",
"SCIENCEEVOLVE_TASK_BACKEND_TOKEN": "SCIENCEEVOLVE_COSCIENTIST_BACKEND_TOKEN",
"SCIENCEEVOLVE_TASK_SCOPE_ID": "SCIENCEEVOLVE_COSCIENTIST_SCOPE_ID"
},
"files": {
"task-bridge.cjs": "'use strict';\n// Transport only. Scientific decisions stay in SCIENCEEVOLVE agents; backends expose atomic tools.\nconst { Server } = require('@modelcontextprotocol/sdk/server/index.js');\nconst { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');\nconst { ListToolsRequestSchema, CallToolRequestSchema } = require('@modelcontextprotocol/sdk/types.js');\nconst { randomUUID, createHash } = require('node:crypto');\nconst spec = require('./tools.json');\n\nfunction check(schema, value, at = 'arguments') {\n if (schema.enum && !schema.enum.includes(value)) throw new Error(`${at}: unsupported enum`);\n if (schema.type === 'object') {\n if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`${at}: object required`);\n for (const key of schema.required || []) if (!(key in value)) throw new Error(`${at}.${key}: required`);\n for (const [key, item] of Object.entries(value)) {\n if (schema.properties?.[key]) check(schema.properties[key], item, `${at}.${key}`);\n else if (schema.additionalProperties === false) throw new Error(`${at}.${key}: unknown field`);\n }\n } else if (schema.type === 'array') {\n if (!Array.isArray(value)) throw new Error(`${at}: array required`);\n if (schema.minItems !== undefined && value.length < schema.minItems) throw new Error(`${at}: too few items`);\n if (schema.maxItems !== undefined && value.length > schema.maxItems) throw new Error(`${at}: too many items`);\n for (const item of value) check(schema.items, item, `${at}[]`);\n } else if (schema.type === 'string') {\n if (typeof value !== 'string' || value.length < (schema.minLength ?? 0) || value.length > (schema.maxLength ?? Infinity)) throw new Error(`${at}: invalid string`);\n if (schema.pattern && !new RegExp(schema.pattern).test(value)) throw new Error(`${at}: invalid pattern`);\n } else if (schema.type === 'integer') {\n if (!Number.isSafeInteger(value) || value < (schema.minimum ?? -Infinity) || value > (schema.maximum ?? Infinity)) throw new Error(`${at}: invalid integer`);\n } else if (schema.type === 'boolean') {\n if (typeof value !== 'boolean') throw new Error(`${at}: boolean required`);\n }\n}\n\nasync function dispatch(tool, args) {\n check(tool.inputSchema, args);\n const raw = process.env.SCIENCEEVOLVE_TASK_BACKEND_URL;\n const scopeId = process.env.SCIENCEEVOLVE_TASK_SCOPE_ID;\n if (!raw || !scopeId) return { isError: true, content: [{ type: 'text', text: JSON.stringify({ status: 'blocked', reason: 'Configure the method-specific backend URL and a fresh scope ID; no backend is bundled.' }) }] };\n const endpoint = new URL(raw);\n if (!(endpoint.protocol === 'https:' || (endpoint.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(endpoint.hostname))) || endpoint.username || endpoint.password || endpoint.search || endpoint.hash) throw new Error('Use an explicit HTTPS or loopback HTTP endpoint without inline credentials');\n const requestId = tool.mode === 'write' ? args.requestId : randomUUID();\n if (tool.mode === 'write' && !requestId) throw new Error('Mutation requires a stable requestId');\n let response;\n try {\n response = await fetch(endpoint, {\n method: 'POST', redirect: 'error', signal: AbortSignal.timeout(30000),\n headers: { 'content-type': 'application/json', ...(process.env.SCIENCEEVOLVE_TASK_BACKEND_TOKEN ? { authorization: `Bearer ${process.env.SCIENCEEVOLVE_TASK_BACKEND_TOKEN}` } : {}) },\n body: JSON.stringify({ schemaVersion: 1, method: spec.method, scopeId, actor: spec.actor, operation: tool.name, requestId, arguments: args })\n });\n } catch {\n // Never retry a possibly accepted training/physical job automatically.\n return { isError: true, content: [{ type: 'text', text: JSON.stringify({ status: tool.mode === 'write' ? 'outcome_unknown' : 'unavailable', requestId, operation: tool.name, reason: 'Transport failed; reconcile this exact requestId using get_request. Do not create a replacement job.' }) }] };\n }\n if (!response.ok) return { isError: true, content: [{ type: 'text', text: JSON.stringify({ status: tool.mode === 'write' ? 'outcome_unknown' : 'unavailable', requestId, operation: tool.name, httpStatus: response.status, reason: 'Reconcile before replaying any mutation.' }) }] };\n const body = await response.text();\n if (Buffer.byteLength(body) > 2 * 1024 * 1024) throw new Error('Backend response too large; use paged evidence');\n const result = JSON.parse(body);\n if (result.schemaVersion !== 1 || result.method !== spec.method || result.scopeId !== scopeId || result.operation !== tool.name || result.requestId !== requestId) throw new Error(`Mismatched backend receipt; mutation outcome remains unknown: ${requestId}`);\n if (!['ok', 'blocked', 'failed', 'unknown'].includes(result.status)) throw new Error(`Invalid receipt status: ${requestId}`);\n if (!Array.isArray(result.evidence)) throw new Error(`Receipt requires an evidence array: ${requestId}`);\n for (const evidence of result.evidence) {\n if (!evidence || typeof evidence.id !== 'string' || typeof evidence.uri !== 'string' || typeof evidence.kind !== 'string' || !/^[a-f0-9]{64}$/.test(evidence.sha256)) throw new Error(`Malformed evidence reference: ${requestId}`);\n }\n if (tool.mode === 'write' && result.status === 'ok' && result.evidence.length === 0) throw new Error(`Successful mutation requires a durable receipt: ${requestId}`);\n if (tool.name === 'read_artifact' && result.status === 'ok') {\n if (typeof result.data?.text !== 'string' || createHash('sha256').update(result.data.text, 'utf8').digest('hex') !== result.data.sha256) throw new Error('Artifact bytes do not match the claimed SHA-256');\n }\n // Evidence references and an HTTP OK are not a scientific pass. Agents audit actual bytes/results.\n return { isError: result.status !== 'ok', content: [{ type: 'text', text: JSON.stringify(result) }] };\n}\n\nconst server = new Server({ name: `${spec.method}-${spec.actor}-atomic-tools`, version: '1.0.0' }, { capabilities: { tools: {} } });\nserver.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: spec.tools.map(({ mode, ...tool }) => tool) }));\nserver.setRequestHandler(CallToolRequestSchema, async request => {\n const tool = spec.tools.find(item => item.name === request.params.name);\n if (!tool) throw new Error('Tool is not assigned to this role');\n try { return await dispatch(tool, request.params.arguments || {}); }\n catch (error) { return { isError: true, content: [{ type: 'text', text: error.message }] }; }\n});\nserver.connect(new StdioServerTransport());\nprocess.stdin.on('end', () => { server.close().finally(() => process.exit()); });\n\n",
"tools.json": "{\n \"method\": \"chemical-coscientist\",\n \"actor\": \"audit\",\n \"tools\": [\n {\n \"name\": \"get_capabilities\",\n \"description\": \"Read actual method/tool versions, resources, scope budget, provenance and capability limitations. A design declaration is not a resource probe.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [],\n \"properties\": {}\n }\n },\n {\n \"name\": \"get_state\",\n \"description\": \"Read durable current scope revision, budgets, jobs, graph/memory/weight versions and accepted evidence. Do not infer current tasks from an old predecessor handoff.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [],\n \"properties\": {}\n }\n },\n {\n \"name\": \"get_request\",\n \"description\": \"Reconcile the original durable request without submitting another job; preserve unknown until authoritative evidence exists.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"originalRequestId\"\n ],\n \"properties\": {\n \"originalRequestId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n }\n }\n }\n },\n {\n \"name\": \"get_job\",\n \"description\": \"Read actual job status, input/output hashes, error or measurement receipts. Accepted/queued is not completed.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"jobId\"\n ],\n \"properties\": {\n \"jobId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n }\n }\n }\n },\n {\n \"name\": \"read_artifact\",\n \"description\": \"Read a small retained UTF-8 artifact and verify its SHA-256 bytes; paginate large evidence at the backend.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"artifactId\"\n ],\n \"properties\": {\n \"artifactId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n }\n }\n }\n },\n {\n \"name\": \"evaluate_objective\",\n \"description\": \"Compute the predeclared chemical objective and uncertainty from authentic observations and controls; retain invalid/failed outcomes.\",\n \"mode\": \"read\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"observationIds\",\n \"objectiveId\"\n ],\n \"properties\": {\n \"observationIds\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n },\n \"minItems\": 1,\n \"maxItems\": 100\n },\n \"objectiveId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n }\n }\n }\n },\n {\n \"name\": \"export_evidence\",\n \"description\": \"Create an immutable scope evidence manifest for this actor. Export receipt does not commit SDK branch files or complete a group.\",\n \"mode\": \"write\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"selection\",\n \"requestId\",\n \"expectedRevision\"\n ],\n \"properties\": {\n \"selection\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 4000\n },\n \"minItems\": 1,\n \"maxItems\": 100\n },\n \"requestId\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"maxLength\": 128\n },\n \"expectedRevision\": {\n \"type\": \"integer\",\n \"minimum\": 0\n }\n }\n }\n }\n ]\n}\n"
}
}
},
"orchestration": {
"schemaVersion": 3,
"agents": [
{
"name": "main",
"template": "main"
},
{
"name": "preflight",
"template": "general"
},
{
"name": "planner",
"template": "general"
},
{
"name": "web_search",
"template": "general"
},
{
"name": "documentation",
"template": "general"
},
{
"name": "code",
"template": "general"
},
{
"name": "experiment",
"template": "general"
},
{
"name": "optimization",
"template": "general"
},
{
"name": "audit",
"template": "general"
}
],
"collaborations": [],
"scopedWorkflow": {
"schemaVersion": 1,
"rootAgentIds": [
"main",
"preflight",
"planner",
"web_search",
"documentation",
"code",
"experiment",
"optimization",
"audit"
],
"groups": []
},
"input": {
"source": "input",
"target": "preflight"
},
"routes": {
"main": [
{
"kind": "weak",
"target": "preflight"
},
{
"kind": "weak",
"target": "audit"
}
],
"preflight": [
{
"kind": "weak",
"target": "planner"
},
{
"kind": "weak",
"target": "main"
}
],
"planner": [
{
"kind": "weak",
"target": "web_search"
},
{
"kind": "weak",
"target": "documentation"
},
{
"kind": "weak",
"target": "code"
},
{
"kind": "weak",
"target": "experiment"
},
{
"kind": "weak",
"target": "audit"
}
],
"web_search": [
{
"kind": "strong",
"target": "planner"
}
],
"documentation": [
{
"kind": "strong",
"target": "planner"
}
],
"code": [
{
"kind": "strong",
"target": "planner"
}
],
"experiment": [
{
"kind": "strong",
"target": "optimization"
}
],
"optimization": [
{
"kind": "strong",
"target": "planner"
}
],
"audit": [
{
"kind": "weak",
"target": "planner"
},
{
"kind": "weak",
"target": "finish"
}
]
},
"decisionPlans": {
"preflight": "完整湿实验目标必须全部真实工具和设备就绪才 planner;缺设备/authorization 则 Main 暂停。",
"planner": "按实际缺口与当前 raw observations 选相应工具;experiment 仅在当前 plan/code/device/docs/授权完全匹配时。目标/预算停止才 audit,不强制工具固定链,不从失败回执推断成功。",
"audit": "methodAccepted 全部成立并正式交付才 finish;预算允许补证据时 planner,服务缺失/unknown 则请求 Main 暂停。"
},
"allowMainReconfiguration": false,
"allowUserIntervention": true,
"termination": {
"agent": "audit",
"condition": "真实 web/docs/code 的按需 Planner 委派、匹配实际设备/版本的可执行协议、授权物理实验与原始测量、结果驱动优化/后续实验、预算与独立审计/正式交付全部成立。simulation 或历史数据模式不能满足完整湿实验方法。",
"decisionBy": "main",
"automatic": false
},
"resultFormat": {
"presets": [
"markdown",
"code"
],
"custom": "交付 report.md、acceptance.json、evidence-index.json、真实源码/结果/失败回执、版本摘要与 SDK/Git/原目录交付证据。完整方法与缩小/受限模式分开;不以配置校验代替方法验收。"
}
}
}