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/mutate.py
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
"""The delta proposer: the meta-agent reads a harness document and emits one `HarnessDelta`.
|
|
2
|
+
|
|
3
|
+
The proposer is shown the parent's surfaces — each with its id, kind, content hash, and content —
|
|
4
|
+
plus the failure evidence for one clustered mechanism, and replies with the delta's ops,
|
|
5
|
+
preconditions, and expected effect as JSON. The trigger, lineage, and identity fields are filled by
|
|
6
|
+
the caller from ground truth (the cluster and the parent's hashes), never trusted from the model.
|
|
7
|
+
|
|
8
|
+
Preconditions are copy-not-guess: the prompt prints every surface's current hash, and the proposer
|
|
9
|
+
must echo the hash of each surface it replaces or removes. `apply_delta` then rejects atomically on
|
|
10
|
+
any mismatch, so a proposal is only ever applied to exactly the document it was drafted against.
|
|
11
|
+
|
|
12
|
+
An unusable reply (no JSON, wrong shape) returns None — the search counts it as a skipped
|
|
13
|
+
iteration; a flaky meta-model costs budget, not the run.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
|
|
20
|
+
from pydantic import BaseModel, Field, ValidationError
|
|
21
|
+
|
|
22
|
+
from wmo.core.parsing import extract_json_object
|
|
23
|
+
from wmo.core.text import normalize_durable_text
|
|
24
|
+
from wmo.evals.closed_loop import ClosedLoopReport, RolloutEvidence
|
|
25
|
+
from wmo.evals.gold import GoldVerdict
|
|
26
|
+
from wmo.evals.tasks import TaskSpec
|
|
27
|
+
from wmo.harness.delta import FailureSignature, HarnessDelta, SurfaceOp, compute_delta_id
|
|
28
|
+
from wmo.harness.doc import HarnessDoc
|
|
29
|
+
from wmo.harness.tools import SUBMIT, TOOL_REGISTRY
|
|
30
|
+
from wmo.providers.base import Message, Provider
|
|
31
|
+
|
|
32
|
+
_TRACE_CHARS_PER_ATTEMPT = 12_000
|
|
33
|
+
|
|
34
|
+
MUTATE_SYSTEM = f"""You are a meta-agent improving an agent harness — a document of named \
|
|
35
|
+
SURFACES that configure an agent. You do NOT solve the agent's tasks yourself. You are shown the \
|
|
36
|
+
harness's current surfaces (each with its id, kind, and content hash) and evidence of ONE failure \
|
|
37
|
+
mechanism its agent exhibits. Propose exactly ONE focused change, grounded in that evidence. Do \
|
|
38
|
+
not shotgun many unrelated changes at once.
|
|
39
|
+
|
|
40
|
+
Choose the lever that can actually EXPRESS the fix:
|
|
41
|
+
- Structural/behavioral mechanisms (the agent claims without acting, loses context, wastes turns,
|
|
42
|
+
never verifies, mishandles errors) -> edit the agent's CODE. For an in-process harness that is
|
|
43
|
+
the singleton `code:runtime` program; for a real multi-file harness (e.g. the vendored pi
|
|
44
|
+
agent) it is the pathful `code:<...>` surface that owns the behavior — the agent's actual
|
|
45
|
+
source, shown to you above with its path. Edit the file where the mechanism lives (the turn
|
|
46
|
+
loop, context compaction, tool dispatch, answer checking). This is the strongest lever:
|
|
47
|
+
loops, verification passes, retries, observation truncation, and compaction are all code.
|
|
48
|
+
- A missing technique on specific tasks -> ADD a `skill:<slug>` teaching it.
|
|
49
|
+
- The agent misusing, missing, or not needing a tool -> edit `tool_policy:main`.
|
|
50
|
+
- Erratic sampling or turn caps -> adjust a `param:*`.
|
|
51
|
+
- Replace a `prompt:*` surface only for wording-level problems; prompt rewrites are the weakest
|
|
52
|
+
lever and most often regress tasks that currently pass.
|
|
53
|
+
|
|
54
|
+
The `code:runtime` contract — a Python module defining `run(kit) -> str` (the final answer):
|
|
55
|
+
- `kit.instruction` (the task), `kit.system_prompt` (the assembled prompt), `kit.task_id`.
|
|
56
|
+
- `kit.complete(system, messages, temperature=..., max_tokens=...) -> str` — one LLM call;
|
|
57
|
+
messages are `("user"|"assistant", content)` tuples. BUDGETED (default 40 calls/episode).
|
|
58
|
+
- `kit.execute(tool, arguments) -> Observation` (`.content`, `.is_error`) — one environment
|
|
59
|
+
action, validated against the tool policy. BUDGETED (default 40/episode). Every call is
|
|
60
|
+
recorded to the transcript the judge scores; work not done through `kit.execute` does not
|
|
61
|
+
exist as far as grading is concerned.
|
|
62
|
+
- `kit.parse_tool_call(text) -> ToolCall | None` (`.tool`, `.arguments`), `kit.tools_text()`,
|
|
63
|
+
`kit.skills_index()`, `kit.read_skill(name)`.
|
|
64
|
+
- Exceptions and exhausted budgets fail the episode (partial transcript kept). The module must
|
|
65
|
+
be self-contained (stdlib imports only) and deterministic apart from LLM calls.
|
|
66
|
+
|
|
67
|
+
Surface kinds:
|
|
68
|
+
- prompt — a section of the agent's system prompt (all prompt surfaces are joined in id order).
|
|
69
|
+
- tool_policy — the tool list, one tool name per line. Valid names: \
|
|
70
|
+
{", ".join(sorted(TOOL_REGISTRY))}. The `{SUBMIT.name}` tool is REQUIRED (without it a run \
|
|
71
|
+
cannot end).
|
|
72
|
+
- param — a scalar loop knob: `param:max-turns` (int >= 1), `param:max-output-tokens` for a
|
|
73
|
+
`pi-node` harness (int >= 1), or `param:temperature` (float in [0, 2]).
|
|
74
|
+
- code — the agent's source. Either the singleton in-process `code:runtime` (contract above; must
|
|
75
|
+
compile and define `run`), OR the pathful `code:<...>` files of a real multi-file harness (the
|
|
76
|
+
vendored pi agent's own source). Editing a pathful `code:` surface REPLACES that file's whole
|
|
77
|
+
content; keep it a valid module and change only what the fix needs — you are editing the real
|
|
78
|
+
harness that runs, not a description of it.
|
|
79
|
+
- skill — one reusable technique, shaped as:
|
|
80
|
+
---
|
|
81
|
+
name: <kebab-slug matching the surface id>
|
|
82
|
+
description: <one-line trigger description>
|
|
83
|
+
---
|
|
84
|
+
<skill body markdown>
|
|
85
|
+
|
|
86
|
+
Reply with ONLY a JSON object, no prose:
|
|
87
|
+
{{"expected_effect": "<falsifiable prediction: what should change if this works>",
|
|
88
|
+
"preconditions": {{"<surface_id>": "<that surface's content hash, copied from above>"}},
|
|
89
|
+
"ops": [{{"op": "add" | "replace" | "remove",
|
|
90
|
+
"surface_id": "<kind>:<kebab-slug>",
|
|
91
|
+
"kind": "<required on add>",
|
|
92
|
+
"content": "<the FULL new content (omit on remove)>",
|
|
93
|
+
"rationale": "<why this op should help>"}}]}}
|
|
94
|
+
|
|
95
|
+
Rules:
|
|
96
|
+
- Every surface you replace or remove MUST appear in `preconditions` with its hash copied verbatim.
|
|
97
|
+
- `content` is the complete new surface content, not a diff.
|
|
98
|
+
- An `add` uses a fresh surface id of the right kind (e.g. a new `skill:<slug>` or a new
|
|
99
|
+
`prompt:<slug>` section)."""
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class _RawDelta(BaseModel):
|
|
103
|
+
"""What the meta-agent is trusted to provide — everything else is filled from ground truth."""
|
|
104
|
+
|
|
105
|
+
expected_effect: str
|
|
106
|
+
preconditions: dict[str, str] = Field(default_factory=dict)
|
|
107
|
+
ops: list[SurfaceOp] = Field(min_length=1)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def propose_delta(
|
|
111
|
+
parent: HarnessDoc,
|
|
112
|
+
trigger: FailureSignature,
|
|
113
|
+
evidence: str,
|
|
114
|
+
provider: Provider,
|
|
115
|
+
*,
|
|
116
|
+
history: list[HarnessDelta] | None = None,
|
|
117
|
+
) -> HarnessDelta | None:
|
|
118
|
+
"""Ask the meta-agent for one delta against `parent`, or None if the reply is unusable.
|
|
119
|
+
|
|
120
|
+
`history` is the run's previously judged deltas (most recent last): their ops and gate
|
|
121
|
+
verdicts are shown to the proposer so it iterates instead of re-proposing rejected ideas.
|
|
122
|
+
"""
|
|
123
|
+
user = _build_prompt(parent, trigger, evidence, history or [])
|
|
124
|
+
# The reply must hold a COMPLETE replacement surface (ops carry full content, not diffs).
|
|
125
|
+
# The largest vendored pi source file is ~36 KB (~10k tokens before JSON escaping), so 4k
|
|
126
|
+
# silently truncated every real code-surface proposal into an unusable reply; the search
|
|
127
|
+
# "ran" its iterations but skipped them all. 16k fits any single-surface rewrite with room
|
|
128
|
+
# for preconditions/rationale, and stays under common provider output caps.
|
|
129
|
+
completion = provider.complete(
|
|
130
|
+
MUTATE_SYSTEM,
|
|
131
|
+
[Message(role="user", content=user)],
|
|
132
|
+
temperature=0.9,
|
|
133
|
+
max_tokens=16384,
|
|
134
|
+
)
|
|
135
|
+
return parse_delta(parent, trigger, completion.text)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def parse_delta(
|
|
139
|
+
parent: HarnessDoc,
|
|
140
|
+
trigger: FailureSignature,
|
|
141
|
+
text: str,
|
|
142
|
+
) -> HarnessDelta | None:
|
|
143
|
+
"""Parse one full-content or compact-edit proposal against ``parent``."""
|
|
144
|
+
raw = extract_json_object(text)
|
|
145
|
+
if raw is None:
|
|
146
|
+
return None
|
|
147
|
+
try:
|
|
148
|
+
value = json.loads(raw)
|
|
149
|
+
if not isinstance(value, dict):
|
|
150
|
+
return None
|
|
151
|
+
value["ops"] = _expand_compact_ops(parent, value.get("ops"))
|
|
152
|
+
proposed = _RawDelta.model_validate(value)
|
|
153
|
+
return HarnessDelta(
|
|
154
|
+
delta_id=compute_delta_id(parent.doc_hash, proposed.ops),
|
|
155
|
+
parent_doc_hash=parent.doc_hash,
|
|
156
|
+
trigger=trigger,
|
|
157
|
+
preconditions=proposed.preconditions,
|
|
158
|
+
ops=proposed.ops,
|
|
159
|
+
expected_effect=proposed.expected_effect,
|
|
160
|
+
)
|
|
161
|
+
except (TypeError, ValueError, ValidationError):
|
|
162
|
+
return None
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _expand_compact_ops(parent: HarnessDoc, value: object) -> list[dict[str, object]]:
|
|
166
|
+
"""Expand exact replacement hunks into ordinary full-content surface ops."""
|
|
167
|
+
if not isinstance(value, list):
|
|
168
|
+
raise ValueError("ops must be an array")
|
|
169
|
+
expanded: list[dict[str, object]] = []
|
|
170
|
+
for item in value:
|
|
171
|
+
if not isinstance(item, dict):
|
|
172
|
+
raise ValueError("each op must be an object")
|
|
173
|
+
op = dict(item)
|
|
174
|
+
edits = op.pop("edits", None)
|
|
175
|
+
if op.get("op") == "replace" and "content" not in op and edits is not None:
|
|
176
|
+
surface_id = op.get("surface_id")
|
|
177
|
+
if not isinstance(surface_id, str) or (surface := parent.surface(surface_id)) is None:
|
|
178
|
+
raise ValueError("compact replace targets an unknown surface")
|
|
179
|
+
op["content"] = _apply_edits(surface.content, edits)
|
|
180
|
+
elif edits is not None:
|
|
181
|
+
raise ValueError("edits are only valid on content-less replace ops")
|
|
182
|
+
expanded.append(op)
|
|
183
|
+
return expanded
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _apply_edits(content: str, value: object) -> str:
|
|
187
|
+
"""Apply ordered exact edits, rejecting missing or ambiguous anchors."""
|
|
188
|
+
if not isinstance(value, list) or not value:
|
|
189
|
+
raise ValueError("edits must be a non-empty array")
|
|
190
|
+
for item in value:
|
|
191
|
+
if not isinstance(item, dict):
|
|
192
|
+
raise ValueError("each edit must be an object")
|
|
193
|
+
old = item.get("old")
|
|
194
|
+
new = item.get("new")
|
|
195
|
+
if not isinstance(old, str) or not old or not isinstance(new, str):
|
|
196
|
+
raise ValueError("edits need non-empty old and string new values")
|
|
197
|
+
if content.count(old) != 1:
|
|
198
|
+
raise ValueError("edit old text must occur exactly once")
|
|
199
|
+
content = content.replace(old, new, 1)
|
|
200
|
+
return content
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def render_evidence(
|
|
204
|
+
trigger: FailureSignature, report: ClosedLoopReport, tasks: list[TaskSpec]
|
|
205
|
+
) -> str:
|
|
206
|
+
"""Render one failure cluster (instructions + unmet assertions) as reflection fuel.
|
|
207
|
+
|
|
208
|
+
A trigger with no failing tasks (the all-pass case) gets an explicit "nothing failed" prompt
|
|
209
|
+
asking for a generalization/efficiency improvement — not a fake failure section that would
|
|
210
|
+
send the meta-agent chasing nonexistent problems.
|
|
211
|
+
"""
|
|
212
|
+
if not trigger.task_ids:
|
|
213
|
+
return (
|
|
214
|
+
"The harness passed every task on every pass. There are no failures to fix. "
|
|
215
|
+
"Propose a change that should GENERALIZE or ECONOMIZE: a tighter, more transferable "
|
|
216
|
+
"prompt surface; a lower param:max-turns if runs finish early; or a reusable skill "
|
|
217
|
+
"distilled from what worked."
|
|
218
|
+
)
|
|
219
|
+
by_id = {task.task_id: task for task in tasks}
|
|
220
|
+
selected = set(trigger.task_ids)
|
|
221
|
+
scorecard = [
|
|
222
|
+
"## Evaluation scorecard",
|
|
223
|
+
"The selected failure is marked TARGET; preserve behavior on the other tasks.",
|
|
224
|
+
]
|
|
225
|
+
for task in tasks:
|
|
226
|
+
outcome = report.per_task.get(task.task_id)
|
|
227
|
+
success = outcome.success_rate if outcome is not None else 0.0
|
|
228
|
+
fraction = outcome.mean_fraction if outcome is not None else 0.0
|
|
229
|
+
instruction = " ".join(normalize_durable_text(task.instruction).split())
|
|
230
|
+
if len(instruction) > 240:
|
|
231
|
+
instruction = f"{instruction[:237]}..."
|
|
232
|
+
marker = "TARGET" if task.task_id in selected else "other"
|
|
233
|
+
scorecard.append(
|
|
234
|
+
f"- [{marker}] {task.task_id}: success={success:.2f}, "
|
|
235
|
+
f"assertion_fraction={fraction:.2f} — {instruction}"
|
|
236
|
+
)
|
|
237
|
+
sections = [
|
|
238
|
+
"\n".join(scorecard),
|
|
239
|
+
f"## Selected failure\n\nFailure mechanism: {trigger.mechanism}",
|
|
240
|
+
]
|
|
241
|
+
for task_id in trigger.task_ids:
|
|
242
|
+
task = by_id.get(task_id)
|
|
243
|
+
outcome = report.per_task.get(task_id)
|
|
244
|
+
instruction = (
|
|
245
|
+
normalize_durable_text(task.instruction) if task is not None else "(unknown task)"
|
|
246
|
+
)
|
|
247
|
+
rate = f"{outcome.success_rate:.2f} over {outcome.passes} passes" if outcome else "?"
|
|
248
|
+
fraction = f", assertion_fraction={outcome.mean_fraction:.2f}" if outcome else ""
|
|
249
|
+
task_section = [
|
|
250
|
+
f"### Task {task_id} (success_rate={rate}{fraction})",
|
|
251
|
+
f"Instruction: {instruction}",
|
|
252
|
+
]
|
|
253
|
+
if outcome is not None:
|
|
254
|
+
for index, verdict in enumerate(outcome.verdicts, 1):
|
|
255
|
+
attempt = outcome.attempts[index - 1] if index <= len(outcome.attempts) else None
|
|
256
|
+
task_section.append(_render_attempt(index=index, attempt=attempt, verdict=verdict))
|
|
257
|
+
sections.append("\n\n".join(task_section))
|
|
258
|
+
unmet = "\n".join(f"- {a}" for a in trigger.unmet_assertions)
|
|
259
|
+
sections.append(
|
|
260
|
+
"Original trigger assertions from the parent (current attempt verdicts above are "
|
|
261
|
+
f"authoritative):\n{unmet or '- (none recorded)'}"
|
|
262
|
+
)
|
|
263
|
+
return "\n\n".join(sections)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _render_attempt(*, index: int, attempt: RolloutEvidence | None, verdict: GoldVerdict) -> str:
|
|
267
|
+
"""Render one rollout plus its judge feedback without unbounded prompt growth."""
|
|
268
|
+
# Keep this helper tolerant of old/deserialized reports that predate RolloutEvidence. The
|
|
269
|
+
# verdict fields remain useful even when no trace was retained.
|
|
270
|
+
lines = [
|
|
271
|
+
f"#### Attempt {index} (passed={str(verdict.passed).lower()}, "
|
|
272
|
+
f"assertion_fraction={verdict.fraction:.2f})"
|
|
273
|
+
]
|
|
274
|
+
if attempt is not None:
|
|
275
|
+
lines.append(
|
|
276
|
+
f"Stop: {attempt.stop_reason.value}; turns={attempt.turns}\n"
|
|
277
|
+
f"Final answer:\n{normalize_durable_text(attempt.answer) or '(none)'}\n\n"
|
|
278
|
+
f"Execution transcript:\n"
|
|
279
|
+
f"{_bounded_trace(normalize_durable_text(attempt.transcript))}"
|
|
280
|
+
)
|
|
281
|
+
if verdict.assertions:
|
|
282
|
+
judged = "\n".join(
|
|
283
|
+
f"- {'PASS' if assertion.passed else 'FAIL'}: "
|
|
284
|
+
f"{normalize_durable_text(assertion.assertion)}"
|
|
285
|
+
f" — {normalize_durable_text(assertion.why) if assertion.why else '(no judge reason)'}"
|
|
286
|
+
for assertion in verdict.assertions
|
|
287
|
+
)
|
|
288
|
+
lines.append(f"Judge feedback:\n{judged}")
|
|
289
|
+
else:
|
|
290
|
+
rationale = normalize_durable_text(verdict.rationale) if verdict.rationale else ""
|
|
291
|
+
lines.append(f"Judge feedback: {rationale or '(no per-assertion feedback)'}")
|
|
292
|
+
return "\n\n".join(lines)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _bounded_trace(trace: str, limit: int = _TRACE_CHARS_PER_ATTEMPT) -> str:
|
|
296
|
+
"""Keep both the setup and terminal behavior when a long run exceeds the evidence budget."""
|
|
297
|
+
if not trace:
|
|
298
|
+
return "(empty)"
|
|
299
|
+
if len(trace) <= limit:
|
|
300
|
+
return trace
|
|
301
|
+
head = limit // 2
|
|
302
|
+
tail = limit - head
|
|
303
|
+
omitted = len(trace) - limit
|
|
304
|
+
return f"{trace[:head]}\n... ({omitted} trace characters omitted) ...\n{trace[-tail:]}"
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def render_history(history: list[HarnessDelta], limit: int = 5) -> str:
|
|
308
|
+
"""The last `limit` judged deltas as lessons: what was tried, and exactly how it fared."""
|
|
309
|
+
lines: list[str] = []
|
|
310
|
+
for delta in history[-limit:]:
|
|
311
|
+
ops = ", ".join(f"{op.op} {op.surface_id}" for op in delta.ops)
|
|
312
|
+
verdict = delta.verdict.reason if delta.verdict is not None else "(never evaluated)"
|
|
313
|
+
rationale = delta.ops[0].rationale if delta.ops else ""
|
|
314
|
+
lines.append(f"- [{ops}] rationale: {rationale[:120]}\n outcome: {verdict}")
|
|
315
|
+
return "\n".join(lines)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _build_prompt(
|
|
319
|
+
parent: HarnessDoc, trigger: FailureSignature, evidence: str, history: list[HarnessDelta]
|
|
320
|
+
) -> str:
|
|
321
|
+
"""The meta-agent's user prompt: every parent surface with its identity, then the evidence."""
|
|
322
|
+
blocks: list[str] = []
|
|
323
|
+
for surface in parent.surfaces:
|
|
324
|
+
budget = f", budget={surface.budget}" if surface.budget is not None else ""
|
|
325
|
+
blocks.append(
|
|
326
|
+
f"### {surface.id} (kind={surface.kind.value}, "
|
|
327
|
+
f"hash={surface.content_hash}{budget})\n```\n{surface.content}\n```"
|
|
328
|
+
)
|
|
329
|
+
surfaces_block = "\n\n".join(blocks)
|
|
330
|
+
history_block = (
|
|
331
|
+
f"## Previous attempts this run (do NOT repeat failed ideas — iterate or change lever)"
|
|
332
|
+
f"\n\n{render_history(history)}\n\n"
|
|
333
|
+
if history
|
|
334
|
+
else ""
|
|
335
|
+
)
|
|
336
|
+
return (
|
|
337
|
+
f"## Current harness surfaces ({parent.name}, doc_hash={parent.doc_hash})\n\n"
|
|
338
|
+
f"{surfaces_block}\n\n"
|
|
339
|
+
f"## Failure evidence\n\n{evidence}\n\n"
|
|
340
|
+
f"{history_block}"
|
|
341
|
+
"Propose ONE focused change as the JSON object described in your instructions. "
|
|
342
|
+
"Remember: copy the hash of every surface you replace or remove into `preconditions`."
|
|
343
|
+
)
|