world-model-optimizer 0.2.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 (308) hide show
  1. llm_waterfall/LICENSE +21 -0
  2. llm_waterfall/__init__.py +53 -0
  3. llm_waterfall/adapters/__init__.py +36 -0
  4. llm_waterfall/adapters/anthropic.py +105 -0
  5. llm_waterfall/adapters/aws_mantle.py +47 -0
  6. llm_waterfall/adapters/azure_openai.py +71 -0
  7. llm_waterfall/adapters/base.py +51 -0
  8. llm_waterfall/adapters/bedrock.py +309 -0
  9. llm_waterfall/adapters/openai.py +130 -0
  10. llm_waterfall/classify.py +184 -0
  11. llm_waterfall/pricing.py +110 -0
  12. llm_waterfall/py.typed +0 -0
  13. llm_waterfall/types.py +295 -0
  14. llm_waterfall/waterfall.py +255 -0
  15. wmo/__init__.py +38 -0
  16. wmo/agents/__init__.py +7 -0
  17. wmo/agents/default.py +29 -0
  18. wmo/agents/meta.py +55 -0
  19. wmo/agents/optimizer.py +55 -0
  20. wmo/agents/project.py +928 -0
  21. wmo/cli/__init__.py +5 -0
  22. wmo/cli/agent_session.py +1123 -0
  23. wmo/cli/app.py +2489 -0
  24. wmo/cli/e2b_cmds.py +212 -0
  25. wmo/cli/eval_closed_loop.py +207 -0
  26. wmo/cli/harness_app.py +1147 -0
  27. wmo/cli/harness_distill.py +659 -0
  28. wmo/cli/hosted_session.py +880 -0
  29. wmo/cli/ingest_cmd.py +165 -0
  30. wmo/cli/model_roles.py +82 -0
  31. wmo/cli/platform_cmds.py +372 -0
  32. wmo/cli/route_app.py +274 -0
  33. wmo/cli/session_state.py +243 -0
  34. wmo/cli/ui.py +1107 -0
  35. wmo/cli/workspace_sync.py +504 -0
  36. wmo/config/__init__.py +60 -0
  37. wmo/config/card.py +129 -0
  38. wmo/config/config.py +367 -0
  39. wmo/config/dotenv.py +67 -0
  40. wmo/config/settings.py +128 -0
  41. wmo/config/store.py +177 -0
  42. wmo/conftest.py +19 -0
  43. wmo/connect/__init__.py +88 -0
  44. wmo/connect/apps.py +78 -0
  45. wmo/connect/brave.py +284 -0
  46. wmo/connect/connector.py +79 -0
  47. wmo/connect/credentials.py +164 -0
  48. wmo/connect/github.py +321 -0
  49. wmo/connect/google.py +627 -0
  50. wmo/connect/notion.py +790 -0
  51. wmo/connect/oauth.py +461 -0
  52. wmo/connect/slack.py +555 -0
  53. wmo/connect/store.py +199 -0
  54. wmo/connect/types.py +156 -0
  55. wmo/core/__init__.py +21 -0
  56. wmo/core/parsing.py +281 -0
  57. wmo/core/render.py +271 -0
  58. wmo/core/text.py +40 -0
  59. wmo/core/types.py +116 -0
  60. wmo/distill/__init__.py +14 -0
  61. wmo/distill/agents.py +140 -0
  62. wmo/distill/config.py +1006 -0
  63. wmo/distill/cost.py +437 -0
  64. wmo/distill/data.py +921 -0
  65. wmo/distill/deadlines.py +254 -0
  66. wmo/distill/fake_tinker.py +734 -0
  67. wmo/distill/gate.py +122 -0
  68. wmo/distill/loop.py +3499 -0
  69. wmo/distill/renderers.py +399 -0
  70. wmo/distill/rendering.py +620 -0
  71. wmo/distill/rollouts.py +726 -0
  72. wmo/distill/samples.py +195 -0
  73. wmo/distill/store.py +829 -0
  74. wmo/distill/teacher.py +714 -0
  75. wmo/distill/tokens.py +535 -0
  76. wmo/distill/tracking.py +552 -0
  77. wmo/distill/tripwire.py +411 -0
  78. wmo/distill/xtoken/byte_offsets.py +152 -0
  79. wmo/distill/xtoken/chunks.py +457 -0
  80. wmo/distill/xtoken/prompt_logprobs.py +475 -0
  81. wmo/distill/xtoken/teacher_render.py +346 -0
  82. wmo/engine/__init__.py +28 -0
  83. wmo/engine/autoconfig.py +367 -0
  84. wmo/engine/build.py +346 -0
  85. wmo/engine/demo.py +77 -0
  86. wmo/engine/eval_suites.py +245 -0
  87. wmo/engine/grounding.py +491 -0
  88. wmo/engine/knowledge.py +291 -0
  89. wmo/engine/loader.py +36 -0
  90. wmo/engine/play.py +92 -0
  91. wmo/engine/prompts.py +99 -0
  92. wmo/engine/replay.py +443 -0
  93. wmo/engine/reporting.py +58 -0
  94. wmo/engine/workspace.py +468 -0
  95. wmo/engine/world_model.py +568 -0
  96. wmo/env/__init__.py +22 -0
  97. wmo/env/base.py +121 -0
  98. wmo/env/closed_loop.py +229 -0
  99. wmo/env/episode.py +107 -0
  100. wmo/env/llm_agent.py +93 -0
  101. wmo/env/scenarios.py +73 -0
  102. wmo/evals/__init__.py +52 -0
  103. wmo/evals/agreement.py +110 -0
  104. wmo/evals/base.py +45 -0
  105. wmo/evals/closed_loop.py +480 -0
  106. wmo/evals/failover.py +96 -0
  107. wmo/evals/gold.py +127 -0
  108. wmo/evals/grid.py +394 -0
  109. wmo/evals/grid_plot.py +205 -0
  110. wmo/evals/harbor/__init__.py +27 -0
  111. wmo/evals/harbor/agent.py +573 -0
  112. wmo/evals/harbor/ctrf.py +171 -0
  113. wmo/evals/harbor/e2b_environment.py +587 -0
  114. wmo/evals/harbor/e2b_template_policy.py +144 -0
  115. wmo/evals/harbor/scorer.py +875 -0
  116. wmo/evals/harbor/tasks.py +140 -0
  117. wmo/evals/open_loop.py +194 -0
  118. wmo/evals/tasks.py +53 -0
  119. wmo/harness/__init__.py +51 -0
  120. wmo/harness/code_runtime.py +288 -0
  121. wmo/harness/create.py +1191 -0
  122. wmo/harness/delta.py +220 -0
  123. wmo/harness/doc.py +556 -0
  124. wmo/harness/e2b_ledger.py +342 -0
  125. wmo/harness/e2b_reap.py +476 -0
  126. wmo/harness/e2b_sandbox.py +350 -0
  127. wmo/harness/environment.py +35 -0
  128. wmo/harness/live_session.py +543 -0
  129. wmo/harness/mutate.py +343 -0
  130. wmo/harness/pi_e2b.py +1710 -0
  131. wmo/harness/pi_entry/entry.ts +268 -0
  132. wmo/harness/pi_entry/runner_frames.ts +92 -0
  133. wmo/harness/pi_entry/runner_live.ts +587 -0
  134. wmo/harness/pi_entry/runner_service.ts +270 -0
  135. wmo/harness/pi_entry/runner_stdio.ts +374 -0
  136. wmo/harness/pi_entry/runner_termination.ts +142 -0
  137. wmo/harness/pi_local.py +262 -0
  138. wmo/harness/pi_runtime.py +495 -0
  139. wmo/harness/pi_vendor.py +65 -0
  140. wmo/harness/population.py +509 -0
  141. wmo/harness/project_proposer.py +569 -0
  142. wmo/harness/proposer.py +977 -0
  143. wmo/harness/runner_link.py +619 -0
  144. wmo/harness/runtime.py +389 -0
  145. wmo/harness/scoring.py +247 -0
  146. wmo/harness/skills.py +116 -0
  147. wmo/harness/source_tree.py +319 -0
  148. wmo/harness/store.py +176 -0
  149. wmo/harness/tools.py +105 -0
  150. wmo/harness/vendor/manifest.sha256 +58 -0
  151. wmo/harness/vendor/pi-agent/CHANGELOG.md +556 -0
  152. wmo/harness/vendor/pi-agent/LICENSE +21 -0
  153. wmo/harness/vendor/pi-agent/README.md +488 -0
  154. wmo/harness/vendor/pi-agent/VENDOR.md +39 -0
  155. wmo/harness/vendor/pi-agent/docs/agent-harness.md +486 -0
  156. wmo/harness/vendor/pi-agent/docs/durable-harness.md +212 -0
  157. wmo/harness/vendor/pi-agent/docs/hooks.md +445 -0
  158. wmo/harness/vendor/pi-agent/docs/models.md +966 -0
  159. wmo/harness/vendor/pi-agent/docs/observability.md +376 -0
  160. wmo/harness/vendor/pi-agent/package.json +60 -0
  161. wmo/harness/vendor/pi-agent/src/agent-loop.ts +748 -0
  162. wmo/harness/vendor/pi-agent/src/agent.ts +575 -0
  163. wmo/harness/vendor/pi-agent/src/harness/agent-harness.ts +1029 -0
  164. wmo/harness/vendor/pi-agent/src/harness/compaction/branch-summarization.ts +261 -0
  165. wmo/harness/vendor/pi-agent/src/harness/compaction/compaction.ts +747 -0
  166. wmo/harness/vendor/pi-agent/src/harness/compaction/utils.ts +144 -0
  167. wmo/harness/vendor/pi-agent/src/harness/env/nodejs.ts +550 -0
  168. wmo/harness/vendor/pi-agent/src/harness/messages.ts +164 -0
  169. wmo/harness/vendor/pi-agent/src/harness/prompt-templates.ts +267 -0
  170. wmo/harness/vendor/pi-agent/src/harness/session/jsonl-repo.ts +177 -0
  171. wmo/harness/vendor/pi-agent/src/harness/session/jsonl-storage.ts +293 -0
  172. wmo/harness/vendor/pi-agent/src/harness/session/memory-repo.ts +50 -0
  173. wmo/harness/vendor/pi-agent/src/harness/session/memory-storage.ts +131 -0
  174. wmo/harness/vendor/pi-agent/src/harness/session/repo-utils.ts +51 -0
  175. wmo/harness/vendor/pi-agent/src/harness/session/session.ts +267 -0
  176. wmo/harness/vendor/pi-agent/src/harness/session/uuid.ts +54 -0
  177. wmo/harness/vendor/pi-agent/src/harness/skills.ts +375 -0
  178. wmo/harness/vendor/pi-agent/src/harness/system-prompt.ts +34 -0
  179. wmo/harness/vendor/pi-agent/src/harness/types.ts +836 -0
  180. wmo/harness/vendor/pi-agent/src/harness/utils/shell-output.ts +135 -0
  181. wmo/harness/vendor/pi-agent/src/harness/utils/truncate.ts +344 -0
  182. wmo/harness/vendor/pi-agent/src/index.ts +44 -0
  183. wmo/harness/vendor/pi-agent/src/node.ts +2 -0
  184. wmo/harness/vendor/pi-agent/src/proxy.ts +367 -0
  185. wmo/harness/vendor/pi-agent/src/types.ts +428 -0
  186. wmo/harness/vendor/pi-agent/test/agent-loop.test.ts +1351 -0
  187. wmo/harness/vendor/pi-agent/test/agent.test.ts +699 -0
  188. wmo/harness/vendor/pi-agent/test/e2e.test.ts +404 -0
  189. wmo/harness/vendor/pi-agent/test/harness/agent-harness-stream.test.ts +213 -0
  190. wmo/harness/vendor/pi-agent/test/harness/agent-harness.test.ts +608 -0
  191. wmo/harness/vendor/pi-agent/test/harness/compaction.test.ts +655 -0
  192. wmo/harness/vendor/pi-agent/test/harness/nodejs-env.test.ts +321 -0
  193. wmo/harness/vendor/pi-agent/test/harness/prompt-templates.test.ts +90 -0
  194. wmo/harness/vendor/pi-agent/test/harness/repo.test.ts +68 -0
  195. wmo/harness/vendor/pi-agent/test/harness/resource-formatting.test.ts +24 -0
  196. wmo/harness/vendor/pi-agent/test/harness/session-test-utils.ts +55 -0
  197. wmo/harness/vendor/pi-agent/test/harness/session-uuid.test.ts +50 -0
  198. wmo/harness/vendor/pi-agent/test/harness/session.test.ts +156 -0
  199. wmo/harness/vendor/pi-agent/test/harness/skills.test.ts +116 -0
  200. wmo/harness/vendor/pi-agent/test/harness/storage.test.ts +299 -0
  201. wmo/harness/vendor/pi-agent/test/harness/system-prompt.test.ts +66 -0
  202. wmo/harness/vendor/pi-agent/test/harness/truncate.test.ts +169 -0
  203. wmo/harness/vendor/pi-agent/test/scratch/simple.ts +72 -0
  204. wmo/harness/vendor/pi-agent/test/utils/calculate.ts +32 -0
  205. wmo/harness/vendor/pi-agent/test/utils/get-current-time.ts +46 -0
  206. wmo/harness/vendor/pi-agent/tsconfig.build.json +13 -0
  207. wmo/harness/vendor/pi-agent/vitest.config.ts +19 -0
  208. wmo/harness/vendor/pi-agent/vitest.harness.config.ts +28 -0
  209. wmo/harness/vendor/vendor_pi.sh +59 -0
  210. wmo/harness/workspace_patch.py +270 -0
  211. wmo/ingest/__init__.py +47 -0
  212. wmo/ingest/adapter.py +72 -0
  213. wmo/ingest/base.py +114 -0
  214. wmo/ingest/braintrust.py +339 -0
  215. wmo/ingest/detect.py +126 -0
  216. wmo/ingest/langfuse.py +291 -0
  217. wmo/ingest/langsmith.py +444 -0
  218. wmo/ingest/mastra.py +330 -0
  219. wmo/ingest/messages.py +170 -0
  220. wmo/ingest/normalize.py +679 -0
  221. wmo/ingest/otel_genai.py +69 -0
  222. wmo/ingest/otel_writer.py +100 -0
  223. wmo/ingest/phoenix.py +150 -0
  224. wmo/ingest/postgres.py +246 -0
  225. wmo/ingest/posthog.py +320 -0
  226. wmo/ingest/quality.py +28 -0
  227. wmo/ingest/stream.py +209 -0
  228. wmo/ingest/testdata/sample_otlp.json +60 -0
  229. wmo/ingest/testdata/sample_spans.jsonl +3 -0
  230. wmo/optimize/__init__.py +25 -0
  231. wmo/optimize/base.py +143 -0
  232. wmo/optimize/gepa.py +806 -0
  233. wmo/optimize/judge.py +262 -0
  234. wmo/optimize/judge_quality.py +359 -0
  235. wmo/optimize/knn.py +468 -0
  236. wmo/optimize/numeric.py +152 -0
  237. wmo/optimize/outcomes.py +103 -0
  238. wmo/optimize/policy.py +669 -0
  239. wmo/optimize/report.py +231 -0
  240. wmo/optimize/reward.py +129 -0
  241. wmo/optimize/routing.py +373 -0
  242. wmo/platform/__init__.py +6 -0
  243. wmo/platform/auth.py +115 -0
  244. wmo/platform/client.py +551 -0
  245. wmo/platform/credentials.py +126 -0
  246. wmo/platform/transfer.py +158 -0
  247. wmo/providers/__init__.py +40 -0
  248. wmo/providers/_bedrock_chat.py +155 -0
  249. wmo/providers/_openai_common.py +182 -0
  250. wmo/providers/_responses_common.py +472 -0
  251. wmo/providers/anthropic.py +134 -0
  252. wmo/providers/azure_openai.py +296 -0
  253. wmo/providers/base.py +300 -0
  254. wmo/providers/bedrock.py +312 -0
  255. wmo/providers/models.py +205 -0
  256. wmo/providers/openai.py +143 -0
  257. wmo/providers/openai_responses.py +240 -0
  258. wmo/providers/pool.py +170 -0
  259. wmo/providers/registry.py +73 -0
  260. wmo/providers/retry.py +151 -0
  261. wmo/providers/tinker.py +936 -0
  262. wmo/providers/waterfall.py +336 -0
  263. wmo/research/__init__.py +81 -0
  264. wmo/research/ablation.py +133 -0
  265. wmo/research/concurrency_plot.py +523 -0
  266. wmo/research/concurrency_run.py +240 -0
  267. wmo/research/concurrency_scaling.py +270 -0
  268. wmo/research/gepa_scaling.py +274 -0
  269. wmo/research/pipeline.py +198 -0
  270. wmo/research/scaling_split.py +82 -0
  271. wmo/research/scenario_fidelity.py +198 -0
  272. wmo/research/scenario_recovery.py +92 -0
  273. wmo/research/seed_stability.py +90 -0
  274. wmo/research/trace_scaling.py +348 -0
  275. wmo/retrieval/__init__.py +6 -0
  276. wmo/retrieval/embedders.py +105 -0
  277. wmo/retrieval/leakfree.py +52 -0
  278. wmo/retrieval/retriever.py +173 -0
  279. wmo/scenarios/__init__.py +58 -0
  280. wmo/scenarios/builder.py +152 -0
  281. wmo/scenarios/mining/__init__.py +27 -0
  282. wmo/scenarios/mining/clustering.py +171 -0
  283. wmo/scenarios/mining/facets.py +226 -0
  284. wmo/scenarios/mining/selection.py +220 -0
  285. wmo/scenarios/synthesis/__init__.py +6 -0
  286. wmo/scenarios/synthesis/scenario_set.py +63 -0
  287. wmo/scenarios/synthesis/synthesizer.py +85 -0
  288. wmo/scenarios/verification/__init__.py +17 -0
  289. wmo/scenarios/verification/judge.py +97 -0
  290. wmo/scenarios/verification/verify.py +135 -0
  291. wmo/serving/__init__.py +5 -0
  292. wmo/serving/builds.py +451 -0
  293. wmo/serving/chat.py +878 -0
  294. wmo/serving/endpoint_config.py +64 -0
  295. wmo/serving/savings.py +250 -0
  296. wmo/serving/server.py +553 -0
  297. wmo/serving/traces_source.py +206 -0
  298. wmo/telemetry.py +213 -0
  299. wmo/tracking/__init__.py +36 -0
  300. wmo/tracking/clock.py +24 -0
  301. wmo/tracking/metered.py +125 -0
  302. wmo/tracking/pricing.py +99 -0
  303. wmo/tracking/store.py +31 -0
  304. wmo/tracking/tracker.py +149 -0
  305. world_model_optimizer-0.2.0.dist-info/METADATA +203 -0
  306. world_model_optimizer-0.2.0.dist-info/RECORD +308 -0
  307. world_model_optimizer-0.2.0.dist-info/WHEEL +4 -0
  308. world_model_optimizer-0.2.0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,977 @@
1
+ """Typed delta proposers for direct providers and project-backed meta agents."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import math
7
+ from collections.abc import Callable, Collection
8
+ from dataclasses import dataclass
9
+ from typing import Protocol
10
+
11
+ from wmo.agents.project import AgentProjectRun
12
+ from wmo.core.types import JsonObject
13
+ from wmo.harness.delta import FailureSignature, HarnessDelta, apply_delta
14
+ from wmo.harness.doc import HarnessDoc
15
+ from wmo.harness.mutate import parse_delta, propose_delta
16
+ from wmo.harness.runtime import HarnessSearchCancelled
17
+ from wmo.providers.base import Provider, ToolCallingProvider
18
+
19
+ _CONTEXT_CONTENT_CHUNK_CHARS = 12_000
20
+ _MAX_PROJECT_REPAIR_TURNS = 2
21
+ _SUPPORTED_RUNTIME_KINDS = frozenset({"kit-python", "pi-node"})
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class _ProposalValidation:
26
+ """Host-validated proposal slots plus the raw files needed to protect valid siblings."""
27
+
28
+ proposals: dict[int, HarnessDelta]
29
+ raw_files: dict[int, str]
30
+ child_hashes: dict[int, str]
31
+ errors: dict[int, str]
32
+
33
+
34
+ class AgentProject(Protocol):
35
+ """Project operations required by the optimizer wiring."""
36
+
37
+ workspace: str
38
+
39
+ def write_text(self, path: str, content: str) -> None: ...
40
+
41
+ def read_text(self, path: str) -> str: ...
42
+
43
+ def run(
44
+ self,
45
+ agent: HarnessDoc,
46
+ provider: ToolCallingProvider,
47
+ instruction: str,
48
+ *,
49
+ should_cancel: Callable[[], bool] | None = None,
50
+ writable_files: Collection[str] | None = None,
51
+ ) -> AgentProjectRun: ...
52
+
53
+
54
+ class DeltaProposer(Protocol):
55
+ """Produce sibling deltas against one selected parent."""
56
+
57
+ def propose_batch(
58
+ self,
59
+ parent: HarnessDoc,
60
+ trigger: FailureSignature,
61
+ evidence: str,
62
+ *,
63
+ history: list[HarnessDelta],
64
+ count: int,
65
+ should_cancel: Callable[[], bool] | None = None,
66
+ ) -> list[HarnessDelta | ProposalFailure | None]: ...
67
+
68
+
69
+ @dataclass(frozen=True)
70
+ class ProposalFailure:
71
+ """One proposal slot whose provider or agent call failed."""
72
+
73
+ reason: str
74
+
75
+
76
+ class ProviderDeltaProposer:
77
+ """Adapt the original single-completion proposer to the batched search contract."""
78
+
79
+ def __init__(self, provider: Provider) -> None:
80
+ self._provider = provider
81
+
82
+ @property
83
+ def provider(self) -> Provider:
84
+ """Return the provider used for direct proposal calls."""
85
+ return self._provider
86
+
87
+ def propose_batch(
88
+ self,
89
+ parent: HarnessDoc,
90
+ trigger: FailureSignature,
91
+ evidence: str,
92
+ *,
93
+ history: list[HarnessDelta],
94
+ count: int,
95
+ should_cancel: Callable[[], bool] | None = None,
96
+ ) -> list[HarnessDelta | ProposalFailure | None]:
97
+ """Make ``count`` independent proposal calls against the same parent."""
98
+ if count < 1:
99
+ raise ValueError(f"proposal count must be positive, got {count}")
100
+ proposals: list[HarnessDelta | ProposalFailure | None] = []
101
+ for _ in range(count):
102
+ _check_cancelled(should_cancel)
103
+ try:
104
+ proposal = propose_delta(
105
+ parent,
106
+ trigger,
107
+ evidence,
108
+ self._provider,
109
+ history=history,
110
+ )
111
+ except HarnessSearchCancelled:
112
+ raise
113
+ except Exception as error: # noqa: BLE001 - isolate one flaky sibling call
114
+ proposal = ProposalFailure(reason=str(error))
115
+ proposals.append(proposal)
116
+ return proposals
117
+
118
+
119
+ class ProjectDeltaProposer:
120
+ """Wire a normal agent in a persistent project into harness search."""
121
+
122
+ def __init__(
123
+ self,
124
+ project: AgentProject,
125
+ agent: HarnessDoc,
126
+ provider: ToolCallingProvider,
127
+ *,
128
+ preserve_runtime_kind: bool = False,
129
+ ) -> None:
130
+ self._project = project
131
+ self._agent = agent
132
+ self._provider = provider
133
+ self._iteration = 0
134
+ self._evaluation_dirs: dict[str, str] = {}
135
+ self._proposal_files: dict[str, str] = {}
136
+ self._parent_manifests: dict[str, JsonObject] = {}
137
+ self._should_cancel: Callable[[], bool] | None = None
138
+ self._preserve_runtime_kind = preserve_runtime_kind
139
+
140
+ def propose_batch(
141
+ self,
142
+ parent: HarnessDoc,
143
+ trigger: FailureSignature,
144
+ evidence: str,
145
+ *,
146
+ history: list[HarnessDelta],
147
+ count: int,
148
+ should_cancel: Callable[[], bool] | None = None,
149
+ ) -> list[HarnessDelta | ProposalFailure | None]:
150
+ """Run one meta-agent turn that writes ``count`` proposal files."""
151
+ if count < 1:
152
+ raise ValueError(f"proposal count must be positive, got {count}")
153
+ self._should_cancel = should_cancel
154
+ _check_cancelled(should_cancel)
155
+ self._iteration += 1
156
+ iteration_dir = f"iteration-{self._iteration:04d}"
157
+ context_dir = f"context/{iteration_dir}"
158
+ proposal_dir = f"proposals/{iteration_dir}"
159
+ parent_context = self._parent_manifests.get(parent.doc_hash)
160
+ if parent_context is None:
161
+ parent_context = _materialize_parent(
162
+ self._project,
163
+ parent,
164
+ context_dir=f"parents/{parent.doc_hash}",
165
+ should_cancel=should_cancel,
166
+ )
167
+ self._parent_manifests[parent.doc_hash] = parent_context
168
+ _write_project_text(
169
+ self._project,
170
+ f"{context_dir}/parent.json",
171
+ json.dumps(parent_context, indent=2),
172
+ should_cancel=should_cancel,
173
+ )
174
+ evidence_context = _materialize_context_content(
175
+ self._project,
176
+ evidence,
177
+ directory=f"{context_dir}/evidence",
178
+ extension=".md",
179
+ should_cancel=should_cancel,
180
+ )
181
+ _write_project_text(
182
+ self._project,
183
+ f"{context_dir}/evidence.json",
184
+ json.dumps(
185
+ {
186
+ "kind": "failure-evidence",
187
+ "format": "markdown",
188
+ **evidence_context,
189
+ },
190
+ indent=2,
191
+ ),
192
+ should_cancel=should_cancel,
193
+ )
194
+ history_content = json.dumps(
195
+ [
196
+ _project_history_entry(
197
+ delta,
198
+ proposal_file=self._proposal_files.get(delta.delta_id),
199
+ evaluation_dir=self._evaluation_dirs.get(delta.delta_id),
200
+ workspace=self._project.workspace,
201
+ )
202
+ for delta in history
203
+ ],
204
+ indent=2,
205
+ )
206
+ history_context = _materialize_context_content(
207
+ self._project,
208
+ history_content,
209
+ directory=f"{context_dir}/history",
210
+ extension=".json.part",
211
+ should_cancel=should_cancel,
212
+ )
213
+ _write_project_text(
214
+ self._project,
215
+ f"{context_dir}/history.json",
216
+ json.dumps(
217
+ {
218
+ "kind": "judged-history",
219
+ "format": "json-array",
220
+ "entry_count": len(history),
221
+ **history_context,
222
+ },
223
+ indent=2,
224
+ ),
225
+ should_cancel=should_cancel,
226
+ )
227
+ request = _project_request(
228
+ workspace=self._project.workspace,
229
+ context_dir=context_dir,
230
+ proposal_dir=proposal_dir,
231
+ count=count,
232
+ runtime_kind=parent.runtime_kind(),
233
+ preserve_runtime_kind=self._preserve_runtime_kind,
234
+ )
235
+ _write_project_text(
236
+ self._project,
237
+ f"{context_dir}/REQUEST.md",
238
+ request,
239
+ should_cancel=should_cancel,
240
+ )
241
+ run_error: Exception | None = None
242
+ try:
243
+ self._project.run(
244
+ self._agent,
245
+ self._provider,
246
+ request,
247
+ should_cancel=should_cancel,
248
+ writable_files=[
249
+ f"{proposal_dir}/proposal-{index:02d}.json" for index in range(1, count + 1)
250
+ ],
251
+ )
252
+ except HarnessSearchCancelled:
253
+ raise
254
+ except Exception as error: # noqa: BLE001 - durable project files may still be complete
255
+ run_error = error
256
+ _check_cancelled(should_cancel)
257
+
258
+ slots = list(range(1, count + 1))
259
+ validation = _validate_project_proposals(
260
+ self._project,
261
+ parent,
262
+ trigger,
263
+ proposal_dir=proposal_dir,
264
+ slots=slots,
265
+ history=history,
266
+ preserve_runtime_kind=self._preserve_runtime_kind,
267
+ should_cancel=should_cancel,
268
+ )
269
+ # A lost terminal frame does not invalidate durable files. Host-preflight whatever was
270
+ # written, then repair only the bad/missing slots in an ordinary follow-up project turn.
271
+ # The last project-channel error matters only while a slot remains unresolved.
272
+ terminal_error = run_error
273
+ if validation.errors:
274
+ validation_report_ready = True
275
+ try:
276
+ _write_project_text(
277
+ self._project,
278
+ f"{context_dir}/proposal-validation-attempt-01.json",
279
+ _proposal_validation_report(
280
+ parent=parent,
281
+ proposal_dir=proposal_dir,
282
+ attempt=1,
283
+ valid_slots=validation.proposals,
284
+ errors=validation.errors,
285
+ ),
286
+ should_cancel=should_cancel,
287
+ )
288
+ except HarnessSearchCancelled:
289
+ raise
290
+ except Exception as error: # noqa: BLE001 - no report means no safe repair prompt
291
+ terminal_error = error
292
+ validation_report_ready = False
293
+
294
+ for repair_turn in range(1, _MAX_PROJECT_REPAIR_TURNS + 1):
295
+ if not validation.errors or not validation_report_ready:
296
+ break
297
+ validation_report_ready = False
298
+ validation_path = (
299
+ f"{context_dir}/proposal-validation-attempt-{repair_turn:02d}.json"
300
+ )
301
+ repair_request = _project_repair_request(
302
+ workspace=self._project.workspace,
303
+ validation_path=validation_path,
304
+ request_path=f"{context_dir}/REQUEST.md",
305
+ proposal_dir=proposal_dir,
306
+ errors=validation.errors,
307
+ valid_slots=validation.proposals,
308
+ runtime_kind=parent.runtime_kind(),
309
+ preserve_runtime_kind=self._preserve_runtime_kind,
310
+ repair_turn=repair_turn,
311
+ )
312
+ invalid_slots = sorted(validation.errors)
313
+ protected_restore_error: Exception | None = None
314
+ turn_error: Exception | None = None
315
+ try:
316
+ _write_project_text(
317
+ self._project,
318
+ f"{context_dir}/REPAIR-{repair_turn:02d}.md",
319
+ repair_request,
320
+ should_cancel=should_cancel,
321
+ )
322
+ try:
323
+ self._project.run(
324
+ self._agent,
325
+ self._provider,
326
+ repair_request,
327
+ should_cancel=should_cancel,
328
+ writable_files=[
329
+ f"{proposal_dir}/proposal-{index:02d}.json"
330
+ for index in invalid_slots
331
+ ],
332
+ )
333
+ except HarnessSearchCancelled:
334
+ raise
335
+ except Exception as error: # noqa: BLE001 - salvage durable repaired files
336
+ turn_error = error
337
+ finally:
338
+ # The agent is asked to rewrite only invalid slots. Restore every
339
+ # byte-exact valid sibling after each turn as an enforcement boundary.
340
+ for index in sorted(validation.proposals):
341
+ try:
342
+ _write_project_text(
343
+ self._project,
344
+ f"{proposal_dir}/proposal-{index:02d}.json",
345
+ validation.raw_files[index],
346
+ should_cancel=should_cancel,
347
+ )
348
+ except HarnessSearchCancelled:
349
+ raise
350
+ except Exception as error: # noqa: BLE001 - attempt every restore
351
+ if protected_restore_error is None:
352
+ protected_restore_error = error
353
+ except HarnessSearchCancelled:
354
+ raise
355
+ except Exception as error: # noqa: BLE001 - preserve good siblings, fail bad ones
356
+ turn_error = error
357
+ if protected_restore_error is not None:
358
+ # The in-memory delta and its durable proposal_file must always name the
359
+ # same bytes. If protection cannot be proven, abort this batch instead of
360
+ # returning a valid object whose persistent provenance may have been changed.
361
+ raise protected_restore_error
362
+ terminal_error = turn_error
363
+
364
+ repaired = _validate_project_proposals(
365
+ self._project,
366
+ parent,
367
+ trigger,
368
+ proposal_dir=proposal_dir,
369
+ slots=invalid_slots,
370
+ history=history,
371
+ valid_proposals=validation.proposals,
372
+ preserve_runtime_kind=self._preserve_runtime_kind,
373
+ should_cancel=should_cancel,
374
+ )
375
+ validation = _ProposalValidation(
376
+ proposals=repaired.proposals,
377
+ raw_files={**validation.raw_files, **repaired.raw_files},
378
+ child_hashes={**validation.child_hashes, **repaired.child_hashes},
379
+ errors=repaired.errors,
380
+ )
381
+ # Each host result becomes the next turn's nested input and the durable final
382
+ # audit. These turns happen wholly inside proposal generation, before search.
383
+ try:
384
+ _write_project_text(
385
+ self._project,
386
+ f"{context_dir}/proposal-validation-attempt-{repair_turn + 1:02d}.json",
387
+ _proposal_validation_report(
388
+ parent=parent,
389
+ proposal_dir=proposal_dir,
390
+ attempt=repair_turn + 1,
391
+ valid_slots=validation.proposals,
392
+ errors=validation.errors,
393
+ ),
394
+ should_cancel=should_cancel,
395
+ )
396
+ validation_report_ready = True
397
+ except HarnessSearchCancelled:
398
+ raise
399
+ except Exception as error: # noqa: BLE001 - preserve validated in-memory output
400
+ if terminal_error is None:
401
+ terminal_error = error
402
+ if not validation.errors:
403
+ # A prior turn can lose its terminal control frame after durable repaired
404
+ # files were written. Successful preflight is authoritative salvage.
405
+ terminal_error = None
406
+
407
+ proposals: list[HarnessDelta | ProposalFailure | None] = []
408
+ for index in slots:
409
+ stamped = validation.proposals.get(index)
410
+ if stamped is not None:
411
+ proposals.append(stamped)
412
+ self._proposal_files.setdefault(
413
+ stamped.delta_id,
414
+ f"{self._project.workspace}/{proposal_dir}/proposal-{index:02d}.json",
415
+ )
416
+ self._evaluation_dirs.setdefault(
417
+ stamped.delta_id,
418
+ f"evaluations/{iteration_dir}/proposal-{index:02d}",
419
+ )
420
+ elif terminal_error is not None:
421
+ proposals.append(ProposalFailure(reason=str(terminal_error)))
422
+ else:
423
+ proposals.append(
424
+ ProposalFailure(
425
+ reason=validation.errors.get(
426
+ index,
427
+ "proposal slot remained invalid after bounded repair turns",
428
+ )
429
+ )
430
+ )
431
+ return proposals
432
+
433
+ def record_evaluation(self, delta: HarnessDelta, *, stage: str, content: str) -> None:
434
+ """Persist one candidate's judged evidence for later project-agent iterations."""
435
+ should_cancel = self._should_cancel
436
+ _check_cancelled(should_cancel)
437
+ root = self._evaluation_dirs.get(
438
+ delta.delta_id,
439
+ f"evaluations/by-delta/{delta.delta_id}",
440
+ )
441
+ context = _materialize_context_content(
442
+ self._project,
443
+ content,
444
+ directory=f"{root}/{stage}",
445
+ extension=".md",
446
+ should_cancel=should_cancel,
447
+ )
448
+ _write_project_text(
449
+ self._project,
450
+ f"{root}/{stage}.json",
451
+ json.dumps(
452
+ {
453
+ "kind": "candidate-evaluation",
454
+ "stage": stage,
455
+ "delta_id": delta.delta_id,
456
+ "format": "markdown",
457
+ **context,
458
+ },
459
+ indent=2,
460
+ ),
461
+ should_cancel=should_cancel,
462
+ )
463
+
464
+
465
+ def _check_cancelled(should_cancel: Callable[[], bool] | None) -> None:
466
+ """Raise the shared search signal without converting it into a failed proposal slot."""
467
+ if should_cancel is not None and should_cancel():
468
+ raise HarnessSearchCancelled("harness search cancelled")
469
+
470
+
471
+ def _write_project_text(
472
+ project: AgentProject,
473
+ path: str,
474
+ content: str,
475
+ *,
476
+ should_cancel: Callable[[], bool] | None,
477
+ ) -> None:
478
+ """Make each E2B filesystem RPC a cancellation boundary."""
479
+ _check_cancelled(should_cancel)
480
+ project.write_text(path, content)
481
+ _check_cancelled(should_cancel)
482
+
483
+
484
+ def _read_project_text(
485
+ project: AgentProject,
486
+ path: str,
487
+ *,
488
+ should_cancel: Callable[[], bool] | None,
489
+ ) -> str:
490
+ """Read one project output without hiding cancellation behind the next iteration."""
491
+ _check_cancelled(should_cancel)
492
+ content = project.read_text(path)
493
+ _check_cancelled(should_cancel)
494
+ return content
495
+
496
+
497
+ def _materialize_parent(
498
+ project: AgentProject,
499
+ parent: HarnessDoc,
500
+ *,
501
+ context_dir: str,
502
+ should_cancel: Callable[[], bool] | None = None,
503
+ ) -> JsonObject:
504
+ """Write a bounded manifest plus individually readable parent-surface chunks.
505
+
506
+ A real pi document is hundreds of kilobytes, while one project ``read_file`` observation is
507
+ intentionally capped. Putting every surface inline in one parent.json therefore hid most of
508
+ the harness from the proposer. The manifest remains small and points to ordered chunks below
509
+ the read cap; concatenating a surface's chunks reconstructs its exact content.
510
+ """
511
+ surface_index: list[JsonObject] = []
512
+ for index, surface in enumerate(parent.surfaces, 1):
513
+ surface_dir = f"{context_dir}/parent-surfaces/surface-{index:03d}"
514
+ content_context = _materialize_context_content(
515
+ project,
516
+ surface.content,
517
+ directory=surface_dir,
518
+ extension=".txt",
519
+ include_contract=False,
520
+ should_cancel=should_cancel,
521
+ )
522
+ source_file: str | None = None
523
+ if surface.path is not None:
524
+ source_relative = f"{context_dir}/parent-source/{surface.path}"
525
+ _write_project_text(
526
+ project,
527
+ source_relative,
528
+ surface.content,
529
+ should_cancel=should_cancel,
530
+ )
531
+ source_file = f"{project.workspace}/{source_relative}"
532
+ entry: JsonObject = {
533
+ "id": surface.id,
534
+ "kind": surface.kind.value,
535
+ "content_hash": surface.content_hash,
536
+ **content_context,
537
+ }
538
+ if surface.budget is not None:
539
+ entry["budget"] = surface.budget
540
+ if surface.path is not None:
541
+ entry["path"] = surface.path
542
+ entry["source_file"] = source_file
543
+ surface_manifest_relative = f"{surface_dir}/manifest.json"
544
+ _write_project_text(
545
+ project,
546
+ surface_manifest_relative,
547
+ json.dumps(entry, indent=2),
548
+ should_cancel=should_cancel,
549
+ )
550
+ surface_index.append(
551
+ {
552
+ "id": surface.id,
553
+ "kind": surface.kind.value,
554
+ "content_hash": surface.content_hash,
555
+ "manifest_file": f"{project.workspace}/{surface_manifest_relative}",
556
+ }
557
+ )
558
+ index_content = json.dumps(surface_index, indent=2)
559
+ index_context = _materialize_context_content(
560
+ project,
561
+ index_content,
562
+ directory=f"{context_dir}/parent-surface-index",
563
+ extension=".json.part",
564
+ should_cancel=should_cancel,
565
+ )
566
+ index_manifest_relative = f"{context_dir}/parent-surfaces.json"
567
+ _write_project_text(
568
+ project,
569
+ index_manifest_relative,
570
+ json.dumps(
571
+ {
572
+ "kind": "parent-surface-index",
573
+ "format": "json-array",
574
+ "entry_count": len(surface_index),
575
+ **index_context,
576
+ },
577
+ indent=2,
578
+ ),
579
+ should_cancel=should_cancel,
580
+ )
581
+ return {
582
+ "name": parent.name,
583
+ "version": parent.version,
584
+ "doc_hash": parent.doc_hash,
585
+ "source_root": f"{project.workspace}/{context_dir}/parent-source",
586
+ "surface_count": len(surface_index),
587
+ "surface_index_manifest": f"{project.workspace}/{index_manifest_relative}",
588
+ "content_contract": (
589
+ "Read surface_index_manifest, then concatenate its content_files and parse that JSON "
590
+ "array. Each index entry points to one independently readable surface manifest. "
591
+ "Within a surface manifest, concatenate content_files exactly to reconstruct the "
592
+ "surface. Pathful code is also mirrored beneath source_root at its exact path."
593
+ ),
594
+ }
595
+
596
+
597
+ def _materialize_context_content(
598
+ project: AgentProject,
599
+ content: str,
600
+ *,
601
+ directory: str,
602
+ extension: str,
603
+ include_contract: bool = True,
604
+ should_cancel: Callable[[], bool] | None = None,
605
+ ) -> JsonObject:
606
+ """Write exact ordered chunks that each fit in one project ``read_file`` result."""
607
+ chunk_count = max(1, math.ceil(len(content) / _CONTEXT_CONTENT_CHUNK_CHARS))
608
+ width = max(3, len(str(chunk_count)))
609
+ content_files: list[str] = []
610
+ for chunk_index in range(chunk_count):
611
+ start = chunk_index * _CONTEXT_CONTENT_CHUNK_CHARS
612
+ chunk = content[start : start + _CONTEXT_CONTENT_CHUNK_CHARS]
613
+ relative = (
614
+ f"{directory}/part-{chunk_index + 1:0{width}d}-of-{chunk_count:0{width}d}{extension}"
615
+ )
616
+ _write_project_text(
617
+ project,
618
+ relative,
619
+ chunk,
620
+ should_cancel=should_cancel,
621
+ )
622
+ content_files.append(f"{project.workspace}/{relative}")
623
+ result: JsonObject = {
624
+ "content_length": len(content),
625
+ "content_files": content_files,
626
+ }
627
+ if include_contract:
628
+ result["content_contract"] = (
629
+ "Read content_files in listed order and concatenate them exactly. Each file is "
630
+ "independently readable without truncation."
631
+ )
632
+ return result
633
+
634
+
635
+ def _project_history_entry(
636
+ delta: HarnessDelta,
637
+ *,
638
+ proposal_file: str | None,
639
+ evaluation_dir: str | None,
640
+ workspace: str,
641
+ ) -> JsonObject:
642
+ """Compact judged metadata while raw proposals retain exact replacement payloads.
643
+
644
+ Re-serializing every prior full code surface into every later iteration makes persistent history
645
+ quadratic in run length. The project already owns each raw proposal file, so history carries
646
+ queryable identities, rationales, sizes, and verdicts plus a direct pointer to the exact bytes.
647
+ """
648
+ ops: list[JsonObject] = []
649
+ for op in delta.ops:
650
+ item: JsonObject = {
651
+ "op": op.op,
652
+ "surface_id": op.surface_id,
653
+ "rationale": op.rationale[:2_000],
654
+ "content_length": len(op.content) if op.content is not None else 0,
655
+ }
656
+ if op.kind is not None:
657
+ item["kind"] = op.kind.value
658
+ if op.path is not None:
659
+ item["path"] = op.path
660
+ if op.budget is not None:
661
+ item["budget"] = op.budget
662
+ ops.append(item)
663
+ return {
664
+ "delta_id": delta.delta_id,
665
+ "parent_doc_hash": delta.parent_doc_hash,
666
+ "child_doc_hash": delta.child_doc_hash,
667
+ "trigger": delta.trigger.model_dump(mode="json"),
668
+ "preconditions": dict(delta.preconditions),
669
+ "expected_effect": delta.expected_effect[:2_000],
670
+ "ops": ops,
671
+ "verdict": delta.verdict.model_dump(mode="json") if delta.verdict is not None else None,
672
+ "proposal_file": proposal_file,
673
+ "evaluation_dir": (f"{workspace}/{evaluation_dir}" if evaluation_dir is not None else None),
674
+ "content_contract": (
675
+ "Exact op content remains in proposal_file; this entry intentionally omits it to "
676
+ "keep cumulative judged history linear and fast."
677
+ ),
678
+ }
679
+
680
+
681
+ def _stamp_project_preconditions(
682
+ parent: HarnessDoc, proposal: HarnessDelta | None
683
+ ) -> HarnessDelta | None:
684
+ """Stamp missing concurrency metadata from the exact project-iteration parent.
685
+
686
+ The ordinary agent still chooses every semantic operation. The host owns this mechanical
687
+ identity field because it wrote the immutable parent snapshot for the same iteration.
688
+ An explicitly supplied but incorrect hash is preserved so normal validation rejects it.
689
+ """
690
+ if proposal is None:
691
+ return None
692
+ for op in proposal.ops:
693
+ if op.op not in ("replace", "remove") or op.surface_id in proposal.preconditions:
694
+ continue
695
+ surface = parent.surface(op.surface_id)
696
+ if surface is not None:
697
+ proposal.preconditions[op.surface_id] = surface.content_hash
698
+ return proposal
699
+
700
+
701
+ def _validate_project_proposals(
702
+ project: AgentProject,
703
+ parent: HarnessDoc,
704
+ trigger: FailureSignature,
705
+ *,
706
+ proposal_dir: str,
707
+ slots: list[int],
708
+ history: list[HarnessDelta],
709
+ valid_proposals: dict[int, HarnessDelta] | None = None,
710
+ preserve_runtime_kind: bool,
711
+ should_cancel: Callable[[], bool] | None,
712
+ ) -> _ProposalValidation:
713
+ """Parse, stamp, apply, and de-duplicate selected project proposal slots.
714
+
715
+ Applying a deep copy exercises the complete typed ``HarnessDoc`` boundary without stamping
716
+ ``child_doc_hash`` onto the delta the search will later apply and archive. Previously the
717
+ project proposer returned syntactically parsed deltas and left this check to the search loop,
718
+ where a missing skill frontmatter block consumed an iteration as ``invalid before eval``.
719
+ """
720
+ accepted = dict(valid_proposals or {})
721
+ raw_files: dict[int, str] = {}
722
+ child_hashes: dict[int, str] = {}
723
+ errors: dict[int, str] = {}
724
+ history_ids = {delta.delta_id for delta in history}
725
+ history_child_hashes = {
726
+ delta.child_doc_hash for delta in history if delta.child_doc_hash is not None
727
+ }
728
+ sibling_ids = {delta.delta_id: index for index, delta in accepted.items()}
729
+ for accepted_index, accepted_delta in accepted.items():
730
+ accepted_child = apply_delta(
731
+ parent,
732
+ accepted_delta.model_copy(deep=True),
733
+ f"{parent.name}-accepted-preflight-{accepted_index:02d}",
734
+ )
735
+ child_hashes[accepted_index] = accepted_child.doc_hash
736
+ sibling_child_hashes = {child_hash: index for index, child_hash in child_hashes.items()}
737
+ parent_runtime_kind = parent.runtime_kind()
738
+ for index in slots:
739
+ _check_cancelled(should_cancel)
740
+ relative = f"{proposal_dir}/proposal-{index:02d}.json"
741
+ try:
742
+ raw = _read_project_text(project, relative, should_cancel=should_cancel)
743
+ except HarnessSearchCancelled:
744
+ raise
745
+ except Exception as error: # noqa: BLE001 - one missing file is one repairable slot
746
+ errors[index] = f"proposal file is missing or unreadable: {error}"
747
+ continue
748
+ raw_files[index] = raw
749
+ proposal = parse_delta(parent, trigger, raw)
750
+ if proposal is None:
751
+ errors[index] = "proposal is not a parseable typed delta JSON object"
752
+ continue
753
+ stamped = _stamp_project_preconditions(parent, proposal)
754
+ assert stamped is not None
755
+ try:
756
+ child = apply_delta(
757
+ parent,
758
+ stamped.model_copy(deep=True),
759
+ f"{parent.name}-proposal-preflight-{index:02d}",
760
+ )
761
+ child_runtime_kind = child.runtime_kind()
762
+ if child_runtime_kind not in _SUPPORTED_RUNTIME_KINDS:
763
+ supported = ", ".join(sorted(_SUPPORTED_RUNTIME_KINDS))
764
+ raise ValueError(
765
+ f"project proposal resolves to unsupported runtime kind "
766
+ f"{child_runtime_kind!r}; choose one of: {supported}"
767
+ )
768
+ if preserve_runtime_kind and child_runtime_kind != parent_runtime_kind:
769
+ raise ValueError(
770
+ "project proposals must preserve the parent's runtime kind "
771
+ f"{parent_runtime_kind!r}; this proposal resolves to {child_runtime_kind!r}"
772
+ )
773
+ except ValueError as error:
774
+ errors[index] = f"delta does not apply to the supplied parent: {error}"
775
+ continue
776
+ child_hash = child.doc_hash
777
+ if child_hash == parent.doc_hash:
778
+ errors[index] = "delta is a semantic no-op: its child document equals the parent"
779
+ continue
780
+ if stamped.delta_id in history_ids:
781
+ errors[index] = (
782
+ f"delta {stamped.delta_id} duplicates a proposal already present in judged history"
783
+ )
784
+ continue
785
+ if child_hash in history_child_hashes:
786
+ errors[index] = (
787
+ f"child document {child_hash} duplicates a proposal already present in "
788
+ "judged history"
789
+ )
790
+ continue
791
+ duplicate_slot = sibling_ids.get(stamped.delta_id)
792
+ if duplicate_slot is not None:
793
+ errors[index] = (
794
+ f"delta {stamped.delta_id} duplicates valid sibling proposal-{duplicate_slot:02d}"
795
+ )
796
+ continue
797
+ duplicate_child_slot = sibling_child_hashes.get(child_hash)
798
+ if duplicate_child_slot is not None:
799
+ errors[index] = (
800
+ f"child document {child_hash} duplicates valid sibling "
801
+ f"proposal-{duplicate_child_slot:02d}"
802
+ )
803
+ continue
804
+ accepted[index] = stamped
805
+ sibling_ids[stamped.delta_id] = index
806
+ child_hashes[index] = child_hash
807
+ sibling_child_hashes[child_hash] = index
808
+ _check_cancelled(should_cancel)
809
+ return _ProposalValidation(
810
+ proposals=accepted,
811
+ raw_files=raw_files,
812
+ child_hashes=child_hashes,
813
+ errors=errors,
814
+ )
815
+
816
+
817
+ def _proposal_validation_report(
818
+ *,
819
+ parent: HarnessDoc,
820
+ proposal_dir: str,
821
+ attempt: int,
822
+ valid_slots: dict[int, HarnessDelta],
823
+ errors: dict[int, str],
824
+ ) -> str:
825
+ """Serialize actionable per-slot host validation for the project and run audit."""
826
+ return json.dumps(
827
+ {
828
+ "kind": "proposal-validation",
829
+ "attempt": attempt,
830
+ "parent_doc_hash": parent.doc_hash,
831
+ "parent_runtime_kind": parent.runtime_kind(),
832
+ "valid_slots": sorted(valid_slots),
833
+ "errors": [
834
+ {
835
+ "slot": index,
836
+ "proposal_file": f"{proposal_dir}/proposal-{index:02d}.json",
837
+ "reason": errors[index],
838
+ }
839
+ for index in sorted(errors)
840
+ ],
841
+ },
842
+ indent=2,
843
+ )
844
+
845
+
846
+ def _project_repair_request(
847
+ *,
848
+ workspace: str,
849
+ validation_path: str,
850
+ request_path: str,
851
+ proposal_dir: str,
852
+ errors: dict[int, str],
853
+ valid_slots: dict[int, HarnessDelta],
854
+ runtime_kind: str,
855
+ preserve_runtime_kind: bool,
856
+ repair_turn: int,
857
+ ) -> str:
858
+ """Render one of the bounded repair turns for only invalid batch slots."""
859
+ invalid_outputs = "\n".join(
860
+ f"- {workspace}/{proposal_dir}/proposal-{index:02d}.json" for index in sorted(errors)
861
+ )
862
+ protected_outputs = "\n".join(
863
+ f"- {workspace}/{proposal_dir}/proposal-{index:02d}.json" for index in sorted(valid_slots)
864
+ )
865
+ if not protected_outputs:
866
+ protected_outputs = "- (none)"
867
+ runtime_constraint = (
868
+ f"preserve its resolved runtime kind {runtime_kind!r}"
869
+ if preserve_runtime_kind
870
+ else "produce a valid resolved runtime kind"
871
+ )
872
+ return f"""Repair exactly {len(errors)} invalid proposal slot(s) from this iteration.
873
+ This is repair turn {repair_turn} of {_MAX_PROJECT_REPAIR_TURNS}.
874
+
875
+ Read the host validation report: {workspace}/{validation_path}
876
+ It contains the exact error for each invalid slot. Re-read the original iteration request at
877
+ {workspace}/{request_path} and its supplied parent manifests as needed, then rewrite ONLY these
878
+ invalid files:
879
+ {invalid_outputs}
880
+
881
+ These siblings already passed host preflight. Do not rewrite them:
882
+ {protected_outputs}
883
+
884
+ Every repaired file must follow the original typed delta JSON schema, apply cleanly to that same
885
+ parent, {runtime_constraint}, produce a child document different from the parent, differ from
886
+ judged history, and differ from every sibling. A skill's content must include the complete
887
+ four-line frontmatter shown in the original request. Rewrite every invalid file immediately after
888
+ reading the validation report; only then spend remaining actions on optional evidence. Validate
889
+ every rewritten file before calling submit with a short summary."""
890
+
891
+
892
+ def _project_request(
893
+ *,
894
+ workspace: str,
895
+ context_dir: str,
896
+ proposal_dir: str,
897
+ count: int,
898
+ runtime_kind: str,
899
+ preserve_runtime_kind: bool,
900
+ ) -> str:
901
+ """Render one filesystem-first proposal task for the ordinary meta agent."""
902
+ absolute_context = f"{workspace}/{context_dir}"
903
+ absolute_proposals = f"{workspace}/{proposal_dir}"
904
+ outputs = "\n".join(
905
+ f"- {absolute_proposals}/proposal-{index:02d}.json" for index in range(1, count + 1)
906
+ )
907
+ runtime_constraint = (
908
+ f"This project must preserve the parent's resolved runtime kind {runtime_kind!r}; do not "
909
+ "add, replace, or remove runtime-kind in a way that changes it."
910
+ if preserve_runtime_kind
911
+ else (
912
+ "A runtime-kind edit is allowed only when the resulting child remains a valid harness; "
913
+ "the search backend makes the final executability decision."
914
+ )
915
+ )
916
+ heading = (
917
+ f"Produce exactly {count} independent harness proposals for this optimization iteration."
918
+ )
919
+ return f"""{heading}
920
+
921
+ Read:
922
+ - parent manifest: {absolute_context}/parent.json
923
+ - follow surface_index_manifest to find every independently readable surface manifest
924
+ - each surface manifest lists ordered content_files; concatenate them to inspect exact content
925
+ - pathful code is also mirrored under source_root with exact source_file paths for direct reads
926
+ - failure evidence manifest: {absolute_context}/evidence.json
927
+ - read its content_files in listed order and concatenate them exactly
928
+ - judged history manifest: {absolute_context}/history.json
929
+ - read its content_files in listed order, concatenate them exactly, then parse the JSON array
930
+ - earlier raw proposals, when useful: {workspace}/proposals/
931
+ - earlier candidate evaluation manifests and traces: {workspace}/evaluations/
932
+
933
+ Write exactly these files, without changing earlier iterations:
934
+ {outputs}
935
+
936
+ Each file must be one JSON object:
937
+ {{"expected_effect":"<falsifiable prediction>",
938
+ "preconditions":{{"<surface id>":"<hash copied from parent>"}},
939
+ "ops":[{{"op":"add|replace|remove","surface_id":"<kind:slug>",
940
+ "kind":"<required for add>","content":"<full content>",
941
+ "rationale":"<why this helps>"}}]}}
942
+
943
+ For a replacement, you may omit content and use compact exact edits instead:
944
+ "edits":[{{"old":"<nonempty text occurring exactly once>","new":"<replacement>"}}].
945
+ The optimizer expands those edits against the parent before validation.
946
+
947
+ Typed surface constraints (host preflight enforces all of these before evaluation):
948
+ - Every surface id is `<kind>:<kebab-slug>` and its prefix must exactly match `kind`.
949
+ - `add` needs a fresh id, `kind`, full `content`, and a nonempty `rationale`. `replace` needs an
950
+ existing id, full `content` or exact `edits`, and a nonempty `rationale`; if it declares `kind`,
951
+ that kind must match the parent. `remove` needs an existing id and rationale and must omit
952
+ content. Every replace/remove target must have its exact parent hash in `preconditions`.
953
+ - A `skill:<slug>` add/replace has kind `skill`; its content is the complete markdown below,
954
+ beginning at the first character, with kebab-case `name` exactly equal to `<slug>`:
955
+ ---
956
+ name: <slug>
957
+ description: <one-line description of when the agent should use this skill>
958
+ ---
959
+ <nonempty reusable technique body>
960
+ - Prompt content is plain text, and the child must retain at least one prompt surface.
961
+ - `tool_policy:main` is one registered tool name per line and must retain `submit`.
962
+ - Supported scalar params are `param:max-turns` and `param:max-output-tokens` (integers >= 1),
963
+ `param:temperature` (number in [0, 2]), and `param:runtime-kind` (`kit-python` or `pi-node`).
964
+ {runtime_constraint}
965
+ - A path-less code surface can only be `code:runtime` and must remain valid Python defining
966
+ `run(kit)`. Pathful code surfaces use safe relative paths without `..`; replacements inherit the
967
+ parent's path unless explicitly supplied, and paths must stay unique.
968
+ - Respect each surface's character budget. Do not remove required singleton surfaces or create
969
+ duplicate ids/paths. Do not emit a semantic no-op or repeat any child document from judged
970
+ history or another sibling, even through differently ordered operations.
971
+
972
+ Every proposal must be focused, valid against the same supplied parent, and meaningfully different
973
+ from its siblings. Your project tool budget is bounded: after reading the three root manifests,
974
+ write a complete, parseable draft to every output before doing deeper optional exploration. Keep
975
+ those files valid as you refine them. The host will parse, stamp mechanical missing preconditions,
976
+ deep-copy apply, and de-duplicate every file. Invalid slots receive at most two repair turns and
977
+ are never evaluated. After all files exist, call submit with a short summary."""