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.
- llm_waterfall/LICENSE +21 -0
- llm_waterfall/__init__.py +53 -0
- llm_waterfall/adapters/__init__.py +36 -0
- llm_waterfall/adapters/anthropic.py +105 -0
- llm_waterfall/adapters/aws_mantle.py +47 -0
- llm_waterfall/adapters/azure_openai.py +71 -0
- llm_waterfall/adapters/base.py +51 -0
- llm_waterfall/adapters/bedrock.py +309 -0
- llm_waterfall/adapters/openai.py +130 -0
- llm_waterfall/classify.py +184 -0
- llm_waterfall/pricing.py +110 -0
- llm_waterfall/py.typed +0 -0
- llm_waterfall/types.py +295 -0
- llm_waterfall/waterfall.py +255 -0
- wmo/__init__.py +38 -0
- wmo/agents/__init__.py +7 -0
- wmo/agents/default.py +29 -0
- wmo/agents/meta.py +55 -0
- wmo/agents/optimizer.py +55 -0
- wmo/agents/project.py +928 -0
- wmo/cli/__init__.py +5 -0
- wmo/cli/agent_session.py +1123 -0
- wmo/cli/app.py +2489 -0
- wmo/cli/e2b_cmds.py +212 -0
- wmo/cli/eval_closed_loop.py +207 -0
- wmo/cli/harness_app.py +1147 -0
- wmo/cli/harness_distill.py +659 -0
- wmo/cli/hosted_session.py +880 -0
- wmo/cli/ingest_cmd.py +165 -0
- wmo/cli/model_roles.py +82 -0
- wmo/cli/platform_cmds.py +372 -0
- wmo/cli/route_app.py +274 -0
- wmo/cli/session_state.py +243 -0
- wmo/cli/ui.py +1107 -0
- wmo/cli/workspace_sync.py +504 -0
- wmo/config/__init__.py +60 -0
- wmo/config/card.py +129 -0
- wmo/config/config.py +367 -0
- wmo/config/dotenv.py +67 -0
- wmo/config/settings.py +128 -0
- wmo/config/store.py +177 -0
- wmo/conftest.py +19 -0
- wmo/connect/__init__.py +88 -0
- wmo/connect/apps.py +78 -0
- wmo/connect/brave.py +284 -0
- wmo/connect/connector.py +79 -0
- wmo/connect/credentials.py +164 -0
- wmo/connect/github.py +321 -0
- wmo/connect/google.py +627 -0
- wmo/connect/notion.py +790 -0
- wmo/connect/oauth.py +461 -0
- wmo/connect/slack.py +555 -0
- wmo/connect/store.py +199 -0
- wmo/connect/types.py +156 -0
- wmo/core/__init__.py +21 -0
- wmo/core/parsing.py +281 -0
- wmo/core/render.py +271 -0
- wmo/core/text.py +40 -0
- wmo/core/types.py +116 -0
- wmo/distill/__init__.py +14 -0
- wmo/distill/agents.py +140 -0
- wmo/distill/config.py +1006 -0
- wmo/distill/cost.py +437 -0
- wmo/distill/data.py +921 -0
- wmo/distill/deadlines.py +254 -0
- wmo/distill/fake_tinker.py +734 -0
- wmo/distill/gate.py +122 -0
- wmo/distill/loop.py +3499 -0
- wmo/distill/renderers.py +399 -0
- wmo/distill/rendering.py +620 -0
- wmo/distill/rollouts.py +726 -0
- wmo/distill/samples.py +195 -0
- wmo/distill/store.py +829 -0
- wmo/distill/teacher.py +714 -0
- wmo/distill/tokens.py +535 -0
- wmo/distill/tracking.py +552 -0
- wmo/distill/tripwire.py +411 -0
- wmo/distill/xtoken/byte_offsets.py +152 -0
- wmo/distill/xtoken/chunks.py +457 -0
- wmo/distill/xtoken/prompt_logprobs.py +475 -0
- wmo/distill/xtoken/teacher_render.py +346 -0
- wmo/engine/__init__.py +28 -0
- wmo/engine/autoconfig.py +367 -0
- wmo/engine/build.py +346 -0
- wmo/engine/demo.py +77 -0
- wmo/engine/eval_suites.py +245 -0
- wmo/engine/grounding.py +491 -0
- wmo/engine/knowledge.py +291 -0
- wmo/engine/loader.py +36 -0
- wmo/engine/play.py +92 -0
- wmo/engine/prompts.py +99 -0
- wmo/engine/replay.py +443 -0
- wmo/engine/reporting.py +58 -0
- wmo/engine/workspace.py +468 -0
- wmo/engine/world_model.py +568 -0
- wmo/env/__init__.py +22 -0
- wmo/env/base.py +121 -0
- wmo/env/closed_loop.py +229 -0
- wmo/env/episode.py +107 -0
- wmo/env/llm_agent.py +93 -0
- wmo/env/scenarios.py +73 -0
- wmo/evals/__init__.py +52 -0
- wmo/evals/agreement.py +110 -0
- wmo/evals/base.py +45 -0
- wmo/evals/closed_loop.py +480 -0
- wmo/evals/failover.py +96 -0
- wmo/evals/gold.py +127 -0
- wmo/evals/grid.py +394 -0
- wmo/evals/grid_plot.py +205 -0
- wmo/evals/harbor/__init__.py +27 -0
- wmo/evals/harbor/agent.py +573 -0
- wmo/evals/harbor/ctrf.py +171 -0
- wmo/evals/harbor/e2b_environment.py +587 -0
- wmo/evals/harbor/e2b_template_policy.py +144 -0
- wmo/evals/harbor/scorer.py +875 -0
- wmo/evals/harbor/tasks.py +140 -0
- wmo/evals/open_loop.py +194 -0
- wmo/evals/tasks.py +53 -0
- wmo/harness/__init__.py +51 -0
- wmo/harness/code_runtime.py +288 -0
- wmo/harness/create.py +1191 -0
- wmo/harness/delta.py +220 -0
- wmo/harness/doc.py +556 -0
- wmo/harness/e2b_ledger.py +342 -0
- wmo/harness/e2b_reap.py +476 -0
- wmo/harness/e2b_sandbox.py +350 -0
- wmo/harness/environment.py +35 -0
- wmo/harness/live_session.py +543 -0
- wmo/harness/mutate.py +343 -0
- wmo/harness/pi_e2b.py +1710 -0
- wmo/harness/pi_entry/entry.ts +268 -0
- wmo/harness/pi_entry/runner_frames.ts +92 -0
- wmo/harness/pi_entry/runner_live.ts +587 -0
- wmo/harness/pi_entry/runner_service.ts +270 -0
- wmo/harness/pi_entry/runner_stdio.ts +374 -0
- wmo/harness/pi_entry/runner_termination.ts +142 -0
- wmo/harness/pi_local.py +262 -0
- wmo/harness/pi_runtime.py +495 -0
- wmo/harness/pi_vendor.py +65 -0
- wmo/harness/population.py +509 -0
- wmo/harness/project_proposer.py +569 -0
- wmo/harness/proposer.py +977 -0
- wmo/harness/runner_link.py +619 -0
- wmo/harness/runtime.py +389 -0
- wmo/harness/scoring.py +247 -0
- wmo/harness/skills.py +116 -0
- wmo/harness/source_tree.py +319 -0
- wmo/harness/store.py +176 -0
- wmo/harness/tools.py +105 -0
- wmo/harness/vendor/manifest.sha256 +58 -0
- wmo/harness/vendor/pi-agent/CHANGELOG.md +556 -0
- wmo/harness/vendor/pi-agent/LICENSE +21 -0
- wmo/harness/vendor/pi-agent/README.md +488 -0
- wmo/harness/vendor/pi-agent/VENDOR.md +39 -0
- wmo/harness/vendor/pi-agent/docs/agent-harness.md +486 -0
- wmo/harness/vendor/pi-agent/docs/durable-harness.md +212 -0
- wmo/harness/vendor/pi-agent/docs/hooks.md +445 -0
- wmo/harness/vendor/pi-agent/docs/models.md +966 -0
- wmo/harness/vendor/pi-agent/docs/observability.md +376 -0
- wmo/harness/vendor/pi-agent/package.json +60 -0
- wmo/harness/vendor/pi-agent/src/agent-loop.ts +748 -0
- wmo/harness/vendor/pi-agent/src/agent.ts +575 -0
- wmo/harness/vendor/pi-agent/src/harness/agent-harness.ts +1029 -0
- wmo/harness/vendor/pi-agent/src/harness/compaction/branch-summarization.ts +261 -0
- wmo/harness/vendor/pi-agent/src/harness/compaction/compaction.ts +747 -0
- wmo/harness/vendor/pi-agent/src/harness/compaction/utils.ts +144 -0
- wmo/harness/vendor/pi-agent/src/harness/env/nodejs.ts +550 -0
- wmo/harness/vendor/pi-agent/src/harness/messages.ts +164 -0
- wmo/harness/vendor/pi-agent/src/harness/prompt-templates.ts +267 -0
- wmo/harness/vendor/pi-agent/src/harness/session/jsonl-repo.ts +177 -0
- wmo/harness/vendor/pi-agent/src/harness/session/jsonl-storage.ts +293 -0
- wmo/harness/vendor/pi-agent/src/harness/session/memory-repo.ts +50 -0
- wmo/harness/vendor/pi-agent/src/harness/session/memory-storage.ts +131 -0
- wmo/harness/vendor/pi-agent/src/harness/session/repo-utils.ts +51 -0
- wmo/harness/vendor/pi-agent/src/harness/session/session.ts +267 -0
- wmo/harness/vendor/pi-agent/src/harness/session/uuid.ts +54 -0
- wmo/harness/vendor/pi-agent/src/harness/skills.ts +375 -0
- wmo/harness/vendor/pi-agent/src/harness/system-prompt.ts +34 -0
- wmo/harness/vendor/pi-agent/src/harness/types.ts +836 -0
- wmo/harness/vendor/pi-agent/src/harness/utils/shell-output.ts +135 -0
- wmo/harness/vendor/pi-agent/src/harness/utils/truncate.ts +344 -0
- wmo/harness/vendor/pi-agent/src/index.ts +44 -0
- wmo/harness/vendor/pi-agent/src/node.ts +2 -0
- wmo/harness/vendor/pi-agent/src/proxy.ts +367 -0
- wmo/harness/vendor/pi-agent/src/types.ts +428 -0
- wmo/harness/vendor/pi-agent/test/agent-loop.test.ts +1351 -0
- wmo/harness/vendor/pi-agent/test/agent.test.ts +699 -0
- wmo/harness/vendor/pi-agent/test/e2e.test.ts +404 -0
- wmo/harness/vendor/pi-agent/test/harness/agent-harness-stream.test.ts +213 -0
- wmo/harness/vendor/pi-agent/test/harness/agent-harness.test.ts +608 -0
- wmo/harness/vendor/pi-agent/test/harness/compaction.test.ts +655 -0
- wmo/harness/vendor/pi-agent/test/harness/nodejs-env.test.ts +321 -0
- wmo/harness/vendor/pi-agent/test/harness/prompt-templates.test.ts +90 -0
- wmo/harness/vendor/pi-agent/test/harness/repo.test.ts +68 -0
- wmo/harness/vendor/pi-agent/test/harness/resource-formatting.test.ts +24 -0
- wmo/harness/vendor/pi-agent/test/harness/session-test-utils.ts +55 -0
- wmo/harness/vendor/pi-agent/test/harness/session-uuid.test.ts +50 -0
- wmo/harness/vendor/pi-agent/test/harness/session.test.ts +156 -0
- wmo/harness/vendor/pi-agent/test/harness/skills.test.ts +116 -0
- wmo/harness/vendor/pi-agent/test/harness/storage.test.ts +299 -0
- wmo/harness/vendor/pi-agent/test/harness/system-prompt.test.ts +66 -0
- wmo/harness/vendor/pi-agent/test/harness/truncate.test.ts +169 -0
- wmo/harness/vendor/pi-agent/test/scratch/simple.ts +72 -0
- wmo/harness/vendor/pi-agent/test/utils/calculate.ts +32 -0
- wmo/harness/vendor/pi-agent/test/utils/get-current-time.ts +46 -0
- wmo/harness/vendor/pi-agent/tsconfig.build.json +13 -0
- wmo/harness/vendor/pi-agent/vitest.config.ts +19 -0
- wmo/harness/vendor/pi-agent/vitest.harness.config.ts +28 -0
- wmo/harness/vendor/vendor_pi.sh +59 -0
- wmo/harness/workspace_patch.py +270 -0
- wmo/ingest/__init__.py +47 -0
- wmo/ingest/adapter.py +72 -0
- wmo/ingest/base.py +114 -0
- wmo/ingest/braintrust.py +339 -0
- wmo/ingest/detect.py +126 -0
- wmo/ingest/langfuse.py +291 -0
- wmo/ingest/langsmith.py +444 -0
- wmo/ingest/mastra.py +330 -0
- wmo/ingest/messages.py +170 -0
- wmo/ingest/normalize.py +679 -0
- wmo/ingest/otel_genai.py +69 -0
- wmo/ingest/otel_writer.py +100 -0
- wmo/ingest/phoenix.py +150 -0
- wmo/ingest/postgres.py +246 -0
- wmo/ingest/posthog.py +320 -0
- wmo/ingest/quality.py +28 -0
- wmo/ingest/stream.py +209 -0
- wmo/ingest/testdata/sample_otlp.json +60 -0
- wmo/ingest/testdata/sample_spans.jsonl +3 -0
- wmo/optimize/__init__.py +25 -0
- wmo/optimize/base.py +143 -0
- wmo/optimize/gepa.py +806 -0
- wmo/optimize/judge.py +262 -0
- wmo/optimize/judge_quality.py +359 -0
- wmo/optimize/knn.py +468 -0
- wmo/optimize/numeric.py +152 -0
- wmo/optimize/outcomes.py +103 -0
- wmo/optimize/policy.py +669 -0
- wmo/optimize/report.py +231 -0
- wmo/optimize/reward.py +129 -0
- wmo/optimize/routing.py +373 -0
- wmo/platform/__init__.py +6 -0
- wmo/platform/auth.py +115 -0
- wmo/platform/client.py +551 -0
- wmo/platform/credentials.py +126 -0
- wmo/platform/transfer.py +158 -0
- wmo/providers/__init__.py +40 -0
- wmo/providers/_bedrock_chat.py +155 -0
- wmo/providers/_openai_common.py +182 -0
- wmo/providers/_responses_common.py +472 -0
- wmo/providers/anthropic.py +134 -0
- wmo/providers/azure_openai.py +296 -0
- wmo/providers/base.py +300 -0
- wmo/providers/bedrock.py +312 -0
- wmo/providers/models.py +205 -0
- wmo/providers/openai.py +143 -0
- wmo/providers/openai_responses.py +240 -0
- wmo/providers/pool.py +170 -0
- wmo/providers/registry.py +73 -0
- wmo/providers/retry.py +151 -0
- wmo/providers/tinker.py +936 -0
- wmo/providers/waterfall.py +336 -0
- wmo/research/__init__.py +81 -0
- wmo/research/ablation.py +133 -0
- wmo/research/concurrency_plot.py +523 -0
- wmo/research/concurrency_run.py +240 -0
- wmo/research/concurrency_scaling.py +270 -0
- wmo/research/gepa_scaling.py +274 -0
- wmo/research/pipeline.py +198 -0
- wmo/research/scaling_split.py +82 -0
- wmo/research/scenario_fidelity.py +198 -0
- wmo/research/scenario_recovery.py +92 -0
- wmo/research/seed_stability.py +90 -0
- wmo/research/trace_scaling.py +348 -0
- wmo/retrieval/__init__.py +6 -0
- wmo/retrieval/embedders.py +105 -0
- wmo/retrieval/leakfree.py +52 -0
- wmo/retrieval/retriever.py +173 -0
- wmo/scenarios/__init__.py +58 -0
- wmo/scenarios/builder.py +152 -0
- wmo/scenarios/mining/__init__.py +27 -0
- wmo/scenarios/mining/clustering.py +171 -0
- wmo/scenarios/mining/facets.py +226 -0
- wmo/scenarios/mining/selection.py +220 -0
- wmo/scenarios/synthesis/__init__.py +6 -0
- wmo/scenarios/synthesis/scenario_set.py +63 -0
- wmo/scenarios/synthesis/synthesizer.py +85 -0
- wmo/scenarios/verification/__init__.py +17 -0
- wmo/scenarios/verification/judge.py +97 -0
- wmo/scenarios/verification/verify.py +135 -0
- wmo/serving/__init__.py +5 -0
- wmo/serving/builds.py +451 -0
- wmo/serving/chat.py +878 -0
- wmo/serving/endpoint_config.py +64 -0
- wmo/serving/savings.py +250 -0
- wmo/serving/server.py +553 -0
- wmo/serving/traces_source.py +206 -0
- wmo/telemetry.py +213 -0
- wmo/tracking/__init__.py +36 -0
- wmo/tracking/clock.py +24 -0
- wmo/tracking/metered.py +125 -0
- wmo/tracking/pricing.py +99 -0
- wmo/tracking/store.py +31 -0
- wmo/tracking/tracker.py +149 -0
- world_model_optimizer-0.2.0.dist-info/METADATA +203 -0
- world_model_optimizer-0.2.0.dist-info/RECORD +308 -0
- world_model_optimizer-0.2.0.dist-info/WHEEL +4 -0
- world_model_optimizer-0.2.0.dist-info/entry_points.txt +2 -0
wmo/harness/proposer.py
ADDED
|
@@ -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."""
|