Skip to content

Design Decisions

santisoutoo edited this page Jul 24, 2026 · 1 revision

Design Decisions

The authoritative decision log (ADR) is docs/decisiones.md, in Spanish — 26 decisions plus phase-closure milestones. This page is an English index: it summarizes each decision and links the full entry. It is not a translation; when in doubt, the ADR wins.

Invariants that never move without a recorded decision: the FBW vendor pin (13bce4b) and the "zero patches to vendored code" rule. Both sections of the ADR (Parches al código vendorizado and the pin in D-002) are empty/unchanged on purpose — benchmark reproducibility depends on it.

Foundations (D-001 – D-006)

# Decision
D-001 Base: FlyByWire A320, not Zibo 737 — FBW's crates run headless; Zibo isn't open-source and has no standalone systems engine.
D-002 FBW vendored and pinned to commit 13bce4b. Pinned to a commit (not a tag — upstream tags are abandoned). Every pin bump is a new ADR entry.
D-003 GPLv3, inherited from vendoring the FBW crates.
D-004 Stack: Rust + PyO3 (pure-Rust + rmcp rejected) — Python is far more ergonomic for the Phase-5 agent/benchmark layer, and the FFI surface is trivial.
D-005 The msfs-rs decoupling turned out unnecessarysystems + a320_systems declare no msfs dependency; the coupling lives in the *_wasm crates, outside our dependency graph. "Decoupling" reduces to not compiling those. Zero vendor patches.
D-006 Branch flow: one issue → one branch → one PR to dev; devmain integrates per epic. The PR is where CI and the reproducibility checklist run.

Runtime, API, frontends (D-007 – D-012)

# Decision
D-007 The runtime does not seed initial state — FBW's seed() is private and unpatched, so every unwritten control reads default (OFF). For cold & dark this is correct; see Controls-and-Variables.
D-008 API error model — names validated against the live registry; unknown get/set is an explicit error (not silent 0), no identifier minting.
D-009 Curated control catalog (list_controls) + range validation, with a cockpit/world domain split.
D-010 PyO3 bindings as an independent workspace (maturin), #[pyclass(unsendable)] — the aircraft's Rc/RefCell means single-thread access; cross-thread use raises a clean RuntimeError.
D-011 CLI REPL on the stdlib (cmd + readline/pyreadline3), no prompt_toolkit.
D-012 An init tick in Runtime::new fixes the battery-contactor wedge when a caller writes before the first tick (a private state machine, not a store variable — nothing to seed).

Failures + detection (D-013 – D-014)

# Decision
D-013 Stable, own failure ids (elec.tr.1) over FBW's FailureType — the enum has no Debug/Serialize/numeric id and changes shape with the pin. The ata field cross-references FBW. See Failures-and-ECAM.
D-014 No FWC exists in the vendored Rust, so the ECAM catalog is a homegrown rule engine over what FBW actually writes; each rule tags its source (vendor_flag vs derived). Detail: docs/fase2-ecam.md.

MCP server (D-015 – D-017)

# Decision
D-015 FastMCP over stdio, synchronous single-thread tools — sync is load-bearing (FastMCP runs sync tools inline on the event loop; the unsendable Sim requires it). advance blocks on purpose.
D-016 What the agent cannot see is part of the contractactive_failures and list_variables are deliberately not tools (they'd leak the ground truth or flood the context). See MCP-Server.
D-017 Tool schemas generated from the catalogs (enums), so the model can't hallucinate a name; the scenario is set up by the harness, not the agent.

More systems (D-018 – D-021)

# Decision
D-018 Fuel is world state seeded once (~6 400 kg) — FBW models no consumption/crossfeed; if the environment rewrote it each tick, no scenario could drain a tank.
D-019 Own engine model: a deterministic first-order N2 spool (FBW's Rust only reads engine simvars; the FADEC is C++/WASM). Detail: docs/fase4-motor.md.
D-020 Engine-start contract — cockpit inputs, ENGINE_STATE transitions, per-engine outputs, and a bleed gate (motoring needs real air).
D-021 Panel resting states seeded and cataloged (GEN 1 LINE, X-BLEED selector, pushback, EDP pumps) — their real resting value isn't 0, and 0 silently means something else.

Benchmark (D-022 – D-026)

# Decision
D-022 Determinism via tolerance checkpoints, not bit-for-bit replay — FBW's RNG isn't seedable; the scorer never re-simulates, so exact replay buys nothing. See Benchmark-Overview.
D-023 The benchmark tool profile withholds inject_failure/clear_failure and adds report_done; the server is a factory. See MCP-Server.
D-024 In-process episode runner with the SDK's memory transport and a self-contained JSONL trajectory (scenario, ground truth, pin, versions embedded).
D-025 Providers via litellm, pinned exact (litellm==1.93.0); a ScriptedAdapter is the no-network CI path.
D-026 The compliance metric: reported component vector + derived scalar, only command actions score, safety as a gate. See Scoring.

Lessons learned

Serious avoidable mistakes are logged in docs/lecciones.md (L-001 … L-006) — e.g. verify with the exact CI commands, don't assert a dependency's internal behavior without reading its code, and test "no-binding" contracts through the real entry point. The log is consulted before a task of the same kind, not just appended to.

Clone this wiki locally