darwinagent 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. darwinagent/__init__.py +49 -0
  2. darwinagent/__main__.py +3 -0
  3. darwinagent/agents/__init__.py +4 -0
  4. darwinagent/agents/answer.py +441 -0
  5. darwinagent/agents/extraction.py +616 -0
  6. darwinagent/agents/protocol.py +202 -0
  7. darwinagent/cli.py +363 -0
  8. darwinagent/config.py +189 -0
  9. darwinagent/contracts.py +514 -0
  10. darwinagent/demo/__init__.py +232 -0
  11. darwinagent/demo/device_maintenance/assets/C/subject.py +5 -0
  12. darwinagent/demo/device_maintenance/assets/F/lookup.py +2 -0
  13. darwinagent/demo/device_maintenance/assets/P/answer.txt +1 -0
  14. darwinagent/demo/device_maintenance/assets/P/extract.txt +1 -0
  15. darwinagent/demo/device_maintenance/assets/P/review.txt +1 -0
  16. darwinagent/demo/device_maintenance/assets/P/tools.txt +1 -0
  17. darwinagent/demo/device_maintenance/assets/S/schema.yaml +6 -0
  18. darwinagent/demo/device_maintenance/assets/index.yaml +15 -0
  19. darwinagent/demo/device_maintenance/task.yaml +10 -0
  20. darwinagent/engine/__init__.py +3 -0
  21. darwinagent/engine/pipeline.py +883 -0
  22. darwinagent/experiments/__init__.py +12 -0
  23. darwinagent/experiments/admission.py +171 -0
  24. darwinagent/experiments/admission_checks.py +181 -0
  25. darwinagent/experiments/admission_composition.py +95 -0
  26. darwinagent/experiments/admission_functions.py +363 -0
  27. darwinagent/experiments/admission_reporting.py +52 -0
  28. darwinagent/experiments/admission_rules.py +31 -0
  29. darwinagent/experiments/admission_samples.py +345 -0
  30. darwinagent/experiments/admission_worker.py +192 -0
  31. darwinagent/experiments/bootstrap.py +541 -0
  32. darwinagent/experiments/campaign.py +542 -0
  33. darwinagent/experiments/constants.py +6 -0
  34. darwinagent/experiments/control.py +349 -0
  35. darwinagent/experiments/feedback.py +526 -0
  36. darwinagent/experiments/graph_trials.py +186 -0
  37. darwinagent/experiments/lifecycle.py +340 -0
  38. darwinagent/experiments/optimization.py +605 -0
  39. darwinagent/experiments/policy.py +65 -0
  40. darwinagent/experiments/proposal.py +142 -0
  41. darwinagent/experiments/proposal_session.py +385 -0
  42. darwinagent/experiments/recovery.py +291 -0
  43. darwinagent/experiments/rounds.py +689 -0
  44. darwinagent/experiments/runner.py +636 -0
  45. darwinagent/experiments/selected_operations.py +485 -0
  46. darwinagent/experiments/snapshots.py +91 -0
  47. darwinagent/experiments/spec.py +152 -0
  48. darwinagent/experiments/stages.py +801 -0
  49. darwinagent/experiments/statistics.py +52 -0
  50. darwinagent/experiments/trials.py +121 -0
  51. darwinagent/experiments/wiki.py +404 -0
  52. darwinagent/experiments/wiki_context.py +168 -0
  53. darwinagent/experiments/wiki_evidence.py +306 -0
  54. darwinagent/experiments/wiki_lessons.py +258 -0
  55. darwinagent/experiments/wiki_service.py +613 -0
  56. darwinagent/kernel/__init__.py +6 -0
  57. darwinagent/kernel/assets.py +221 -0
  58. darwinagent/kernel/checks.py +355 -0
  59. darwinagent/kernel/counterexamples.py +239 -0
  60. darwinagent/kernel/execution.py +65 -0
  61. darwinagent/kernel/functions.py +131 -0
  62. darwinagent/kernel/registration.py +25 -0
  63. darwinagent/kernel/revision.py +175 -0
  64. darwinagent/kernel/spec.py +208 -0
  65. darwinagent/kernel/validation.py +272 -0
  66. darwinagent/kg/__init__.py +33 -0
  67. darwinagent/kg/assembler.py +580 -0
  68. darwinagent/kg/graph.py +308 -0
  69. darwinagent/llm/__init__.py +13 -0
  70. darwinagent/llm/client.py +512 -0
  71. darwinagent/llm/recorded.py +23 -0
  72. darwinagent/llm/registry.py +159 -0
  73. darwinagent/llm/settings.py +46 -0
  74. darwinagent/operators/__init__.py +4 -0
  75. darwinagent/operators/calendar.py +220 -0
  76. darwinagent/operators/data.py +205 -0
  77. darwinagent/operators/dates.py +318 -0
  78. darwinagent/operators/sandbox.py +536 -0
  79. darwinagent/presets.py +13 -0
  80. darwinagent/runtime/__init__.py +5 -0
  81. darwinagent/runtime/artifacts.py +40 -0
  82. darwinagent/runtime/budgets.py +39 -0
  83. darwinagent/runtime/continuation.py +218 -0
  84. darwinagent/runtime/deadline.py +25 -0
  85. darwinagent/runtime/execution.py +114 -0
  86. darwinagent/runtime/execution_cli.py +47 -0
  87. darwinagent/runtime/identity.py +67 -0
  88. darwinagent/runtime/journal_client.py +64 -0
  89. darwinagent/runtime/leases.py +46 -0
  90. darwinagent/runtime/migration.py +719 -0
  91. darwinagent/runtime/steps.py +184 -0
  92. darwinagent/runtime/workspace.py +600 -0
  93. darwinagent/schema/__init__.py +12 -0
  94. darwinagent/schema/graphcheck.py +69 -0
  95. darwinagent/schema/model.py +298 -0
  96. darwinagent/schema/owlcheck.py +322 -0
  97. darwinagent/vector/__init__.py +8 -0
  98. darwinagent/vector/embedder.py +128 -0
  99. darwinagent/vector/index.py +27 -0
  100. darwinagent/vector/store.py +118 -0
  101. darwinagent-0.1.0.dist-info/METADATA +96 -0
  102. darwinagent-0.1.0.dist-info/RECORD +105 -0
  103. darwinagent-0.1.0.dist-info/WHEEL +4 -0
  104. darwinagent-0.1.0.dist-info/entry_points.txt +2 -0
  105. darwinagent-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,49 @@
1
+ """DarwinAgent: Evolution for the Agent Era.
2
+
3
+ Importing the package does not read credentials, create clients, or touch the filesystem.
4
+ """
5
+
6
+ from .config import Config, RunConfig
7
+ from .contracts import (
8
+ AnswerResult,
9
+ CaseInput,
10
+ CorpusBlock,
11
+ DatasetAdapter,
12
+ EvaluationResult,
13
+ Evaluator,
14
+ QuestionInput,
15
+ RunResult,
16
+ SourceRef,
17
+ )
18
+ from .engine import Pipeline
19
+ from .experiments import (
20
+ AdoptionPolicy,
21
+ CampaignController,
22
+ ExperimentRunner,
23
+ ExperimentSpec,
24
+ SelectionPolicy,
25
+ )
26
+ from .kernel import KernelBundle, TaskSpec
27
+
28
+ __version__ = "0.1.0"
29
+ __all__ = [
30
+ "Config",
31
+ "RunConfig",
32
+ "CaseInput",
33
+ "CorpusBlock",
34
+ "QuestionInput",
35
+ "SourceRef",
36
+ "AnswerResult",
37
+ "RunResult",
38
+ "EvaluationResult",
39
+ "DatasetAdapter",
40
+ "Evaluator",
41
+ "Pipeline",
42
+ "TaskSpec",
43
+ "KernelBundle",
44
+ "ExperimentRunner",
45
+ "AdoptionPolicy",
46
+ "CampaignController",
47
+ "ExperimentSpec",
48
+ "SelectionPolicy",
49
+ ]
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ raise SystemExit(main())
@@ -0,0 +1,4 @@
1
+ from .answer import AnswerAgent
2
+ from .extraction import ExtractionAgent
3
+
4
+ __all__ = ["ExtractionAgent", "AnswerAgent"]
@@ -0,0 +1,441 @@
1
+ """Fixed collect -> generate -> check -> review -> retry -> publish flow."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+
7
+ from darwinagent.contracts import AnswerResult, plain
8
+ from darwinagent.kernel.functions import DataCapabilities
9
+ from darwinagent.kernel.validation import validate_candidate, validate_published
10
+
11
+ from .protocol import (
12
+ ANSWER_PROTOCOL,
13
+ REVIEW_PROTOCOL,
14
+ TOOLS_PROTOCOL,
15
+ ModelSession,
16
+ ProtocolError,
17
+ validate_review,
18
+ )
19
+
20
+
21
+ class AnswerAgent:
22
+ def __init__(self, runtime, client, config, spec, namespace):
23
+ self.runtime, self.client, self.config, self.spec, self.namespace = (
24
+ runtime,
25
+ client,
26
+ config,
27
+ spec,
28
+ namespace,
29
+ )
30
+
31
+ async def answer(self, question, graph, journal=None, stages=None):
32
+ from darwinagent.runtime.artifacts import digest
33
+
34
+ session = ModelSession(
35
+ self.client,
36
+ self.config,
37
+ f"{self.namespace}_q_{digest(question.id)[:12]}",
38
+ self.config.calls_per_question,
39
+ journal=journal,
40
+ )
41
+ caps = DataCapabilities(graph)
42
+ visible = set()
43
+ tool_results = []
44
+ feedback = []
45
+ trace = []
46
+ vector_once = self.config.retrieval_mode == "vector_once"
47
+ stages = set(stages or ("retrieval", "answer", "check", "review"))
48
+ prior = None
49
+ if "retrieval" not in stages:
50
+ if journal is None:
51
+ raise ValueError("Missing saved retrieval input")
52
+ prior = journal.state()
53
+ visible = set(prior["visible"])
54
+ tool_results, feedback, trace = prior["tool_results"], prior["feedback"], prior["trace"]
55
+ journal.position = prior["position"]
56
+ try:
57
+ for attempt in range(self.config.answer_attempts):
58
+ if "retrieval" not in stages:
59
+ pass
60
+ elif vector_once:
61
+ # V0 纯向量基线:一次确定性检索即全部证据预算——无工具循环,检索后冻结;
62
+ # 作答/检查/审查流程与 G1 完全共享。
63
+ rows = await asyncio.to_thread(
64
+ caps.semantic_search, question.text, limit=self.config.vector_k
65
+ )
66
+ visible = {r["node_id"] for r in rows}
67
+ tool_results = [
68
+ {
69
+ "asset_id": "vector_once",
70
+ "asset_fingerprint": "",
71
+ "data": rows,
72
+ "node_ids": sorted(visible),
73
+ "source_ids": sorted({s for r in rows for s in r["source_ids"]}),
74
+ "read_operations": caps.read_operations,
75
+ }
76
+ ]
77
+ trace.append(
78
+ {
79
+ "stage": "retrieval",
80
+ "mode": "vector_once",
81
+ "k": self.config.vector_k,
82
+ "rows": len(rows),
83
+ }
84
+ )
85
+ else:
86
+ for step in range(self.config.tool_steps):
87
+
88
+ def valid_tool(obj):
89
+ if obj == {"action": "ready"}:
90
+ return obj
91
+ if (
92
+ set(obj) != {"action", "asset_id", "parameters"}
93
+ or obj["action"] != "call"
94
+ or obj["asset_id"] not in self.runtime.functions.functions
95
+ ):
96
+ raise ValueError("Unregistered tool or invalid control action")
97
+ return obj
98
+
99
+ action = await session.request(
100
+ self.config.tools_role,
101
+ TOOLS_PROTOCOL + "\n任务工具指引:\n" + self.runtime.prompt("tools"),
102
+ {
103
+ "question": question.text,
104
+ "parameters": plain(question.parameters),
105
+ "tools": self.runtime.functions.descriptions(),
106
+ "previous_results": tool_results,
107
+ "feedback": feedback,
108
+ },
109
+ valid_tool,
110
+ )
111
+ if action["action"] == "ready":
112
+ break
113
+ aid = action["asset_id"]
114
+ asset = self.runtime.functions.functions[aid][0]
115
+ from darwinagent.runtime.artifacts import digest
116
+
117
+ call = {
118
+ "stage": "call_start",
119
+ "attempt": attempt,
120
+ "step": step,
121
+ "asset_id": aid,
122
+ "asset_fingerprint": asset.fingerprint,
123
+ "input_ref": digest(plain(action["parameters"])),
124
+ "parameters": plain(action["parameters"]),
125
+ }
126
+ trace.append(call)
127
+ observation = {}
128
+ boundary = getattr(self.client, "_control_boundary", None)
129
+ if boundary is not None:
130
+ boundary("tool:" + aid)
131
+ try:
132
+
133
+ def execute_tool(aid=aid, action=action, observation=observation):
134
+ value = self.runtime.call(
135
+ aid, action["parameters"], graph, observation
136
+ )
137
+ return {"result": plain(value), "observation": plain(observation)}
138
+
139
+ saved_tool = (
140
+ execute_tool()
141
+ if journal is None
142
+ else journal.tool(
143
+ {
144
+ "asset_id": aid,
145
+ "asset_fingerprint": asset.fingerprint,
146
+ "parameters": plain(action["parameters"]),
147
+ },
148
+ execute_tool,
149
+ )
150
+ )
151
+ result = saved_tool["result"]
152
+ observation.update(saved_tool["observation"])
153
+ except ValueError as exc:
154
+ if not str(exc).startswith("tool.params:"):
155
+ raise
156
+ # 参数契约违规进反馈环(2026-10-07 运行二事故:DeepSeek 工具
157
+ # 调用发明未声明字段 {'主题'},一击致命成题级确定性故障,B0 门
158
+ # 即挂)。调用仍被拒(契约不放松),但同题内给出「未声明字段
159
+ # +合法 schema」反馈让模型重试;步数与协议重试天然有界,
160
+ # 反复不改才由既有 ProtocolError 兜底成故障。
161
+ contract = asset.input_contract or {}
162
+ allowed = sorted((contract.get("properties") or {}).keys())
163
+ trace.append(
164
+ {
165
+ "stage": "tool_error",
166
+ **{k: v for k, v in call.items() if k != "stage"},
167
+ "error_type": "ValueError",
168
+ "error": str(exc),
169
+ "observation": observation,
170
+ }
171
+ )
172
+ feedback.append(
173
+ {
174
+ "tool_call_reject": {
175
+ "asset_id": aid,
176
+ "parameters": plain(action["parameters"]),
177
+ "error": str(exc),
178
+ "allowed_fields": allowed,
179
+ "hint": "工具参数违反声明契约:未声明字段会被拒绝执行;"
180
+ "请只用 allowed_fields 里的字段重新调用",
181
+ }
182
+ }
183
+ )
184
+ continue
185
+ except Exception as exc:
186
+ trace.append(
187
+ {
188
+ "stage": "tool_error",
189
+ **{k: v for k, v in call.items() if k != "stage"},
190
+ "error_type": type(exc).__name__,
191
+ "error": str(exc),
192
+ "observation": observation,
193
+ }
194
+ )
195
+ raise
196
+ visible.update(result["node_ids"])
197
+ tool_results.append(result)
198
+ # 参数在真实执行点入轨迹(评审②):协议重试中被拒的旧动作不会错配到
199
+ # 成功调用上;反馈摘要据此读取,不再依赖 raw_outputs 顺序配对。
200
+ trace.append(
201
+ {
202
+ "stage": "tool",
203
+ "attempt": attempt,
204
+ "step": step,
205
+ "parameters": plain(action["parameters"]),
206
+ "observation": observation,
207
+ **result,
208
+ }
209
+ )
210
+ if journal is not None:
211
+ journal.save_state(
212
+ {
213
+ "phase": "retrieved",
214
+ "visible": sorted(visible),
215
+ "tool_results": tool_results,
216
+ "feedback": feedback,
217
+ "trace": trace,
218
+ "candidate": None if prior is None else prior.get("candidate"),
219
+ }
220
+ )
221
+ if not (stages & {"answer", "check", "review"}):
222
+ return None
223
+ if "answer" not in stages:
224
+ candidate = None if prior is None else prior.get("candidate")
225
+ if candidate is None:
226
+ raise ValueError("Missing saved candidate; answer stage is not selected")
227
+ else:
228
+ candidate = await session.request(
229
+ self.config.answer_role,
230
+ ANSWER_PROTOCOL + "\n任务作答指引:\n" + self.runtime.prompt("answer"),
231
+ {
232
+ "question": question.text,
233
+ "parameters": plain(question.parameters),
234
+ "answer_format": self.spec.answer_format,
235
+ "answer_contract": plain(self.spec.answer_contract),
236
+ "tool_results": tool_results,
237
+ "visible_evidence": [caps.rows[x] for x in sorted(visible)],
238
+ "feedback": feedback,
239
+ },
240
+ lambda obj, visible=visible: self._candidate(obj, visible),
241
+ )
242
+ if candidate["status"] == "abstained" and not any(
243
+ t["read_operations"] for t in tool_results
244
+ ):
245
+ # 拒答前必须做过实际查询:作为反馈给重试机会(与候选校验同路),
246
+ # 重试耗尽仍无查询才整体失败。
247
+ feedback.append(
248
+ {
249
+ "candidate": candidate,
250
+ "task_checks": [
251
+ {
252
+ "check_id": "fixed.query_before_abstain",
253
+ "ok": False,
254
+ "issues": ["语义拒答前必须先完成至少一次实际数据查询"],
255
+ }
256
+ ],
257
+ }
258
+ )
259
+ continue
260
+ if journal is not None:
261
+ journal.save_state(
262
+ {
263
+ "phase": "candidate",
264
+ "visible": sorted(visible),
265
+ "tool_results": tool_results,
266
+ "feedback": feedback,
267
+ "trace": trace,
268
+ "candidate": candidate,
269
+ }
270
+ )
271
+ if "check" in stages:
272
+ snapshot, opinions = self.runtime.check_candidate(
273
+ question, candidate, graph, visible
274
+ )
275
+ else:
276
+ # Fixed shape/evidence validation still applies; semantic C is optional by selection.
277
+ self._candidate(candidate, visible)
278
+ snapshot = {"question": question.text, **candidate}
279
+ opinions = []
280
+ trace.append({"stage": "check", "status": "not_selected"})
281
+ trace.append(
282
+ {
283
+ "stage": "candidate",
284
+ "attempt": attempt,
285
+ "candidate": candidate,
286
+ "checks": opinions,
287
+ }
288
+ )
289
+ failures = [x for x in opinions if not x["ok"]]
290
+ if failures:
291
+ if "answer" not in stages:
292
+ raise ProtocolError(
293
+ "Saved candidate rejected; answer stage is not selected", session.raw
294
+ )
295
+ # C 失败候选的检查快照原地留存(可验证经验回放原料,2026-10-05 Travel
296
+ # 冻结容器误判事故):输入与判定事实原样进检查点,供后续候选准入回放;
297
+ # visible_evidence 行数封顶防爆轨迹,整体超限则只留截断标记。
298
+ saved = plain(snapshot)
299
+ if len(saved.get("visible_evidence") or ()) > 60:
300
+ saved["visible_evidence"] = list(saved["visible_evidence"][:60])
301
+ saved["visible_evidence_truncated"] = True
302
+ import json as _json
303
+
304
+ if len(_json.dumps(saved, ensure_ascii=False, default=str)) <= 200000:
305
+ trace[-1]["check_snapshot"] = saved
306
+ else:
307
+ trace[-1]["check_snapshot_truncated"] = True
308
+ feedback.append({"candidate": candidate, "task_checks": failures})
309
+ continue
310
+ relevant = (
311
+ set(candidate["node_ids"]) if candidate["status"] == "answered" else visible
312
+ )
313
+ source_ids = {s for nid in relevant for s in caps.rows[nid]["source_ids"]}
314
+ # A refusal is audited against every graph node, not only the subset retrieved by F.
315
+ # This is fixed review behavior; it never amends an answer or changes tool permissions.
316
+ review_inputs = []
317
+ if candidate["status"] == "answered":
318
+ review_inputs = [
319
+ {
320
+ "candidate": snapshot,
321
+ "sources": [graph.sources[s].to_dict() for s in sorted(source_ids)],
322
+ }
323
+ ]
324
+ else:
325
+ # 拒答审计证据范围两臂统一(评审#5):只看已召回证据及其来源,不读全图——
326
+ # 融合版需要补证必须显式调用登记工具(调用/返回/成本都进轨迹),
327
+ # 不得借审计通道隐式获得未召回的全量信息,否则召回收益归因被混淆。
328
+ rows = [caps.rows[x] for x in sorted(visible)]
329
+ ids = {s for row in rows for s in row["source_ids"]}
330
+ review_inputs = [
331
+ {
332
+ "candidate": {**snapshot, "visible_evidence": rows},
333
+ "refusal_audit": {
334
+ "covers_full_graph": False,
335
+ "mode": "vector_once" if vector_once else "agentic_retrieved",
336
+ },
337
+ "sources": [graph.sources[s].to_dict() for s in sorted(ids)],
338
+ }
339
+ ]
340
+ rejected = None
341
+ if "review" not in stages:
342
+ trace.append({"stage": "review", "status": "not_selected"})
343
+ review_inputs = []
344
+ for review_input in review_inputs:
345
+ review = await session.request(
346
+ self.config.review_role,
347
+ REVIEW_PROTOCOL + "\n任务语义审查指引:\n" + self.runtime.prompt("review"),
348
+ review_input,
349
+ lambda obj, candidate=candidate: validate_review(obj, candidate["status"]),
350
+ )
351
+ trace.append({"stage": "review", "attempt": attempt, **review})
352
+ if not review["accepted"]:
353
+ rejected = review
354
+ break
355
+ if journal is not None:
356
+ journal.save_state(
357
+ {
358
+ "phase": "reviewed",
359
+ "visible": sorted(visible),
360
+ "tool_results": tool_results,
361
+ "feedback": feedback,
362
+ "trace": trace,
363
+ "candidate": candidate,
364
+ }
365
+ )
366
+ if rejected:
367
+ feedback.append({"candidate": candidate, "review": rejected})
368
+ if "answer" not in stages:
369
+ raise ProtocolError(
370
+ "Saved candidate rejected; answer stage is not selected", session.raw
371
+ )
372
+ continue
373
+ evidence = (
374
+ tuple(graph.sources[s].source for s in sorted(source_ids))
375
+ if candidate["status"] == "answered"
376
+ else ()
377
+ )
378
+ # 发布点契约错误进反馈环(缺口⑤⑥修复,2026-10-06):此前 AnswerResult
379
+ # 构造/validate_published 抛 ValueError 会直接落到兜底 except 记执行
380
+ # 故障、不给模型重答机会。现改为与检查/审查拒绝同路:反馈里给出
381
+ # 逐引用节点的来源数(含无出处行——缺口⑥的显式处理),重试耗尽才
382
+ # 由既有 ProtocolError 兜底。
383
+ try:
384
+ result = AnswerResult(
385
+ question.id,
386
+ candidate["status"],
387
+ candidate["answer"],
388
+ evidence,
389
+ node_ids=tuple(candidate["node_ids"]),
390
+ raw_outputs=tuple(session.raw),
391
+ trace=tuple(trace),
392
+ )
393
+ validate_published(result, question, graph)
394
+ except ValueError as exc:
395
+ cited_sources = {
396
+ nid: (len(caps.rows[nid]["source_ids"]) if nid in caps.rows else -1)
397
+ for nid in candidate["node_ids"] or ()
398
+ }
399
+ feedback.append(
400
+ {
401
+ "candidate": candidate,
402
+ "publish_reject": {
403
+ "error": str(exc),
404
+ "cited_node_source_counts": cited_sources,
405
+ "hint": "answered 候选必须引用带出处(source_ids 非空)的事实行;"
406
+ "来源数为 0 的行不可作为证据,请改引有出处的行或如实拒答",
407
+ },
408
+ }
409
+ )
410
+ continue
411
+ return result
412
+ raise ProtocolError(
413
+ "Feedback retries exhausted without a publishable candidate", session.raw
414
+ )
415
+ except Exception as exc:
416
+ from darwinagent.runtime.steps import AwaitingBudget, RequestAbandoned, UnknownRequest
417
+
418
+ if getattr(exc, "continuation_signal", False) or isinstance(
419
+ exc, (UnknownRequest, AwaitingBudget, RequestAbandoned)
420
+ ):
421
+ raise
422
+ trace.append(
423
+ {
424
+ "stage": "execution_error",
425
+ "type": type(exc).__name__,
426
+ "error": str(exc),
427
+ "events": session.events,
428
+ }
429
+ )
430
+ return AnswerResult(
431
+ question.id,
432
+ "execution_error",
433
+ "",
434
+ error=f"{type(exc).__name__}: {exc}",
435
+ raw_outputs=tuple(session.raw),
436
+ trace=tuple(trace),
437
+ )
438
+
439
+ def _candidate(self, obj, visible):
440
+ validate_candidate(obj, visible, self.spec)
441
+ return obj