Spaces:
Running
Running
Add living project map documenting all modules and relationships
Browse filesdocs/map/ covers: models, scenarios, agents, validation, scoring
(planned), server, frontend, config, and tests. Updated after each
implementation session to serve as codebase memory.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- docs/map/README.md +115 -0
- docs/map/agents.md +287 -0
- docs/map/config.md +61 -0
- docs/map/frontend.md +141 -0
- docs/map/models.md +219 -0
- docs/map/scenarios.md +153 -0
- docs/map/scoring.md +57 -0
- docs/map/server.md +128 -0
- docs/map/tests.md +194 -0
- docs/map/validation.md +99 -0
docs/map/README.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ReplicaLab Project Map
|
| 2 |
+
|
| 3 |
+
> Living reference of every module, class, function, and relationship.
|
| 4 |
+
> Updated after each implementation session.
|
| 5 |
+
>
|
| 6 |
+
> **Last updated:** 2026-03-07
|
| 7 |
+
|
| 8 |
+
## Module Index
|
| 9 |
+
|
| 10 |
+
| File | What it covers |
|
| 11 |
+
|------|---------------|
|
| 12 |
+
| [models.md](models.md) | Data contracts β actions, observations, protocol, reward, episode state |
|
| 13 |
+
| [scenarios.md](scenarios.md) | Scenario generation β templates, constraints, resources, hidden specs |
|
| 14 |
+
| [agents.md](agents.md) | Agent policies β scientist prompt/parse/retry, lab manager feasibility/suggest/compose |
|
| 15 |
+
| [validation.md](validation.md) | Protocol validation β deterministic checks against scenario constraints |
|
| 16 |
+
| [scoring.md](scoring.md) | Judge scoring β rigor, feasibility, fidelity (NOT YET IMPLEMENTED) |
|
| 17 |
+
| [server.md](server.md) | FastAPI server β REST + WebSocket endpoints, stub environment |
|
| 18 |
+
| [frontend.md](frontend.md) | React UI β dashboard, episode viewer, components |
|
| 19 |
+
| [config.md](config.md) | Shared constants β rounds, budget, timeouts |
|
| 20 |
+
| [tests.md](tests.md) | Test coverage β 87 tests across 6 files |
|
| 21 |
+
|
| 22 |
+
## Dependency Graph
|
| 23 |
+
|
| 24 |
+
```
|
| 25 |
+
server/app.py
|
| 26 |
+
βββ replicalab.config
|
| 27 |
+
βββ replicalab.models
|
| 28 |
+
βββ replicalab.scenarios (generate_scenario, available_scenario_families)
|
| 29 |
+
βββ replicalab.agents (check_feasibility, suggest_alternative, compose_lab_manager_response)
|
| 30 |
+
|
| 31 |
+
replicalab/agents/scientist_policy.py
|
| 32 |
+
βββ replicalab.models (ScientistAction, ScientistObservation, Protocol, ConversationEntry)
|
| 33 |
+
βββ replicalab.scenarios (NormalizedScenarioPack)
|
| 34 |
+
|
| 35 |
+
replicalab/agents/lab_manager_policy.py
|
| 36 |
+
βββ replicalab.models (LabManagerAction, LabManagerActionType, Protocol)
|
| 37 |
+
βββ replicalab.scenarios (NormalizedScenarioPack)
|
| 38 |
+
βββ replicalab.utils.validation (ValidationResult, validate_protocol)
|
| 39 |
+
|
| 40 |
+
replicalab/scenarios/templates.py
|
| 41 |
+
βββ replicalab.config (MAX_BUDGET, MAX_ROUNDS)
|
| 42 |
+
βββ replicalab.models (ScientistObservation, LabManagerObservation)
|
| 43 |
+
βββ replicalab.scenarios.{math_reasoning, ml_benchmark, finance_trading}
|
| 44 |
+
βββ replicalab.utils.seed (seed_rng)
|
| 45 |
+
|
| 46 |
+
replicalab/utils/validation.py
|
| 47 |
+
βββ replicalab.models (Protocol)
|
| 48 |
+
βββ replicalab.scenarios.templates (NormalizedScenarioPack)
|
| 49 |
+
|
| 50 |
+
replicalab/scoring/ <-- NOT YET IMPLEMENTED
|
| 51 |
+
βββ replicalab.models (Protocol, RewardBreakdown)
|
| 52 |
+
βββ replicalab.scenarios (NormalizedScenarioPack, HiddenReferenceSpec)
|
| 53 |
+
βββ replicalab.agents.lab_manager_policy (check_feasibility, FeasibilityCheckResult)
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
## File Tree (implemented only)
|
| 57 |
+
|
| 58 |
+
```
|
| 59 |
+
replicalab/
|
| 60 |
+
βββ __init__.py (empty)
|
| 61 |
+
βββ config.py (shared constants)
|
| 62 |
+
βββ models.py (25 classes β all data contracts)
|
| 63 |
+
βββ agents/
|
| 64 |
+
β βββ __init__.py (re-exports from submodules)
|
| 65 |
+
β βββ scientist_policy.py (AGT 01-04: prompt, formatter, parser, retry, baseline)
|
| 66 |
+
β βββ lab_manager_policy.py(AGT 05-07: feasibility, suggest, compose)
|
| 67 |
+
βββ scenarios/
|
| 68 |
+
β βββ __init__.py (re-exports from templates)
|
| 69 |
+
β βββ templates.py (NormalizedScenarioPack, generate_scenario, apply_difficulty)
|
| 70 |
+
β βββ math_reasoning.py (2 cases: Cauchy-Schwarz, Jensen's inequality)
|
| 71 |
+
β βββ ml_benchmark.py (2 cases: AG News TinyBERT, CIFAR-10 ResNet-18)
|
| 72 |
+
β βββ finance_trading.py (2 cases: SPY/QQQ mean-reversion, momentum futures)
|
| 73 |
+
βββ scoring/ <-- EMPTY (JDG 01-03 not yet built)
|
| 74 |
+
β βββ .gitkeep
|
| 75 |
+
βββ utils/
|
| 76 |
+
βββ seed.py (deterministic RNG from SHA256)
|
| 77 |
+
βββ validation.py (MOD 05: protocol validation, 5 checks)
|
| 78 |
+
|
| 79 |
+
server/
|
| 80 |
+
βββ app.py (FastAPI + WebSocket + _StubEnv)
|
| 81 |
+
|
| 82 |
+
frontend/
|
| 83 |
+
βββ package.json (React 19, Three.js, Framer Motion, Recharts, Tailwind)
|
| 84 |
+
βββ src/
|
| 85 |
+
β βββ App.tsx (router: /, /episode, /episode/:id)
|
| 86 |
+
β βββ types/index.ts (TypeScript interfaces mirroring Python models)
|
| 87 |
+
β βββ lib/
|
| 88 |
+
β β βββ api.ts (REST + WebSocket client + mock data generators)
|
| 89 |
+
β β βββ audio.ts (audio utilities)
|
| 90 |
+
β β βββ utils.ts (shared helpers)
|
| 91 |
+
β βββ components/ (15 React components)
|
| 92 |
+
β βββ pages/ (DashboardPage, EpisodePage)
|
| 93 |
+
βββ vite.config.ts
|
| 94 |
+
|
| 95 |
+
tests/
|
| 96 |
+
βββ test_config.py (3 tests)
|
| 97 |
+
βββ test_models.py (15 tests)
|
| 98 |
+
βββ test_scenarios.py (8 tests)
|
| 99 |
+
βββ test_validation.py (13 tests)
|
| 100 |
+
βββ test_scientist_policy.py (18 tests)
|
| 101 |
+
βββ test_lab_manager_policy.py(13 tests)
|
| 102 |
+
```
|
| 103 |
+
|
| 104 |
+
## Task Completion Status
|
| 105 |
+
|
| 106 |
+
| Area | Done | Remaining | Key gaps |
|
| 107 |
+
|------|------|-----------|----------|
|
| 108 |
+
| Models (MOD) | MOD 01-05, 09, 11-12 | MOD 06 | Semantic validators for impossible plans |
|
| 109 |
+
| Scenarios (SCN) | SCN 01-12 | SCN 13 | Booking/scheduling data model |
|
| 110 |
+
| Agents (AGT) | AGT 01-07, 11 | AGT 08-10 | LLM-backed scientist, model selection |
|
| 111 |
+
| Judge (JDG) | β | JDG 01-08 | Entire scoring engine |
|
| 112 |
+
| Environment (ENV) | β | ENV 01-11 | Entire real environment |
|
| 113 |
+
| Server (API) | API 01-04, 06 (partial) | API 05, 07-10 | Replay, auth, rate limiting |
|
| 114 |
+
| Frontend (FND) | FND 01-10 | β | Complete |
|
| 115 |
+
| Training (TRN) | β | TRN 01-18 | Entire RL pipeline |
|
docs/map/agents.md
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Agents Map β `replicalab/agents/`
|
| 2 |
+
|
| 3 |
+
> Deterministic policy helpers for Scientist and Lab Manager agents.
|
| 4 |
+
> No LLM calls in this module β the LLM backend is injected via `GenerateFn`.
|
| 5 |
+
>
|
| 6 |
+
> **Tasks implemented:** AGT 01-07, 11
|
| 7 |
+
|
| 8 |
+
## Exports β `__init__.py`
|
| 9 |
+
|
| 10 |
+
```python
|
| 11 |
+
# From lab_manager_policy
|
| 12 |
+
AlternativeSuggestion, FeasibilityCheckResult, SuggestionChange
|
| 13 |
+
check_feasibility, compose_lab_manager_response, suggest_alternative
|
| 14 |
+
|
| 15 |
+
# From scientist_policy
|
| 16 |
+
RetryMetadata, ScientistCallResult, ScientistOutputParseError
|
| 17 |
+
build_baseline_scientist_action, build_scientist_system_prompt
|
| 18 |
+
call_scientist_with_retry, format_scientist_observation, parse_scientist_output
|
| 19 |
+
```
|
| 20 |
+
|
| 21 |
+
---
|
| 22 |
+
|
| 23 |
+
## Scientist Policy β `scientist_policy.py`
|
| 24 |
+
|
| 25 |
+
### Pipeline Flow
|
| 26 |
+
|
| 27 |
+
```
|
| 28 |
+
scenario β build_scientist_system_prompt() β system_prompt
|
| 29 |
+
β
|
| 30 |
+
observation β format_scientist_observation() β user_message
|
| 31 |
+
β
|
| 32 |
+
call_scientist_with_retry(generate_fn, system_prompt, obs)
|
| 33 |
+
β calls generate_fn(messages)
|
| 34 |
+
β calls parse_scientist_output(raw_text)
|
| 35 |
+
β on failure: _build_correction_prompt(error)
|
| 36 |
+
β retries up to max_retries times
|
| 37 |
+
β ScientistCallResult(action, metadata)
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
### Public Functions
|
| 41 |
+
|
| 42 |
+
#### `build_scientist_system_prompt(scenario) -> str` β AGT 01
|
| 43 |
+
Builds a domain-neutral system prompt from a `NormalizedScenarioPack`.
|
| 44 |
+
|
| 45 |
+
**Sections rendered (in order):**
|
| 46 |
+
1. Role statement ("You are the Scientist agent in ReplicaLab")
|
| 47 |
+
2. Job description (negotiate strongest feasible plan)
|
| 48 |
+
3. Domain ID
|
| 49 |
+
4. Task summary
|
| 50 |
+
5. Success criteria (bulleted)
|
| 51 |
+
6. Constraints (with hard/soft labels, quantities, comparators)
|
| 52 |
+
7. Available resources (with availability status)
|
| 53 |
+
8. Allowed substitutions (original β alternative with conditions)
|
| 54 |
+
9. Output contract (exactly one JSON, no extra keys)
|
| 55 |
+
10. Allowed action_type values
|
| 56 |
+
11. Action-specific field requirements
|
| 57 |
+
|
| 58 |
+
#### `format_scientist_observation(obs: ScientistObservation) -> str` β AGT 02
|
| 59 |
+
Converts a per-turn observation into the user message string.
|
| 60 |
+
|
| 61 |
+
**Sections (fixed order, tested):**
|
| 62 |
+
1. Round status: `"Round {n} of {max}"`
|
| 63 |
+
2. Paper summary: title, hypothesis, method, key finding, goal
|
| 64 |
+
3. Conversation history or "No conversation history yet"
|
| 65 |
+
4. Current protocol or "No protocol has been proposed yet"
|
| 66 |
+
5. ScientistAction schema reminder (field list, action_type values)
|
| 67 |
+
6. Closing instruction: "Respond with exactly one JSON object"
|
| 68 |
+
|
| 69 |
+
#### `parse_scientist_output(raw_text: str) -> ScientistAction` β MOD 09
|
| 70 |
+
Strict parser from raw model text into validated `ScientistAction`.
|
| 71 |
+
|
| 72 |
+
**Accepts:**
|
| 73 |
+
- Plain JSON objects
|
| 74 |
+
- `\`\`\`json` fenced blocks
|
| 75 |
+
- Prose containing one JSON object
|
| 76 |
+
|
| 77 |
+
**Error codes:**
|
| 78 |
+
| Code | Meaning |
|
| 79 |
+
|------|---------|
|
| 80 |
+
| `no_json` | No JSON object found in output |
|
| 81 |
+
| `invalid_json` | JSON syntax error (trailing comma, etc.) |
|
| 82 |
+
| `invalid_action` | Valid JSON but fails ScientistAction validation |
|
| 83 |
+
|
| 84 |
+
#### `call_scientist_with_retry(generate_fn, system_prompt, observation, max_retries=2) -> ScientistCallResult` β AGT 03
|
| 85 |
+
Retry loop with error-specific correction prompts.
|
| 86 |
+
|
| 87 |
+
**Behavior:**
|
| 88 |
+
1. Builds messages: `[system, user]`
|
| 89 |
+
2. Calls `generate_fn(messages)` β raw text
|
| 90 |
+
3. Calls `parse_scientist_output(raw_text)`
|
| 91 |
+
4. On success: returns `ScientistCallResult(action, metadata)`
|
| 92 |
+
5. On failure: appends `[assistant(bad_output), user(correction)]` to messages, retries
|
| 93 |
+
6. After `max_retries` failures: raises last `ScientistOutputParseError`
|
| 94 |
+
|
| 95 |
+
**Correction prompts (`_build_correction_prompt`):**
|
| 96 |
+
- `no_json`: "Your previous response did not contain a JSON object..."
|
| 97 |
+
- `invalid_json`: "Your previous response contained malformed JSON: {error}..."
|
| 98 |
+
- `invalid_action`: "...failed ScientistAction validation: {detail}. Fix the validation error..."
|
| 99 |
+
|
| 100 |
+
#### `build_baseline_scientist_action(observation) -> ScientistAction` β AGT 04
|
| 101 |
+
Deterministic non-LLM action for smoke tests. No API calls.
|
| 102 |
+
|
| 103 |
+
**Decision tree:**
|
| 104 |
+
1. If protocol exists AND at max rounds β `accept`
|
| 105 |
+
2. If protocol exists AND latest lab_manager feedback indicates blocker β `revise_protocol` (halve sample, reduce duration)
|
| 106 |
+
3. If protocol exists AND no blocker β `accept`
|
| 107 |
+
4. If no protocol β `propose_protocol` (domain-inferred defaults)
|
| 108 |
+
|
| 109 |
+
**Domain inference (`_infer_domain`):**
|
| 110 |
+
- Checks paper fields for ML hints (benchmark, dataset, gpu, bert...) β `machine_learning`
|
| 111 |
+
- Checks for finance hints (backtest, sharpe, trading...) β `finance_trading`
|
| 112 |
+
- Default β `mathematics`
|
| 113 |
+
|
| 114 |
+
**Blocker detection (`_feedback_indicates_blocker`):**
|
| 115 |
+
- Returns `False` if action_type is `accept` or `report_feasibility`
|
| 116 |
+
- Otherwise checks message for blocker hints: booked, unavailable, exceeds, tight, budget, cost, etc.
|
| 117 |
+
|
| 118 |
+
### Classes
|
| 119 |
+
|
| 120 |
+
#### `ScientistOutputParseError(ValueError)`
|
| 121 |
+
| Attribute | Type | Purpose |
|
| 122 |
+
|-----------|------|---------|
|
| 123 |
+
| `code` | `Literal["no_json", "invalid_json", "invalid_action"]` | Machine-readable error type |
|
| 124 |
+
| `message` | `str` | Human-readable detail |
|
| 125 |
+
| `raw_text` | `str` | Original model output |
|
| 126 |
+
| `parsed_payload` | `dict \| None` | Decoded JSON if parsing succeeded |
|
| 127 |
+
|
| 128 |
+
#### `RetryMetadata(BaseModel)` β `extra="forbid"`
|
| 129 |
+
| Field | Type | Purpose |
|
| 130 |
+
|-------|------|---------|
|
| 131 |
+
| `attempt_count` | `int` | Total attempts (1 = success on first try) |
|
| 132 |
+
| `retry_count` | `int` | `attempt_count - 1` |
|
| 133 |
+
| `last_error_code` | `str \| None` | Error code from last failure |
|
| 134 |
+
| `last_error_message` | `str \| None` | Error message from last failure |
|
| 135 |
+
|
| 136 |
+
#### `ScientistCallResult(BaseModel)` β `extra="forbid"`
|
| 137 |
+
| Field | Type |
|
| 138 |
+
|-------|------|
|
| 139 |
+
| `action` | `ScientistAction` |
|
| 140 |
+
| `metadata` | `RetryMetadata` |
|
| 141 |
+
|
| 142 |
+
### Type Aliases
|
| 143 |
+
|
| 144 |
+
```python
|
| 145 |
+
GenerateFn = Callable[[list[dict[str, str]]], str]
|
| 146 |
+
```
|
| 147 |
+
|
| 148 |
+
### Constants
|
| 149 |
+
|
| 150 |
+
```python
|
| 151 |
+
_ML_HINTS = ("benchmark", "dataset", "accuracy", "tokenizer", "train", "gpu", ...)
|
| 152 |
+
_FINANCE_HINTS = ("backtest", "drawdown", "sharpe", "trading", "slippage", ...)
|
| 153 |
+
_BLOCKER_HINTS = ("booked", "unavailable", "exceeds", "tight", "budget", "cost", ...)
|
| 154 |
+
```
|
| 155 |
+
|
| 156 |
+
---
|
| 157 |
+
|
| 158 |
+
## Lab Manager Policy β `lab_manager_policy.py`
|
| 159 |
+
|
| 160 |
+
### Pipeline Flow
|
| 161 |
+
|
| 162 |
+
```
|
| 163 |
+
protocol + scenario β check_feasibility()
|
| 164 |
+
β
|
| 165 |
+
FeasibilityCheckResult (7 dimensions)
|
| 166 |
+
β
|
| 167 |
+
suggest_alternative(protocol, check, scenario)
|
| 168 |
+
β
|
| 169 |
+
AlternativeSuggestion | None
|
| 170 |
+
β
|
| 171 |
+
compose_lab_manager_response(check, suggestion)
|
| 172 |
+
β
|
| 173 |
+
LabManagerAction (typed, with explanation)
|
| 174 |
+
```
|
| 175 |
+
|
| 176 |
+
### Public Functions
|
| 177 |
+
|
| 178 |
+
#### `check_feasibility(protocol, scenario) -> FeasibilityCheckResult` β AGT 05
|
| 179 |
+
Runs 7 deterministic dimension checks. No LLM calls.
|
| 180 |
+
|
| 181 |
+
**Checks performed:**
|
| 182 |
+
| Dimension | Function | What it checks |
|
| 183 |
+
|-----------|----------|---------------|
|
| 184 |
+
| `protocol` | `_build_protocol_check` | Wraps `validate_protocol()` from MOD 05 |
|
| 185 |
+
| `budget` | `_check_budget` | `_estimate_protocol_cost()` vs `budget_remaining` |
|
| 186 |
+
| `equipment` | `_check_equipment` | Items available/booked, finds substitutions |
|
| 187 |
+
| `reagents` | `_check_reagents` | Items in-stock/out-of-stock, finds substitutions |
|
| 188 |
+
| `schedule` | `_check_schedule` | `duration_days` vs `time_limit_days` |
|
| 189 |
+
| `staff` | `_check_staff` | `_estimate_staff_load()` vs `staff_count` |
|
| 190 |
+
| `policy` | `_check_policy` | Safety restrictions (e.g., offline-only execution) |
|
| 191 |
+
|
| 192 |
+
**Cost estimation (`_estimate_protocol_cost`):**
|
| 193 |
+
```
|
| 194 |
+
base = sample_size * 10
|
| 195 |
+
+ duration_days * 50
|
| 196 |
+
+ len(controls) * 25
|
| 197 |
+
+ len(required_equipment) * 100
|
| 198 |
+
+ len(required_reagents) * 75
|
| 199 |
+
```
|
| 200 |
+
|
| 201 |
+
**Staff estimation (`_estimate_staff_load`):**
|
| 202 |
+
```
|
| 203 |
+
base = 1
|
| 204 |
+
+ (1 if sample_size > 20)
|
| 205 |
+
+ (1 if len(controls) > 2)
|
| 206 |
+
+ (1 if duration_days > 5)
|
| 207 |
+
+ (1 if len(required_equipment) > 2)
|
| 208 |
+
```
|
| 209 |
+
|
| 210 |
+
#### `suggest_alternative(protocol, check_result, scenario) -> AlternativeSuggestion | None` β AGT 06
|
| 211 |
+
Deterministic revision engine. Returns `None` if already feasible.
|
| 212 |
+
|
| 213 |
+
**Fix order (deterministic):**
|
| 214 |
+
1. Equipment substitutions β replace booked items with alternatives
|
| 215 |
+
2. Reagent substitutions β replace out-of-stock items with alternatives
|
| 216 |
+
3. Duration clamp β reduce to `time_limit_days` if over
|
| 217 |
+
4. Sample size reduction β iterative halving until budget fits (max 10 iterations)
|
| 218 |
+
|
| 219 |
+
**Post-fix recheck:** runs `check_feasibility()` on revised protocol.
|
| 220 |
+
**Returns:** revised protocol, list of changes, remaining failures, pre/post checks.
|
| 221 |
+
|
| 222 |
+
#### `compose_lab_manager_response(check_result, suggestion=None, explanation_renderer=None) -> LabManagerAction` β AGT 07
|
| 223 |
+
Converts grounded results into a typed `LabManagerAction`.
|
| 224 |
+
|
| 225 |
+
**Action type selection (`_select_lab_manager_action_type`):**
|
| 226 |
+
| Condition | Action |
|
| 227 |
+
|-----------|--------|
|
| 228 |
+
| All 7 dimensions pass | `ACCEPT` |
|
| 229 |
+
| Suggestion exists AND improved AND only non-lab failures remain | `SUGGEST_ALTERNATIVE` |
|
| 230 |
+
| Lab constraints fail AND no suggestion | `REJECT` |
|
| 231 |
+
| Only policy/protocol fail (not lab constraints) | `REPORT_FEASIBILITY` |
|
| 232 |
+
| Suggestion exists but didn't improve | `REJECT` |
|
| 233 |
+
|
| 234 |
+
**Lab constraints = budget, equipment, reagents, schedule, staff (not protocol, not policy).**
|
| 235 |
+
|
| 236 |
+
### Classes
|
| 237 |
+
|
| 238 |
+
#### `DimensionCheck(BaseModel)` β `extra="forbid"`
|
| 239 |
+
| Field | Type | Default |
|
| 240 |
+
|-------|------|---------|
|
| 241 |
+
| `ok` | `bool` | `True` |
|
| 242 |
+
| `reasons` | `list[str]` | `[]` |
|
| 243 |
+
|
| 244 |
+
#### `FeasibilityCheckResult(BaseModel)` β `extra="forbid"`
|
| 245 |
+
| Field | Type |
|
| 246 |
+
|-------|------|
|
| 247 |
+
| `protocol` | `DimensionCheck` |
|
| 248 |
+
| `budget` | `DimensionCheck` |
|
| 249 |
+
| `equipment` | `DimensionCheck` |
|
| 250 |
+
| `reagents` | `DimensionCheck` |
|
| 251 |
+
| `schedule` | `DimensionCheck` |
|
| 252 |
+
| `staff` | `DimensionCheck` |
|
| 253 |
+
| `policy` | `DimensionCheck` |
|
| 254 |
+
| `estimated_cost` | `float` |
|
| 255 |
+
| `required_staff` | `int` |
|
| 256 |
+
| `substitution_options` | `dict[str, list[str]]` |
|
| 257 |
+
| `validation_result` | `ValidationResult` |
|
| 258 |
+
|
| 259 |
+
**Computed properties:** `protocol_ok`, `budget_ok`, `equipment_ok`, `reagents_ok`, `schedule_ok`, `staff_ok`, `feasible`, `summary`
|
| 260 |
+
|
| 261 |
+
#### `SuggestionChange(BaseModel)` β `extra="forbid"`
|
| 262 |
+
| Field | Type | Purpose |
|
| 263 |
+
|-------|------|---------|
|
| 264 |
+
| `field` | `str` | Which protocol field was changed |
|
| 265 |
+
| `original` | `str` | Original value (stringified) |
|
| 266 |
+
| `revised` | `str` | New value (stringified) |
|
| 267 |
+
| `reason` | `str` | Why it was changed |
|
| 268 |
+
| `tradeoff` | `str` | What is lost |
|
| 269 |
+
|
| 270 |
+
#### `AlternativeSuggestion(BaseModel)` β `extra="forbid"`
|
| 271 |
+
| Field | Type |
|
| 272 |
+
|-------|------|
|
| 273 |
+
| `revised_protocol` | `Protocol` |
|
| 274 |
+
| `applied_changes` | `list[SuggestionChange]` |
|
| 275 |
+
| `remaining_failures` | `list[str]` |
|
| 276 |
+
| `improved` | `bool` |
|
| 277 |
+
| `pre_check` | `FeasibilityCheckResult` |
|
| 278 |
+
| `post_check` | `FeasibilityCheckResult` |
|
| 279 |
+
|
| 280 |
+
### Type Aliases
|
| 281 |
+
|
| 282 |
+
```python
|
| 283 |
+
ExplanationRenderer = Callable[
|
| 284 |
+
[LabManagerActionType, FeasibilityCheckResult, Optional[AlternativeSuggestion]],
|
| 285 |
+
str,
|
| 286 |
+
]
|
| 287 |
+
```
|
docs/map/config.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Config Map β `replicalab/config.py`
|
| 2 |
+
|
| 3 |
+
> Shared constants used across the entire project.
|
| 4 |
+
|
| 5 |
+
## Constants
|
| 6 |
+
|
| 7 |
+
| Constant | Value | Used by |
|
| 8 |
+
|----------|-------|---------|
|
| 9 |
+
| `DEFAULT_SCENARIO_TEMPLATE` | `"math_reasoning"` | server (reset defaults) |
|
| 10 |
+
| `DEFAULT_DIFFICULTY` | `"easy"` | server (reset defaults) |
|
| 11 |
+
| `MAX_ROUNDS` | `6` | scenarios (observation.max_rounds), server |
|
| 12 |
+
| `MAX_BUDGET` | `5000.0` | scenarios (budget_total base) |
|
| 13 |
+
| `TIMEOUT_SECONDS` | `300` | server (session TTL base) |
|
| 14 |
+
| `ROUND_TIME_LIMIT_SECONDS` | `300` | server (per-round timeout) |
|
| 15 |
+
| `SESSION_TTL_SECONDS` | `300` (= TIMEOUT_SECONDS) | server (session cleanup) |
|
| 16 |
+
| `WS_IDLE_TIMEOUT_SECONDS` | `300` (= TIMEOUT_SECONDS) | server (WebSocket idle) |
|
| 17 |
+
| `STUB_ACCEPT_REWARD` | `5.0` | server (_StubEnv reward on accept) |
|
| 18 |
+
| `API_HOST` | `"0.0.0.0"` | server (uvicorn bind) |
|
| 19 |
+
| `API_PORT` | `7860` | server (uvicorn port) |
|
| 20 |
+
|
| 21 |
+
## Who Imports This
|
| 22 |
+
|
| 23 |
+
| Consumer | Constants used |
|
| 24 |
+
|----------|---------------|
|
| 25 |
+
| `scenarios/templates.py` | `MAX_BUDGET`, `MAX_ROUNDS` |
|
| 26 |
+
| `server/app.py` | `API_HOST`, `API_PORT`, `DEFAULT_SCENARIO_TEMPLATE`, `DEFAULT_DIFFICULTY`, `MAX_ROUNDS`, `ROUND_TIME_LIMIT_SECONDS`, `SESSION_TTL_SECONDS`, `STUB_ACCEPT_REWARD`, `WS_IDLE_TIMEOUT_SECONDS` |
|
| 27 |
+
| `tests/test_config.py` | All constants (validation tests) |
|
| 28 |
+
|
| 29 |
+
## Project Config β `pyproject.toml`
|
| 30 |
+
|
| 31 |
+
| Key | Value |
|
| 32 |
+
|-----|-------|
|
| 33 |
+
| Name | `replicalab` |
|
| 34 |
+
| Version | `0.1.0` |
|
| 35 |
+
| Python | `>=3.10` |
|
| 36 |
+
| License | MIT |
|
| 37 |
+
|
| 38 |
+
### Dependencies
|
| 39 |
+
| Package | Version | Purpose |
|
| 40 |
+
|---------|---------|---------|
|
| 41 |
+
| `pydantic` | `>=2.7,<3.0` | Data validation |
|
| 42 |
+
| `fastapi` | `>=0.115,<1.0` | REST API framework |
|
| 43 |
+
| `uvicorn[standard]` | `>=0.34,<1.0` | ASGI server |
|
| 44 |
+
| `websockets` | `>=15.0,<17.0` | WebSocket support |
|
| 45 |
+
| `openenv-core[core]` | `>=0.2.1,<0.3.0` | Environment base (not yet used) |
|
| 46 |
+
|
| 47 |
+
### Dev Dependencies
|
| 48 |
+
| Package | Purpose |
|
| 49 |
+
|---------|---------|
|
| 50 |
+
| `pytest` | Testing |
|
| 51 |
+
| `pytest-cov` | Coverage |
|
| 52 |
+
| `pytest-asyncio` | Async test support |
|
| 53 |
+
| `httpx` | HTTP client for API tests |
|
| 54 |
+
| `ruff` | Linting |
|
| 55 |
+
| `mypy` | Type checking |
|
| 56 |
+
|
| 57 |
+
### Entry Point
|
| 58 |
+
```
|
| 59 |
+
[project.scripts]
|
| 60 |
+
server = "server.app:main"
|
| 61 |
+
```
|
docs/map/frontend.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Frontend Map β `frontend/`
|
| 2 |
+
|
| 3 |
+
> React 19 + TypeScript + Vite UI for ReplicaLab.
|
| 4 |
+
>
|
| 5 |
+
> **Tasks implemented:** FND 01-10
|
| 6 |
+
|
| 7 |
+
## Stack
|
| 8 |
+
|
| 9 |
+
| Technology | Version | Purpose |
|
| 10 |
+
|------------|---------|---------|
|
| 11 |
+
| React | 19.2.0 | UI framework |
|
| 12 |
+
| React Router | 7.13.1 | Client-side routing |
|
| 13 |
+
| Three.js | 0.183.2 | 3D molecule scene |
|
| 14 |
+
| @react-three/fiber | 9.5.0 | React Three.js bindings |
|
| 15 |
+
| @react-three/drei | 10.7.7 | Three.js helpers |
|
| 16 |
+
| Framer Motion | 12.35.1 | Animations |
|
| 17 |
+
| @xyflow/react | 12.10.1 | Flow diagrams |
|
| 18 |
+
| Recharts | 3.8.0 | Charts and graphs |
|
| 19 |
+
| Tailwind CSS | 4.2.1 | Utility-first styling |
|
| 20 |
+
| Lucide React | 0.577.0 | Icons |
|
| 21 |
+
|
| 22 |
+
## Routes β `App.tsx`
|
| 23 |
+
|
| 24 |
+
| Path | Component | Purpose |
|
| 25 |
+
|------|-----------|---------|
|
| 26 |
+
| `/` | `DashboardPage` | Training overview, scenario selection |
|
| 27 |
+
| `/episode` | `EpisodePage` | Live episode viewer (new episode) |
|
| 28 |
+
| `/episode/:episodeId` | `EpisodePage` | Replay of completed episode |
|
| 29 |
+
|
| 30 |
+
## Pages
|
| 31 |
+
|
| 32 |
+
### `DashboardPage.tsx`
|
| 33 |
+
- Scenario selection (family + difficulty)
|
| 34 |
+
- Training metrics display
|
| 35 |
+
- Episode history list
|
| 36 |
+
- Start new episode button
|
| 37 |
+
|
| 38 |
+
### `EpisodePage.tsx`
|
| 39 |
+
- Live negotiation between Scientist and Lab Manager
|
| 40 |
+
- Protocol display and evolution
|
| 41 |
+
- Score breakdown when episode completes
|
| 42 |
+
- Replay controls for completed episodes
|
| 43 |
+
|
| 44 |
+
## Components (15 files)
|
| 45 |
+
|
| 46 |
+
### Negotiation & Protocol
|
| 47 |
+
| Component | Purpose |
|
| 48 |
+
|-----------|---------|
|
| 49 |
+
| `NegotiationLog.tsx` | Scrollable conversation between agents |
|
| 50 |
+
| `ProtocolPanel.tsx` | Current protocol details display |
|
| 51 |
+
| `PaperPanel.tsx` | Paper summary (title, hypothesis, method, finding) |
|
| 52 |
+
| `LabInventory.tsx` | Equipment and reagent availability |
|
| 53 |
+
| `Controls.tsx` | User controls (start, step, auto-play) |
|
| 54 |
+
|
| 55 |
+
### Visualization
|
| 56 |
+
| Component | Purpose |
|
| 57 |
+
|-----------|---------|
|
| 58 |
+
| `ScorePanel.tsx` | Rigor/feasibility/fidelity score bars |
|
| 59 |
+
| `JudgeAuditPanel.tsx` | Judge reasoning and audit trail |
|
| 60 |
+
| `TrainingResults.tsx` | Training metrics charts |
|
| 61 |
+
| `ReplayViewer.tsx` | Step-through replay of completed episodes |
|
| 62 |
+
|
| 63 |
+
### 3D & Animation
|
| 64 |
+
| Component | Purpose |
|
| 65 |
+
|-----------|---------|
|
| 66 |
+
| `CharacterStage.tsx` | 3D stage for agent characters |
|
| 67 |
+
| `CharacterAvatar.tsx` | Individual agent avatar |
|
| 68 |
+
| `AnimatedCharacter.tsx` | Character with animations |
|
| 69 |
+
| `MoleculeScene.tsx` | 3D molecule visualization |
|
| 70 |
+
| `TiltCard.tsx` | Tilt-on-hover card component |
|
| 71 |
+
|
| 72 |
+
### Layout
|
| 73 |
+
| Component | Purpose |
|
| 74 |
+
|-----------|---------|
|
| 75 |
+
| `Header.tsx` | Top navigation bar |
|
| 76 |
+
|
| 77 |
+
## API Client β `lib/api.ts`
|
| 78 |
+
|
| 79 |
+
### REST Functions
|
| 80 |
+
| Function | Method | Endpoint |
|
| 81 |
+
|----------|--------|----------|
|
| 82 |
+
| `healthCheck()` | GET | `/health` |
|
| 83 |
+
| `getScenarios()` | GET | `/scenarios` |
|
| 84 |
+
| `resetEpisode(params)` | POST | `/reset` |
|
| 85 |
+
| `stepEpisode(action)` | POST | `/step` |
|
| 86 |
+
| `getReplay(episodeId)` | GET | `/replay/{episodeId}` |
|
| 87 |
+
|
| 88 |
+
### WebSocket
|
| 89 |
+
| Function | Purpose |
|
| 90 |
+
|----------|---------|
|
| 91 |
+
| `createWebSocket(onMessage, onOpen, onClose, onError)` | Connect to `/ws` |
|
| 92 |
+
| `sendWsMessage(ws, msg)` | Send typed message |
|
| 93 |
+
|
| 94 |
+
### Mock Data (for offline development)
|
| 95 |
+
| Function | Returns |
|
| 96 |
+
|----------|---------|
|
| 97 |
+
| `createMockConversation()` | `NegotiationMessage[]` |
|
| 98 |
+
| `createMockScores()` | `ScoreBreakdown` |
|
| 99 |
+
| `createMockEpisodeState(done)` | `EpisodeState` |
|
| 100 |
+
| `createMockProtocol()` | `Protocol` |
|
| 101 |
+
| `createMockJudgeAudit()` | `JudgeAudit` |
|
| 102 |
+
|
| 103 |
+
## TypeScript Types β `types/index.ts`
|
| 104 |
+
|
| 105 |
+
Mirrors Python models:
|
| 106 |
+
|
| 107 |
+
| TS Interface | Python Model |
|
| 108 |
+
|-------------|--------------|
|
| 109 |
+
| `ScientistAction` | `ScientistAction` |
|
| 110 |
+
| `LabManagerAction` | `LabManagerAction` |
|
| 111 |
+
| `Protocol` | `Protocol` |
|
| 112 |
+
| `EpisodeState` | `EpisodeState` |
|
| 113 |
+
| `StepResult` | `StepResult` |
|
| 114 |
+
| `ScoreBreakdown` | `RewardBreakdown` |
|
| 115 |
+
| `FeasibilityReport` | `FeasibilityCheckResult` (partial) |
|
| 116 |
+
| `JudgeAudit` | `StepInfo.judge_notes` + `verdict` |
|
| 117 |
+
| `NegotiationMessage` | `ConversationEntry` |
|
| 118 |
+
|
| 119 |
+
Additional frontend-only types:
|
| 120 |
+
- `TrainingMetrics` β loss, reward curves
|
| 121 |
+
- `TrainingComparison` β baseline vs trained model
|
| 122 |
+
- `PaperSummary` β paper details for display
|
| 123 |
+
- `LabConstraints` β lab resource summary
|
| 124 |
+
- `SuggestedChange` β protocol revision display
|
| 125 |
+
|
| 126 |
+
## Utility Files
|
| 127 |
+
|
| 128 |
+
### `lib/utils.ts`
|
| 129 |
+
Shared helpers (class merging, formatting, etc.)
|
| 130 |
+
|
| 131 |
+
### `lib/audio.ts`
|
| 132 |
+
Audio feedback utilities for UI interactions.
|
| 133 |
+
|
| 134 |
+
## Assets
|
| 135 |
+
|
| 136 |
+
```
|
| 137 |
+
frontend/public/characters/
|
| 138 |
+
judge.png (~1.2 MB)
|
| 139 |
+
lab-manager.png (~900 KB)
|
| 140 |
+
scientist.png (~900 KB)
|
| 141 |
+
```
|
docs/map/models.md
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Models Map β `replicalab/models.py`
|
| 2 |
+
|
| 3 |
+
> All Pydantic data contracts. Frozen with `extra="forbid"` unless noted.
|
| 4 |
+
>
|
| 5 |
+
> **Tasks implemented:** MOD 01, 02, 03, 04, 09, 11, 12
|
| 6 |
+
|
| 7 |
+
## Enums
|
| 8 |
+
|
| 9 |
+
### `ScientistActionType(str, Enum)`
|
| 10 |
+
| Value | Meaning |
|
| 11 |
+
|-------|---------|
|
| 12 |
+
| `propose_protocol` | First protocol submission |
|
| 13 |
+
| `revise_protocol` | Modify existing protocol |
|
| 14 |
+
| `request_info` | Ask lab manager a question |
|
| 15 |
+
| `accept` | Agree to current protocol |
|
| 16 |
+
|
| 17 |
+
### `LabManagerActionType(str, Enum)`
|
| 18 |
+
| Value | Meaning |
|
| 19 |
+
|-------|---------|
|
| 20 |
+
| `report_feasibility` | Report on feasibility without suggestions |
|
| 21 |
+
| `suggest_alternative` | Propose revised protocol |
|
| 22 |
+
| `reject` | Reject protocol outright |
|
| 23 |
+
| `accept` | Approve protocol |
|
| 24 |
+
|
| 25 |
+
## Action Models
|
| 26 |
+
|
| 27 |
+
### `ScientistAction(BaseModel)` β `extra="forbid"`
|
| 28 |
+
MOD 01 + MOD 09. Strict contract for scientist output.
|
| 29 |
+
|
| 30 |
+
| Field | Type | Constraint | Notes |
|
| 31 |
+
|-------|------|-----------|-------|
|
| 32 |
+
| `action_type` | `ScientistActionType` | required | |
|
| 33 |
+
| `sample_size` | `int` | `ge=0` | Must be `>=1` for propose/revise |
|
| 34 |
+
| `controls` | `list[str]` | normalized | |
|
| 35 |
+
| `technique` | `str` | stripped | Required for propose/revise |
|
| 36 |
+
| `duration_days` | `int` | `ge=0` | |
|
| 37 |
+
| `required_equipment` | `list[str]` | normalized | |
|
| 38 |
+
| `required_reagents` | `list[str]` | normalized | |
|
| 39 |
+
| `questions` | `list[str]` | normalized | Required non-empty for request_info |
|
| 40 |
+
| `rationale` | `str` | stripped | Required for propose/revise |
|
| 41 |
+
|
| 42 |
+
**Validation rules:**
|
| 43 |
+
- `propose_protocol` / `revise_protocol`: sample_size >= 1, technique required, rationale required, questions must be empty
|
| 44 |
+
- `request_info`: questions non-empty, no protocol payload fields
|
| 45 |
+
- `accept`: no questions, no protocol payload fields
|
| 46 |
+
|
| 47 |
+
### `LabManagerAction(BaseModel)` β `extra="forbid"`
|
| 48 |
+
MOD 02. Strict contract with feasible-flag consistency.
|
| 49 |
+
|
| 50 |
+
| Field | Type | Constraint | Notes |
|
| 51 |
+
|-------|------|-----------|-------|
|
| 52 |
+
| `action_type` | `LabManagerActionType` | required | |
|
| 53 |
+
| `feasible` | `bool` | required | Must equal AND of all constraint flags |
|
| 54 |
+
| `budget_ok` | `bool` | required | |
|
| 55 |
+
| `equipment_ok` | `bool` | required | |
|
| 56 |
+
| `reagents_ok` | `bool` | required | |
|
| 57 |
+
| `schedule_ok` | `bool` | required | |
|
| 58 |
+
| `staff_ok` | `bool` | required | |
|
| 59 |
+
| `suggested_technique` | `str` | stripped | Only for suggest_alternative |
|
| 60 |
+
| `suggested_sample_size` | `int` | `ge=0` | Only for suggest_alternative |
|
| 61 |
+
| `suggested_controls` | `list[str]` | normalized | Only for suggest_alternative |
|
| 62 |
+
| `explanation` | `str` | required non-empty | |
|
| 63 |
+
|
| 64 |
+
**Validation rules:**
|
| 65 |
+
- `feasible` must equal `all(budget_ok, equipment_ok, reagents_ok, schedule_ok, staff_ok)`
|
| 66 |
+
- `accept` requires `feasible=True`
|
| 67 |
+
- `reject` requires `feasible=False`
|
| 68 |
+
- `suggest_alternative` requires `feasible=False` + at least one suggestion field
|
| 69 |
+
- Suggestion fields forbidden for non-suggest_alternative actions
|
| 70 |
+
|
| 71 |
+
## Observation Models
|
| 72 |
+
|
| 73 |
+
### `ConversationEntry(BaseModel)` β `extra="forbid"`
|
| 74 |
+
| Field | Type | Notes |
|
| 75 |
+
|-------|------|-------|
|
| 76 |
+
| `role` | `Literal["scientist", "lab_manager", "system"]` | |
|
| 77 |
+
| `message` | `str` | Required non-empty |
|
| 78 |
+
| `round_number` | `int` | `ge=0` |
|
| 79 |
+
| `action_type` | `Optional[str]` | Null or non-empty |
|
| 80 |
+
|
| 81 |
+
### `Protocol(BaseModel)` β `extra="forbid"`
|
| 82 |
+
Shared protocol payload used in observations and actions.
|
| 83 |
+
|
| 84 |
+
| Field | Type | Notes |
|
| 85 |
+
|-------|------|-------|
|
| 86 |
+
| `sample_size` | `int` | `ge=0` |
|
| 87 |
+
| `controls` | `list[str]` | normalized |
|
| 88 |
+
| `technique` | `str` | required non-empty |
|
| 89 |
+
| `duration_days` | `int` | `ge=0` |
|
| 90 |
+
| `required_equipment` | `list[str]` | normalized |
|
| 91 |
+
| `required_reagents` | `list[str]` | normalized |
|
| 92 |
+
| `rationale` | `str` | required non-empty |
|
| 93 |
+
|
| 94 |
+
### `ScientistObservation(BaseModel)` β `extra="forbid"`
|
| 95 |
+
| Field | Type |
|
| 96 |
+
|-------|------|
|
| 97 |
+
| `paper_title` | `str` |
|
| 98 |
+
| `paper_hypothesis` | `str` |
|
| 99 |
+
| `paper_method` | `str` |
|
| 100 |
+
| `paper_key_finding` | `str` |
|
| 101 |
+
| `experiment_goal` | `str` |
|
| 102 |
+
| `conversation_history` | `list[ConversationEntry]` |
|
| 103 |
+
| `current_protocol` | `Optional[Protocol]` |
|
| 104 |
+
| `round_number` | `int` (ge=0) |
|
| 105 |
+
| `max_rounds` | `int` (ge=0) |
|
| 106 |
+
|
| 107 |
+
### `LabManagerObservation(BaseModel)` β `extra="forbid"`
|
| 108 |
+
| Field | Type |
|
| 109 |
+
|-------|------|
|
| 110 |
+
| `budget_total` | `float` (ge=0) |
|
| 111 |
+
| `budget_remaining` | `float` (ge=0) |
|
| 112 |
+
| `equipment_available` | `list[str]` |
|
| 113 |
+
| `equipment_booked` | `list[str]` |
|
| 114 |
+
| `reagents_in_stock` | `list[str]` |
|
| 115 |
+
| `reagents_out_of_stock` | `list[str]` |
|
| 116 |
+
| `staff_count` | `int` (ge=0) |
|
| 117 |
+
| `time_limit_days` | `int` (ge=0) |
|
| 118 |
+
| `safety_restrictions` | `list[str]` |
|
| 119 |
+
| `conversation_history` | `list[ConversationEntry]` |
|
| 120 |
+
| `current_protocol` | `Optional[Protocol]` |
|
| 121 |
+
| `round_number` | `int` (ge=0) |
|
| 122 |
+
| `max_rounds` | `int` (ge=0) |
|
| 123 |
+
|
| 124 |
+
### `Observation(BaseModel)` β `extra="forbid"`
|
| 125 |
+
Combined wrapper. Each role receives its own view.
|
| 126 |
+
|
| 127 |
+
| Field | Type |
|
| 128 |
+
|-------|------|
|
| 129 |
+
| `scientist` | `Optional[ScientistObservation]` |
|
| 130 |
+
| `lab_manager` | `Optional[LabManagerObservation]` |
|
| 131 |
+
|
| 132 |
+
## Reward & Step Models
|
| 133 |
+
|
| 134 |
+
### `RewardBreakdown(BaseModel)` β default `extra="forbid"`
|
| 135 |
+
MOD 11. Component scores from judge rubric engine.
|
| 136 |
+
|
| 137 |
+
| Field | Type | Default | Range |
|
| 138 |
+
|-------|------|---------|-------|
|
| 139 |
+
| `rigor` | `float` | 0.0 | [0, 1] |
|
| 140 |
+
| `feasibility` | `float` | 0.0 | [0, 1] |
|
| 141 |
+
| `fidelity` | `float` | 0.0 | [0, 1] |
|
| 142 |
+
| `efficiency_bonus` | `float` | 0.0 | unbounded |
|
| 143 |
+
| `communication_bonus` | `float` | 0.0 | unbounded |
|
| 144 |
+
| `penalties` | `dict[str, float]` | {} | unbounded |
|
| 145 |
+
|
| 146 |
+
### `StepInfo(BaseModel)` β `extra="allow"`
|
| 147 |
+
MOD 11. Extensible metadata returned with each step.
|
| 148 |
+
|
| 149 |
+
| Field | Type | Default |
|
| 150 |
+
|-------|------|---------|
|
| 151 |
+
| `agreement_reached` | `bool` | False |
|
| 152 |
+
| `error` | `Optional[str]` | None |
|
| 153 |
+
| `reward_breakdown` | `Optional[RewardBreakdown]` | None |
|
| 154 |
+
| `judge_notes` | `Optional[str]` | None |
|
| 155 |
+
| `verdict` | `Optional[str]` | None |
|
| 156 |
+
|
| 157 |
+
### `StepResult(BaseModel)`
|
| 158 |
+
| Field | Type | Default |
|
| 159 |
+
|-------|------|---------|
|
| 160 |
+
| `observation` | `Optional[Observation]` | None |
|
| 161 |
+
| `reward` | `float` | 0.0 |
|
| 162 |
+
| `done` | `bool` | False |
|
| 163 |
+
| `info` | `StepInfo` | StepInfo() |
|
| 164 |
+
|
| 165 |
+
## Episode Models
|
| 166 |
+
|
| 167 |
+
### `EpisodeState(BaseModel)` β MOD 04
|
| 168 |
+
Full internal state for debugging and replay.
|
| 169 |
+
|
| 170 |
+
| Field | Type | Default |
|
| 171 |
+
|-------|------|---------|
|
| 172 |
+
| `seed` | `int` | 0 |
|
| 173 |
+
| `scenario_template` | `str` | "" |
|
| 174 |
+
| `difficulty` | `str` | "easy" |
|
| 175 |
+
| `paper_title` | `str` | "" |
|
| 176 |
+
| `paper_hypothesis` | `str` | "" |
|
| 177 |
+
| `paper_method` | `str` | "" |
|
| 178 |
+
| `paper_key_finding` | `str` | "" |
|
| 179 |
+
| `experiment_goal` | `str` | "" |
|
| 180 |
+
| `lab_budget_total` | `float` | 0.0 |
|
| 181 |
+
| `lab_budget_remaining` | `float` | 0.0 |
|
| 182 |
+
| `lab_equipment` | `list[str]` | [] |
|
| 183 |
+
| `lab_reagents` | `list[str]` | [] |
|
| 184 |
+
| `lab_staff_count` | `int` | 0 |
|
| 185 |
+
| `lab_time_limit_days` | `int` | 0 |
|
| 186 |
+
| `current_protocol` | `Optional[Protocol]` | None |
|
| 187 |
+
| `conversation_history` | `list[ConversationEntry]` | [] |
|
| 188 |
+
| `round_number` | `int` | 0 |
|
| 189 |
+
| `max_rounds` | `int` | 0 |
|
| 190 |
+
| `done` | `bool` | False |
|
| 191 |
+
| `agreement_reached` | `bool` | False |
|
| 192 |
+
| `reward` | `float` | 0.0 |
|
| 193 |
+
| `rigor_score` | `float` | 0.0 |
|
| 194 |
+
| `feasibility_score` | `float` | 0.0 |
|
| 195 |
+
| `fidelity_score` | `float` | 0.0 |
|
| 196 |
+
|
| 197 |
+
### `EpisodeLog(BaseModel)` β MOD 04
|
| 198 |
+
Completed episode record for logging, replay, evaluation.
|
| 199 |
+
|
| 200 |
+
| Field | Type | Default |
|
| 201 |
+
|-------|------|---------|
|
| 202 |
+
| `episode_id` | `str` | "" |
|
| 203 |
+
| `seed` | `int` | 0 |
|
| 204 |
+
| `scenario_template` | `str` | "" |
|
| 205 |
+
| `difficulty` | `str` | "easy" |
|
| 206 |
+
| `final_state` | `Optional[EpisodeState]` | None |
|
| 207 |
+
| `transcript` | `list[ConversationEntry]` | [] |
|
| 208 |
+
| `reward_breakdown` | `Optional[RewardBreakdown]` | None |
|
| 209 |
+
| `total_reward` | `float` | 0.0 |
|
| 210 |
+
| `rounds_used` | `int` | 0 |
|
| 211 |
+
| `agreement_reached` | `bool` | False |
|
| 212 |
+
| `judge_notes` | `str` | "" |
|
| 213 |
+
| `verdict` | `str` | "" |
|
| 214 |
+
|
| 215 |
+
## Helper Functions
|
| 216 |
+
|
| 217 |
+
| Function | Purpose |
|
| 218 |
+
|----------|---------|
|
| 219 |
+
| `_normalize_string_list(value)` | Strip whitespace, reject empty strings |
|
docs/map/scenarios.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Scenarios Map β `replicalab/scenarios/`
|
| 2 |
+
|
| 3 |
+
> Normalized scenario generation across 3 domains with seeded determinism.
|
| 4 |
+
>
|
| 5 |
+
> **Tasks implemented:** SCN 01-12
|
| 6 |
+
|
| 7 |
+
## Entry Point
|
| 8 |
+
|
| 9 |
+
### `generate_scenario(seed, template, difficulty) -> NormalizedScenarioPack`
|
| 10 |
+
Located in `templates.py`. The main public API.
|
| 11 |
+
|
| 12 |
+
**Flow:**
|
| 13 |
+
1. `seed_rng(seed)` β deterministic `random.Random` instance
|
| 14 |
+
2. `load_template(template)` β picks the template builder function
|
| 15 |
+
3. `builder(rng)` β raw draft dict (randomly selects one of 2 cases per domain)
|
| 16 |
+
4. `apply_difficulty(draft, difficulty, rng)` β scales budget, time, staff, resources
|
| 17 |
+
5. `_build_pack(seed, template, draft)` β constructs `NormalizedScenarioPack`
|
| 18 |
+
|
| 19 |
+
### `available_scenario_families() -> list[dict]`
|
| 20 |
+
Returns `[{"family": name, "difficulties": ["easy", "medium", "hard"]}]` for each template.
|
| 21 |
+
|
| 22 |
+
## Core Data Classes (all in `templates.py`)
|
| 23 |
+
|
| 24 |
+
### `NormalizedScenarioPack(BaseModel)` β `extra="forbid"`
|
| 25 |
+
The complete scenario definition. Every downstream consumer uses this.
|
| 26 |
+
|
| 27 |
+
| Field | Type | Source |
|
| 28 |
+
|-------|------|--------|
|
| 29 |
+
| `scenario_id` | `str` | `"{template}_{seed}"` |
|
| 30 |
+
| `template` | `TemplateName` | input param |
|
| 31 |
+
| `domain_id` | `str` | from template case |
|
| 32 |
+
| `difficulty` | `Difficulty` | input param |
|
| 33 |
+
| `seed` | `int` | input param |
|
| 34 |
+
| `task_summary` | `str` | from template case |
|
| 35 |
+
| `success_criteria` | `list[str]` | from template case |
|
| 36 |
+
| `constraints` | `list[ScenarioConstraint]` | from template + difficulty scaling |
|
| 37 |
+
| `resources` | `list[ScenarioResource]` | from template + difficulty scaling |
|
| 38 |
+
| `allowed_substitutions` | `list[AllowedSubstitution]` | from template case |
|
| 39 |
+
| `hidden_reference_spec` | `HiddenReferenceSpec` | from template case |
|
| 40 |
+
| `scientist_observation` | `ScientistObservation` | built from case fields |
|
| 41 |
+
| `lab_manager_observation` | `LabManagerObservation` | built from case fields |
|
| 42 |
+
|
| 43 |
+
### `ScenarioConstraint(BaseModel)`
|
| 44 |
+
| Field | Type | Example |
|
| 45 |
+
|-------|------|---------|
|
| 46 |
+
| `key` | `str` | `"gpu_hours"` |
|
| 47 |
+
| `label` | `str` | `"Maximum GPU budget"` |
|
| 48 |
+
| `quantity` | `float \| int \| None` | `8` |
|
| 49 |
+
| `unit` | `str \| None` | `"gpu_hours"` |
|
| 50 |
+
| `comparator` | `Literal["<=", ">=", "="]` | `"<="` |
|
| 51 |
+
| `hard` | `bool` | `True` |
|
| 52 |
+
| `details` | `str` | `"The full run must fit within eight GPU-hours."` |
|
| 53 |
+
|
| 54 |
+
### `ScenarioResource(BaseModel)`
|
| 55 |
+
| Field | Type | Example |
|
| 56 |
+
|-------|------|---------|
|
| 57 |
+
| `key` | `str` | `"gpu_node"` |
|
| 58 |
+
| `label` | `str` | `"A100 GPU node"` |
|
| 59 |
+
| `quantity` | `float \| int \| None` | `1` |
|
| 60 |
+
| `unit` | `str \| None` | `"node"` |
|
| 61 |
+
| `available` | `bool` | `True` |
|
| 62 |
+
| `category` | `str` | `"compute"` |
|
| 63 |
+
| `details` | `str` | `"Reserved for one benchmark run at a time."` |
|
| 64 |
+
|
| 65 |
+
### `AllowedSubstitution(BaseModel)`
|
| 66 |
+
| Field | Type | Example |
|
| 67 |
+
|-------|------|---------|
|
| 68 |
+
| `original` | `str` | `"A100 GPU node"` |
|
| 69 |
+
| `alternative` | `str` | `"V100 GPU node"` |
|
| 70 |
+
| `condition` | `str` | `"Use if A100 is booked."` |
|
| 71 |
+
| `tradeoff` | `str` | `"V100 is slower; extend training by ~30%."` |
|
| 72 |
+
|
| 73 |
+
### `HiddenReferenceSpec(BaseModel)`
|
| 74 |
+
Ground truth the judge uses to score fidelity. The scientist never sees this.
|
| 75 |
+
|
| 76 |
+
| Field | Type | Example |
|
| 77 |
+
|-------|------|---------|
|
| 78 |
+
| `summary` | `str` | `"A valid plan keeps the published split..."` |
|
| 79 |
+
| `required_elements` | `list[str]` | `["published data split", "held-out accuracy evaluation"]` |
|
| 80 |
+
| `flexible_elements` | `list[str]` | `["batch size", "learning-rate schedule"]` |
|
| 81 |
+
| `target_metric` | `str` | `"held_out_accuracy"` |
|
| 82 |
+
| `target_value` | `str` | `"within one point of the reported baseline"` |
|
| 83 |
+
|
| 84 |
+
## Template Builders
|
| 85 |
+
|
| 86 |
+
Each returns a raw `dict[str, Any]` with one randomly selected case.
|
| 87 |
+
|
| 88 |
+
### `build_math_reasoning_template(rng)` β `math_reasoning.py`
|
| 89 |
+
- **Domain:** `mathematics`
|
| 90 |
+
- **Case A:** Cauchy-Schwarz inequality β structured proof verification
|
| 91 |
+
- **Case B:** Jensen's inequality β convexity-based proof
|
| 92 |
+
- **Equipment:** Structured proof notebook, Automated proof checker
|
| 93 |
+
- **Reagents:** Graduate reviewer, Reference textbook
|
| 94 |
+
- **Substitutions:** Graduate reviewer β self-check rubric
|
| 95 |
+
|
| 96 |
+
### `build_ml_benchmark_template(rng)` β `ml_benchmark.py`
|
| 97 |
+
- **Domain:** `machine_learning`
|
| 98 |
+
- **Case A:** AG News TinyBERT β text classification replication
|
| 99 |
+
- **Case B:** CIFAR-10 ResNet-18 β image classification replication
|
| 100 |
+
- **Equipment:** A100 GPU node, Dataset mirror, Experiment tracker
|
| 101 |
+
- **Reagents:** Pre-trained checkpoint, Evaluation harness
|
| 102 |
+
- **Substitutions:** A100 β V100 (slower), full dataset β stratified sample
|
| 103 |
+
|
| 104 |
+
### `build_finance_trading_template(rng)` β `finance_trading.py`
|
| 105 |
+
- **Domain:** `finance_trading`
|
| 106 |
+
- **Case A:** SPY/QQQ mean-reversion β pairs trading backtest
|
| 107 |
+
- **Case B:** Momentum futures β trend-following strategy
|
| 108 |
+
- **Equipment:** Backtest engine, Historical daily bar dataset
|
| 109 |
+
- **Reagents:** Risk reviewer, Compliance packet
|
| 110 |
+
- **Substitutions:** Daily bars β weekly bars, risk reviewer β automated risk check
|
| 111 |
+
- **Safety restrictions:** offline-only execution policy
|
| 112 |
+
|
| 113 |
+
## Difficulty Scaling β `apply_difficulty(draft, difficulty, rng)`
|
| 114 |
+
|
| 115 |
+
| Parameter | Easy | Medium | Hard |
|
| 116 |
+
|-----------|------|--------|------|
|
| 117 |
+
| `budget_total` | Γ1.15 | Γ0.95 | Γ0.80 |
|
| 118 |
+
| `time_limit_days` | unchanged | β1 day | β1 day |
|
| 119 |
+
| `staff_count` | unchanged | unchanged | β1 person |
|
| 120 |
+
| Resources tightened | 0 | 1 | 2 |
|
| 121 |
+
| Conflict constraint | no | yes (1) | yes (1) |
|
| 122 |
+
|
| 123 |
+
**`_tighten_one_resource`**: picks a random resource, sets `available=False`.
|
| 124 |
+
**`_append_conflict_constraint`**: adds a soft constraint noting resource conflict.
|
| 125 |
+
|
| 126 |
+
## Utility β `replicalab/utils/seed.py`
|
| 127 |
+
|
| 128 |
+
| Function | Purpose |
|
| 129 |
+
|----------|---------|
|
| 130 |
+
| `get_deterministic_seed(seed, namespace)` | SHA256-based child seed derivation |
|
| 131 |
+
| `seed_rng(seed, namespace)` | Returns `random.Random(derived_seed)` |
|
| 132 |
+
|
| 133 |
+
## Type Aliases
|
| 134 |
+
|
| 135 |
+
```python
|
| 136 |
+
Difficulty = Literal["easy", "medium", "hard"]
|
| 137 |
+
TemplateName = Literal["math_reasoning", "ml_benchmark", "finance_trading"]
|
| 138 |
+
TemplateBuilder = Callable[[Any], dict[str, Any]]
|
| 139 |
+
```
|
| 140 |
+
|
| 141 |
+
## Constants
|
| 142 |
+
|
| 143 |
+
```python
|
| 144 |
+
GOLDEN_SCENARIO_SPECS_PATH = Path("tests/fixtures/golden_scenarios.json")
|
| 145 |
+
```
|
| 146 |
+
|
| 147 |
+
## Who Consumes This
|
| 148 |
+
|
| 149 |
+
- **`validation.py`** β reads constraints, resources, substitutions, hidden_reference_spec
|
| 150 |
+
- **`lab_manager_policy.py`** β reads lab_manager_observation, substitutions, constraints
|
| 151 |
+
- **`scientist_policy.py`** β reads scenario pack for system prompt generation
|
| 152 |
+
- **`server/app.py`** β calls `generate_scenario()` on reset, stores pack for lab manager
|
| 153 |
+
- **`scoring/`** (future) β will read hidden_reference_spec for fidelity scoring
|
docs/map/scoring.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Scoring Map β `replicalab/scoring/`
|
| 2 |
+
|
| 3 |
+
> Judge scoring engine for protocol evaluation.
|
| 4 |
+
>
|
| 5 |
+
> **Status:** NOT YET IMPLEMENTED
|
| 6 |
+
> **Tasks remaining:** JDG 01-08
|
| 7 |
+
|
| 8 |
+
## Planned Architecture
|
| 9 |
+
|
| 10 |
+
```
|
| 11 |
+
replicalab/scoring/
|
| 12 |
+
__init__.py # exports: score_rigor, score_feasibility, score_fidelity, compute_reward
|
| 13 |
+
rigor.py # JDG 01 β protocol structural quality
|
| 14 |
+
feasibility.py # JDG 02 β resource feasibility (wraps AGT 05)
|
| 15 |
+
fidelity.py # JDG 03 β adherence to hidden reference spec
|
| 16 |
+
```
|
| 17 |
+
|
| 18 |
+
## Planned Functions
|
| 19 |
+
|
| 20 |
+
### `score_rigor(protocol, scenario) -> float` β JDG 01
|
| 21 |
+
Score range: [0.0, 1.0]
|
| 22 |
+
Measures: structural completeness, success criteria coverage, required element coverage.
|
| 23 |
+
|
| 24 |
+
### `score_feasibility(protocol, scenario, check_result=None) -> float` β JDG 02
|
| 25 |
+
Score range: [0.0, 1.0]
|
| 26 |
+
Measures: whether the lab can execute the protocol (budget, equipment, reagents, schedule, staff, policy).
|
| 27 |
+
Reuses `check_feasibility()` from AGT 05. Adds partial credit (continuous signal vs binary pass/fail).
|
| 28 |
+
|
| 29 |
+
### `score_fidelity(protocol, scenario) -> float` β JDG 03
|
| 30 |
+
Score range: [0.0, 1.0]
|
| 31 |
+
Measures: how closely the protocol matches `hidden_reference_spec`.
|
| 32 |
+
Substitution-aware β allowed substitutions get partial credit.
|
| 33 |
+
|
| 34 |
+
### `compute_reward(protocol, scenario, check_result=None) -> RewardBreakdown` β JDG 04/05
|
| 35 |
+
Combines rigor + feasibility + fidelity into `RewardBreakdown`.
|
| 36 |
+
Applies efficiency bonus, communication bonus, and penalties.
|
| 37 |
+
|
| 38 |
+
## Data Consumed
|
| 39 |
+
|
| 40 |
+
| Source | Used by | For what |
|
| 41 |
+
|--------|---------|----------|
|
| 42 |
+
| `Protocol` (models.py) | All scorers | The final agreed protocol |
|
| 43 |
+
| `NormalizedScenarioPack` (scenarios) | All scorers | Constraints, resources, success criteria |
|
| 44 |
+
| `HiddenReferenceSpec` (scenarios) | JDG 01, JDG 03 | Required/flexible elements, target metric |
|
| 45 |
+
| `FeasibilityCheckResult` (agents) | JDG 02 | 7 dimension checks |
|
| 46 |
+
| `AllowedSubstitution` (scenarios) | JDG 03 | Partial credit for substitutions |
|
| 47 |
+
| `RewardBreakdown` (models.py) | JDG 04/05 | Output container |
|
| 48 |
+
|
| 49 |
+
## Data Produced
|
| 50 |
+
|
| 51 |
+
`RewardBreakdown` populates:
|
| 52 |
+
- `rigor: float` β from JDG 01
|
| 53 |
+
- `feasibility: float` β from JDG 02
|
| 54 |
+
- `fidelity: float` β from JDG 03
|
| 55 |
+
- `efficiency_bonus: float` β from JDG 04 (rounds used / max rounds)
|
| 56 |
+
- `communication_bonus: float` β from JDG 05 (negotiation quality)
|
| 57 |
+
- `penalties: dict[str, float]` β from JDG 06-08 (policy violations, etc.)
|
docs/map/server.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Server Map β `server/app.py`
|
| 2 |
+
|
| 3 |
+
> FastAPI backend with REST + WebSocket endpoints and stub environment.
|
| 4 |
+
>
|
| 5 |
+
> **Tasks implemented:** API 01-04, 06 (partial)
|
| 6 |
+
|
| 7 |
+
## Environment
|
| 8 |
+
|
| 9 |
+
### `_StubEnv`
|
| 10 |
+
Minimal environment stub used until the real `ReplicaLabEnv` is implemented (ENV 01-11).
|
| 11 |
+
|
| 12 |
+
**State:**
|
| 13 |
+
| Attribute | Type | Purpose |
|
| 14 |
+
|-----------|------|---------|
|
| 15 |
+
| `_state` | `EpisodeState` | Full episode state |
|
| 16 |
+
| `_episode_id` | `str` | UUID for this episode |
|
| 17 |
+
| `_scenario_pack` | `NormalizedScenarioPack \| None` | Stored for lab manager pipeline |
|
| 18 |
+
| `_logs` | `list[ConversationEntry]` | Conversation transcript |
|
| 19 |
+
|
| 20 |
+
**Methods:**
|
| 21 |
+
|
| 22 |
+
| Method | Returns | Behavior |
|
| 23 |
+
|--------|---------|----------|
|
| 24 |
+
| `reset(seed, scenario, difficulty)` | `Observation` | Generates scenario, builds initial observations |
|
| 25 |
+
| `step(action: ScientistAction)` | `StepResult` | Processes scientist action, runs lab manager pipeline |
|
| 26 |
+
| `state()` | `EpisodeState` | Returns current state snapshot |
|
| 27 |
+
| `episode_id()` | `str` | Returns episode UUID |
|
| 28 |
+
| `close()` | `None` | No-op |
|
| 29 |
+
|
| 30 |
+
**Lab Manager Integration (AGT 07):**
|
| 31 |
+
The `_lab_manager_action()` method runs the full deterministic pipeline:
|
| 32 |
+
1. `check_feasibility(protocol, scenario_pack)` β `FeasibilityCheckResult`
|
| 33 |
+
2. `suggest_alternative(protocol, check_result, scenario_pack)` β `AlternativeSuggestion | None`
|
| 34 |
+
3. `compose_lab_manager_response(check_result, suggestion)` β `LabManagerAction`
|
| 35 |
+
|
| 36 |
+
**Termination logic:**
|
| 37 |
+
- Episode ends (`done=True`) when `agreement_reached=True` (both agents accept)
|
| 38 |
+
- `agreement_reached` when lab manager action_type is `accept` (2-round stub logic)
|
| 39 |
+
- On termination: reward = `STUB_ACCEPT_REWARD` (5.0)
|
| 40 |
+
|
| 41 |
+
### `_make_env() -> _StubEnv`
|
| 42 |
+
Factory that tries to import `ReplicaLabEnv` from `replicalab.env`, falls back to `_StubEnv`.
|
| 43 |
+
|
| 44 |
+
## REST Endpoints
|
| 45 |
+
|
| 46 |
+
### `GET /health`
|
| 47 |
+
Returns `{"status": "ok"}`.
|
| 48 |
+
|
| 49 |
+
### `POST /reset`
|
| 50 |
+
**Request:** `ResetRequest`
|
| 51 |
+
| Field | Type | Default |
|
| 52 |
+
|-------|------|---------|
|
| 53 |
+
| `seed` | `int \| None` | `None` (random) |
|
| 54 |
+
| `scenario` | `str` | `DEFAULT_SCENARIO_TEMPLATE` |
|
| 55 |
+
| `difficulty` | `str` | `DEFAULT_DIFFICULTY` |
|
| 56 |
+
| `session_id` | `str \| None` | `None` (auto-generated) |
|
| 57 |
+
|
| 58 |
+
**Response:** `ResetResponse`
|
| 59 |
+
| Field | Type |
|
| 60 |
+
|-------|------|
|
| 61 |
+
| `session_id` | `str` |
|
| 62 |
+
| `episode_id` | `str` |
|
| 63 |
+
| `observation` | `Observation` |
|
| 64 |
+
|
| 65 |
+
### `POST /step`
|
| 66 |
+
**Request:** `StepRequest`
|
| 67 |
+
| Field | Type |
|
| 68 |
+
|-------|------|
|
| 69 |
+
| `session_id` | `str` |
|
| 70 |
+
| `action` | `ScientistAction` |
|
| 71 |
+
|
| 72 |
+
**Response:** `StepResult` (observation, reward, done, info)
|
| 73 |
+
|
| 74 |
+
When `done=True`, the episode log is stored in `_replay_store`.
|
| 75 |
+
|
| 76 |
+
### `GET /scenarios`
|
| 77 |
+
Returns `available_scenario_families()` β list of families with difficulties.
|
| 78 |
+
|
| 79 |
+
### `GET /replay/{episode_id}`
|
| 80 |
+
Returns `EpisodeLog` for a completed episode, or 404 if not found.
|
| 81 |
+
|
| 82 |
+
## WebSocket Endpoint
|
| 83 |
+
|
| 84 |
+
### `WS /ws`
|
| 85 |
+
Bidirectional session with JSON messages.
|
| 86 |
+
|
| 87 |
+
**Client β Server messages:**
|
| 88 |
+
| Type | Payload | Behavior |
|
| 89 |
+
|------|---------|----------|
|
| 90 |
+
| `reset` | `{seed, scenario, difficulty}` | Creates env, returns initial state |
|
| 91 |
+
| `step` | `{action: ScientistAction}` | Steps env, returns result |
|
| 92 |
+
| `ping` | β | Returns `{"type": "pong"}` |
|
| 93 |
+
|
| 94 |
+
**Server β Client messages:**
|
| 95 |
+
| Type | Payload |
|
| 96 |
+
|------|---------|
|
| 97 |
+
| `state` | `{observation, episode_id}` |
|
| 98 |
+
| `step_result` | `StepResult.info.model_dump()` |
|
| 99 |
+
| `pong` | `{}` |
|
| 100 |
+
| `error` | `{message}` |
|
| 101 |
+
|
| 102 |
+
## Session Management
|
| 103 |
+
|
| 104 |
+
| Store | Type | Purpose |
|
| 105 |
+
|-------|------|---------|
|
| 106 |
+
| `_sessions` | `dict[str, dict]` | Active REST sessions (env + last_active) |
|
| 107 |
+
| `_replay_store` | `dict[str, EpisodeLog]` | Completed episode logs |
|
| 108 |
+
|
| 109 |
+
**Cleanup:** Background task runs every 60s, removes sessions older than `SESSION_TTL_SECONDS` (300s).
|
| 110 |
+
|
| 111 |
+
## Helper Functions
|
| 112 |
+
|
| 113 |
+
| Function | Purpose |
|
| 114 |
+
|----------|---------|
|
| 115 |
+
| `_reward_breakdown_from_state(state)` | Extract RewardBreakdown from EpisodeState scores |
|
| 116 |
+
| `_build_episode_log(episode_id, state)` | Build EpisodeLog from final state |
|
| 117 |
+
| `_touch(session_id)` | Update last_active timestamp |
|
| 118 |
+
| `_cleanup_stale_sessions()` | Remove expired sessions |
|
| 119 |
+
|
| 120 |
+
## Dependencies
|
| 121 |
+
|
| 122 |
+
```python
|
| 123 |
+
from replicalab.agents import check_feasibility, compose_lab_manager_response, suggest_alternative
|
| 124 |
+
from replicalab.config import API_HOST, API_PORT, DEFAULT_DIFFICULTY, ...
|
| 125 |
+
from replicalab.models import (ConversationEntry, EpisodeLog, EpisodeState, LabManagerAction,
|
| 126 |
+
Observation, Protocol, RewardBreakdown, ScientistAction, StepInfo, StepResult, ...)
|
| 127 |
+
from replicalab.scenarios import NormalizedScenarioPack, available_scenario_families, generate_scenario
|
| 128 |
+
```
|
docs/map/tests.md
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Tests Map β `tests/`
|
| 2 |
+
|
| 3 |
+
> 87 tests across 6 files. All passing.
|
| 4 |
+
>
|
| 5 |
+
> **Last verified:** 2026-03-07
|
| 6 |
+
|
| 7 |
+
## Summary
|
| 8 |
+
|
| 9 |
+
| File | Tests | What it covers |
|
| 10 |
+
|------|-------|---------------|
|
| 11 |
+
| `test_config.py` | 3 | Shared constants consistency |
|
| 12 |
+
| `test_models.py` | 15 | All Pydantic model contracts |
|
| 13 |
+
| `test_scenarios.py` | 8 | Scenario generation and determinism |
|
| 14 |
+
| `test_validation.py` | 13 | Protocol validation checks |
|
| 15 |
+
| `test_scientist_policy.py` | 18 | Parser, retry, formatter, baseline |
|
| 16 |
+
| `test_lab_manager_policy.py` | 13 | Feasibility, suggestion, response |
|
| 17 |
+
| **Total** | **87** | |
|
| 18 |
+
|
| 19 |
+
## Missing Coverage (not yet implemented)
|
| 20 |
+
|
| 21 |
+
| File (planned) | Would cover |
|
| 22 |
+
|---------------|-------------|
|
| 23 |
+
| `test_reward.py` | JDG 01-03 scoring functions |
|
| 24 |
+
| `test_env.py` | ENV 01-11 real environment |
|
| 25 |
+
| `test_server.py` | API endpoint integration tests |
|
| 26 |
+
|
| 27 |
+
---
|
| 28 |
+
|
| 29 |
+
## `test_config.py` (3 tests)
|
| 30 |
+
|
| 31 |
+
| Test | What it verifies |
|
| 32 |
+
|------|-----------------|
|
| 33 |
+
| `test_reset_request_defaults_match_shared_config` | ResetRequest defaults match config constants |
|
| 34 |
+
| `test_generated_scenarios_respect_shared_round_and_budget_caps` | Scenarios use MAX_ROUNDS and MAX_BUDGET |
|
| 35 |
+
| `test_timeout_exports_share_the_same_default_value` | SESSION_TTL, WS_IDLE, ROUND_TIME all equal TIMEOUT |
|
| 36 |
+
|
| 37 |
+
## `test_models.py` (15 tests)
|
| 38 |
+
|
| 39 |
+
### ScientistAction (6 tests)
|
| 40 |
+
| Test | What it verifies |
|
| 41 |
+
|------|-----------------|
|
| 42 |
+
| `test_scientist_action_accepts_valid_protocol_payload` | propose_protocol with full fields passes |
|
| 43 |
+
| `test_scientist_action_rejects_unknown_action_type` | Invalid enum value rejected |
|
| 44 |
+
| `test_scientist_action_rejects_request_info_without_questions` | questions must be non-empty for request_info |
|
| 45 |
+
| `test_scientist_action_rejects_protocol_payload_for_request_info` | No protocol fields with request_info |
|
| 46 |
+
| `test_scientist_action_rejects_protocol_with_zero_sample_size` | sample_size >= 1 for protocol actions |
|
| 47 |
+
| `test_scientist_action_rejects_extra_fields` | extra="forbid" enforcement |
|
| 48 |
+
|
| 49 |
+
### LabManagerAction (4 tests)
|
| 50 |
+
| Test | What it verifies |
|
| 51 |
+
|------|-----------------|
|
| 52 |
+
| `test_lab_manager_action_accepts_valid_suggestion_payload` | suggest_alternative with all fields passes |
|
| 53 |
+
| `test_lab_manager_action_rejects_feasible_flag_mismatch` | feasible must match constraint flags AND |
|
| 54 |
+
| `test_lab_manager_action_rejects_missing_suggestion_fields` | suggest_alternative needs suggestion fields |
|
| 55 |
+
| `test_lab_manager_action_rejects_suggestions_for_report_feasibility` | Suggestion fields forbidden for non-suggest |
|
| 56 |
+
|
| 57 |
+
### Observation (2 tests)
|
| 58 |
+
| Test | What it verifies |
|
| 59 |
+
|------|-----------------|
|
| 60 |
+
| `test_observation_coerces_nested_dicts_to_typed_models` | Dict coercion to ConversationEntry/Protocol |
|
| 61 |
+
| `test_observation_rejects_invalid_conversation_role` | Only scientist/lab_manager/system |
|
| 62 |
+
| `test_observation_rejects_negative_budget` | budget_total ge=0 |
|
| 63 |
+
|
| 64 |
+
### Episode Models β MOD 04 (3 tests)
|
| 65 |
+
| Test | What it verifies |
|
| 66 |
+
|------|-----------------|
|
| 67 |
+
| `test_episode_state_accepts_typed_protocol_and_history` | Protocol + ConversationEntry fields |
|
| 68 |
+
| `test_episode_state_accepts_none_protocol` | Optional[Protocol] = None |
|
| 69 |
+
| `test_episode_state_json_round_trip` | model_dump_json β model_validate_json |
|
| 70 |
+
| `test_episode_log_accepts_typed_fields` | Typed transcript + reward_breakdown |
|
| 71 |
+
| `test_episode_log_none_reward_breakdown` | Optional[RewardBreakdown] = None |
|
| 72 |
+
| `test_episode_log_json_round_trip` | Serialization round-trip |
|
| 73 |
+
| `test_episode_log_nested_state_preserves_typed_fields` | final_state nesting |
|
| 74 |
+
| `test_step_result_with_typed_info` | StepInfo with RewardBreakdown |
|
| 75 |
+
|
| 76 |
+
## `test_scenarios.py` (8 tests)
|
| 77 |
+
|
| 78 |
+
| Test | What it verifies |
|
| 79 |
+
|------|-----------------|
|
| 80 |
+
| `test_generate_scenario_is_deterministic_for_same_seed` | Same seed β same output |
|
| 81 |
+
| `test_generate_scenario_varies_across_seeded_cases` | Different seeds β different output |
|
| 82 |
+
| `test_available_scenario_families_exposes_three_domain_families` | 3 families, each with 3 difficulties |
|
| 83 |
+
| `test_hard_finance_scenario_exposes_unavailable_resource_and_safety_rules` | Hard mode tightens resources |
|
| 84 |
+
| `test_difficulty_levels_mechanically_change_budget_and_constraints` | Easy > medium > hard budget |
|
| 85 |
+
| `test_generated_scenarios_keep_unique_constraint_and_resource_keys` | No duplicate keys |
|
| 86 |
+
| `test_golden_scenario_specs_exist_for_manual_prompt_checks` | Golden file exists |
|
| 87 |
+
| `test_golden_scenarios_match_expected_title_and_domain` | Golden content matches |
|
| 88 |
+
|
| 89 |
+
## `test_validation.py` (13 tests)
|
| 90 |
+
|
| 91 |
+
| Test | What it verifies |
|
| 92 |
+
|------|-----------------|
|
| 93 |
+
| `test_valid_protocol_passes` | Well-formed protocol β valid=True |
|
| 94 |
+
| `test_zero_sample_size_is_error` | sample_size < 1 β ERROR |
|
| 95 |
+
| `test_zero_duration_is_error` | duration_days < 1 β ERROR |
|
| 96 |
+
| `test_duration_exceeding_time_limit_is_error` | Over limit β ERROR |
|
| 97 |
+
| `test_duration_within_limit_passes` | Under limit β pass |
|
| 98 |
+
| `test_unknown_equipment_is_warning` | Unknown item β WARNING |
|
| 99 |
+
| `test_booked_equipment_without_substitution_is_error` | Booked + no sub β ERROR |
|
| 100 |
+
| `test_out_of_stock_reagent_without_substitution_is_error` | Out + no sub β ERROR |
|
| 101 |
+
| `test_unknown_reagent_is_warning` | Unknown reagent β WARNING |
|
| 102 |
+
| `test_required_element_warning_when_not_addressed` | Missing element β WARNING |
|
| 103 |
+
| `test_no_controls_is_warning` | Empty controls β WARNING |
|
| 104 |
+
| `test_validation_result_never_raises` | Always returns, never throws |
|
| 105 |
+
| `test_validation_result_json_round_trip` | Serialization round-trip |
|
| 106 |
+
|
| 107 |
+
## `test_scientist_policy.py` (18 tests)
|
| 108 |
+
|
| 109 |
+
### Parser β MOD 09 (5 tests)
|
| 110 |
+
| Test | What it verifies |
|
| 111 |
+
|------|-----------------|
|
| 112 |
+
| `test_parse_scientist_output_accepts_plain_json` | Plain JSON parsing |
|
| 113 |
+
| `test_parse_scientist_output_accepts_fenced_json_with_prose` | Fenced block extraction |
|
| 114 |
+
| `test_parse_scientist_output_raises_explicit_error_when_json_is_missing` | no_json error code |
|
| 115 |
+
| `test_parse_scientist_output_raises_explicit_error_when_json_is_invalid` | invalid_json error code |
|
| 116 |
+
| `test_parse_scientist_output_raises_explicit_error_when_schema_is_invalid` | invalid_action error code |
|
| 117 |
+
|
| 118 |
+
### System Prompt β AGT 01 (1 test)
|
| 119 |
+
| Test | What it verifies |
|
| 120 |
+
|------|-----------------|
|
| 121 |
+
| `test_build_scientist_system_prompt_uses_normalized_scenario_data` | Contains role, task, criteria, action types |
|
| 122 |
+
|
| 123 |
+
### Observation Formatter β AGT 02 (5 tests)
|
| 124 |
+
| Test | What it verifies |
|
| 125 |
+
|------|-----------------|
|
| 126 |
+
| `test_format_observation_empty_history_no_protocol` | Empty state formatting |
|
| 127 |
+
| `test_format_observation_with_history_and_protocol` | Populated state formatting |
|
| 128 |
+
| `test_format_observation_stable_section_order` | Section order is deterministic |
|
| 129 |
+
| `test_format_observation_history_entry_without_action_type` | Null action_type handled |
|
| 130 |
+
| `test_format_observation_from_generated_scenario` | Works with real scenario data |
|
| 131 |
+
|
| 132 |
+
### Retry Loop β AGT 03 (7 tests)
|
| 133 |
+
| Test | What it verifies |
|
| 134 |
+
|------|-----------------|
|
| 135 |
+
| `test_retry_success_on_first_try` | No retry needed |
|
| 136 |
+
| `test_retry_malformed_json_then_valid` | Recovers from bad JSON |
|
| 137 |
+
| `test_retry_invalid_action_then_valid` | Recovers from schema error |
|
| 138 |
+
| `test_retry_exhausted_raises_last_error` | Raises after max retries |
|
| 139 |
+
| `test_retry_correction_message_includes_parser_error` | Correction prompt has error detail |
|
| 140 |
+
| `test_retry_correction_for_invalid_action_includes_validation_detail` | Schema error in correction |
|
| 141 |
+
| `test_retry_metadata_serializable` | RetryMetadata JSON round-trip |
|
| 142 |
+
|
| 143 |
+
### Baseline Action β AGT 04 (4 tests β user-added)
|
| 144 |
+
| Test | What it verifies |
|
| 145 |
+
|------|-----------------|
|
| 146 |
+
| `test_baseline_scientist_proposes_protocol_for_fresh_observation` | No protocol β propose |
|
| 147 |
+
| `test_baseline_scientist_accepts_existing_protocol_without_blocker` | Accepted β accept |
|
| 148 |
+
| `test_baseline_scientist_revises_when_latest_feedback_has_blocker` | Blocker β revise |
|
| 149 |
+
| `test_baseline_scientist_finishes_stub_episode_without_crashing` | Full 2-round stub episode |
|
| 150 |
+
|
| 151 |
+
## `test_lab_manager_policy.py` (13 tests)
|
| 152 |
+
|
| 153 |
+
### Feasibility β AGT 05 (7 tests β user-added)
|
| 154 |
+
| Test | What it verifies |
|
| 155 |
+
|------|-----------------|
|
| 156 |
+
| `test_check_feasibility_passes_for_viable_protocol` | All 7 dimensions pass |
|
| 157 |
+
| `test_check_feasibility_flags_budget_overrun` | Over-budget detected |
|
| 158 |
+
| `test_check_feasibility_flags_unavailable_resource_and_lists_substitution` | Out-of-stock + substitution |
|
| 159 |
+
| `test_check_feasibility_flags_schedule_overrun` | Duration over limit |
|
| 160 |
+
| `test_check_feasibility_flags_staff_overload` | Staff insufficient |
|
| 161 |
+
| `test_check_feasibility_flags_policy_violation` | Policy violation detected |
|
| 162 |
+
| `test_check_feasibility_is_deterministic` | Same inputs β same output |
|
| 163 |
+
|
| 164 |
+
### Suggestion β AGT 06 (8 tests)
|
| 165 |
+
| Test | What it verifies |
|
| 166 |
+
|------|-----------------|
|
| 167 |
+
| `test_suggest_alternative_returns_none_for_feasible_protocol` | Feasible β None |
|
| 168 |
+
| `test_suggest_alternative_substitutes_equipment` | Equipment swap applied |
|
| 169 |
+
| `test_suggest_alternative_substitutes_reagent` | Reagent swap applied |
|
| 170 |
+
| `test_suggest_alternative_clamps_duration` | Duration reduced to limit |
|
| 171 |
+
| `test_suggest_alternative_reduces_sample_size_for_budget` | Sample size halved for budget |
|
| 172 |
+
| `test_suggest_alternative_is_deterministic` | Same inputs β same output |
|
| 173 |
+
| `test_suggest_alternative_post_check_is_not_worse` | Post-fix has <= pre-fix failures |
|
| 174 |
+
| `test_suggest_alternative_reports_remaining_failures` | Unfixable failures listed |
|
| 175 |
+
|
| 176 |
+
### Response Composition β AGT 07 (5 tests β user-added)
|
| 177 |
+
| Test | What it verifies |
|
| 178 |
+
|------|-----------------|
|
| 179 |
+
| `test_compose_lab_manager_response_accepts_feasible_protocol` | Feasible β ACCEPT |
|
| 180 |
+
| `test_compose_lab_manager_response_suggests_alternative_when_revision_exists` | Has suggestion β SUGGEST |
|
| 181 |
+
| `test_compose_lab_manager_response_rejects_when_no_revision_exists` | No fix β REJECT |
|
| 182 |
+
| `test_compose_lab_manager_response_reports_non_lab_issues` | Policy-only β REPORT |
|
| 183 |
+
| `test_compose_lab_manager_response_uses_custom_renderer_without_changing_verdict` | Custom renderer works |
|
| 184 |
+
|
| 185 |
+
## Test Helpers
|
| 186 |
+
|
| 187 |
+
### Shared fixtures in test files
|
| 188 |
+
| Helper | File | Purpose |
|
| 189 |
+
|--------|------|---------|
|
| 190 |
+
| `_scenario(template, difficulty)` | test_lab_manager_policy | Generate scenario with seed=123 |
|
| 191 |
+
| `_protocol_for_scenario(scenario, **overrides)` | test_lab_manager_policy | Build viable protocol from scenario |
|
| 192 |
+
| `_base_observation(**overrides)` | test_scientist_policy | Build ScientistObservation with defaults |
|
| 193 |
+
| `_make_system_prompt()` | test_scientist_policy | Build prompt from math_reasoning scenario |
|
| 194 |
+
| `_VALID_REQUEST_INFO_JSON` | test_scientist_policy | Valid request_info JSON string |
|
docs/map/validation.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Validation Map β `replicalab/utils/validation.py`
|
| 2 |
+
|
| 3 |
+
> Deterministic protocol validation against scenario constraints.
|
| 4 |
+
> Pure functions β no LLM calls, no side effects.
|
| 5 |
+
>
|
| 6 |
+
> **Tasks implemented:** MOD 05
|
| 7 |
+
|
| 8 |
+
## Public API
|
| 9 |
+
|
| 10 |
+
### `validate_protocol(protocol: Protocol, scenario: NormalizedScenarioPack) -> ValidationResult`
|
| 11 |
+
Main entry point. Never raises β always returns a `ValidationResult`.
|
| 12 |
+
|
| 13 |
+
**Checks run (in order):**
|
| 14 |
+
1. `_check_obvious_impossibilities` β sample_size < 1, no controls, duration < 1
|
| 15 |
+
2. `_check_duration_vs_time_limit` β protocol days vs lab time_limit_days
|
| 16 |
+
3. `_check_equipment_vocabulary` β items vs available/booked/substitutable
|
| 17 |
+
4. `_check_reagent_vocabulary` β items vs in-stock/out-of-stock/substitutable
|
| 18 |
+
5. `_check_required_element_coverage` β protocol text vs hidden_reference_spec.required_elements
|
| 19 |
+
|
| 20 |
+
**Result:** `valid=True` only if zero ERROR-level issues.
|
| 21 |
+
|
| 22 |
+
## Data Classes
|
| 23 |
+
|
| 24 |
+
### `IssueSeverity(str, Enum)`
|
| 25 |
+
| Value | Meaning |
|
| 26 |
+
|-------|---------|
|
| 27 |
+
| `error` | Hard failure β protocol cannot proceed |
|
| 28 |
+
| `warning` | Advisory β protocol is suboptimal but possible |
|
| 29 |
+
|
| 30 |
+
### `ValidationIssue(BaseModel)` β `extra="forbid"`
|
| 31 |
+
| Field | Type | Example |
|
| 32 |
+
|-------|------|---------|
|
| 33 |
+
| `severity` | `IssueSeverity` | `ERROR` |
|
| 34 |
+
| `category` | `str` | `"equipment"`, `"duration"`, `"sample_size"` |
|
| 35 |
+
| `message` | `str` | `"Equipment 'X' is booked and has no substitution."` |
|
| 36 |
+
|
| 37 |
+
### `ValidationResult(BaseModel)` β `extra="forbid"`
|
| 38 |
+
| Field | Type |
|
| 39 |
+
|-------|------|
|
| 40 |
+
| `valid` | `bool` |
|
| 41 |
+
| `issues` | `list[ValidationIssue]` |
|
| 42 |
+
|
| 43 |
+
**Properties:**
|
| 44 |
+
- `errors` β `list[ValidationIssue]` (severity=ERROR only)
|
| 45 |
+
- `warnings` β `list[ValidationIssue]` (severity=WARNING only)
|
| 46 |
+
|
| 47 |
+
## Check Details
|
| 48 |
+
|
| 49 |
+
### `_check_obvious_impossibilities`
|
| 50 |
+
| Condition | Severity | Category |
|
| 51 |
+
|-----------|----------|----------|
|
| 52 |
+
| `sample_size < 1` | ERROR | `sample_size` |
|
| 53 |
+
| `controls` empty | WARNING | `controls` |
|
| 54 |
+
| `duration_days < 1` | ERROR | `duration` |
|
| 55 |
+
|
| 56 |
+
### `_check_duration_vs_time_limit`
|
| 57 |
+
| Condition | Severity | Category |
|
| 58 |
+
|-----------|----------|----------|
|
| 59 |
+
| `duration_days > time_limit_days` | ERROR | `duration` |
|
| 60 |
+
|
| 61 |
+
### `_check_equipment_vocabulary`
|
| 62 |
+
| Condition | Severity | Category |
|
| 63 |
+
|-----------|----------|----------|
|
| 64 |
+
| Item available | β (pass) | β |
|
| 65 |
+
| Item booked + has substitution | WARNING | `equipment` |
|
| 66 |
+
| Item booked + no substitution | ERROR | `equipment` |
|
| 67 |
+
| Item unknown (not in inventory) | WARNING | `equipment` |
|
| 68 |
+
|
| 69 |
+
### `_check_reagent_vocabulary`
|
| 70 |
+
| Condition | Severity | Category |
|
| 71 |
+
|-----------|----------|----------|
|
| 72 |
+
| Item in stock | β (pass) | β |
|
| 73 |
+
| Item out of stock + has substitution | WARNING | `reagent` |
|
| 74 |
+
| Item out of stock + no substitution | ERROR | `reagent` |
|
| 75 |
+
| Item unknown (not in inventory) | WARNING | `reagent` |
|
| 76 |
+
|
| 77 |
+
### `_check_required_element_coverage`
|
| 78 |
+
Checks each `hidden_reference_spec.required_elements` against protocol text fields using token matching.
|
| 79 |
+
|
| 80 |
+
**Protocol text searched:** technique, rationale, controls, equipment, reagents (joined, lowercased).
|
| 81 |
+
**Token extraction:** `_element_tokens(element)` splits on spaces, keeps tokens with 3+ chars.
|
| 82 |
+
**Match:** any token from element found in protocol text β covered.
|
| 83 |
+
|
| 84 |
+
| Condition | Severity | Category |
|
| 85 |
+
|-----------|----------|----------|
|
| 86 |
+
| Element not addressed | WARNING | `required_element` |
|
| 87 |
+
|
| 88 |
+
## Internal Helpers
|
| 89 |
+
|
| 90 |
+
| Function | Purpose |
|
| 91 |
+
|----------|---------|
|
| 92 |
+
| `_normalize(label)` | Lowercase, strip, collapse whitespace |
|
| 93 |
+
| `_element_tokens(element)` | Split element string into searchable tokens (3+ chars) |
|
| 94 |
+
| `_substitution_alternatives(scenario)` | Set of normalized original items from `allowed_substitutions` |
|
| 95 |
+
|
| 96 |
+
## Who Consumes This
|
| 97 |
+
|
| 98 |
+
- **`lab_manager_policy.py`** β `check_feasibility()` calls `validate_protocol()` and wraps result in `protocol` DimensionCheck
|
| 99 |
+
- **`scoring/`** (future) β JDG 01 rigor score will reuse `_element_tokens` for required element matching
|