DGXAI/driftcall
0
1# step_10_env — DriftCallEnv2 3Implements `docs/modules/env.md` and `DESIGN.md §4`.4 5## Public surface6 7| Symbol | Kind | Notes |8|---|---|---|9| `DriftCallEnv` | class | OpenEnv-compliant RL environment. Single-session, single-episode-at-a-time. |10| `EnvConfig` | frozen dataclass | Validated config snapshot. Built via `EnvConfig.from_mapping(...)`. |11| `Episode` | frozen dataclass | Terminal-only snapshot fed to `cells.step_08_rewards.compute_rewards`. |12| `DriftScheduler` | Protocol | `(stage, seed, goal) -> tuple[DriftEvent, ...]`. Default: `drift_injector.build_schedule`. |13| `TTSEngine` / `ASREngine` | Protocols | Audio boundary contracts (env.md §2.1). |14| `DriftCallEnvError` and 12 subclasses | exceptions | E1..E12 typed taxonomy. |15 16## Wiring17 18```19reset(seed)20 └── task_generator.generate(seed, stage, language_weights)21 └── per-domain vendor.initial_state(seed, goal) # airline, cab, restaurant, hotel, payment22 └── scheduler(stage, seed, goal) # default = drift_injector.build_schedule23 └── audio_boundary_enabled? tts_engine.synthesize(seed_utterance, language)24 └── DriftCallObservation(turn=0, ...)25 26step(action, *, force_drift_pattern=None)27 1a. _validate_action(action) # pure, raises InvalidActionError BEFORE mutation28 1b. force_drift_pattern resolved # unknown -> InvalidActionError29 2. turn += 1 # via dataclasses.replace30 3. drift fold: # forced pattern OR scheduled pending drifts31 - sort by (turn asc, pattern_id asc)32 - apply via drift_injector.apply_drift33 4. side-channel emit pass # vendor.emit_side_channel_if_pending per domain34 5. dispatch:35 TOOL_CALL -> vendor.dispatch(...) and merge any pending notice into ToolResult36 SPEAK/CLARIFY-> no state change37 PROBE_SCHEMA -> vendor.describe_schema(state, version), wrapped as ToolResult38 SUBMIT -> terminate("SUBMIT")39 ABORT -> terminate("ABORT")40 6. record action (and ToolResult, if any) via dataclasses.replace41 7. if turn >= max_turns -> terminate("TIMEOUT")42 8. if terminal -> build Episode + step_08_rewards.compute_rewards (memoized)43 9. return DriftCallObservation44```45 46## Termination47 48`terminated_by ∈ {SUBMIT, ABORT, TIMEOUT, ANTI_HACK}`. Reward layer reads `terminated_by` to force `r1=0` for ABORT/TIMEOUT/ANTI_HACK. `Episode` and `Rewards` are write-once; `episode()`/`rewards()` return memoized identities.49 50## Determinism contract51 52Same `(config, seed)` ⇒ byte-identical `goal`, `drift_schedule`, and initial `vendor_states`. The only non-deterministic field is `episode_id` (uuid4), which is purely an audit handle (env.md §9 Q5).53 54## Error taxonomy (E1–E12)55 56All extend `DriftCallEnvError(Exception)`:57 58| # | Class | When |59|---|---|---|60| E1 | `InvalidConfigError` | unknown key, bad weights, missing audio engine, etc. |61| E2 | `EnvNotReadyError` | step/state/episode/rewards before reset |62| E3 | `EnvClosedError` | reset/step after close |63| E4 | `InvalidActionError` | per-`ActionType` field-matrix violation; force_drift_pattern unknown |64| E5 | `EpisodeAlreadyTerminalError` | step after termination |65| E6 | `EpisodeNotTerminalError` | episode/rewards before termination |66| E7 | `ConcurrentStepError` | reentrant step |67| E8 | `UnknownDomainError` | PROBE_SCHEMA on unregistered domain |68| E9 | `UnknownToolError` | TOOL_CALL with tool_name not in available_tools |69| E10 | `DriftInjectionError` | drift fold failure (propagated from drift_injector) |70| E11 | `RewardComputationError` | compute_rewards failure |71| E12 | `AudioPipelineError` | TTS/ASR engine raised at boundary |72 73Validation in `_validate_action` is strictly pure: raises before any state mutation, so the env remains valid for a subsequent `step()`.74 75## Audio boundary76 77`audio_boundary_enabled=True` requires both `tts_engine` and `asr_engine`. On `reset()` the env calls `tts_engine.synthesize(goal.seed_utterance, goal.language)`; the canonical `last_transcript` remains the textual `seed_utterance`. The audio pipeline never feeds bytes back into reward computation.78 79## Out of scope80 81- LLM judging — never. The env is the judge.82- Concurrency — single-session by contract; no locks, no asyncio.83- Disk/network I/O at `__init__` — strictly forbidden.84 