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/engine/knowledge.py
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"""Cross-session knowledge base: the world model's persistent, human-editable memory.
|
|
2
|
+
|
|
3
|
+
A `KnowledgeBase` is a directory of plain markdown files under the model artifact
|
|
4
|
+
(`models/<name>/knowledge/`) holding the environment's *canonical* facts — entities, business
|
|
5
|
+
rules, response schemas, and the state-dependent gates (auth, availability, preconditions) an LLM
|
|
6
|
+
env otherwise guesses wrong. It is:
|
|
7
|
+
|
|
8
|
+
- **seeded at build time** from TRAIN traces only (`seed_knowledge`, an LLM extraction pass),
|
|
9
|
+
- **read at serve time** — rendered whole (size-budgeted) into the env prompt's KNOWLEDGE BASE
|
|
10
|
+
section (`wmo.core.render.build_env_prompt`),
|
|
11
|
+
- **written at serve time** — the env's `kb_note` contract field appends to `learned.md` and the
|
|
12
|
+
grounder caches web results in `grounded.md`; the seeded files are never auto-modified,
|
|
13
|
+
- **edited by humans** — it's just markdown; open the folder in any editor.
|
|
14
|
+
|
|
15
|
+
The session scratchpad (`EnvState.scratchpad`) stays session-local; this store is what persists
|
|
16
|
+
ACROSS sessions. Models without a `knowledge/` directory load and serve exactly as before.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import tempfile
|
|
22
|
+
import threading
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
from pydantic import BaseModel, ValidationError
|
|
26
|
+
|
|
27
|
+
from wmo.core.parsing import extract_json_object
|
|
28
|
+
from wmo.core.render import render_demo
|
|
29
|
+
from wmo.core.types import Trace
|
|
30
|
+
from wmo.providers.base import Message, Provider
|
|
31
|
+
|
|
32
|
+
# Build-time seeded files (never auto-modified after seeding; humans edit freely).
|
|
33
|
+
SEEDED_FILES = ("rules.md", "entities.md", "schemas.md")
|
|
34
|
+
# Serve-time append targets: env kb_notes and grounder cache. Auto-appended, size-capped.
|
|
35
|
+
LEARNED_FILE = "learned.md"
|
|
36
|
+
GROUNDED_FILE = "grounded.md"
|
|
37
|
+
|
|
38
|
+
# Rendering budget (chars) for the KNOWLEDGE BASE prompt section. The KB is curated facts, not a
|
|
39
|
+
# record dump — tau-bench's full 3.3MB DB per step is exactly the failure mode this cap prevents.
|
|
40
|
+
DEFAULT_RENDER_BUDGET = 24_000
|
|
41
|
+
# Per-file cap (chars) for the auto-appended files, so serve-time writes can't grow unboundedly.
|
|
42
|
+
DEFAULT_APPEND_CAP = 50_000
|
|
43
|
+
|
|
44
|
+
_TRUNCATION_MARKER = "\n[KNOWLEDGE BASE TRUNCATED: over render budget — curate the files]\n"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class KnowledgeBase:
|
|
48
|
+
"""A directory of markdown files acting as the env's cross-session memory.
|
|
49
|
+
|
|
50
|
+
Missing directory == empty knowledge base; nothing is created until the first write, so
|
|
51
|
+
models built before this feature (no `knowledge/` dir) are served unchanged.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(self, directory: str | Path, *, append_cap: int = DEFAULT_APPEND_CAP) -> None:
|
|
55
|
+
self.directory = Path(directory)
|
|
56
|
+
self._append_cap = append_cap
|
|
57
|
+
# Serializes read-check-append against itself: FastAPI sync handlers run in a thread
|
|
58
|
+
# pool, so two sessions stepping the SAME served model can race append_learned /
|
|
59
|
+
# append_grounded and double-append (or blow past the cap). In-process only — one
|
|
60
|
+
# server process owns an artifact's knowledge/ dir; cross-process co-serving of one
|
|
61
|
+
# artifact is out of scope.
|
|
62
|
+
self._write_lock = threading.Lock()
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def is_empty(self) -> bool:
|
|
66
|
+
"""True when no markdown file has any content."""
|
|
67
|
+
return not any(content.strip() for content in self.files().values())
|
|
68
|
+
|
|
69
|
+
def files(self) -> dict[str, str]:
|
|
70
|
+
"""Return {file name: content} for every markdown file, in sorted-name order."""
|
|
71
|
+
if not self.directory.is_dir():
|
|
72
|
+
return {}
|
|
73
|
+
with self._write_lock: # a torn read of a file mid-write must not reach the prompt
|
|
74
|
+
return {
|
|
75
|
+
path.name: path.read_text(encoding="utf-8")
|
|
76
|
+
for path in sorted(self.directory.glob("*.md"))
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
def write_file(self, name: str, content: str) -> None:
|
|
80
|
+
"""Create/replace one markdown file (seeding, HTTP edit surface)."""
|
|
81
|
+
_validate_file_name(name)
|
|
82
|
+
with self._write_lock: # an HTTP edit racing a stepping session's append
|
|
83
|
+
self.directory.mkdir(parents=True, exist_ok=True)
|
|
84
|
+
(self.directory / name).write_text(content, encoding="utf-8")
|
|
85
|
+
|
|
86
|
+
def render(self, budget: int = DEFAULT_RENDER_BUDGET) -> str:
|
|
87
|
+
"""Render the whole KB for the env prompt: `## <file>` sections, sorted, budget-capped.
|
|
88
|
+
|
|
89
|
+
Deterministic: what a human sees in the files is exactly what the env reads. Over-budget
|
|
90
|
+
content is cut with a loud marker rather than silently — an oversized KB is a curation
|
|
91
|
+
problem the user should see, not hidden lossage.
|
|
92
|
+
"""
|
|
93
|
+
sections = [
|
|
94
|
+
f"## {name}\n{content.strip()}"
|
|
95
|
+
for name, content in self.files().items()
|
|
96
|
+
if content.strip()
|
|
97
|
+
]
|
|
98
|
+
text = "\n\n".join(sections)
|
|
99
|
+
if len(text) <= budget:
|
|
100
|
+
return text
|
|
101
|
+
keep = max(budget - len(_TRUNCATION_MARKER), 0)
|
|
102
|
+
return (text[:keep] + _TRUNCATION_MARKER)[:budget]
|
|
103
|
+
|
|
104
|
+
def append_learned(self, fact: str, *, provenance: str) -> bool:
|
|
105
|
+
"""Append one cross-session fact (env `kb_note`) to `learned.md` with provenance.
|
|
106
|
+
|
|
107
|
+
Returns False (and writes nothing) when the exact fact is already recorded or the file is
|
|
108
|
+
at its cap. Only `learned.md` is ever auto-written — seeded files and human edits are
|
|
109
|
+
never clobbered.
|
|
110
|
+
"""
|
|
111
|
+
fact = fact.strip()
|
|
112
|
+
if not fact:
|
|
113
|
+
return False
|
|
114
|
+
with self._write_lock:
|
|
115
|
+
existing = self._read(LEARNED_FILE)
|
|
116
|
+
# Exact-fact dedupe: a raw substring test treated any new fact that PREFIXES an
|
|
117
|
+
# existing entry as already recorded, silently dropping distinct general facts.
|
|
118
|
+
recorded = {
|
|
119
|
+
line[2:].split(" <!--", 1)[0].strip()
|
|
120
|
+
for line in existing.splitlines()
|
|
121
|
+
if line.startswith("- ")
|
|
122
|
+
}
|
|
123
|
+
if fact in recorded:
|
|
124
|
+
return False
|
|
125
|
+
if len(existing) > self._append_cap:
|
|
126
|
+
return False
|
|
127
|
+
entry = f"- {fact} <!-- {provenance} -->\n"
|
|
128
|
+
self._append(LEARNED_FILE, entry)
|
|
129
|
+
return True
|
|
130
|
+
|
|
131
|
+
def append_grounded(self, query: str, results_text: str) -> None:
|
|
132
|
+
"""Cache one grounder result under `grounded.md` so a query is searched at most once."""
|
|
133
|
+
entry = f"### query: {query.strip()}\n{results_text.strip()}\n\n"
|
|
134
|
+
with self._write_lock:
|
|
135
|
+
# Same query grounded by two racing sessions: keep the first cache entry.
|
|
136
|
+
if f"### query: {query.strip()}\n" in self._read(GROUNDED_FILE):
|
|
137
|
+
return
|
|
138
|
+
self._append(GROUNDED_FILE, entry)
|
|
139
|
+
|
|
140
|
+
def lookup_grounded(self, query: str) -> str | None:
|
|
141
|
+
"""Return the cached results for `query`, or None on a cache miss."""
|
|
142
|
+
content = self._read(GROUNDED_FILE)
|
|
143
|
+
header = f"### query: {query.strip()}\n"
|
|
144
|
+
start = content.find(header)
|
|
145
|
+
if start == -1:
|
|
146
|
+
return None
|
|
147
|
+
body_start = start + len(header)
|
|
148
|
+
end = content.find("### query: ", body_start)
|
|
149
|
+
return content[body_start : end if end != -1 else len(content)].strip()
|
|
150
|
+
|
|
151
|
+
def _read(self, name: str) -> str:
|
|
152
|
+
path = self.directory / name
|
|
153
|
+
return path.read_text(encoding="utf-8") if path.exists() else ""
|
|
154
|
+
|
|
155
|
+
def _append(self, name: str, text: str) -> None:
|
|
156
|
+
self.directory.mkdir(parents=True, exist_ok=True)
|
|
157
|
+
with (self.directory / name).open("a", encoding="utf-8") as fh:
|
|
158
|
+
fh.write(text)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _validate_file_name(name: str) -> None:
|
|
162
|
+
if name != Path(name).name or not name.endswith(".md"):
|
|
163
|
+
raise ValueError(
|
|
164
|
+
f"knowledge file name must be a bare '*.md' name (got {name!r}); "
|
|
165
|
+
"the knowledge base is a flat folder of markdown files"
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class _Extraction(BaseModel):
|
|
170
|
+
"""The seeding LLM's per-chunk output: full updated content of each seeded file."""
|
|
171
|
+
|
|
172
|
+
rules: str = ""
|
|
173
|
+
entities: str = ""
|
|
174
|
+
schemas: str = ""
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
_SEED_SYSTEM_PROMPT = """You are building the canonical KNOWLEDGE BASE for a simulated \
|
|
178
|
+
environment reconstructed from real agent traces. Extract only DURABLE, CROSS-SESSION facts the \
|
|
179
|
+
environment must stay consistent about:
|
|
180
|
+
|
|
181
|
+
- rules: business rules and STATE-DEPENDENT GATES the environment ITSELF enforces — auth \
|
|
182
|
+
requirements, availability checks, preconditions, completion/cancellation rules. These decide \
|
|
183
|
+
success vs. error. Distinguish carefully: if traces show a tool executing successfully despite \
|
|
184
|
+
a policy the agent was told to follow, that policy is NOT an environment gate — record it as \
|
|
185
|
+
"agent policy (NOT enforced: tools execute mechanically)". Also record SYSTEM LIMITS evidenced \
|
|
186
|
+
in traces with their observed values: command timeouts, rate limits, output truncation, caps.
|
|
187
|
+
- entities: canonical entities that exist (ids, names, relations) — stated generally, never \
|
|
188
|
+
per-session conversation details.
|
|
189
|
+
- schemas: tool/API response shapes, field names, and exact error formats/messages.
|
|
190
|
+
|
|
191
|
+
Do NOT copy session-specific events ("the agent booked X") — only what is true of the \
|
|
192
|
+
environment itself. Prefer terse markdown bullet lists. You are shown the CURRENT knowledge base \
|
|
193
|
+
and a NEW trace excerpt; return the UPDATED full content of all three files (carry existing \
|
|
194
|
+
facts forward, merge and dedupe, drop nothing that is still true).
|
|
195
|
+
|
|
196
|
+
Respond with ONLY a JSON object: {"rules": "<markdown>", "entities": "<markdown>", \
|
|
197
|
+
"schemas": "<markdown>"}"""
|
|
198
|
+
|
|
199
|
+
# Chars of rendered trace text per extraction call. Together with `max_calls` this bounds the
|
|
200
|
+
# build-time seeding cost regardless of corpus size.
|
|
201
|
+
_SEED_CHUNK_CHARS = 40_000
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def seed_knowledge(
|
|
205
|
+
kb: KnowledgeBase,
|
|
206
|
+
train_traces: list[Trace],
|
|
207
|
+
provider: Provider,
|
|
208
|
+
*,
|
|
209
|
+
max_calls: int = 8,
|
|
210
|
+
reporter_note: list[str] | None = None,
|
|
211
|
+
) -> None:
|
|
212
|
+
"""Seed `kb` from TRAIN traces via chunked knowledge-accumulation extraction.
|
|
213
|
+
|
|
214
|
+
Renders the corpus steps into text chunks and folds each chunk into the running KB (the same
|
|
215
|
+
accumulate-and-carry-forward pattern GEPA's reflection uses), writing `SEEDED_FILES` after
|
|
216
|
+
every successful call so a partial run still leaves a usable KB. `max_calls` is the hard cost
|
|
217
|
+
bound: chunks beyond it are skipped (coverage saturates quickly — rules/schemas repeat across
|
|
218
|
+
traces). Eval-integrity note: callers must pass TRAIN traces only (mirrors
|
|
219
|
+
`wmo.retrieval.leakfree`), so a KB used during eval can never contain a held-out answer.
|
|
220
|
+
"""
|
|
221
|
+
chunks = _corpus_chunks(train_traces)
|
|
222
|
+
skipped = max(len(chunks) - max_calls, 0)
|
|
223
|
+
current = _Extraction(
|
|
224
|
+
rules=kb.files().get("rules.md", ""),
|
|
225
|
+
entities=kb.files().get("entities.md", ""),
|
|
226
|
+
schemas=kb.files().get("schemas.md", ""),
|
|
227
|
+
)
|
|
228
|
+
for chunk in chunks[:max_calls]:
|
|
229
|
+
user = (
|
|
230
|
+
f"CURRENT KNOWLEDGE BASE:\n"
|
|
231
|
+
f"rules.md:\n{current.rules or '(empty)'}\n\n"
|
|
232
|
+
f"entities.md:\n{current.entities or '(empty)'}\n\n"
|
|
233
|
+
f"schemas.md:\n{current.schemas or '(empty)'}\n\n"
|
|
234
|
+
f"NEW TRACE EXCERPT:\n{chunk}"
|
|
235
|
+
)
|
|
236
|
+
completion = provider.complete(_SEED_SYSTEM_PROMPT, [Message(role="user", content=user)])
|
|
237
|
+
extraction = _parse_extraction(completion.text)
|
|
238
|
+
if extraction is None:
|
|
239
|
+
continue # off-contract reply: keep accumulating from the current state
|
|
240
|
+
current = extraction
|
|
241
|
+
kb.write_file("rules.md", current.rules)
|
|
242
|
+
kb.write_file("entities.md", current.entities)
|
|
243
|
+
kb.write_file("schemas.md", current.schemas)
|
|
244
|
+
if skipped and reporter_note is not None:
|
|
245
|
+
reporter_note.append(
|
|
246
|
+
f"knowledge seeding: {skipped} corpus chunk(s) beyond the {max_calls}-call budget "
|
|
247
|
+
"were not read"
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def seeded_knowledge_text(
|
|
252
|
+
train_traces: list[Trace], provider: Provider, *, max_calls: int = 8
|
|
253
|
+
) -> str | None:
|
|
254
|
+
"""Seed an ephemeral KB from TRAIN traces and return its rendered text (None when empty).
|
|
255
|
+
|
|
256
|
+
For eval/research contexts that need train-derived knowledge in the prompt without touching
|
|
257
|
+
any model artifact: the KB lives in a temp dir for the duration of seeding only, so nothing a
|
|
258
|
+
serve session ever wrote (learned/grounded facts) can leak into a scored run.
|
|
259
|
+
"""
|
|
260
|
+
with tempfile.TemporaryDirectory(prefix="wmo-kb-") as tmp:
|
|
261
|
+
kb = KnowledgeBase(Path(tmp))
|
|
262
|
+
seed_knowledge(kb, train_traces, provider, max_calls=max_calls)
|
|
263
|
+
return kb.render() or None
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _parse_extraction(text: str) -> _Extraction | None:
|
|
267
|
+
raw = extract_json_object(text)
|
|
268
|
+
if raw is None:
|
|
269
|
+
return None
|
|
270
|
+
try:
|
|
271
|
+
return _Extraction.model_validate_json(raw)
|
|
272
|
+
except ValidationError:
|
|
273
|
+
return None
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _corpus_chunks(traces: list[Trace]) -> list[str]:
|
|
277
|
+
"""Render every step once (canonical `render_demo`) and pack into char-bounded chunks."""
|
|
278
|
+
chunks: list[str] = []
|
|
279
|
+
buffer: list[str] = []
|
|
280
|
+
size = 0
|
|
281
|
+
for trace in traces:
|
|
282
|
+
for step in trace.steps:
|
|
283
|
+
text = render_demo(step)
|
|
284
|
+
if size + len(text) > _SEED_CHUNK_CHARS and buffer:
|
|
285
|
+
chunks.append("\n\n".join(buffer))
|
|
286
|
+
buffer, size = [], 0
|
|
287
|
+
buffer.append(text)
|
|
288
|
+
size += len(text)
|
|
289
|
+
if buffer:
|
|
290
|
+
chunks.append("\n\n".join(buffer))
|
|
291
|
+
return chunks
|
wmo/engine/loader.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Load a built world model from its artifact directory.
|
|
2
|
+
|
|
3
|
+
One place owns the "artifact dir -> live WorldModel" sequence (read config -> construct the serve
|
|
4
|
+
provider -> `WorldModel.load`). The CLI and the serving layer both call this so the loading path
|
|
5
|
+
stays identical no matter how the model was selected (by name, by picker, or served in bulk).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from wmo.config import load_config
|
|
13
|
+
from wmo.engine.world_model import WorldModel
|
|
14
|
+
from wmo.providers import provider_or_chain
|
|
15
|
+
from wmo.providers.base import Provider
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def load_world_model(
|
|
19
|
+
model_dir: str | Path,
|
|
20
|
+
*,
|
|
21
|
+
telemetry_root: str | Path | None = None,
|
|
22
|
+
max_fidelity: bool = False,
|
|
23
|
+
) -> tuple[WorldModel, Provider]:
|
|
24
|
+
"""Load the world model under `model_dir`, returning it with the serve provider it was built on.
|
|
25
|
+
|
|
26
|
+
The provider is returned alongside so callers that also need it (e.g. `wmo demo`, which runs an
|
|
27
|
+
LLM agent against the same provider) don't re-read the config or reconstruct it.
|
|
28
|
+
`max_fidelity` turns on the online extras (the build-measured winner when the artifact has
|
|
29
|
+
one); a plain load runs pure RAG.
|
|
30
|
+
"""
|
|
31
|
+
config = load_config(str(model_dir))
|
|
32
|
+
provider = provider_or_chain(config.serve_provider_config())
|
|
33
|
+
wm = WorldModel.load(
|
|
34
|
+
str(model_dir), provider, telemetry_root=telemetry_root, max_fidelity=max_fidelity
|
|
35
|
+
)
|
|
36
|
+
return wm, provider
|
wmo/engine/play.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""`wmo play`: a human drives the agent inside the reconstructed environment.
|
|
2
|
+
|
|
3
|
+
This is the interactive sibling of `wmo demo`. Instead of replaying a recorded trajectory,
|
|
4
|
+
a *person* types actions — `tool_name {json args}` or a free-text message — and the world model
|
|
5
|
+
returns the observation, advancing the session (history + scratchpad "database") exactly as a real
|
|
6
|
+
agent would experience it.
|
|
7
|
+
|
|
8
|
+
The engine here is UI-agnostic: it parses a typed line into an `Action` and steps the world model,
|
|
9
|
+
returning the observation alongside the exact env prompt that produced it. The REPL loop, prompts,
|
|
10
|
+
and rendering live in the CLI (`wmo.cli.ui`); this module is what the CLI and its tests call.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
|
|
17
|
+
from pydantic import BaseModel, JsonValue
|
|
18
|
+
|
|
19
|
+
from wmo.core.parsing import extract_json_object
|
|
20
|
+
from wmo.core.types import Action, ActionKind, JsonObject, Observation
|
|
21
|
+
from wmo.engine.world_model import WorldModel
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class PlayTurn(BaseModel):
|
|
25
|
+
"""The outcome of one human turn: the action taken, the observation, and the env prompt sent."""
|
|
26
|
+
|
|
27
|
+
action: Action
|
|
28
|
+
observation: Observation
|
|
29
|
+
env_prompt: str
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def parse_action(line: str) -> Action:
|
|
33
|
+
"""Parse a typed REPL line into an `Action`.
|
|
34
|
+
|
|
35
|
+
Grammar (forgiving, human-first):
|
|
36
|
+
- `get_user {"id": "u1"}` -> tool call `get_user` with JSON arguments
|
|
37
|
+
- `list_flights` -> tool call `list_flights` with no arguments
|
|
38
|
+
- `say hello there` -> free-text message "hello there"
|
|
39
|
+
- anything else -> a free-text message (so the env can react to plain prose)
|
|
40
|
+
|
|
41
|
+
A bare first token is treated as a tool name when it looks like an identifier; otherwise the
|
|
42
|
+
whole line is a message. The `say ` prefix forces a message even when it looks tool-like.
|
|
43
|
+
"""
|
|
44
|
+
text = line.strip()
|
|
45
|
+
if not text:
|
|
46
|
+
raise ValueError("empty action")
|
|
47
|
+
|
|
48
|
+
if text.startswith("say "):
|
|
49
|
+
return Action(kind=ActionKind.MESSAGE, content=text[4:].strip())
|
|
50
|
+
|
|
51
|
+
head, _, rest = text.partition(" ")
|
|
52
|
+
rest = rest.strip()
|
|
53
|
+
# A tool call is a bare identifier (`list_flights`) or `name {json args}`. A trailing tail not
|
|
54
|
+
# starting with a JSON bracket means prose (e.g. "what is the weather?") -> treat as a message.
|
|
55
|
+
if _looks_like_tool_name(head) and (not rest or rest.startswith(("{", "["))):
|
|
56
|
+
return Action(kind=ActionKind.TOOL_CALL, name=head, arguments=_parse_arguments(rest))
|
|
57
|
+
return Action(kind=ActionKind.MESSAGE, content=text)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def play_turn(world_model: WorldModel, session_id: str, action: Action) -> PlayTurn:
|
|
61
|
+
"""Render the env prompt for `action`, step the world model, and return the full turn."""
|
|
62
|
+
env_prompt = world_model.render_step_prompt(session_id, action)
|
|
63
|
+
observation = world_model.step(session_id, action)
|
|
64
|
+
return PlayTurn(action=action, observation=observation, env_prompt=env_prompt)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _looks_like_tool_name(token: str) -> bool:
|
|
68
|
+
"""A tool name is an ASCII identifier-ish token: [A-Za-z][A-Za-z0-9._-]*.
|
|
69
|
+
|
|
70
|
+
ASCII-only on purpose: `str.isalpha()`/`isalnum()` accept Unicode letters/digits, so without
|
|
71
|
+
this a non-ASCII first word (e.g. "café ..." or "日本 ...") would be misread as a tool call
|
|
72
|
+
instead of a prose message.
|
|
73
|
+
"""
|
|
74
|
+
if not token or not ("a" <= token[0].lower() <= "z"):
|
|
75
|
+
return False
|
|
76
|
+
return all(c.isascii() and (c.isalnum() or c in "._-") for c in token)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _parse_arguments(rest: str) -> JsonObject:
|
|
80
|
+
"""Parse the argument tail into a JSON object; empty tail -> no arguments."""
|
|
81
|
+
if not rest:
|
|
82
|
+
return {}
|
|
83
|
+
raw = extract_json_object(rest)
|
|
84
|
+
if raw is None:
|
|
85
|
+
raise ValueError(
|
|
86
|
+
f"could not read tool arguments from {rest!r}; pass a JSON object like "
|
|
87
|
+
'{"id": "u1"} (or omit it for no arguments)'
|
|
88
|
+
)
|
|
89
|
+
parsed: JsonValue = json.loads(raw)
|
|
90
|
+
if not isinstance(parsed, dict):
|
|
91
|
+
raise ValueError('tool arguments must be a JSON object, e.g. {"id": "u1"}')
|
|
92
|
+
return parsed
|
wmo/engine/prompts.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Prompt assembly for the world model.
|
|
2
|
+
|
|
3
|
+
The env prompt is the heart of the system. It composes:
|
|
4
|
+
- the optimized base prompt (layer a / GEPA winner, layer b)
|
|
5
|
+
- the task instruction (tau)
|
|
6
|
+
- the interaction history {(s_i, a_i)}
|
|
7
|
+
- the top-k retrieved demos {d_j}
|
|
8
|
+
- the incoming action
|
|
9
|
+
into the single completion that predicts the next observation (DreamGym Eq. 4).
|
|
10
|
+
|
|
11
|
+
The actual rendering lives in `wmo.core.render` (which depends on nothing), so the serving engine
|
|
12
|
+
and the GEPA optimizer share one assembly — prompts are evolved against exactly what the world model
|
|
13
|
+
serves. This module is the engine-facing entry point: it adapts a live `Session` to that renderer.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from wmo.core.render import build_env_prompt as _build_env_prompt
|
|
19
|
+
from wmo.core.types import Action, Session, Step
|
|
20
|
+
|
|
21
|
+
# Layer (a): the env-agnostic base prompt. GEPA (layer b) evolves a specialized version of this.
|
|
22
|
+
# Tuned via replay-fidelity measurement across example traces:
|
|
23
|
+
# the failure modes a generic prompt makes are (1) fabricating concrete data the env alone knows,
|
|
24
|
+
# (2) inventing stdout when the real command prints nothing, and (3) guessing success/error wrong.
|
|
25
|
+
# This base targets all three while staying domain-agnostic, so it is a strong GEPA starting point.
|
|
26
|
+
BASE_ENV_PROMPT = """You ARE the environment the agent is acting on — a real system (shell, tools,
|
|
27
|
+
database, files), not an assistant. Output ONLY what this environment would actually return for the
|
|
28
|
+
agent's latest action, exactly as the agent would observe it.
|
|
29
|
+
|
|
30
|
+
Infer what KIND of environment this is from the state, history, and examples, and stay in character
|
|
31
|
+
as that system no matter what the agent sends:
|
|
32
|
+
- A shell/terminal responds like a shell. A conversational or malformed action (e.g. the agent types
|
|
33
|
+
"hi" or prose) yields what the shell would emit — e.g. `hi: command not found` and a non-zero
|
|
34
|
+
exit — never a chat reply.
|
|
35
|
+
- An API/tool responds in that API's shape (the same JSON/result schema the examples show), and an
|
|
36
|
+
unrecognized or malformed call yields that API's own error (bad request, unknown endpoint), not an
|
|
37
|
+
explanation.
|
|
38
|
+
- Whatever the surface, react as it would; never break character to talk to the agent.
|
|
39
|
+
|
|
40
|
+
Ground every prediction in the evidence you are given:
|
|
41
|
+
- The environment STATE and INTERACTION HISTORY are the source of truth for concrete values
|
|
42
|
+
(records, ids, prices, file contents, prior effects). Reuse those exact values verbatim.
|
|
43
|
+
- SIMILAR PAST EXAMPLES show how this environment formats responses for analogous actions. Match
|
|
44
|
+
their format, field names, ordering, and error conventions; reuse their values only when the
|
|
45
|
+
current state implies the same ones.
|
|
46
|
+
- When a lookup/read targets something the task or history implies EXISTS (the agent is acting on a
|
|
47
|
+
known id, a referenced record, a file it just created), the environment returns the full populated
|
|
48
|
+
result — so produce a complete, schema-correct, internally-consistent record, not an empty result
|
|
49
|
+
or a "not found" error. Returning "not found" for something that exists is the worst possible
|
|
50
|
+
answer: it flips the outcome. Only return empty/absent when the evidence says it is genuinely
|
|
51
|
+
missing. The fields you can't know (exact prices, dates, ids) should be plausible and mutually
|
|
52
|
+
consistent; the SHAPE and the outcome (found vs. not) are what matter most.
|
|
53
|
+
|
|
54
|
+
Predict precisely:
|
|
55
|
+
- Output exactly the bytes that reach the agent (e.g. stdout/stderr), nothing more. Many commands
|
|
56
|
+
(assignments, writes, redirected output, successful mutations) print NOTHING — return an empty
|
|
57
|
+
observation in that case rather than narrating success.
|
|
58
|
+
- Decide success vs. error from what the action would really do given the state. If it would fail
|
|
59
|
+
(missing record, bad input, syntax error), return the error the environment emits and mark it as
|
|
60
|
+
an error. If it would succeed, do not invent an error.
|
|
61
|
+
- Stay consistent with everything established earlier in the session.
|
|
62
|
+
|
|
63
|
+
Never address the agent, explain your reasoning, or add commentary. Emit only the observation in the
|
|
64
|
+
required output format."""
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def build_env_prompt(
|
|
68
|
+
base_prompt: str,
|
|
69
|
+
session: Session,
|
|
70
|
+
action: Action,
|
|
71
|
+
demos: list[Step],
|
|
72
|
+
*,
|
|
73
|
+
knowledge: str | None = None,
|
|
74
|
+
reasoning: bool = False,
|
|
75
|
+
grounding: bool = False,
|
|
76
|
+
confidence: bool = False,
|
|
77
|
+
confidence_why: bool = False,
|
|
78
|
+
max_retrieved_observation_chars: int | None = None,
|
|
79
|
+
) -> tuple[str, str]:
|
|
80
|
+
"""Return (system, user) text for a world-model completion.
|
|
81
|
+
|
|
82
|
+
Mirrors M_exp(R_t | {(s_i,a_i)}, {d_j}, tau): base+task -> system, history+demos+action -> user.
|
|
83
|
+
Delegates to the shared renderer, supplying the session's task, state, and history; the
|
|
84
|
+
agentic-mode flags (`knowledge`/`reasoning`/`grounding`/`confidence`) pass straight through.
|
|
85
|
+
"""
|
|
86
|
+
return _build_env_prompt(
|
|
87
|
+
base_prompt,
|
|
88
|
+
session.task,
|
|
89
|
+
session.state,
|
|
90
|
+
action,
|
|
91
|
+
history=session.history,
|
|
92
|
+
demos=demos,
|
|
93
|
+
knowledge=knowledge,
|
|
94
|
+
reasoning=reasoning,
|
|
95
|
+
grounding=grounding,
|
|
96
|
+
confidence=confidence,
|
|
97
|
+
confidence_why=confidence_why,
|
|
98
|
+
max_retrieved_observation_chars=max_retrieved_observation_chars,
|
|
99
|
+
)
|