bd938a559c
The v0.6.3 RLM loop had Algorithm 1's outer shape but the substrate was
non-functional: `llm_query()` was a Python stub that returned a hardcoded
string and `child_model` was bound with an underscore prefix and silently
dropped. The recursive sub-LLM call advertised by /rlm never fired.
This commit wires the substrate end-to-end per Zhang/Kraska/Khattab
(arXiv:2512.24601, Algorithm 1):
- New axum HTTP sidecar (`rlm/sidecar.rs`) bound to 127.0.0.1:0 for the
duration of one RLM turn. Python's `llm_query()` and `sub_rlm()` are
real `urllib.request` POSTs; Rust services them via the existing
DeepSeek client. Token usage from sidecar-served calls folds into the
parent `RlmTurnResult.usage`.
- `child_model` is plumbed through `Op::RlmQuery` → `AppAction::RlmQuery`
→ `run_rlm_turn` → sidecar handlers; default remains `deepseek-v4-flash`.
- New `sub_rlm(prompt)` Python helper runs a full Algorithm-1 turn at
depth-1 (paper's `sub_RLM`). Default `max_depth = 2` from `/rlm`. The
recursive opaque-future cycle is broken by returning a concrete
`Pin<Box<dyn Future + Send>>` from `run_rlm_turn_inner`.
- Strict termination: the loop ends only via `FINAL(value)` (or the
iteration cap). One fence-less round is tolerated with a reminder
appended; two consecutive ones surface the model text as a
`RlmTermination::DirectAnswer` exit. New `RlmTermination` enum lets
callers tell `Final | DirectAnswer | Exhausted | Error` apart.
- Richer `Metadata(state)`: includes paper-required access patterns
(`repl_get` / slicing / `splitlines` / `repl_set` / `llm_query` /
`sub_rlm` / `FINAL`) and a live list of variable keys currently in
the REPL state file.
- Unicode-safe `truncate_text` (was mixing bytes with chars), per-turn
state-file cleanup, `ROOM_TEMPERATURE` typo → `ROOT_TEMPERATURE`.
- New end-to-end test `sidecar_url_is_exported_to_python_env` stands up
a stand-in axum server, runs `print(llm_query('hello'))` in the real
PythonRuntime, and asserts the reply round-trips. Catches future
regressions in sidecar URL passthrough.
Versions: workspace 0.6.3 → 0.6.4 in Cargo.toml; npm wrapper 0.6.3 → 0.6.4
in npm/deepseek-tui/package.json.
Gates: cargo fmt, cargo clippy --all-targets --all-features --locked
-D warnings, cargo test --workspace --all-features --locked (1088
passed), parity_protocol/parity_state/snapshot, RUSTDOCFLAGS=-Dwarnings
cargo doc --workspace --no-deps — all green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>